diff --git a/src/features/cards/components/hashnodeCard/ArticleItem.tsx b/src/features/cards/components/hashnodeCard/ArticleItem.tsx
index 2165b620..a751fefa 100644
--- a/src/features/cards/components/hashnodeCard/ArticleItem.tsx
+++ b/src/features/cards/components/hashnodeCard/ArticleItem.tsx
@@ -11,14 +11,12 @@ import { AiTwotoneHeart } from 'react-icons/ai'
import { Attributes } from 'src/lib/analytics'
const ArticleItem = (props: BaseItemPropsType) => {
- const { item, index, selectedTag, analyticsTag } = props
+ const { item, selectedTag, analyticsTag } = props
const { listingMode } = useUserPreferences()
return (
diff --git a/src/features/cards/components/indiehackersCard/ArticleItem.tsx b/src/features/cards/components/indiehackersCard/ArticleItem.tsx
index fca31da1..a25b3d7c 100644
--- a/src/features/cards/components/indiehackersCard/ArticleItem.tsx
+++ b/src/features/cards/components/indiehackersCard/ArticleItem.tsx
@@ -9,13 +9,12 @@ import { Article, BaseItemPropsType } from 'src/types'
import { format } from 'timeago.js'
export const ArticleItem = (props: BaseItemPropsType) => {
- const { item, index, analyticsTag } = props
+ const { item, analyticsTag } = props
const { listingMode } = useUserPreferences()
return (
) => {
+const ArticleItem = ({ item, analyticsTag }: BaseItemPropsType) => {
const { listingMode } = useUserPreferences()
return (
diff --git a/src/features/cards/components/mediumCard/ArticleItem.tsx b/src/features/cards/components/mediumCard/ArticleItem.tsx
index 3330c604..583cc7d3 100644
--- a/src/features/cards/components/mediumCard/ArticleItem.tsx
+++ b/src/features/cards/components/mediumCard/ArticleItem.tsx
@@ -6,13 +6,12 @@ import { useUserPreferences } from 'src/stores/preferences'
import { Article, BaseItemPropsType } from 'src/types'
import { format } from 'timeago.js'
-const ArticleItem = ({ item, index, selectedTag, analyticsTag }: BaseItemPropsType) => {
+const ArticleItem = ({ item, selectedTag, analyticsTag }: BaseItemPropsType) => {
const { listingMode } = useUserPreferences()
return (
{
)
}
-const ArticleItem = ({ item, index, analyticsTag }: BaseItemPropsType) => {
+const ArticleItem = ({ item, analyticsTag }: BaseItemPropsType) => {
const { listingMode } = useUserPreferences()
const subReddit = useMemo(() => {
@@ -41,8 +41,6 @@ const ArticleItem = ({ item, index, analyticsTag }: BaseItemPropsType)
return (
diff --git a/src/features/cards/components/rssCard/ArticleItem.tsx b/src/features/cards/components/rssCard/ArticleItem.tsx
index 9eb6361f..195e6a01 100644
--- a/src/features/cards/components/rssCard/ArticleItem.tsx
+++ b/src/features/cards/components/rssCard/ArticleItem.tsx
@@ -5,7 +5,7 @@ import { Article, BaseItemPropsType } from 'src/types'
import { format } from 'timeago.js'
const ArticleItem = (props: BaseItemPropsType) => {
- const { item, index, selectedTag, analyticsTag } = props
+ const { item, selectedTag, analyticsTag } = props
if (!item) {
return null
}
@@ -13,8 +13,6 @@ const ArticleItem = (props: BaseItemPropsType) => {
diff --git a/src/features/feed/components/Feed.tsx b/src/features/feed/components/Feed.tsx
index 2bdbc531..66589381 100644
--- a/src/features/feed/components/Feed.tsx
+++ b/src/features/feed/components/Feed.tsx
@@ -75,13 +75,7 @@ export const Feed = () => {
{(feed?.pages.flatMap((page) => page.data) || []).map((article, index) => {
return (
-
+
)
})}
{hasNextPage && (
diff --git a/src/features/feed/components/feedItems/ArticleFeedItem.tsx b/src/features/feed/components/feedItems/ArticleFeedItem.tsx
index e990dd9a..f284b39a 100644
--- a/src/features/feed/components/feedItems/ArticleFeedItem.tsx
+++ b/src/features/feed/components/feedItems/ArticleFeedItem.tsx
@@ -7,16 +7,14 @@ import { FeedItemHeader } from '../FeedItemHeader'
import { FeedItemSource } from '../FeedItemSource'
export const ArticleFeedItem = (props: BaseItemPropsType
) => {
- const { item, index, analyticsTag, className } = props
+ const { item, analyticsTag, className } = props
const { listingMode } = useUserPreferences()
return (
diff --git a/src/features/feed/components/feedItems/ProductFeedItem.tsx b/src/features/feed/components/feedItems/ProductFeedItem.tsx
index 91a641d7..cd5db5fa 100644
--- a/src/features/feed/components/feedItems/ProductFeedItem.tsx
+++ b/src/features/feed/components/feedItems/ProductFeedItem.tsx
@@ -7,16 +7,14 @@ import { FeedItemHeader } from '../FeedItemHeader'
import { FeedItemSource } from '../FeedItemSource'
export const ProductFeedItem = (props: BaseItemPropsType) => {
- const { item, index, analyticsTag, className } = props
+ const { item, analyticsTag, className } = props
const { listingMode } = useUserPreferences()
return (
diff --git a/src/features/feed/components/feedItems/RepoFeedItem.tsx b/src/features/feed/components/feedItems/RepoFeedItem.tsx
index d2545555..ae7dc72d 100644
--- a/src/features/feed/components/feedItems/RepoFeedItem.tsx
+++ b/src/features/feed/components/feedItems/RepoFeedItem.tsx
@@ -11,16 +11,14 @@ function numberWithCommas(x: number | string) {
}
export const RepoFeedItem = (props: BaseItemPropsType) => {
- const { item, index, analyticsTag, className } = props
+ const { item, analyticsTag, className } = props
const { listingMode } = useUserPreferences()
return (
Date: Sat, 8 Nov 2025 19:34:42 +0100
Subject: [PATCH 30/89] refactor: remove unused initState function from
useUserPreferences
---
src/stores/preferences.ts | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/src/stores/preferences.ts b/src/stores/preferences.ts
index 0c8eac60..11880f16 100644
--- a/src/stores/preferences.ts
+++ b/src/stores/preferences.ts
@@ -150,10 +150,7 @@ export const useUserPreferences = create(
setCards: (selectedCards: SelectedCard[]) => set({ cards: selectedCards }),
setTags: (selectedTags: Tag[]) => set({ userSelectedTags: selectedTags }),
setMaxVisibleCards: (maxVisibleCards: number) => set({ maxVisibleCards: maxVisibleCards }),
- initState: (newState: UserPreferencesState) =>
- set(() => {
- return { ...newState }
- }),
+
setCardSettings: (card: string, settings: CardSettingsType) =>
set((state) => ({
cardsSettings: {
From 3351dfbb7a77f06456e2aaa8b2082dd2fab2c8d8 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 9 Nov 2025 18:59:17 +0100
Subject: [PATCH 31/89] refactor: change div to span for subTitle in
ArticleItem component
---
src/features/cards/components/lobstersCard/ArticleItem.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/features/cards/components/lobstersCard/ArticleItem.tsx b/src/features/cards/components/lobstersCard/ArticleItem.tsx
index 5375e24d..c2123c6a 100644
--- a/src/features/cards/components/lobstersCard/ArticleItem.tsx
+++ b/src/features/cards/components/lobstersCard/ArticleItem.tsx
@@ -34,7 +34,7 @@ const ArticleItem = ({ item, analyticsTag }: BaseItemPropsType) => {
)}
- {item.title}
+ {item.title}
{listingMode === 'normal' && (
From 51819a0a84859cee6d7d96487e9997f3e86452b2 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 9 Nov 2025 18:59:41 +0100
Subject: [PATCH 32/89] refactor: enhance styling for menu items and block
headers in App.css
---
src/assets/App.css | 110 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 108 insertions(+), 2 deletions(-)
diff --git a/src/assets/App.css b/src/assets/App.css
index 27746bed..410663cf 100644
--- a/src/assets/App.css
+++ b/src/assets/App.css
@@ -35,6 +35,7 @@ a {
text-align: center;
margin: auto;
font-size: 16px;
+ line-height: 24px;
padding: 16px;
}
@@ -269,16 +270,94 @@ a {
max-width: 180px;
}
-.blockHeader:hover .blockHeaderLink {
+.blockHeader:hover .blockHeaderLink,
+.blockHeader:hover .blockHeaderSettingsButton {
opacity: 1;
}
+.menuItem {
+ font-size: 1em;
+ display: flex;
+ flex-direction: row;
+ font-family: 'nunito';
+ column-gap: 8px;
+ svg {
+ font-size: 1.1em;
+ }
+}
+
+.szh-menu__header {
+ margin-bottom: 6px;
+}
+.light {
+ .menuItem {
+ color: var(--card-header-text-color);
+ }
+ .szh-menu__item--disabled {
+ color: var(--secondary-text-color);
+ opacity: 0.6;
+ cursor: not-allowed;
+ }
+}
+
+.dark {
+ .szh-menu {
+ background-color: var(--card-background-color);
+ border-radius: 12px;
+ border: 1px solid var(--card-border-color);
+ box-shadow: 0 0 20px var(--card-border-color);
+ }
+ .szh-menu__item {
+ color: var(--primary-text-color);
+ }
+
+ .szh-menu__divider {
+ background-color: var(--card-border-color);
+ }
+
+ .szh-menu__item--hover {
+ background-color: var(--app-name-text-color);
+ color: var(--background-color);
+ }
+ .szh-menu__item--hover {
+ background-color: var(--app-name-text-color);
+ }
+ .szh-menu__submenu {
+ background-color: var(--card-background-color);
+
+ .szh-menu__item--hover {
+ background-color: var(--app-name-text-color);
+ color: var(--background-color);
+ }
+ }
+ .szh-menu__item--disabled {
+ color: var(--secondary-text-color);
+ opacity: 0.6;
+ cursor: not-allowed;
+ }
+}
+
+.blockHeaderSettingsButton {
+ color: #96a2ae;
+ display: flex;
+ justify-content: center;
+ opacity: 0;
+ font-size: 1.2em;
+ transition: opacity 0.2s linear;
+ cursor: pointer;
+ &:focus {
+ cursor: pointer;
+ opacity: 1;
+ }
+}
+
.blockHeaderLink {
align-items: center;
color: #96a2ae;
display: flex;
justify-content: center;
opacity: 0;
+ font-size: 1.2em;
transition: opacity 0.2s linear;
}
@@ -311,7 +390,13 @@ a {
transform: rotate(3deg);
opacity: 0.5;
}
-
+.blockHeaderHighlight {
+ border-radius: 15px;
+ padding: 1px 6px;
+ border: 1px solid var(--chip-border-color);
+ background-color: var(--chip-background);
+ font-size: 0.9em;
+}
.blockHeaderIcon {
display: flex;
height: 16px;
@@ -330,6 +415,11 @@ a {
padding: 0 6px;
text-transform: lowercase;
color: white;
+
+ &.past {
+ background-color: #dd5353;
+ margin-right: 4px;
+ }
}
.blockHeaderIcon img {
display: block;
@@ -567,6 +657,10 @@ a {
transition: opacity 0.3s ease-out 0.1s, transform 0.3s ease-out 0.1s,
visibility 0.3s ease-out 0.1s;
width: 100%;
+
+ button.tag {
+ cursor: pointer;
+ }
}
.tag {
@@ -921,7 +1015,14 @@ Producthunt item
padding: 0 32px 0 48px;
width: 100%;
background-color: var(--card-header-background-color);
+
+ &::placeholder {
+ color: var(--primary-text-color);
+ font-size: 0.9em;
+ opacity: 0.5;
+ }
}
+
.searchBarInput:focus {
outline: none;
}
@@ -1159,6 +1260,11 @@ Producthunt item
right: 16px;
width: 48px;
z-index: 2;
+
+ & svg {
+ color: white;
+ font-size: 24px;
+ }
}
.floatingFilterBottomSheet .title {
From 449b68003bb8f417b128093e17ce4c96655395f2 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 9 Nov 2025 18:59:54 +0100
Subject: [PATCH 33/89] refactor: remove unused index prop from
CardItemWithActions component
---
.../Elements/CardWithActions/CardItemWithActions.tsx | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/components/Elements/CardWithActions/CardItemWithActions.tsx b/src/components/Elements/CardWithActions/CardItemWithActions.tsx
index ca674a2f..2e5591b2 100644
--- a/src/components/Elements/CardWithActions/CardItemWithActions.tsx
+++ b/src/components/Elements/CardWithActions/CardItemWithActions.tsx
@@ -11,7 +11,6 @@ import { BaseEntry } from 'src/types'
type CardItemWithActionsProps = {
item: BaseEntry
- index: number
source: string
cardItem: React.ReactNode
sourceType?: 'rss' | 'supported'
@@ -20,7 +19,6 @@ type CardItemWithActionsProps = {
export const CardItemWithActions = ({
cardItem,
item,
- index,
source,
sourceType = 'supported',
}: CardItemWithActionsProps) => {
@@ -73,7 +71,7 @@ export const CardItemWithActions = ({
}
return (
-
+
setShareModalData(undefined)}
From eafcfef065567365f4b8a27df1eee03d7b4eb1fb Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 9 Nov 2025 19:04:12 +0100
Subject: [PATCH 34/89] refactor: update Feed component to use feedLoading
class for loading state and enhance placeholder styling
---
src/features/feed/components/Feed.tsx | 2 +-
src/features/feed/components/feed.css | 10 +++++++++-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/src/features/feed/components/Feed.tsx b/src/features/feed/components/Feed.tsx
index 66589381..c9cd831a 100644
--- a/src/features/feed/components/Feed.tsx
+++ b/src/features/feed/components/Feed.tsx
@@ -57,7 +57,7 @@ export const Feed = () => {
if (isInitialLoading) {
return (
-
+
{Array.from({
length: 10,
}).map((_, index) => (
diff --git a/src/features/feed/components/feed.css b/src/features/feed/components/feed.css
index f791348b..38824082 100644
--- a/src/features/feed/components/feed.css
+++ b/src/features/feed/components/feed.css
@@ -49,6 +49,14 @@
}
}
+.feed > * {
+ box-sizing: border-box;
+ max-width: 100%;
+ overflow: hidden;
+}
+.feedLoading .placeholder {
+ min-height: 360px;
+}
.feed .placeholder {
animation-duration: 1.5s;
animation-name: cardPlaceholderPulse;
@@ -56,8 +64,8 @@
padding: 12px;
display: flex;
flex-direction: column;
- min-height: 360px;
gap: 16px;
+ box-sizing: border-box;
.image {
background-color: var(--placeholder-background-color);
border: 1px solid var(--placeholder-border-color);
From 34224aa2d0f03658a52de74d9c3bdcc52a1c122a Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 9 Nov 2025 19:51:55 +0100
Subject: [PATCH 35/89] refactor: simplify ArticleItem component by removing
PostFlair rendering
---
.../cards/components/redditCard/ArticleItem.tsx | 11 +----------
1 file changed, 1 insertion(+), 10 deletions(-)
diff --git a/src/features/cards/components/redditCard/ArticleItem.tsx b/src/features/cards/components/redditCard/ArticleItem.tsx
index d78b949f..615ae392 100644
--- a/src/features/cards/components/redditCard/ArticleItem.tsx
+++ b/src/features/cards/components/redditCard/ArticleItem.tsx
@@ -60,16 +60,7 @@ const ArticleItem = ({ item, analyticsTag }: BaseItemPropsType) => {
)}
-
- {item.flair_text && (
-
- )}
- {item.title}
-
+
{item.title}
From 9a6e2bb8ac44942c52dc2a03a63863041bbc0596 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 9 Nov 2025 19:52:04 +0100
Subject: [PATCH 36/89] refactor: remove FloatingFilter export from index.ts
---
src/components/Elements/index.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/components/Elements/index.ts b/src/components/Elements/index.ts
index 14add55f..7457df73 100644
--- a/src/components/Elements/index.ts
+++ b/src/components/Elements/index.ts
@@ -6,7 +6,6 @@ export * from './CardWithActions'
export * from './ChipsSet'
export * from './ClickableItem'
export * from './ColoredLanguagesBadges'
-export * from './FloatingFilter'
export * from './InlineTextFilter'
export * from './Modal'
export * from './Panel'
From d2d3505aba1a67aa6720301c36c428bb4b026024 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 9 Nov 2025 19:52:12 +0100
Subject: [PATCH 37/89] refactor: update Card component to improve settings
component rendering with responsive breakpoints
---
src/components/Elements/Card/Card.tsx | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/components/Elements/Card/Card.tsx b/src/components/Elements/Card/Card.tsx
index 676a4641..f2b1eec8 100644
--- a/src/components/Elements/Card/Card.tsx
+++ b/src/components/Elements/Card/Card.tsx
@@ -1,6 +1,8 @@
import clsx from 'clsx'
import React, { useEffect, useState } from 'react'
import { AdvBanner } from 'src/features/adv'
+import { DesktopBreakpoint } from 'src/providers/DesktopBreakpoint'
+import { MobileBreakpoint } from 'src/providers/MobileBreakpoint'
import { CardPropsType } from 'src/types'
type RootCardProps = CardPropsType & {
@@ -46,12 +48,17 @@ export const Card = ({
return (
+
+ {settingsComponent && {settingsComponent} }
+
{knob}
{icon} {titleComponent || label}{' '}
- {settingsComponent && (
- {settingsComponent}
- )}
+
+ {settingsComponent && (
+ {settingsComponent}
+ )}
+
{badge && {badge} }
From cb597db810904d29e115a82e3f088089d29356a3 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 9 Nov 2025 19:57:47 +0100
Subject: [PATCH 38/89] refactor: update FreecodecampCard component to improve
tag handling and optimize rendering
---
.../freecodecampCard/FreecodecampCard.tsx | 33 +++++++++++--------
1 file changed, 19 insertions(+), 14 deletions(-)
diff --git a/src/features/cards/components/freecodecampCard/FreecodecampCard.tsx b/src/features/cards/components/freecodecampCard/FreecodecampCard.tsx
index f752c06e..7b6c993d 100644
--- a/src/features/cards/components/freecodecampCard/FreecodecampCard.tsx
+++ b/src/features/cards/components/freecodecampCard/FreecodecampCard.tsx
@@ -1,49 +1,54 @@
-import { Card, FloatingFilter } from 'src/components/Elements'
+import { useCallback } from 'react'
+import { Card } from 'src/components/Elements'
import { ListPostComponent } from 'src/components/List/ListPostComponent'
import { Article, CardPropsType } from 'src/types'
import { useGetSourceArticles } from '../../api/getSourceArticles'
import { useSelectedTags } from '../../hooks/useSelectedTags'
-import { CardHeader } from '../CardHeader'
-import { CardSettings } from '../CardSettings'
+import { MemoizedCardHeader } from '../CardHeader'
+import { MemoizedCardSettings } from '../CardSettings'
import ArticleItem from './ArticleItem'
-const GLOBAL_TAG = { label: 'Global', value: 'programming' }
+const GLOBAL_TAG = { label: 'Global', value: 'global' }
export function FreecodecampCard(props: CardPropsType) {
const { meta } = props
- const { queryTags, selectedTag, cardSettings } = useSelectedTags({
+ const {
+ queryTags,
+ selectedTag,
+ cardSettings: { sortBy, language } = {},
+ } = useSelectedTags({
source: meta.value,
fallbackTag: GLOBAL_TAG,
})
const { data, isLoading } = useGetSourceArticles({
source: 'freecodecamp',
- tags: queryTags.map((tag) => tag.value),
+ tags: queryTags,
})
- const renderItem = (item: Article, index: number) => (
-
+ const renderItem = useCallback(
+ (item: Article) => ,
+ [meta.analyticsTag]
)
return (
+
}
settingsComponent={
-
}
{...props}>
-
Date: Sun, 9 Nov 2025 19:57:57 +0100
Subject: [PATCH 39/89] refactor: optimize DevtoCard component by removing
unused imports and improving tag handling
---
.../cards/components/devtoCard/DevtoCard.tsx | 31 +++++++++++--------
1 file changed, 18 insertions(+), 13 deletions(-)
diff --git a/src/features/cards/components/devtoCard/DevtoCard.tsx b/src/features/cards/components/devtoCard/DevtoCard.tsx
index a0ca936d..e0644e31 100644
--- a/src/features/cards/components/devtoCard/DevtoCard.tsx
+++ b/src/features/cards/components/devtoCard/DevtoCard.tsx
@@ -1,12 +1,13 @@
+import { useCallback } from 'react'
import { AiOutlineLike } from 'react-icons/ai'
import { BiCommentDetail } from 'react-icons/bi'
-import { Card, FloatingFilter } from 'src/components/Elements'
+import { Card } from 'src/components/Elements'
import { ListPostComponent } from 'src/components/List/ListPostComponent'
import { Article, CardPropsType } from 'src/types'
import { useGetSourceArticles } from '../../api/getSourceArticles'
import { useSelectedTags } from '../../hooks/useSelectedTags'
-import { CardHeader } from '../CardHeader'
-import { CardSettings } from '../CardSettings'
+import { MemoizedCardHeader } from '../CardHeader'
+import { MemoizedCardSettings } from '../CardSettings'
import ArticleItem from './ArticleItem'
const GLOBAL_TAG = { label: 'Global', value: 'programming' }
@@ -14,7 +15,11 @@ const GLOBAL_TAG = { label: 'Global', value: 'programming' }
export function DevtoCard(props: CardPropsType) {
const { meta } = props
- const { queryTags, selectedTag, cardSettings } = useSelectedTags({
+ const {
+ queryTags,
+ selectedTag,
+ cardSettings: { sortBy, language } = {},
+ } = useSelectedTags({
source: meta.value,
fallbackTag: GLOBAL_TAG,
})
@@ -25,25 +30,26 @@ export function DevtoCard(props: CardPropsType) {
isLoading,
} = useGetSourceArticles({
source: 'devto',
- tags: queryTags.map((tag) => tag.value),
+ tags: queryTags,
})
- const renderItem = (item: Article, index: number) => (
-
+ const renderItem = useCallback(
+ (item: Article) => ,
+ [meta.analyticsTag]
)
return (
+
}
settingsComponent={
- [
...defaults,
{
@@ -60,9 +66,8 @@ export function DevtoCard(props: CardPropsType) {
/>
}
{...props}>
-
Date: Sun, 9 Nov 2025 19:58:40 +0100
Subject: [PATCH 40/89] refactor: optimize CardHeader and CardSettings
components for improved performance and readability
---
src/features/cards/components/CardHeader.tsx | 23 ++++-----
.../cards/components/CardSettings.tsx | 48 ++++++++++++-------
2 files changed, 40 insertions(+), 31 deletions(-)
diff --git a/src/features/cards/components/CardHeader.tsx b/src/features/cards/components/CardHeader.tsx
index 4696573b..a1afc221 100644
--- a/src/features/cards/components/CardHeader.tsx
+++ b/src/features/cards/components/CardHeader.tsx
@@ -1,3 +1,4 @@
+import { memo, useMemo } from 'react'
import { MY_LANGUAGES_OPTION } from '../config'
type HeaderTitleProps = {
@@ -12,27 +13,23 @@ type HeaderTitleProps = {
}
children?: React.ReactNode
}
-export const CardHeader = ({ label, fallbackTag, selectedTag, children }: HeaderTitleProps) => {
+const CardHeader = ({ label, fallbackTag, selectedTag, children }: HeaderTitleProps) => {
if (children) {
return <>{children}>
}
- if (!selectedTag || selectedTag.value === fallbackTag.value) {
- return <>{label}>
- }
-
- if (selectedTag.value === MY_LANGUAGES_OPTION.value) {
- return (
- <>
- {label} {MY_LANGUAGES_OPTION.label}
- >
- )
- }
+ const highlightLabel = useMemo(() => {
+ if (!selectedTag || selectedTag.value === fallbackTag.value) return null
+ if (selectedTag.value === MY_LANGUAGES_OPTION.value) return MY_LANGUAGES_OPTION.label
+ return selectedTag.label
+ }, [selectedTag, fallbackTag])
return (
<>
{label}
- {selectedTag.label}
+ {highlightLabel && {highlightLabel} }
>
)
}
+
+export const MemoizedCardHeader = memo(CardHeader)
diff --git a/src/features/cards/components/CardSettings.tsx b/src/features/cards/components/CardSettings.tsx
index 5eaab830..14a19eb6 100644
--- a/src/features/cards/components/CardSettings.tsx
+++ b/src/features/cards/components/CardSettings.tsx
@@ -1,13 +1,16 @@
import { Menu, MenuDivider, MenuItem, SubMenu } from '@szhsin/react-menu'
-import { useCallback, useMemo } from 'react'
+import { memo, useCallback, useMemo } from 'react'
import { AiOutlineCode } from 'react-icons/ai'
import { BsBoxArrowInUpRight } from 'react-icons/bs'
+import { FiFilter } from 'react-icons/fi'
import { GoGear } from 'react-icons/go'
import { IoTrashBinOutline } from 'react-icons/io5'
import { LiaSortSolid } from 'react-icons/lia'
import { MdDateRange } from 'react-icons/md'
+import { useMediaQuery } from 'react-responsive'
import { ref } from 'src/config'
import { useUserPreferences } from 'src/stores/preferences'
+import { useShallow } from 'zustand/shallow'
import { MY_LANGUAGES_OPTION } from '../config'
type SortOption = { label: string; value: string; icon?: React.ReactNode }
@@ -27,7 +30,7 @@ type CardSettingsProps = {
const DEFAULT_SORT_OPTIONS = [{ label: 'Newest', value: 'published_at', icon: }]
const SPECIAL_LABELS = ['global', MY_LANGUAGES_OPTION.label.toLowerCase()]
-export const CardSettings = ({
+const CardSettings = ({
id,
url,
sortBy,
@@ -38,30 +41,34 @@ export const CardSettings = ({
customStartMenuItems,
sortOptions,
}: CardSettingsProps) => {
- const userSelectedTags = useUserPreferences((state) => state.userSelectedTags)
- const openLinksNewTab = useUserPreferences((state) => state.openLinksNewTab)
- const removeCard = useUserPreferences((state) => state.removeCard)
- const setCardSettings = useUserPreferences((state) => state.setCardSettings)
- const cardSettings = useUserPreferences((state) => state.cardsSettings[id])
+ const { userSelectedTags, openLinksNewTab, removeCard, setCardSettings, cardSettings } =
+ useUserPreferences(
+ useShallow((state) => ({
+ userSelectedTags: state.userSelectedTags,
+ openLinksNewTab: state.openLinksNewTab,
+ removeCard: state.removeCard,
+ setCardSettings: state.setCardSettings,
+ cardSettings: state.cardsSettings?.[id],
+ }))
+ )
const userTagsMemo = useMemo(() => {
- const newTags = userSelectedTags.sort((a, b) => a.label.localeCompare(b.label))
- let tags = [...newTags]
- if (globalTag) {
- tags = [...tags, globalTag]
- }
-
- tags = [...tags, MY_LANGUAGES_OPTION]
+ const tags = [...userSelectedTags]
+ .sort((a, b) => a.label.localeCompare(b.label))
+ .concat(globalTag ? [globalTag] : [])
+ .concat(MY_LANGUAGES_OPTION)
return tags
}, [userSelectedTags, globalTag])
- const resolvedSortOptions =
- typeof sortOptions === 'function'
+ const resolvedSortOptions = useMemo(() => {
+ return typeof sortOptions === 'function'
? sortOptions(DEFAULT_SORT_OPTIONS)
: sortOptions || DEFAULT_SORT_OPTIONS
+ }, [sortOptions])
const onOpenSourceUrlClicked = useCallback(() => {
- let link = `${url}?${ref}`
+ if (!url) return
+ const link = `${url}?${ref}`
window.open(link, openLinksNewTab ? '_blank' : '_self')
}, [url, openLinksNewTab])
@@ -70,9 +77,12 @@ export const CardSettings = ({
[userTagsMemo]
)
+ const isMobile = useMediaQuery({ maxWidth: 767 })
+ const menuIcon = isMobile ? :
+
return (
}
+ menuButton={menuIcon}
theming="dark"
portal={true}
className={`menuItem`}
@@ -144,3 +154,5 @@ export const CardSettings = ({
)
}
+
+export const MemoizedCardSettings = memo(CardSettings)
From 4e958539730099b15b114832c7cb1bc665146ecc Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 9 Nov 2025 19:58:51 +0100
Subject: [PATCH 41/89] refactor: add value properties to occupations in
HelloTab component for improved data handling
---
src/features/onboarding/components/steps/HelloTab.tsx | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/features/onboarding/components/steps/HelloTab.tsx b/src/features/onboarding/components/steps/HelloTab.tsx
index 40d75cb2..ed3529c5 100644
--- a/src/features/onboarding/components/steps/HelloTab.tsx
+++ b/src/features/onboarding/components/steps/HelloTab.tsx
@@ -11,12 +11,14 @@ import { Occupation } from '../../types'
const OCCUPATIONS: Occupation[] = [
{
title: 'Front-End Engineer',
+ value: 'frontend',
icon: FaPaintBrush,
sources: ['devto', 'github', 'medium', 'hashnode'],
tags: ['frontend', 'javascript', 'typescript', 'css', 'react', 'vue', 'angular'],
},
{
title: 'Back-End Engineer',
+ value: 'backend',
icon: BsFillGearFill,
sources: ['devto', 'github', 'medium', 'hashnode'],
tags: ['backend', 'go', 'php', 'ruby', 'rust', 'r'],
@@ -24,11 +26,13 @@ const OCCUPATIONS: Occupation[] = [
{
title: 'Full Stack Engineer',
icon: RiDeviceFill,
+ value: 'fullstack',
sources: ['devto', 'github', 'medium', 'hashnode'],
tags: ['javascript', 'typescript', 'php', 'ruby', 'rust'],
},
{
title: 'Mobile',
+ value: 'mobile',
icon: AiFillMobile,
sources: ['reddit', 'github', 'medium', 'hashnode'],
tags: [
@@ -45,30 +49,35 @@ const OCCUPATIONS: Occupation[] = [
},
{
title: 'Devops Engineer',
+ value: 'devops',
icon: FaServer,
sources: ['freecodecamp', 'github', 'reddit', 'devto'],
tags: ['devops', 'kubernetes', 'docker', 'bash'],
},
{
title: 'Data Engineer',
+ value: 'data',
icon: FaDatabase,
sources: ['freecodecamp', 'github', 'reddit', 'devto'],
tags: ['data science', 'python', 'artificial intelligence', 'machine learning'],
},
{
title: 'Security Engineer',
+ value: 'security',
icon: AiFillSecurityScan,
sources: ['freecodecamp', 'github', 'reddit', 'devto'],
tags: ['security', 'cpp', 'bash', 'python'],
},
{
title: 'ML Engineer',
+ value: 'ai',
icon: FaRobot,
sources: ['github', 'freecodecamp', 'hackernews', 'devto'],
tags: ['machine learning', 'artificial intelligence', 'python'],
},
{
title: 'Other',
+ value: 'other',
icon: TbDots,
sources: ['hackernews', 'github', 'producthunt', 'devto'],
tags: ['webdev', 'mobile'],
@@ -84,7 +93,7 @@ export const HelloTab = () => {
const onStartClicked = () => {
const selectedOccupation = OCCUPATIONS.find((occ) => occ.title === occupation)
if (selectedOccupation) {
- setOccupation(selectedOccupation.title)
+ setOccupation(selectedOccupation.value)
setCards(
selectedOccupation.sources.map((source, index) => ({
id: index,
From 383dc675a0a1747b91cca4b005d3b8ee57395f72 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 9 Nov 2025 19:59:13 +0100
Subject: [PATCH 42/89] refactor: optimize ListComponent by improving
memoization and error handling
---
src/components/List/ListComponent.tsx | 30 +++++++++++++--------------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/src/components/List/ListComponent.tsx b/src/components/List/ListComponent.tsx
index 7890a61f..c283cf38 100644
--- a/src/components/List/ListComponent.tsx
+++ b/src/components/List/ListComponent.tsx
@@ -1,4 +1,4 @@
-import React, { ReactNode, useMemo } from 'react'
+import React, { memo, ReactNode, useMemo } from 'react'
import { Placeholder } from 'src/components/placeholders'
import { MAX_ITEMS_PER_CARD } from 'src/config'
@@ -6,7 +6,7 @@ type PlaceholdersProps = {
placeholder: ReactNode
}
-const Placeholders = React.memo(({ placeholder }) => {
+const Placeholders = memo(({ placeholder }) => {
return (
<>
{[...Array(7)].map((_, i) => (
@@ -42,18 +42,6 @@ export function ListComponent(props: ListComponentPropsType) {
limit = MAX_ITEMS_PER_CARD,
} = props
- if (error) {
- return {error?.message || error}
- }
-
- if (items && items.length == 0) {
- return (
-
- No items found, try adjusting your filter or choosing a different tag.
-
- )
- }
-
const sortedData = useMemo(() => {
if (!items || items.length == 0) return []
if (!sortBy) return items
@@ -88,7 +76,19 @@ export function ListComponent(props: ListComponentPropsType) {
} catch (e) {
return []
}
- }, [sortedData])
+ }, [sortedData, header, renderItem, limit])
+
+ if (error) {
+ return {error?.message || error}
+ }
+
+ if (items && items.length == 0) {
+ return (
+
+ No items found, try adjusting your filter or choosing a different tag.
+
+ )
+ }
return <>{isLoading ? : enrichedItems}>
}
From d58d4d17bdb396e00fc573c40c3e44c367c51317 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 9 Nov 2025 19:59:27 +0100
Subject: [PATCH 43/89] refactor: enhance UserTags component by improving tag
handling and integrating user preferences
---
src/components/Elements/UserTags/UserTags.tsx | 24 ++++++++++++++++---
1 file changed, 21 insertions(+), 3 deletions(-)
diff --git a/src/components/Elements/UserTags/UserTags.tsx b/src/components/Elements/UserTags/UserTags.tsx
index c07bccb7..68d8d4cb 100644
--- a/src/components/Elements/UserTags/UserTags.tsx
+++ b/src/components/Elements/UserTags/UserTags.tsx
@@ -1,16 +1,34 @@
+import { useCallback } from 'react'
import { TiPlus } from 'react-icons/ti'
import { Link } from 'react-router-dom'
import { useUserPreferences } from 'src/stores/preferences'
+import { useShallow } from 'zustand/shallow'
export const UserTags = () => {
- const { userSelectedTags } = useUserPreferences()
+ const { cards, userSelectedTags, cardsSettings, setCardSettings } = useUserPreferences(
+ useShallow((state) => ({
+ cards: state.cards,
+ userSelectedTags: state.userSelectedTags,
+ cardsSettings: state.cardsSettings,
+ setCardSettings: state.setCardSettings,
+ }))
+ )
+
+ const onTagClicked = useCallback((tagValue: string) => {
+ cards.forEach((card) => {
+ setCardSettings(card.name, {
+ ...cardsSettings[card.id],
+ language: tagValue,
+ })
+ })
+ }, [])
return (
{userSelectedTags.map((tag, index) => (
-
+ onTagClicked(tag.value)}>
{tag.value}
-
+
))}
From b44f7863b8ad8efd89296b2966e381f94c720615 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 9 Nov 2025 20:31:28 +0100
Subject: [PATCH 44/89] feat: implement lazy loading for cards using
IntersectionObserver and refactor related components for improved performance
---
src/components/Elements/Card/Card.tsx | 110 +++++++++---------
.../cards/components/aiCard/AICard.tsx | 26 ++++-
.../conferencesCard/ConferencesCard.tsx | 38 ++++--
.../cards/components/devtoCard/DevtoCard.tsx | 6 +
.../freecodecampCard/FreecodecampCard.tsx | 6 +
.../components/githubCard/GithubCard.tsx | 69 ++++++-----
.../hackernewsCard/HackernewsCard.tsx | 25 ++--
.../components/hashnodeCard/HashnodeCard.tsx | 63 +++++-----
.../indiehackersCard/IndiehackersCard.tsx | 25 ++--
.../components/lobstersCard/LobstersCard.tsx | 25 ++--
.../components/mediumCard/MediumCard.tsx | 50 +++++---
.../producthuntCard/ProducthuntCard.tsx | 23 ++--
.../components/redditCard/RedditCard.tsx | 50 +++++---
.../components/rssCard/CustomRssCard.tsx | 20 +++-
src/features/cards/hooks/useLazyListLoad.tsx | 37 ++++++
15 files changed, 367 insertions(+), 206 deletions(-)
create mode 100644 src/features/cards/hooks/useLazyListLoad.tsx
diff --git a/src/components/Elements/Card/Card.tsx b/src/components/Elements/Card/Card.tsx
index f2b1eec8..3396218c 100644
--- a/src/components/Elements/Card/Card.tsx
+++ b/src/components/Elements/Card/Card.tsx
@@ -11,64 +11,68 @@ type RootCardProps = CardPropsType & {
settingsComponent?: React.ReactNode
fullBlock?: boolean
}
+export const Card = React.forwardRef(
+ (
+ {
+ meta,
+ titleComponent,
+ settingsComponent,
+ className,
+ withAds = false,
+ children,
+ fullBlock = false,
+ knob,
+ },
+ ref
+ ) => {
+ const { icon, label, badge } = meta
+ const [canAdsLoad, setCanAdsLoad] = useState(true)
-export const Card = ({
- meta,
- titleComponent,
- settingsComponent,
- className,
- withAds = false,
- children,
- fullBlock = false,
- knob,
-}: RootCardProps) => {
- const { icon, label, badge } = meta
- const [canAdsLoad, setCanAdsLoad] = useState(true)
-
- useEffect(() => {
- if (!withAds) {
- return
- }
-
- const handleClassChange = () => {
- if (document.documentElement.classList.contains('dndState')) {
- setCanAdsLoad(false)
- } else {
- setCanAdsLoad(true)
+ useEffect(() => {
+ if (!withAds) {
+ return
}
- }
- const observer = new MutationObserver(handleClassChange)
- observer.observe(document.documentElement, { attributes: true })
+ const handleClassChange = () => {
+ if (document.documentElement.classList.contains('dndState')) {
+ setCanAdsLoad(false)
+ } else {
+ setCanAdsLoad(true)
+ }
+ }
- return () => {
- observer.disconnect()
- }
- }, [withAds])
+ const observer = new MutationObserver(handleClassChange)
+ observer.observe(document.documentElement, { attributes: true })
- return (
-
-
- {settingsComponent && {settingsComponent} }
-
-
- {knob}
- {icon} {titleComponent || label}{' '}
-
- {settingsComponent && (
- {settingsComponent}
- )}
-
- {badge && {badge} }
-
+ return () => {
+ observer.disconnect()
+ }
+ }, [withAds])
- {canAdsLoad && withAds && (
-
-
+ return (
+
+
+ {settingsComponent && {settingsComponent} }
+
+
+ {knob}
+ {icon} {titleComponent || label}{' '}
+
+ {settingsComponent && (
+ {settingsComponent}
+ )}
+
+ {badge && {badge} }
- )}
-
{children}
-
- )
-}
+ {canAdsLoad && withAds && (
+
+ )}
+
+
{children}
+
+ )
+ }
+)
diff --git a/src/features/cards/components/aiCard/AICard.tsx b/src/features/cards/components/aiCard/AICard.tsx
index 13b36c12..6ab131f5 100644
--- a/src/features/cards/components/aiCard/AICard.tsx
+++ b/src/features/cards/components/aiCard/AICard.tsx
@@ -1,32 +1,46 @@
+import { useCallback, useMemo } from 'react'
import { Card } from 'src/components/Elements'
import { ListComponent } from 'src/components/List'
import { FeedItem, useGetFeed } from 'src/features/feed'
import { useUserPreferences } from 'src/stores/preferences'
import { CardPropsType, FeedItemData } from 'src/types'
-import { CardSettings } from '../CardSettings'
+import { useShallow } from 'zustand/shallow'
+import { useLazyListLoad } from '../../hooks/useLazyListLoad'
+import { MemoizedCardSettings } from '../CardSettings'
export function AICard(props: CardPropsType) {
const { meta } = props
- const { userSelectedTags } = useUserPreferences()
+ const userSelectedTags = useUserPreferences(useShallow((state) => state.userSelectedTags))
+ const { ref, isVisible } = useLazyListLoad()
+ const queryTags = useMemo(
+ () => userSelectedTags.map((tag) => tag.label.toLocaleLowerCase()),
+ [userSelectedTags]
+ )
+
const {
data: articles,
isLoading,
error,
} = useGetFeed({
- tags: userSelectedTags.map((tag) => tag.label.toLocaleLowerCase()),
+ tags: queryTags,
config: {
cacheTime: 0,
staleTime: 0,
useErrorBoundary: false,
+ enabled: isVisible,
},
})
- const renderItem = (item: FeedItemData, index: number) => (
-
+ const renderItem = useCallback(
+ (item: FeedItemData) =>
,
+ [meta.analyticsTag]
)
return (
-
} {...props}>
+
}
+ {...props}>
items={articles?.pages.flatMap((page) => page.data) || []}
error={error}
diff --git a/src/features/cards/components/conferencesCard/ConferencesCard.tsx b/src/features/cards/components/conferencesCard/ConferencesCard.tsx
index 4972656e..84173fac 100644
--- a/src/features/cards/components/conferencesCard/ConferencesCard.tsx
+++ b/src/features/cards/components/conferencesCard/ConferencesCard.tsx
@@ -1,39 +1,53 @@
+import { useCallback } from 'react'
import { Card } from 'src/components/Elements'
import { ListConferenceComponent } from 'src/components/List/ListConferenceComponent'
import { CardPropsType, Conference } from 'src/types'
import { useGetConferences } from '../../api/getConferences'
+import { useLazyListLoad } from '../../hooks/useLazyListLoad'
import { useSelectedTags } from '../../hooks/useSelectedTags'
-import { CardHeader } from '../CardHeader'
-import { CardSettings } from '../CardSettings'
+import { MemoizedCardHeader } from '../CardHeader'
+import { MemoizedCardSettings } from '../CardSettings'
import ConferenceItem from './ConferenceItem'
-const GLOBAL_TAG = { label: 'Global', value: 'tech' }
+const GLOBAL_TAG = { label: 'Global', value: 'general' }
export function ConferencesCard(props: CardPropsType) {
const { meta } = props
- const { queryTags, selectedTag, cardSettings } = useSelectedTags({
+ const { ref, isVisible } = useLazyListLoad()
+ const {
+ queryTags,
+ selectedTag,
+ cardSettings: { sortBy, language } = {},
+ } = useSelectedTags({
source: meta.value,
fallbackTag: GLOBAL_TAG,
})
const { isLoading, data: results } = useGetConferences({
- tags: queryTags.map((tag) => tag.value),
+ tags: queryTags,
+ config: {
+ enabled: isVisible,
+ },
})
- const renderItem = (item: Conference, index: number) => (
-
+ const renderItem = useCallback(
+ (item: Conference) => (
+
+ ),
+ [meta.analyticsTag]
)
return (
+
}
settingsComponent={
-
}>
}
diff --git a/src/features/cards/components/freecodecampCard/FreecodecampCard.tsx b/src/features/cards/components/freecodecampCard/FreecodecampCard.tsx
index 7b6c993d..34bd637c 100644
--- a/src/features/cards/components/freecodecampCard/FreecodecampCard.tsx
+++ b/src/features/cards/components/freecodecampCard/FreecodecampCard.tsx
@@ -3,6 +3,7 @@ import { Card } from 'src/components/Elements'
import { ListPostComponent } from 'src/components/List/ListPostComponent'
import { Article, CardPropsType } from 'src/types'
import { useGetSourceArticles } from '../../api/getSourceArticles'
+import { useLazyListLoad } from '../../hooks/useLazyListLoad'
import { useSelectedTags } from '../../hooks/useSelectedTags'
import { MemoizedCardHeader } from '../CardHeader'
import { MemoizedCardSettings } from '../CardSettings'
@@ -12,6 +13,7 @@ const GLOBAL_TAG = { label: 'Global', value: 'global' }
export function FreecodecampCard(props: CardPropsType) {
const { meta } = props
+ const { ref, isVisible } = useLazyListLoad()
const {
queryTags,
selectedTag,
@@ -24,6 +26,9 @@ export function FreecodecampCard(props: CardPropsType) {
const { data, isLoading } = useGetSourceArticles({
source: 'freecodecamp',
tags: queryTags,
+ config: {
+ enabled: isVisible,
+ },
})
const renderItem = useCallback(
@@ -33,6 +38,7 @@ export function FreecodecampCard(props: CardPropsType) {
return (
}
diff --git a/src/features/cards/components/githubCard/GithubCard.tsx b/src/features/cards/components/githubCard/GithubCard.tsx
index 9211935f..df2d2cf8 100644
--- a/src/features/cards/components/githubCard/GithubCard.tsx
+++ b/src/features/cards/components/githubCard/GithubCard.tsx
@@ -1,14 +1,15 @@
import { MenuDivider, MenuItem } from '@szhsin/react-menu'
-import { useMemo } from 'react'
+import { useCallback, useMemo } from 'react'
import { VscRepoForked, VscStarFull } from 'react-icons/vsc'
-import { Card, FloatingFilter } from 'src/components/Elements'
+import { Card } from 'src/components/Elements'
import { ListRepoComponent } from 'src/components/List/ListRepoComponent'
import { dateRanges } from 'src/config'
import { useUserPreferences } from 'src/stores/preferences'
import { CardPropsType, Repository } from 'src/types'
import { useGetGithubRepos } from '../../api/getGithubRepos'
+import { useLazyListLoad } from '../../hooks/useLazyListLoad'
import { useSelectedTags } from '../../hooks/useSelectedTags'
-import { CardSettings } from '../CardSettings'
+import { MemoizedCardSettings } from '../CardSettings'
import RepoItem from './RepoItem'
const GLOBAL_TAG = { label: 'Global', value: 'global' }
@@ -16,48 +17,63 @@ const GLOBAL_TAG = { label: 'Global', value: 'global' }
export function GithubCard(props: CardPropsType) {
const { meta } = props
+ const { ref, isVisible } = useLazyListLoad()
const setCardSettings = useUserPreferences((state) => state.setCardSettings)
- const { queryTags, selectedTag, cardSettings } = useSelectedTags({
+ const {
+ queryTags,
+ selectedTag,
+ cardSettings: { dateRange, sortBy, language } = {},
+ } = useSelectedTags({
source: meta.value,
fallbackTag: GLOBAL_TAG,
})
const selectedDateRange = useMemo(
- () => dateRanges.find((date) => date.value === cardSettings?.dateRange) || dateRanges[0],
- [cardSettings]
+ () => dateRanges.find((date) => date.value === dateRange) || dateRanges[0],
+ [dateRange]
)
const { data, error, isLoading } = useGetGithubRepos({
- tags: queryTags.map((tag) => tag.value),
+ tags: queryTags,
dateRange: selectedDateRange.value,
+ config: {
+ enabled: isVisible,
+ },
})
- const renderItem = (item: Repository, index: number) => (
-
+ const renderItem = useCallback(
+ (item: Repository) => (
+
+ ),
+ [meta.analyticsTag, selectedTag]
)
+ const headerTitle = useMemo(() => {
+ return (
+ <>
+ Github {selectedTag.label} {' '}
+ {selectedDateRange.label.toLowerCase()}
+ >
+ )
+ }, [selectedTag, selectedDateRange])
+
return (
- Github {selectedTag.label} {' '}
- {selectedDateRange.label.toLowerCase()}
-
- }
+ titleComponent={headerTitle}
settingsComponent={
-
{dateRanges.map((date) => (
@@ -66,7 +82,7 @@ export function GithubCard(props: CardPropsType) {
value={date.value}
disabled={selectedDateRange.value === date.value}
onClick={() => {
- setCardSettings(meta.value, { ...cardSettings, dateRange: date.value })
+ setCardSettings(meta.value, { dateRange: date.value, language, sortBy })
}}>
{date.label}
@@ -89,9 +105,8 @@ export function GithubCard(props: CardPropsType) {
/>
}
{...props}>
-
state.cardsSettings[meta.value])
+ const { ref, isVisible } = useLazyListLoad()
+ const sortBy = useUserPreferences(
+ useShallow((state) => state.cardsSettings?.[meta.value]?.sortBy)
+ )
const { data, isLoading, error } = useGetSourceArticles({
source: 'hackernews',
+ config: {
+ enabled: isVisible,
+ },
})
- const renderItem = (item: Article, index: number) => (
-
+ const renderItem = useCallback(
+ (item: Article) => ,
+ [meta.analyticsTag]
)
return (
[
...defaults,
{
@@ -43,7 +54,7 @@ export function HackernewsCard(props: CardPropsType) {
/>
}>
tag.value),
+ tags: queryTags,
+ config: {
+ enabled: isVisible,
+ },
})
- const renderItem = (item: Article, index: number) => (
-
+ const renderItem = useCallback(
+ (item: Article) => ,
+ [meta.analyticsTag]
)
return (
+
}
settingsComponent={
- [
- ...defaults,
- {
- label: 'Reactions',
- value: 'points_count',
- icon: ,
- },
- {
- label: 'Comments',
- value: 'comments_count',
- icon: ,
- },
- ]}
+ sortOptions={[]}
/>
}
{...props}>
-
-
+ sortBy={sortBy as keyof Article}
items={data}
isLoading={isLoading}
renderItem={renderItem}
diff --git a/src/features/cards/components/indiehackersCard/IndiehackersCard.tsx b/src/features/cards/components/indiehackersCard/IndiehackersCard.tsx
index c9161073..7d9ee416 100644
--- a/src/features/cards/components/indiehackersCard/IndiehackersCard.tsx
+++ b/src/features/cards/components/indiehackersCard/IndiehackersCard.tsx
@@ -1,32 +1,43 @@
+import { useCallback } from 'react'
import { FaChevronUp } from 'react-icons/fa'
import { Card } from 'src/components/Elements'
import { ListPostComponent } from 'src/components/List/ListPostComponent'
import { useUserPreferences } from 'src/stores/preferences'
import { Article, CardPropsType } from 'src/types'
+import { useShallow } from 'zustand/shallow'
import { useGetSourceArticles } from '../../api/getSourceArticles'
-import { CardSettings } from '../CardSettings'
+import { useLazyListLoad } from '../../hooks/useLazyListLoad'
+import { MemoizedCardSettings } from '../CardSettings'
import { ArticleItem } from './ArticleItem'
export function IndiehackersCard(props: CardPropsType) {
const { meta } = props
- const cardSettings = useUserPreferences((state) => state.cardsSettings[meta.value])
+ const { ref, isVisible } = useLazyListLoad()
+ const sortBy = useUserPreferences(
+ useShallow((state) => state.cardsSettings?.[meta.value]?.sortBy)
+ )
const { data, isLoading, error } = useGetSourceArticles({
source: 'indiehackers',
+ config: {
+ enabled: isVisible,
+ },
})
- const renderItem = (item: Article, index: number) => (
-
+ const renderItem = useCallback(
+ (item: Article) => ,
+ [meta.analyticsTag]
)
return (
[
...defaults,
{
@@ -39,7 +50,7 @@ export function IndiehackersCard(props: CardPropsType) {
}
{...props}>
state.cardsSettings[meta.value])
+ const { ref, isVisible } = useLazyListLoad()
+ const sortBy = useUserPreferences(
+ useShallow((state) => state.cardsSettings?.[meta.value]?.sortBy)
+ )
const { data, isLoading, error } = useGetSourceArticles({
source: 'lobsters',
+ config: {
+ enabled: isVisible,
+ },
})
- const renderItem = (item: Article, index: number) => (
-
+ const renderItem = useCallback(
+ (item: Article) => ,
+ [meta.analyticsTag]
)
return (
[
...defaults,
{
@@ -38,7 +49,7 @@ export function LobstersCard(props: CardPropsType) {
/>
}>
tag.value),
+ tags: queryTags,
+ config: {
+ enabled: isVisible,
+ },
})
- const renderItem = (item: Article, index: number) => (
-
+ const renderItem = useCallback(
+ (item: Article) => (
+
+ ),
+ [selectedTag, meta.analyticsTag]
)
return (
+
}
settingsComponent={
- [
...defaults,
{
@@ -60,9 +73,8 @@ export function MediumCard(props: CardPropsType) {
/>
}
{...props}>
-
state.cardsSettings?.[meta.value])
+ const { ref, isVisible } = useLazyListLoad()
+ const sortBy = useUserPreferences(
+ useShallow((state) => state.cardsSettings?.[meta.value]?.sortBy)
+ )
const {
data: products = [],
@@ -20,24 +26,25 @@ export function ProductHuntCard(props: CardPropsType) {
} = useGeProductHuntProducts({
date: new Date().toISOString().split('T')[0],
config: {
- staleTime: 900000, //15 minutes
- cacheTime: 3600000, // 1 Day
+ enabled: isVisible,
},
})
- const renderItem = (item: Article, index: number) => (
-
+ const renderItem = useCallback(
+ (item: Article) => ,
+ [meta.analyticsTag]
)
return (
tag.value),
+ tags: queryTags,
+ config: {
+ enabled: isVisible,
+ },
})
- const renderItem = (item: Article, index: number) => (
-
+ const renderItem = useCallback(
+ (item: Article) => (
+
+ ),
+ [selectedTag, meta.analyticsTag]
)
return (
+
}
{...props}
settingsComponent={
- [
...defaults,
{
@@ -55,9 +68,8 @@ export function RedditCard(props: CardPropsType) {
]}
/>
}>
-
{
export function CustomRssCard(props: CardPropsType) {
const { meta } = props
- const { data = [], isLoading } = useRssFeed({ feedUrl: meta.feedUrl || '' })
+ const { ref, isVisible } = useLazyListLoad()
+ const { data = [], isLoading } = useRssFeed({
+ feedUrl: meta.feedUrl || '',
+ config: {
+ enabled: isVisible,
+ },
+ })
- const renderItem = (item: Article, index: number) => (
-
+ const renderItem = useCallback(
+ (item: Article) => ,
+ [meta.analyticsTag]
)
return (
}
{...props}
meta={{ ...meta, icon: }}
settingsComponent={
- {
+ const ref = useRef(null)
+ const [isVisible, setIsVisible] = useState(false)
+
+ useEffect(() => {
+ if (!ref.current) return
+
+ const observer = new IntersectionObserver(
+ (entries) => {
+ const entry = entries[0]
+ if (entry.isIntersecting) {
+ setIsVisible(true)
+ observer.unobserve(entry.target)
+ }
+ },
+ { threshold: 0.1, rootMargin }
+ )
+
+ observer.observe(ref.current)
+
+ return () => observer.disconnect()
+ }, [rootMargin])
+
+ return { ref, isVisible }
+}
From a05af28f73f5ad3390a71c2ad2c9e5567a98f7b9 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 16 Nov 2025 19:32:44 +0100
Subject: [PATCH 45/89] refactor: update streak handling in AppLayout and
useAuth for improved logic and state management
---
src/components/Layout/AppLayout.tsx | 6 +++---
src/features/auth/hooks/useAuth.ts | 18 ++++++++++++++++--
src/features/auth/stores/authStore.ts | 23 ++++++++++++++++++++---
3 files changed, 39 insertions(+), 8 deletions(-)
diff --git a/src/components/Layout/AppLayout.tsx b/src/components/Layout/AppLayout.tsx
index bcafb398..b20b5795 100644
--- a/src/components/Layout/AppLayout.tsx
+++ b/src/components/Layout/AppLayout.tsx
@@ -11,17 +11,17 @@ import { AuthProvider } from 'src/providers/AuthProvider'
import { Header } from './Header'
export const AppLayout = () => {
- const { isAuthModalOpen, setStreak, isConnected } = useAuth()
+ const { isAuthModalOpen, setStreak, shouldCountStreak } = useAuth()
const postStreakMutation = usePostStreak()
useEffect(() => {
- if (isConnected) {
+ if (shouldCountStreak()) {
postStreakMutation.mutateAsync(undefined).then((data) => {
setStreak(data.streak)
identifyUserStreak(data.streak)
})
}
- }, [isConnected])
+ }, [shouldCountStreak])
return (
diff --git a/src/features/auth/hooks/useAuth.ts b/src/features/auth/hooks/useAuth.ts
index 93fcf6a5..953924d4 100644
--- a/src/features/auth/hooks/useAuth.ts
+++ b/src/features/auth/hooks/useAuth.ts
@@ -1,4 +1,5 @@
import { signOut } from 'firebase/auth/web-extension'
+import { useCallback } from 'react'
import { AuthModalStore, AuthStore } from 'src/features/auth'
import { trackUserDisconnect } from 'src/lib/analytics'
import { firebaseAuth } from 'src/lib/firebase'
@@ -9,17 +10,30 @@ export const useAuth = () => {
const isConnected = authStore.user != null
- const logout = async () => {
+ const shouldCountStreak = useCallback(() => {
+ if (!isConnected) return false
+
+ const last = authStore.lastStreakUpdate
+ if (!last) return true
+
+ const today = new Date().toDateString()
+ const lastDay = new Date(last).toDateString()
+
+ return today !== lastDay
+ }, [isConnected, authStore.lastStreakUpdate])
+
+ const logout = useCallback(async () => {
trackUserDisconnect()
signOut(firebaseAuth)
authStore.clear()
return await firebaseAuth.signOut()
- }
+ }, [authStore])
return {
...authModalStore,
...authStore,
isConnected,
+ shouldCountStreak,
logout,
}
}
diff --git a/src/features/auth/stores/authStore.ts b/src/features/auth/stores/authStore.ts
index 17c18ebc..ab369025 100644
--- a/src/features/auth/stores/authStore.ts
+++ b/src/features/auth/stores/authStore.ts
@@ -4,20 +4,25 @@ import { persist } from 'zustand/middleware'
type AuthState = {
user: User | null
+ lastStreakUpdate?: number
providerId: string | null
}
type AuthActions = {
initState: (state: AuthState) => void
setStreak: (streak: number) => void
+ setLastStreakUpdate: (timestamp: number) => void
clear: () => void
}
+type AuthStoreType = AuthState & AuthActions
export const AuthStore = create(
- persist(
+ persist(
(set) => ({
user: null,
providerId: null,
+ lastStreakUpdate: undefined,
+ setLastStreakUpdate: (timestamp: number) => set({ lastStreakUpdate: timestamp }),
initState: (newState: AuthState) =>
set({
user: newState.user,
@@ -25,15 +30,27 @@ export const AuthStore = create(
}),
setStreak: (streak: number) =>
set((state) => ({
+ lastStreakUpdate: Date.now(),
user: {
...state.user!,
streak,
},
})),
- clear: () => set({ user: null }),
+ clear: () => set({ user: null, lastStreakUpdate: undefined }),
}),
{
- name: 'auth-storage', // key in localStorage
+ version: 1,
+ name: 'auth-storage',
+ migrate: (persistedState, version) => {
+ const typedPersistedState = persistedState as unknown as AuthStoreType
+ if (version === 0) {
+ return {
+ ...typedPersistedState,
+ lastStreakUpdate: undefined,
+ }
+ }
+ return typedPersistedState
+ },
}
)
)
From bb3f5d8e004ffe30c42fcbb476e6473f700e4125 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 16 Nov 2025 19:32:54 +0100
Subject: [PATCH 46/89] refactor: update button labels in AuthModal to clarify
last used provider
---
src/features/auth/components/AuthModal.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/features/auth/components/AuthModal.tsx b/src/features/auth/components/AuthModal.tsx
index e555c850..518e9442 100644
--- a/src/features/auth/components/AuthModal.tsx
+++ b/src/features/auth/components/AuthModal.tsx
@@ -98,7 +98,7 @@ export const AuthModal = ({ showAuth }: AuthModalProps) => {
}}
className="relative"
size="medium">
- {providerId === 'github.com' && Last }
+ {providerId === 'github.com' && Last used }
Connect with Github
{
onClick={() => signIn(googleAuthProvider)}
className="relative"
size="medium">
- {providerId === 'google.com' && Last }
+ {providerId === 'google.com' && Last used }
Connect with Google
From f82ccc056cbac8c51f87b6d8055698c0c9034ab5 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 16 Nov 2025 19:33:06 +0100
Subject: [PATCH 47/89] refactor: improve ArticleItem component by utilizing
user preferences for listing mode
---
.../freecodecampCard/ArticleItem.tsx | 26 +++++++++++--------
1 file changed, 15 insertions(+), 11 deletions(-)
diff --git a/src/features/cards/components/freecodecampCard/ArticleItem.tsx b/src/features/cards/components/freecodecampCard/ArticleItem.tsx
index 32babafa..01262328 100644
--- a/src/features/cards/components/freecodecampCard/ArticleItem.tsx
+++ b/src/features/cards/components/freecodecampCard/ArticleItem.tsx
@@ -1,11 +1,13 @@
import { MdAccessTime } from 'react-icons/md'
import { CardItemWithActions, CardLink, ColoredLanguagesBadge } from 'src/components/Elements'
import { Attributes } from 'src/lib/analytics'
+import { useUserPreferences } from 'src/stores/preferences'
import { Article, BaseItemPropsType } from 'src/types'
import { format } from 'timeago.js'
const ArticleItem = (props: BaseItemPropsType) => {
const { item, selectedTag, analyticsTag } = props
+ const { listingMode } = useUserPreferences()
return (
) => {
}}>
{item.title}
- <>
-
-
-
- {format(new Date(item.published_at))}
-
-
-
-
-
- >
+ {listingMode === 'normal' && (
+ <>
+
+
+
+ {format(new Date(item.published_at))}
+
+
+
+
+
+ >
+ )}
>
}
/>
From f805f0ee18e0fd237bc71ff8b62c436f6de0382b Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 16 Nov 2025 19:33:47 +0100
Subject: [PATCH 48/89] refactor: center align tabFooter buttons for improved
layout consistency
---
src/features/onboarding/components/steps/tabs.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/features/onboarding/components/steps/tabs.css b/src/features/onboarding/components/steps/tabs.css
index 278f16bf..e4426f61 100644
--- a/src/features/onboarding/components/steps/tabs.css
+++ b/src/features/onboarding/components/steps/tabs.css
@@ -77,7 +77,7 @@
column-gap: 12px;
align-items: center;
margin-top: 32px;
- justify-content: flex-end;
+ justify-content: center;
}
.tabFooter button {
border: none;
From 5971020bd68bcd5ce04931458c534832b62d37da Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 16 Nov 2025 19:33:55 +0100
Subject: [PATCH 49/89] refactor: remove duplicate 'value' property from
Occupation type definition
---
src/features/onboarding/types/index.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/features/onboarding/types/index.ts b/src/features/onboarding/types/index.ts
index 8d3b6bc1..40fdb665 100644
--- a/src/features/onboarding/types/index.ts
+++ b/src/features/onboarding/types/index.ts
@@ -2,6 +2,7 @@ import { IconType } from 'react-icons/lib'
export type Occupation = {
title: string
+ value: string
icon: IconType
sources: string[]
tags: string[]
From f6f831f59700eba12965db30ef87efdacbe7bfda Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 16 Nov 2025 19:34:03 +0100
Subject: [PATCH 50/89] refactor: remove unused key prop from
CardItemWithActions in ArticleItem component
---
src/features/cards/components/mediumCard/ArticleItem.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/features/cards/components/mediumCard/ArticleItem.tsx b/src/features/cards/components/mediumCard/ArticleItem.tsx
index 583cc7d3..62490ca3 100644
--- a/src/features/cards/components/mediumCard/ArticleItem.tsx
+++ b/src/features/cards/components/mediumCard/ArticleItem.tsx
@@ -12,7 +12,6 @@ const ArticleItem = ({ item, selectedTag, analyticsTag }: BaseItemPropsType
From 166ca1b46970ead7d7d0215b3d02054223f8ee1c Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 16 Nov 2025 19:34:28 +0100
Subject: [PATCH 51/89] refactor: simplify loading state handling in
ListComponent
---
src/components/List/ListComponent.tsx | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/components/List/ListComponent.tsx b/src/components/List/ListComponent.tsx
index c283cf38..7bab2714 100644
--- a/src/components/List/ListComponent.tsx
+++ b/src/components/List/ListComponent.tsx
@@ -78,6 +78,9 @@ export function ListComponent(props: ListComponentPropsType) {
}
}, [sortedData, header, renderItem, limit])
+ if (isLoading) {
+ return
+ }
if (error) {
return {error?.message || error}
}
@@ -90,5 +93,5 @@ export function ListComponent(props: ListComponentPropsType) {
)
}
- return <>{isLoading ? : enrichedItems}>
+ return <>{enrichedItems}>
}
From 21e11333f7346e995ce268eb467a7de6e057ebcd Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 16 Nov 2025 19:34:39 +0100
Subject: [PATCH 52/89] refactor: optimize useSelectedTags hook by
consolidating user preferences retrieval
---
src/features/cards/hooks/useSelectedTags.tsx | 41 +++++++++++++-------
1 file changed, 28 insertions(+), 13 deletions(-)
diff --git a/src/features/cards/hooks/useSelectedTags.tsx b/src/features/cards/hooks/useSelectedTags.tsx
index f4c38c36..7b2c7a4c 100644
--- a/src/features/cards/hooks/useSelectedTags.tsx
+++ b/src/features/cards/hooks/useSelectedTags.tsx
@@ -1,5 +1,6 @@
import { useMemo } from 'react'
import { useUserPreferences } from 'src/stores/preferences'
+import { useShallow } from 'zustand/shallow'
import { MY_LANGUAGES_OPTION } from '../config'
type useSelectedTagsProps = {
@@ -10,27 +11,41 @@ type useSelectedTagsProps = {
}
}
export const useSelectedTags = ({ source, fallbackTag }: useSelectedTagsProps) => {
- const cardSettings = useUserPreferences((state) => state.cardsSettings?.[source])
- const { userSelectedTags } = useUserPreferences()
-
+ const { cardSettings, userSelectedTags } = useUserPreferences(
+ useShallow((state) => {
+ return {
+ cardSettings: state.cardsSettings?.[source],
+ userSelectedTags: state.userSelectedTags,
+ }
+ })
+ )
+ const { language } = cardSettings || {}
const selectedTags = useMemo(() => {
- if (!cardSettings?.language) {
+ if (!language || (language === MY_LANGUAGES_OPTION.value && userSelectedTags.length === 0)) {
return [fallbackTag]
}
- if (cardSettings.language === MY_LANGUAGES_OPTION.value) {
+ if (language === MY_LANGUAGES_OPTION.value) {
return userSelectedTags
}
- return [userSelectedTags.find((lang) => lang.value === cardSettings?.language) || fallbackTag]
- }, [userSelectedTags, cardSettings])
+ return [userSelectedTags.find((lang) => lang.value === language) || fallbackTag]
+ }, [userSelectedTags, language])
+
+ const selectedTag = useMemo(() => {
+ return language
+ ? [MY_LANGUAGES_OPTION, ...userSelectedTags].find((lang) => lang.value === language) ||
+ fallbackTag
+ : fallbackTag
+ }, [language, userSelectedTags])
+
+ const queryTags = useMemo(() => {
+ return selectedTags.map((tag) => tag.value)
+ }, [selectedTags])
return {
- queryTags: selectedTags,
- selectedTag: cardSettings?.language
- ? [MY_LANGUAGES_OPTION, ...userSelectedTags].find(
- (lang) => lang.value === cardSettings.language
- ) || fallbackTag
- : fallbackTag,
+ selectedTags,
+ queryTags,
+ selectedTag,
cardSettings,
}
}
From a19477e6727e832d0be76bd062d39d16febadf3c Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 16 Nov 2025 19:34:48 +0100
Subject: [PATCH 53/89] refactor: update RemoteConfig type by removing unused
properties and standardizing tags structure
---
src/features/remoteConfig/types/index.ts | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/src/features/remoteConfig/types/index.ts b/src/features/remoteConfig/types/index.ts
index e75ce7e7..ef20d0e5 100644
--- a/src/features/remoteConfig/types/index.ts
+++ b/src/features/remoteConfig/types/index.ts
@@ -1,14 +1,9 @@
export type Tag = {
label: string
value: string
+ category?: string
}
export type RemoteConfig = {
- supportedTags: Tag[]
- marketingBannerConfig?: any
- adsConfig: {
- rowPosition: number
- columnPosition: number
- enabled: boolean
- }
+ tags: Tag[]
}
From 868546e311d4758cd35b7ec807dec30ad11a8034 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 16 Nov 2025 19:35:16 +0100
Subject: [PATCH 54/89] refactor: enhance sortOptions in HashnodeCard with
reactions and comments
---
.../components/hashnodeCard/HashnodeCard.tsx | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/features/cards/components/hashnodeCard/HashnodeCard.tsx b/src/features/cards/components/hashnodeCard/HashnodeCard.tsx
index d5e485c1..2082aed7 100644
--- a/src/features/cards/components/hashnodeCard/HashnodeCard.tsx
+++ b/src/features/cards/components/hashnodeCard/HashnodeCard.tsx
@@ -1,4 +1,6 @@
import { useCallback } from 'react'
+import { AiTwotoneHeart } from 'react-icons/ai'
+import { BiCommentDetail } from 'react-icons/bi'
import { Card } from 'src/components/Elements'
import { ListComponent } from 'src/components/List'
import { Article, CardPropsType } from 'src/types'
@@ -48,7 +50,19 @@ export function HashnodeCard(props: CardPropsType) {
sortBy={sortBy}
language={language || GLOBAL_TAG.value}
globalTag={GLOBAL_TAG}
- sortOptions={[]}
+ sortOptions={(defaults) => [
+ ...defaults,
+ {
+ label: 'Reactions',
+ value: 'points_count',
+ icon: ,
+ },
+ {
+ label: 'Comments',
+ value: 'comments_count',
+ icon: ,
+ },
+ ]}
/>
}
{...props}>
From 62def59b2b098b0d0a4f917c647f691827d3ce47 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Tue, 18 Nov 2025 21:09:45 +0100
Subject: [PATCH 55/89] refactor: enhance Header component with layout toggle
functionality and optimize callbacks
---
src/components/Layout/Header.tsx | 35 ++++++++++++++++++++++++--------
1 file changed, 26 insertions(+), 9 deletions(-)
diff --git a/src/components/Layout/Header.tsx b/src/components/Layout/Header.tsx
index d5131be8..707527eb 100644
--- a/src/components/Layout/Header.tsx
+++ b/src/components/Layout/Header.tsx
@@ -1,9 +1,11 @@
import { clsx } from 'clsx'
-import { useEffect, useState } from 'react'
+import { useCallback, useEffect, useState } from 'react'
import { BsFillBookmarksFill, BsFillGearFill, BsMoonFill } from 'react-icons/bs'
import { CgTab } from 'react-icons/cg'
import { IoMdSunny } from 'react-icons/io'
import { MdDoDisturbOff } from 'react-icons/md'
+import { RiDashboardHorizontalFill } from 'react-icons/ri'
+import { TfiLayoutColumn4Alt } from 'react-icons/tfi'
import { Link, useLocation, useNavigate } from 'react-router-dom'
import AvatarPlaceholder from 'src/assets/icons/avatar.svg?react'
import StreakIcon from 'src/assets/icons/fire_icon.svg?react'
@@ -11,7 +13,12 @@ import HackertabLogo from 'src/assets/logo.svg?react'
import { UserTags } from 'src/components/Elements/UserTags'
import { useAuth } from 'src/features/auth'
import { Changelog } from 'src/features/changelog'
-import { identifyUserTheme, trackDNDDisable, trackThemeSelect } from 'src/lib/analytics'
+import {
+ identifyUserTheme,
+ trackDNDDisable,
+ trackDisplayTypeChange,
+ trackThemeSelect,
+} from 'src/lib/analytics'
import { useUserPreferences } from 'src/stores/preferences'
import { Button, CircleButton } from '../Elements'
import { SearchEngineBar } from '../Elements/SearchBar/SearchEngineBar'
@@ -19,7 +26,8 @@ export const Header = () => {
const { openAuthModal, user, isConnected, isConnecting } = useAuth()
const [themeIcon, setThemeIcon] = useState( )
- const { theme, setTheme, setDNDDuration, isDNDModeActive } = useUserPreferences()
+ const { theme, setTheme, setDNDDuration, isDNDModeActive, layout, setLayout } =
+ useUserPreferences()
const navigate = useNavigate()
const location = useLocation()
@@ -38,21 +46,27 @@ export const Header = () => {
}
}, [theme])
- const onThemeChange = () => {
+ const onThemeChange = useCallback(() => {
const newTheme = theme === 'dark' ? 'light' : 'dark'
setTheme(newTheme)
trackThemeSelect(newTheme)
identifyUserTheme(newTheme)
- }
+ }, [theme, setTheme])
- const onSettingsClick = () => {
+ const onLayoutChange = useCallback(() => {
+ const newLayout = layout === 'cards' ? 'grid' : 'cards'
+ trackDisplayTypeChange(newLayout)
+ setLayout(newLayout)
+ }, [layout, setLayout])
+
+ const onSettingsClick = useCallback(() => {
navigate('/settings/general')
- }
+ }, [navigate])
- const onUnpauseClicked = () => {
+ const onUnpauseClicked = useCallback(() => {
trackDNDDisable()
setDNDDuration('never')
- }
+ }, [setDNDDuration])
return (
<>
@@ -83,6 +97,9 @@ export const Header = () => {
+
+ {layout === 'cards' ? : }
+
{themeIcon}
From 701cb2689e508134bea5bf8cb95489e4a1805e1f Mon Sep 17 00:00:00 2001
From: John Doe
Date: Tue, 18 Nov 2025 22:28:57 +0100
Subject: [PATCH 56/89] refactor: streamline Article and Product types by
reorganizing properties and enhancing clarity
---
src/types/index.ts | 26 ++++++++++++--------------
1 file changed, 12 insertions(+), 14 deletions(-)
diff --git a/src/types/index.ts b/src/types/index.ts
index adb7cd9b..52efb3ab 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -32,26 +32,25 @@ export type BaseEntry = {
id: string
url: string
title: string
-}
-
-export type Article = BaseEntry & {
- published_at: number
tags: Array
- points_count: number
comments_count: number
- votes_count: number
+ points_count: number
image_url: string
- tagline?: string
+ published_at: number
+ description?: string
+}
+
+export type Article = BaseEntry & {
source: string
original_url?: string
comments_url?: string
- description?: string
- subreddit?: string
- flair_text?: string
- flair_background_color?: string
- flair_text_color?: string
}
+export type Product = BaseEntry & {
+ tagline: string
+ votes_count: number
+ topics: Array
+}
export type FeedItem = {
title: string
id: string
@@ -106,7 +105,7 @@ export type Repository = BaseEntry & {
export type Conference = BaseEntry & {
start_date: number
end_date: number
- tag: string
+ tags: string[]
online: Boolean
city?: string
country?: string
@@ -136,7 +135,6 @@ export type BaseItemPropsType<
id: string
}
> = {
- index: number
item: T
className?: string
analyticsTag: string
From 6a23f7ba3608c0e46443bb4dae87929e8d264561 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Tue, 18 Nov 2025 22:29:02 +0100
Subject: [PATCH 57/89] refactor: update CardItemWithActionsProps type to
define item structure explicitly
---
.../Elements/CardWithActions/CardItemWithActions.tsx | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/components/Elements/CardWithActions/CardItemWithActions.tsx b/src/components/Elements/CardWithActions/CardItemWithActions.tsx
index 2e5591b2..8c2f8ef3 100644
--- a/src/components/Elements/CardWithActions/CardItemWithActions.tsx
+++ b/src/components/Elements/CardWithActions/CardItemWithActions.tsx
@@ -7,10 +7,13 @@ import { ShareModalData } from 'src/features/shareModal/types'
import { Attributes, trackLinkBookmark, trackLinkUnBookmark } from 'src/lib/analytics'
import { useBookmarks } from 'src/stores/bookmarks'
import { useUserPreferences } from 'src/stores/preferences'
-import { BaseEntry } from 'src/types'
type CardItemWithActionsProps = {
- item: BaseEntry
+ item: {
+ title: string
+ url: string
+ id: string
+ }
source: string
cardItem: React.ReactNode
sourceType?: 'rss' | 'supported'
From dfe822bb83075625275b2a34d5630f5df84727ef Mon Sep 17 00:00:00 2001
From: John Doe
Date: Tue, 18 Nov 2025 22:57:06 +0100
Subject: [PATCH 58/89] refactor: replace useQueries with useQuery in
getConferences and getGithubRepos; update getArticles in
getProductHuntProducts and add getSourceArticles
---
src/features/cards/api/getConferences.ts | 26 ++++++-------
src/features/cards/api/getGithubRepos.ts | 37 +++++++++++--------
.../cards/api/getProductHuntProducts.ts | 19 ++++++----
src/features/cards/api/getSourceArticles.ts | 35 ++++++++++++++++++
4 files changed, 82 insertions(+), 35 deletions(-)
create mode 100644 src/features/cards/api/getSourceArticles.ts
diff --git a/src/features/cards/api/getConferences.ts b/src/features/cards/api/getConferences.ts
index acbe8a93..c6c0ad33 100644
--- a/src/features/cards/api/getConferences.ts
+++ b/src/features/cards/api/getConferences.ts
@@ -1,10 +1,14 @@
-import { useQueries, UseQueryOptions } from '@tanstack/react-query'
-import { QueryConfig } from 'src/lib/react-query'
-import { Conference } from 'src/types'
+import { useQuery } from '@tanstack/react-query'
import { axios } from 'src/lib/axios'
+import { ExtractFnReturnType, QueryConfig } from 'src/lib/react-query'
+import { Conference } from 'src/types'
-const getConferences = async (tag: string): Promise => {
- return axios.get(`/data/v2/conferences/${tag}.json`)
+const getConferences = async (tags: string[]): Promise => {
+ return axios.get(`/engine/conferences`, {
+ params: {
+ tags: tags?.join(','),
+ },
+ })
}
type QueryFnType = typeof getConferences
@@ -15,13 +19,9 @@ type UseGetConferencesOptions = {
}
export const useGetConferences = ({ config, tags }: UseGetConferencesOptions) => {
- return useQueries({
- queries: tags.map>((tag) => {
- return {
- ...config,
- queryKey: ['conferences', tag],
- queryFn: () => getConferences(tag),
- }
- })
+ return useQuery>({
+ ...config,
+ queryKey: ['conferences', ...tags],
+ queryFn: () => getConferences(tags),
})
}
diff --git a/src/features/cards/api/getGithubRepos.ts b/src/features/cards/api/getGithubRepos.ts
index 07b7916a..5618db1e 100644
--- a/src/features/cards/api/getGithubRepos.ts
+++ b/src/features/cards/api/getGithubRepos.ts
@@ -1,10 +1,21 @@
-import { useQueries, UseQueryOptions } from '@tanstack/react-query'
-import { QueryConfig } from 'src/lib/react-query'
-import { Repository } from 'src/types'
+import { useQuery } from '@tanstack/react-query'
import { axios } from 'src/lib/axios'
+import { ExtractFnReturnType, QueryConfig } from 'src/lib/react-query'
+import { Repository } from 'src/types'
-const getRepos = async (tag: string, dateRange: string): Promise => {
- return axios.get(`/data/v2/github/${tag}/${dateRange}.json`)
+const getRepos = async ({
+ tags,
+ dateRange,
+}: {
+ tags: string[]
+ dateRange: string
+}): Promise => {
+ return axios.get(`/engine/repos`, {
+ params: {
+ range: dateRange,
+ tags: tags.join(','),
+ },
+ })
}
type QueryFnType = typeof getRepos
@@ -12,17 +23,13 @@ type QueryFnType = typeof getRepos
type UseGetReposOptions = {
config?: QueryConfig
tags: string[]
- dateRange: "daily" | "monthly" | "weekly"
+ dateRange: 'daily' | 'monthly' | 'weekly'
}
export const useGetGithubRepos = ({ config, tags, dateRange }: UseGetReposOptions) => {
- return useQueries({
- queries: tags.map>((tag) => {
- return {
- ...config,
- queryKey: ['github', tag, dateRange],
- queryFn: () => getRepos(tag, dateRange),
- }
- })
+ return useQuery>({
+ ...config,
+ queryKey: ['github', ...tags, dateRange],
+ queryFn: () => getRepos({ tags, dateRange }),
})
-}
\ No newline at end of file
+}
diff --git a/src/features/cards/api/getProductHuntProducts.ts b/src/features/cards/api/getProductHuntProducts.ts
index fcff8f87..f37e4809 100644
--- a/src/features/cards/api/getProductHuntProducts.ts
+++ b/src/features/cards/api/getProductHuntProducts.ts
@@ -1,22 +1,27 @@
import { useQuery } from '@tanstack/react-query'
-import { ExtractFnReturnType, QueryConfig } from 'src/lib/react-query'
-import { Article } from 'src/types'
import { axios } from 'src/lib/axios'
+import { ExtractFnReturnType, QueryConfig } from 'src/lib/react-query'
+import { Product } from 'src/types'
-const getArticles = async (): Promise => {
- return axios.get('/data/v2/producthunt.json')
+const getArticles = async ({ date }: { date: string }): Promise => {
+ return axios.get(`/engine/products`, {
+ params: {
+ date,
+ },
+ })
}
type QueryFnType = typeof getArticles
type UseGetArticlesOptions = {
config?: QueryConfig
+ date: string
}
-export const useGeProductHuntProducts = ({ config }: UseGetArticlesOptions = {}) => {
+export const useGeProductHuntProducts = ({ date, config }: UseGetArticlesOptions) => {
return useQuery>({
...config,
- queryKey: ['producthunt'],
- queryFn: () => getArticles(),
+ queryKey: ['producthunt', date],
+ queryFn: () => getArticles({ date }),
})
}
diff --git a/src/features/cards/api/getSourceArticles.ts b/src/features/cards/api/getSourceArticles.ts
new file mode 100644
index 00000000..eef2bdd5
--- /dev/null
+++ b/src/features/cards/api/getSourceArticles.ts
@@ -0,0 +1,35 @@
+import { useQuery } from '@tanstack/react-query'
+import { axios } from 'src/lib/axios'
+import { ExtractFnReturnType, QueryConfig } from 'src/lib/react-query'
+import { Article } from 'src/types'
+
+const getArticles = async ({
+ source,
+ tags,
+}: {
+ source: string
+ tags?: string[]
+}): Promise => {
+ return axios.get(`/engine/feeds`, {
+ params: {
+ source,
+ ...(tags?.length ? { tags: tags.join(',') } : {}),
+ },
+ })
+}
+
+type QueryFnType = typeof getArticles
+
+type UseGetArticlesOptions = {
+ config?: QueryConfig
+ source: string
+ tags?: string[]
+}
+
+export const useGetSourceArticles = ({ config, source, tags }: UseGetArticlesOptions) => {
+ return useQuery>({
+ ...config,
+ queryKey: [source, ...(tags || [])],
+ queryFn: () => getArticles({ source, tags }),
+ })
+}
From 9f450f1def809164ac548871b5b167d93cd5fb47 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Tue, 18 Nov 2025 22:57:13 +0100
Subject: [PATCH 59/89] fix: update getRemoteConfig to fetch from the correct
config file path
---
src/features/remoteConfig/api/getRemoteConfig.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/features/remoteConfig/api/getRemoteConfig.ts b/src/features/remoteConfig/api/getRemoteConfig.ts
index 166ed770..8d41b728 100644
--- a/src/features/remoteConfig/api/getRemoteConfig.ts
+++ b/src/features/remoteConfig/api/getRemoteConfig.ts
@@ -5,7 +5,7 @@ import { useRemoteConfigStore } from '../stores/remoteConfig'
import { RemoteConfig } from '../types'
const getRemoteConfig = async (): Promise => {
- return axios.get('/data/remoteConfiguration.json')
+ return axios.get('/data/config.json')
}
type QueryFnType = typeof getRemoteConfig
From 2b8251feebdfc16a328420381d3cc64f534f11de Mon Sep 17 00:00:00 2001
From: John Doe
Date: Tue, 18 Nov 2025 22:57:18 +0100
Subject: [PATCH 60/89] fix: adjust title display in ConferencesItem to improve
layout
---
.../cards/components/conferencesCard/ConferenceItem.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/features/cards/components/conferencesCard/ConferenceItem.tsx b/src/features/cards/components/conferencesCard/ConferenceItem.tsx
index 050d476d..d6811e37 100644
--- a/src/features/cards/components/conferencesCard/ConferenceItem.tsx
+++ b/src/features/cards/components/conferencesCard/ConferenceItem.tsx
@@ -83,7 +83,8 @@ const ConferencesItem = ({ item, analyticsTag }: BaseItemPropsType)
}}>
{differenceInDays < 0 && Ended }{' '}
- {conferenceLocation?.icon} {item.title}
+ {conferenceLocation?.icon}
+ {item.title}
{listingMode === 'normal' ? (
From 16802976170a089d1d9178bda611e786cba08d4b Mon Sep 17 00:00:00 2001
From: John Doe
Date: Tue, 18 Nov 2025 22:57:37 +0100
Subject: [PATCH 61/89] refactor: simplify cardSettings destructuring in
GithubCard component
---
src/features/cards/components/githubCard/GithubCard.tsx | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/src/features/cards/components/githubCard/GithubCard.tsx b/src/features/cards/components/githubCard/GithubCard.tsx
index df2d2cf8..074634b7 100644
--- a/src/features/cards/components/githubCard/GithubCard.tsx
+++ b/src/features/cards/components/githubCard/GithubCard.tsx
@@ -19,14 +19,11 @@ export function GithubCard(props: CardPropsType) {
const { ref, isVisible } = useLazyListLoad()
const setCardSettings = useUserPreferences((state) => state.setCardSettings)
- const {
- queryTags,
- selectedTag,
- cardSettings: { dateRange, sortBy, language } = {},
- } = useSelectedTags({
+ const { queryTags, selectedTag, cardSettings } = useSelectedTags({
source: meta.value,
fallbackTag: GLOBAL_TAG,
})
+ const { dateRange, language, sortBy } = cardSettings
const selectedDateRange = useMemo(
() => dateRanges.find((date) => date.value === dateRange) || dateRanges[0],
@@ -82,7 +79,7 @@ export function GithubCard(props: CardPropsType) {
value={date.value}
disabled={selectedDateRange.value === date.value}
onClick={() => {
- setCardSettings(meta.value, { dateRange: date.value, language, sortBy })
+ setCardSettings(meta.value, { ...cardSettings, dateRange: date.value })
}}>
{date.label}
From 79c268ec9ed4de921652e01105bdae8c8c90aa75 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Tue, 18 Nov 2025 22:57:45 +0100
Subject: [PATCH 62/89] fix: update ArticleItem component to use Product type
instead of Article type
---
src/features/cards/components/producthuntCard/ArticleItem.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/features/cards/components/producthuntCard/ArticleItem.tsx b/src/features/cards/components/producthuntCard/ArticleItem.tsx
index 096a9911..c062ba33 100644
--- a/src/features/cards/components/producthuntCard/ArticleItem.tsx
+++ b/src/features/cards/components/producthuntCard/ArticleItem.tsx
@@ -3,9 +3,9 @@ import { VscTriangleUp } from 'react-icons/vsc'
import { CardItemWithActions, CardLink } from 'src/components/Elements'
import { Attributes } from 'src/lib/analytics'
import { useUserPreferences } from 'src/stores/preferences'
-import { Article, BaseItemPropsType } from 'src/types'
+import { BaseItemPropsType, Product } from 'src/types'
-const ArticleItem = ({ item, analyticsTag }: BaseItemPropsType) => {
+const ArticleItem = ({ item, analyticsTag }: BaseItemPropsType) => {
const { listingMode } = useUserPreferences()
return (
From 75ddc98eddf4789ba449285f7edbdb1a263778f6 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Tue, 18 Nov 2025 22:57:54 +0100
Subject: [PATCH 63/89] fix: update ProductHuntCard to use Product type instead
of Article type
---
.../cards/components/producthuntCard/ProducthuntCard.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/features/cards/components/producthuntCard/ProducthuntCard.tsx b/src/features/cards/components/producthuntCard/ProducthuntCard.tsx
index 75d73b34..f685965a 100644
--- a/src/features/cards/components/producthuntCard/ProducthuntCard.tsx
+++ b/src/features/cards/components/producthuntCard/ProducthuntCard.tsx
@@ -5,7 +5,7 @@ import { Card } from 'src/components/Elements'
import { ListComponent } from 'src/components/List'
import { ProductHuntPlaceholder } from 'src/components/placeholders'
import { useUserPreferences } from 'src/stores/preferences'
-import { Article, CardPropsType } from 'src/types'
+import { CardPropsType, Product } from 'src/types'
import { useShallow } from 'zustand/shallow'
import { useGeProductHuntProducts } from '../../api/getProductHuntProducts'
import { useLazyListLoad } from '../../hooks/useLazyListLoad'
@@ -31,7 +31,7 @@ export function ProductHuntCard(props: CardPropsType) {
})
const renderItem = useCallback(
- (item: Article) => ,
+ (item: Product) => ,
[meta.analyticsTag]
)
@@ -59,7 +59,7 @@ export function ProductHuntCard(props: CardPropsType) {
]}
/>
}>
-
items={products}
error={error}
isLoading={isLoading}
From 3c336eeeeb57a93650648e720133ec16a9f37604 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Tue, 18 Nov 2025 22:58:08 +0100
Subject: [PATCH 64/89] fix: migrate userSelectedTags to enhance tag
compatibility and update storage handling
---
src/stores/preferences.ts | 64 +++++++++++++++++++++------------------
1 file changed, 35 insertions(+), 29 deletions(-)
diff --git a/src/stores/preferences.ts b/src/stores/preferences.ts
index 11880f16..d127fec7 100644
--- a/src/stores/preferences.ts
+++ b/src/stores/preferences.ts
@@ -72,14 +72,29 @@ const defaultStorage: StateStorage = {
state: UserPreferencesState
} = JSON.parse(item)
- const remoteConfigStore = useRemoteConfigStore.getState()
-
const newState = {
...state,
- userSelectedTags: enhanceTags(
- remoteConfigStore,
- state.userSelectedTags as unknown as string[]
- ),
+ }
+ if (version == 0) {
+ const MAP_OLD_TAGS: Record = {
+ 'artificial-intelligence': 'artificial intelligence',
+ 'machine-learning': 'machine learning',
+ c: 'clang',
+ cpp: 'c++',
+ csharp: 'c#',
+ 'data-science': 'data science',
+ go: 'golang',
+ 'objective-c': 'objectivec',
+ }
+
+ const stateTags = state.userSelectedTags as unknown as string[]
+ const newTags = stateTags.map((tag) => {
+ if (MAP_OLD_TAGS[tag]) {
+ return MAP_OLD_TAGS[tag]
+ }
+ return tag
+ })
+ newState.userSelectedTags = enhanceTags(useRemoteConfigStore.getState(), newTags)
}
return JSON.stringify({ state: newState, version })
@@ -89,21 +104,7 @@ const defaultStorage: StateStorage = {
},
setItem: (name: string, value: string) => {
try {
- let {
- state,
- version,
- }: {
- version: number
- state: UserPreferencesState
- } = JSON.parse(value)
-
- const newState = {
- ...state,
- userSelectedTags: state.userSelectedTags.map((tag) => tag.value),
- }
-
- const newValue = JSON.stringify({ state: newState, version })
- window.localStorage.setItem(name, newValue)
+ window.localStorage.setItem(name, value)
} catch (e) {
window.localStorage.setItem(name, '')
}
@@ -142,15 +143,14 @@ export const useUserPreferences = create(
userCustomCards: [],
DNDDuration: 'never',
advStatus: false,
- setLayout: (layout) => set({ layout: layout }),
- setPromptEngine: (promptEngine: string) => set({ promptEngine: promptEngine }),
- setListingMode: (listingMode: ListingMode) => set({ listingMode: listingMode }),
- setTheme: (theme: Theme) => set({ theme: theme }),
- setOpenLinksNewTab: (openLinksNewTab: boolean) => set({ openLinksNewTab: openLinksNewTab }),
+ setLayout: (layout) => set({ layout }),
+ setPromptEngine: (promptEngine: string) => set({ promptEngine }),
+ setListingMode: (listingMode: ListingMode) => set({ listingMode }),
+ setTheme: (theme: Theme) => set({ theme }),
+ setOpenLinksNewTab: (openLinksNewTab: boolean) => set({ openLinksNewTab }),
setCards: (selectedCards: SelectedCard[]) => set({ cards: selectedCards }),
setTags: (selectedTags: Tag[]) => set({ userSelectedTags: selectedTags }),
- setMaxVisibleCards: (maxVisibleCards: number) => set({ maxVisibleCards: maxVisibleCards }),
-
+ setMaxVisibleCards: (maxVisibleCards: number) => set({ maxVisibleCards }),
setCardSettings: (card: string, settings: CardSettingsType) =>
set((state) => ({
cardsSettings: {
@@ -218,7 +218,7 @@ export const useUserPreferences = create(
set((state) => {
const exists = state.userSelectedTags.find((t) => t.value === tag.value)
if (exists) {
- return state // No change if tag already followed
+ return state
}
return {
userSelectedTags: [...state.userSelectedTags, tag],
@@ -233,7 +233,13 @@ export const useUserPreferences = create(
}),
{
name: 'preferences_storage',
+ version: 1,
storage: createJSONStorage(() => defaultStorage),
+ migrate: (persistedState) => {
+ const state = persistedState as unknown as UserPreferencesState &
+ UserPreferencesStoreActions
+ return state
+ },
}
)
)
From 91bee9bcda3316ccc9d07f3bb19b413921b56e0e Mon Sep 17 00:00:00 2001
From: John Doe
Date: Tue, 18 Nov 2025 22:58:14 +0100
Subject: [PATCH 65/89] fix: enhance TopicSettings component with improved tag
handling and search functionality
---
.../settings/components/TopicSettings.tsx | 184 +++++++++++++++---
1 file changed, 154 insertions(+), 30 deletions(-)
diff --git a/src/features/settings/components/TopicSettings.tsx b/src/features/settings/components/TopicSettings.tsx
index 86d50a7d..0be73258 100644
--- a/src/features/settings/components/TopicSettings.tsx
+++ b/src/features/settings/components/TopicSettings.tsx
@@ -1,45 +1,169 @@
-import { ChipsSet } from 'src/components/Elements'
+import { useMemo, useState } from 'react'
+import { AiFillMobile } from 'react-icons/ai'
+import { BsChevronDown, BsChevronUp, BsFillGearFill, BsFillShieldLockFill } from 'react-icons/bs'
+import { FaDatabase, FaPaintBrush, FaRobot, FaServer } from 'react-icons/fa'
+import { IoMdSearch } from 'react-icons/io'
+import { RiDeviceFill } from 'react-icons/ri'
+import { SiBnbchain } from 'react-icons/si'
+import { ChipsSet, SearchBar } from 'src/components/Elements'
import { SettingsContentLayout } from 'src/components/Layout/SettingsContentLayout/SettingsContentLayout'
+import { repository } from 'src/config'
import { Tag, useRemoteConfigStore } from 'src/features/remoteConfig'
import { trackLanguageAdd, trackLanguageRemove } from 'src/lib/analytics'
import { useUserPreferences } from 'src/stores/preferences'
+const CATEGORY_TO_ICON: Record = {
+ frontend: ,
+ backend: ,
+ fullstack: ,
+ mobile: ,
+ devops: ,
+ ai: ,
+ data: ,
+ security: ,
+ blockchain: ,
+}
export const TopicSettings = () => {
- const { userSelectedTags, setTags } = useUserPreferences()
+ const { userSelectedTags, occupation, followTag, unfollowTag } = useUserPreferences()
+
+ const { tags } = useRemoteConfigStore()
+ const [searchKeyword, setSearchKeyword] = useState('')
+ const filteredTags = useMemo(() => {
+ if (searchKeyword.trim() === '') {
+ return tags
+ }
+ return tags.filter(
+ (tag) =>
+ tag.label.toLowerCase().includes(searchKeyword.trim().toLowerCase()) ||
+ tag.category?.toLowerCase().includes(searchKeyword.trim().toLowerCase())
+ )
+ }, [tags, searchKeyword])
+
+ const groupedTags = useMemo(() => {
+ const groups = filteredTags.reduce>((acc, tag) => {
+ ;(acc[tag.category || 'Other'] ??= []).push(tag)
+ return acc
+ }, {})
+
+ if ('Other' in groups) {
+ const { Other, ...rest } = groups
+ return { ...rest, Other }
+ }
- const { supportedTags } = useRemoteConfigStore()
+ return groups
+ }, [filteredTags])
- const tags = supportedTags
- .map((tag) => {
- return {
- label: tag.label,
- value: tag.value,
- }
- })
- .sort((a, b) => (a.label > b.label ? 1 : -1))
+ const [expandedCategories, setExpandedCategories] = useState(
+ occupation ? [occupation] : ['backend', 'frontend']
+ )
return (
- tag.value)}
- onChange={(changes, selectedChips) => {
- const selectedTags =
- (selectedChips
- .map((tag) => supportedTags.find((st) => st.value === tag.value))
- .filter(Boolean) as Tag[]) || []
- setTags(selectedTags)
-
- if (changes.action == 'ADD') {
- trackLanguageAdd(changes.option.value)
- } else {
- trackLanguageRemove(changes.option.value)
- }
- }}
- />
+ {userSelectedTags.length > 0 ? (
+ tag.value)}
+ onChange={(changes) => {
+ if (changes.action == 'ADD') {
+ followTag(changes.option as Tag)
+ trackLanguageAdd(changes.option.value)
+ } else {
+ unfollowTag(changes.option as Tag)
+ trackLanguageRemove(changes.option.value)
+ }
+ }}
+ />
+ ) : (
+
+
You are not following any topics yet. Start exploring below!
+
+ )}
+
+
+
+
+
+ }
+ placeholder="Search by programming language, framework, or topic"
+ onChange={(keyword) => {
+ setSearchKeyword(keyword)
+ }}
+ />
+
+
+ {Object.keys(groupedTags).length == 0 ? (
+
+
+ No results found, try adjusting your search keyword.
+
+ If you think this technology is missing, feel free to{' '}
+
+ open an issue
+ {' '}
+ to suggest it.
+
+
+ ) : (
+ Object.keys(groupedTags).map((category) => (
+
+
+ setExpandedCategories(
+ expandedCategories.includes(category)
+ ? expandedCategories.filter((c) => c !== category)
+ : [...expandedCategories, category]
+ )
+ }>
+ {CATEGORY_TO_ICON[category.toLowerCase()]} {category}{' '}
+
+ {expandedCategories.includes(category) ? (
+
+ ) : (
+
+ )}
+
+
+
+ {expandedCategories.includes(category) && (
+ (a.label > b.label ? 1 : -1))
+ .map((tag) => ({
+ label: tag.label,
+ value: tag.value,
+ }))}
+ defaultValues={userSelectedTags.map((tag) => tag.value)}
+ onChange={(changes) => {
+ if (changes.action == 'ADD') {
+ followTag(changes.option as Tag)
+ trackLanguageAdd(changes.option.value)
+ } else {
+ unfollowTag(changes.option as Tag)
+ trackLanguageRemove(changes.option.value)
+ }
+ }}
+ />
+ )}
+
+ ))
+ )}
+
)
}
From 7f6d34aca342a2f200807e34873eabe97de36314 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Tue, 18 Nov 2025 22:58:18 +0100
Subject: [PATCH 66/89] fix: add styles for category titles, subtitle buttons,
and expand buttons in SettingsContentLayout
---
.../settingsContentLayout.css | 49 +++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/src/components/Layout/SettingsContentLayout/settingsContentLayout.css b/src/components/Layout/SettingsContentLayout/settingsContentLayout.css
index a79379a8..6cab9b24 100644
--- a/src/components/Layout/SettingsContentLayout/settingsContentLayout.css
+++ b/src/components/Layout/SettingsContentLayout/settingsContentLayout.css
@@ -13,6 +13,55 @@
display: flex;
flex-direction: column;
gap: 16px;
+
+ .categoryTitle {
+ text-transform: capitalize;
+ font-size: 16px;
+ margin-bottom: 16px;
+ color: var(--primary-text-color);
+
+ & .icon {
+ vertical-align: top;
+ }
+ }
+
+ .subTitleButton {
+ cursor: pointer;
+ background: none;
+ border: none;
+ margin: 0;
+ padding: 0;
+ }
+ .subTitleIcon {
+ font-size: 12px;
+ }
+ .topicsFlex {
+ display: flex;
+ margin-top: 12px;
+ flex-direction: column;
+ flex-wrap: wrap;
+ gap: 24px;
+ }
+ .categoryContent {
+ margin-top: 12px;
+ }
+ .expandButton {
+ width: auto;
+ font-size: 12px;
+ height: 20px;
+ border: none;
+ background-color: var(--button-background-color);
+ border-radius: 20px;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ display: inline-flex;
+ padding: 0 6px;
+ display: inline-flex;
+ column-gap: 4px;
+ text-transform: lowercase;
+ cursor: pointer;
+ }
}
.settingsContent header {
display: flex;
From cdc2a18a01e1f52938d92e82e2fa4f53843f60b4 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Wed, 19 Nov 2025 18:52:22 +0100
Subject: [PATCH 67/89] fix: update Vite configuration to include additional
dependencies and disable sourcemaps
---
vite.config.mjs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/vite.config.mjs b/vite.config.mjs
index cb8180db..e6e6930d 100644
--- a/vite.config.mjs
+++ b/vite.config.mjs
@@ -75,13 +75,17 @@ export default defineConfig(({ mode }) => {
'react-select',
'react-share',
'react-simple-toasts',
+ 'react-responsive',
'react-toggle',
'react-tooltip',
'react-icons',
'react-markdown',
+ 'react-modal',
'react-spring-bottom-sheet',
+ 'react-infinite-scroll-hook',
'@dnd-kit/core',
'@dnd-kit/sortable',
+ '@szhsin/react-menu',
],
utils: [
'@amplitude/analytics-browser',
@@ -97,6 +101,7 @@ export default defineConfig(({ mode }) => {
},
server: {
open: true,
+ sourcemap: false,
proxy: {
'/api': {
target: env.VITE_API_URL,
From 8d650bb80c5641c161ac2925241dbc41d05ec67d Mon Sep 17 00:00:00 2001
From: John Doe
Date: Wed, 19 Nov 2025 19:04:54 +0100
Subject: [PATCH 68/89] fix: rename shouldCountStreak to shouldIcrementStreak
and update date comparison logic
---
src/components/Layout/AppLayout.tsx | 6 +++---
src/features/auth/hooks/useAuth.ts | 8 ++++----
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/components/Layout/AppLayout.tsx b/src/components/Layout/AppLayout.tsx
index b20b5795..30dee94a 100644
--- a/src/components/Layout/AppLayout.tsx
+++ b/src/components/Layout/AppLayout.tsx
@@ -11,17 +11,17 @@ import { AuthProvider } from 'src/providers/AuthProvider'
import { Header } from './Header'
export const AppLayout = () => {
- const { isAuthModalOpen, setStreak, shouldCountStreak } = useAuth()
+ const { isAuthModalOpen, setStreak, shouldIcrementStreak } = useAuth()
const postStreakMutation = usePostStreak()
useEffect(() => {
- if (shouldCountStreak()) {
+ if (shouldIcrementStreak()) {
postStreakMutation.mutateAsync(undefined).then((data) => {
setStreak(data.streak)
identifyUserStreak(data.streak)
})
}
- }, [shouldCountStreak])
+ }, [])
return (
diff --git a/src/features/auth/hooks/useAuth.ts b/src/features/auth/hooks/useAuth.ts
index 953924d4..c795ddac 100644
--- a/src/features/auth/hooks/useAuth.ts
+++ b/src/features/auth/hooks/useAuth.ts
@@ -10,14 +10,14 @@ export const useAuth = () => {
const isConnected = authStore.user != null
- const shouldCountStreak = useCallback(() => {
+ const shouldIcrementStreak = useCallback(() => {
if (!isConnected) return false
const last = authStore.lastStreakUpdate
if (!last) return true
- const today = new Date().toDateString()
- const lastDay = new Date(last).toDateString()
+ const today = new Date().toISOString()
+ const lastDay = new Date(last).toISOString()
return today !== lastDay
}, [isConnected, authStore.lastStreakUpdate])
@@ -33,7 +33,7 @@ export const useAuth = () => {
...authModalStore,
...authStore,
isConnected,
- shouldCountStreak,
+ shouldIcrementStreak,
logout,
}
}
From 0bf827e9bf76ea603733e25651e1c5f1fbaf045f Mon Sep 17 00:00:00 2001
From: John Doe
Date: Wed, 19 Nov 2025 19:44:05 +0100
Subject: [PATCH 69/89] fix: handle potential null value for cardSettings in
GithubCard component
---
src/features/cards/components/githubCard/GithubCard.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/features/cards/components/githubCard/GithubCard.tsx b/src/features/cards/components/githubCard/GithubCard.tsx
index 074634b7..26f06f63 100644
--- a/src/features/cards/components/githubCard/GithubCard.tsx
+++ b/src/features/cards/components/githubCard/GithubCard.tsx
@@ -23,7 +23,7 @@ export function GithubCard(props: CardPropsType) {
source: meta.value,
fallbackTag: GLOBAL_TAG,
})
- const { dateRange, language, sortBy } = cardSettings
+ const { dateRange, language, sortBy } = cardSettings ?? {}
const selectedDateRange = useMemo(
() => dateRanges.find((date) => date.value === dateRange) || dateRanges[0],
From fba2a5149c80957403c9d68c4797968c94dfc60b Mon Sep 17 00:00:00 2001
From: John Doe
Date: Wed, 19 Nov 2025 20:45:01 +0100
Subject: [PATCH 70/89] fix: update build command to use development mode and
adjust Sentry configuration for development
---
.github/workflows/develop.yml | 2 +-
vite.config.mjs | 11 +++++++----
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml
index de6f1dd6..4a212e3a 100644
--- a/.github/workflows/develop.yml
+++ b/.github/workflows/develop.yml
@@ -30,7 +30,7 @@ jobs:
run: yarn
- name: Build project
- run: yarn build:web
+ run: yarn build:web --mode development
- name: Copy build to remote host
uses: appleboy/scp-action@v0.1.4
diff --git a/vite.config.mjs b/vite.config.mjs
index e6e6930d..45655d7c 100644
--- a/vite.config.mjs
+++ b/vite.config.mjs
@@ -9,6 +9,7 @@ import viteTsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
+ const isDev = mode === 'development'
const buildTarget = env.VITE_BUILD_TARGET || 'web'
const buildPlatform = env.VITE_BUILD_PLATFORM
const manifestPath = path.resolve(__dirname, 'public', 'base.manifest.json')
@@ -38,13 +39,15 @@ export default defineConfig(({ mode }) => {
org: 'hackertabdev',
project: 'hackertab',
authToken: env.VITE_SENTRY_TOKEN,
- disable: mode === 'development',
+ disable: isDev,
release: {
name: `${appVersion}-${buildTarget == 'extension' ? buildPlatform : buildTarget}`,
},
- sourcemaps: {
- filesToDeleteAfterUpload: ['./dist/assets/*.map', './assets/*.map'],
- },
+ sourcemaps: !isDev
+ ? {
+ filesToDeleteAfterUpload: ['./dist/assets/*.map', './assets/*.map'],
+ }
+ : false,
}),
],
define: {
From 788498c5188423126ea08903239745cf530dce5c Mon Sep 17 00:00:00 2001
From: John Doe
Date: Wed, 19 Nov 2025 20:51:40 +0100
Subject: [PATCH 71/89] fix: set NODE_ENV to development during project build
in GitHub Actions workflow
---
.github/workflows/develop.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml
index 4a212e3a..765a7eb0 100644
--- a/.github/workflows/develop.yml
+++ b/.github/workflows/develop.yml
@@ -30,7 +30,7 @@ jobs:
run: yarn
- name: Build project
- run: yarn build:web --mode development
+ run: NODE_ENV=development yarn build:web --mode development
- name: Copy build to remote host
uses: appleboy/scp-action@v0.1.4
From 8a3c49decb73181e8ee29fd8ba683e7317ed9cf6 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Wed, 19 Nov 2025 21:21:02 +0100
Subject: [PATCH 72/89] fix: remove NODE_ENV setting from GitHub Actions build
step and update build script to pass arguments
---
.github/workflows/develop.yml | 2 +-
script/build.sh | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml
index 765a7eb0..4a212e3a 100644
--- a/.github/workflows/develop.yml
+++ b/.github/workflows/develop.yml
@@ -30,7 +30,7 @@ jobs:
run: yarn
- name: Build project
- run: NODE_ENV=development yarn build:web --mode development
+ run: yarn build:web --mode development
- name: Copy build to remote host
uses: appleboy/scp-action@v0.1.4
diff --git a/script/build.sh b/script/build.sh
index 8cc7aceb..806d0383 100755
--- a/script/build.sh
+++ b/script/build.sh
@@ -4,7 +4,7 @@ build() {
echo 'Building Hackertab...'
rm -rf dist
tsc
- vite build
+ vite build "$@"
}
-build
\ No newline at end of file
+build "$@"
\ No newline at end of file
From 0309c97419a8405dc4eefe0a97ad9657c6fe4808 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Wed, 19 Nov 2025 21:21:07 +0100
Subject: [PATCH 73/89] chore: remove unused dependencies from package.json and
yarn.lock
- Removed react-spinners and react-spring-bottom-sheet from dependencies in package.json.
- Cleaned up related entries in yarn.lock to reflect the removal of these packages.
---
package.json | 2 -
yarn.lock | 235 ++-------------------------------------------------
2 files changed, 7 insertions(+), 230 deletions(-)
diff --git a/package.json b/package.json
index d32fc59a..1022c78d 100644
--- a/package.json
+++ b/package.json
@@ -33,8 +33,6 @@
"react-select": "^5.0.1",
"react-share": "^4.4.1",
"react-simple-toasts": "^6.1.0",
- "react-spinners": "^0.10.4",
- "react-spring-bottom-sheet": "^3.4.1",
"react-toggle": "^4.1.1",
"react-tooltip": "^4.2.21",
"timeago.js": "^4.0.2",
diff --git a/yarn.lock b/yarn.lock
index ff97f18c..ee059c67 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -277,7 +277,7 @@
dependencies:
"@babel/types" "^7.23.0"
-"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.15":
+"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.15":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0"
integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==
@@ -1266,7 +1266,7 @@
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
-"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
+"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
version "7.23.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.6.tgz#c05e610dc228855dc92ef1b53d07389ed8ab521d"
integrity sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==
@@ -1385,16 +1385,6 @@
source-map "^0.5.7"
stylis "4.2.0"
-"@emotion/cache@^10.0.27":
- version "10.0.29"
- resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0"
- integrity sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==
- dependencies:
- "@emotion/sheet" "0.9.4"
- "@emotion/stylis" "0.8.5"
- "@emotion/utils" "0.11.3"
- "@emotion/weak-memoize" "0.2.5"
-
"@emotion/cache@^11.11.0", "@emotion/cache@^11.4.0":
version "11.11.0"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff"
@@ -1406,42 +1396,11 @@
"@emotion/weak-memoize" "^0.3.1"
stylis "4.2.0"
-"@emotion/core@^10.0.35":
- version "10.3.1"
- resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.3.1.tgz#4021b6d8b33b3304d48b0bb478485e7d7421c69d"
- integrity sha512-447aUEjPIm0MnE6QYIaFz9VQOHSXf4Iu6EWOIqq11EAPqinkSZmfymPTmlOE3QjLv846lH4JVZBUOtwGbuQoww==
- dependencies:
- "@babel/runtime" "^7.5.5"
- "@emotion/cache" "^10.0.27"
- "@emotion/css" "^10.0.27"
- "@emotion/serialize" "^0.11.15"
- "@emotion/sheet" "0.9.4"
- "@emotion/utils" "0.11.3"
-
-"@emotion/css@^10.0.27":
- version "10.0.27"
- resolved "https://registry.yarnpkg.com/@emotion/css/-/css-10.0.27.tgz#3a7458198fbbebb53b01b2b87f64e5e21241e14c"
- integrity sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==
- dependencies:
- "@emotion/serialize" "^0.11.15"
- "@emotion/utils" "0.11.3"
- babel-plugin-emotion "^10.0.27"
-
-"@emotion/hash@0.8.0":
- version "0.8.0"
- resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
- integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
-
"@emotion/hash@^0.9.1":
version "0.9.1"
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43"
integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==
-"@emotion/memoize@0.7.4":
- version "0.7.4"
- resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
- integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
-
"@emotion/memoize@^0.8.1":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17"
@@ -1461,17 +1420,6 @@
"@emotion/weak-memoize" "^0.3.1"
hoist-non-react-statics "^3.3.1"
-"@emotion/serialize@^0.11.15", "@emotion/serialize@^0.11.16":
- version "0.11.16"
- resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.11.16.tgz#dee05f9e96ad2fb25a5206b6d759b2d1ed3379ad"
- integrity sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==
- dependencies:
- "@emotion/hash" "0.8.0"
- "@emotion/memoize" "0.7.4"
- "@emotion/unitless" "0.7.5"
- "@emotion/utils" "0.11.3"
- csstype "^2.5.7"
-
"@emotion/serialize@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.2.tgz#017a6e4c9b8a803bd576ff3d52a0ea6fa5a62b51"
@@ -1483,26 +1431,11 @@
"@emotion/utils" "^1.2.1"
csstype "^3.0.2"
-"@emotion/sheet@0.9.4":
- version "0.9.4"
- resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-0.9.4.tgz#894374bea39ec30f489bbfc3438192b9774d32e5"
- integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==
-
"@emotion/sheet@^1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec"
integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==
-"@emotion/stylis@0.8.5":
- version "0.8.5"
- resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04"
- integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==
-
-"@emotion/unitless@0.7.5":
- version "0.7.5"
- resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
- integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
-
"@emotion/unitless@^0.8.1":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3"
@@ -1513,21 +1446,11 @@
resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963"
integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==
-"@emotion/utils@0.11.3":
- version "0.11.3"
- resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.11.3.tgz#a759863867befa7e583400d322652a3f44820924"
- integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==
-
"@emotion/utils@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4"
integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==
-"@emotion/weak-memoize@0.2.5":
- version "0.2.5"
- resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
- integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
-
"@emotion/weak-memoize@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6"
@@ -2185,11 +2108,6 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
-"@juggle/resize-observer@^3.2.0":
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60"
- integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==
-
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
version "5.1.1-v1"
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"
@@ -2271,23 +2189,6 @@
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
-"@reach/portal@^0.13.0":
- version "0.13.2"
- resolved "https://registry.yarnpkg.com/@reach/portal/-/portal-0.13.2.tgz#6f2a6f4afc14894bde9c6435667bb9b660887ed9"
- integrity sha512-g74BnCdtuTGthzzHn2cWW+bcyIYb0iIE/yRsm89i8oNzNgpopbkh9UY8TPbhNlys52h7U60s4kpRTmcq+JqsTA==
- dependencies:
- "@reach/utils" "0.13.2"
- tslib "^2.1.0"
-
-"@reach/utils@0.13.2":
- version "0.13.2"
- resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.13.2.tgz#87e8fef8ebfe583fa48250238a1a3ed03189fcc8"
- integrity sha512-3ir6cN60zvUrwjOJu7C6jec/samqAeyAB12ZADK+qjnmQPdzSYldrFWwDVV5H0WkhbYXR3uh+eImu13hCetNPQ==
- dependencies:
- "@types/warning" "^3.0.0"
- tslib "^2.1.0"
- warning "^4.0.3"
-
"@remix-run/router@1.14.0":
version "1.14.0"
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.14.0.tgz#9bc39a5a3a71b81bdb310eba6def5bc3966695b7"
@@ -2921,11 +2822,6 @@
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.10.tgz#04ffa7f406ab628f7f7e97ca23e290cd8ab15efc"
integrity sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==
-"@types/warning@^3.0.0":
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.3.tgz#d1884c8cc4a426d1ac117ca2611bf333834c6798"
- integrity sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==
-
"@types/yargs-parser@*":
version "21.0.3"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15"
@@ -3040,14 +2936,6 @@
"@types/babel__core" "^7.20.5"
react-refresh "^0.14.0"
-"@xstate/react@^1.2.0":
- version "1.6.3"
- resolved "https://registry.yarnpkg.com/@xstate/react/-/react-1.6.3.tgz#706f3beb7bc5879a78088985c8fd43b9dab7f725"
- integrity sha512-NCUReRHPGvvCvj2yLZUTfR0qVp6+apc8G83oXSjN4rl89ZjyujiKrTff55bze/HrsvCsP/sUJASf2n0nzMF1KQ==
- dependencies:
- use-isomorphic-layout-effect "^1.0.0"
- use-subscription "^1.3.0"
-
acorn@^8.8.1:
version "8.15.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816"
@@ -3244,31 +3132,6 @@ axobject-query@^3.2.1:
dependencies:
dequal "^2.0.3"
-babel-plugin-emotion@^10.0.27:
- version "10.2.2"
- resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz#a1fe3503cff80abfd0bdda14abd2e8e57a79d17d"
- integrity sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==
- dependencies:
- "@babel/helper-module-imports" "^7.0.0"
- "@emotion/hash" "0.8.0"
- "@emotion/memoize" "0.7.4"
- "@emotion/serialize" "^0.11.16"
- babel-plugin-macros "^2.0.0"
- babel-plugin-syntax-jsx "^6.18.0"
- convert-source-map "^1.5.0"
- escape-string-regexp "^1.0.5"
- find-root "^1.1.0"
- source-map "^0.5.7"
-
-babel-plugin-macros@^2.0.0:
- version "2.8.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138"
- integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==
- dependencies:
- "@babel/runtime" "^7.7.2"
- cosmiconfig "^6.0.0"
- resolve "^1.12.0"
-
babel-plugin-macros@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1"
@@ -3302,11 +3165,6 @@ babel-plugin-polyfill-regenerator@^0.5.3:
dependencies:
"@babel/helper-define-polyfill-provider" "^0.4.4"
-babel-plugin-syntax-jsx@^6.18.0:
- version "6.18.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
- integrity sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==
-
babel-plugin-transform-react-remove-prop-types@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a"
@@ -3349,11 +3207,6 @@ binary-extensions@^2.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
-body-scroll-lock@^3.1.5:
- version "3.1.5"
- resolved "https://registry.yarnpkg.com/body-scroll-lock/-/body-scroll-lock-3.1.5.tgz#c1392d9217ed2c3e237fee1e910f6cdd80b7aaec"
- integrity sha512-Yi1Xaml0EvNA0OYWxXiYNqY24AfWkbA6w5vxE7GWxtKfzIbZM+Qw+aSmkgsbWzbHiy/RCSkUZBplVxTA+E4jJg==
-
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -3584,17 +3437,6 @@ core-js-compat@^3.31.0, core-js-compat@^3.33.1:
dependencies:
browserslist "^4.22.2"
-cosmiconfig@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982"
- integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==
- dependencies:
- "@types/parse-json" "^4.0.0"
- import-fresh "^3.1.0"
- parse-json "^5.0.0"
- path-type "^4.0.0"
- yaml "^1.7.2"
-
cosmiconfig@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
@@ -3631,11 +3473,6 @@ css-mediaquery@^0.1.2:
resolved "https://registry.yarnpkg.com/css-mediaquery/-/css-mediaquery-0.1.2.tgz#6a2c37344928618631c54bd33cedd301da18bea0"
integrity sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==
-csstype@^2.5.7:
- version "2.6.21"
- resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.21.tgz#2efb85b7cc55c80017c66a5ad7cbd931fda3a90e"
- integrity sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==
-
csstype@^3.0.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
@@ -4384,13 +4221,6 @@ firebase@^11.2.0:
"@firebase/util" "1.10.3"
"@firebase/vertexai" "1.0.3"
-focus-trap@^6.2.2:
- version "6.9.4"
- resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-6.9.4.tgz#436da1a1d935c48b97da63cd8f361c6f3aa16444"
- integrity sha512-v2NTsZe2FF59Y+sDykKY+XjqZ0cPfhq/hikWVL88BqLivnNiEffAsac6rP6H45ff9wG9LL5ToiDqrLEP9GX9mw==
- dependencies:
- tabbable "^5.3.3"
-
follow-redirects@^1.15.6:
version "1.15.9"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
@@ -4679,7 +4509,7 @@ ignore@^5.2.0:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78"
integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==
-import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.3.0:
+import-fresh@^3.2.1, import-fresh@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
@@ -5738,7 +5568,7 @@ progress@^2.0.3:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
-prop-types@^15.0.0, prop-types@^15.0.0-0, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
+prop-types@^15.0.0, prop-types@^15.0.0-0, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -5931,35 +5761,6 @@ react-simple-toasts@^6.1.0:
resolved "https://registry.yarnpkg.com/react-simple-toasts/-/react-simple-toasts-6.1.0.tgz#2bc68c4127b7b41e0a850f806e4c2dfa6b0f5bd7"
integrity sha512-6XRkaBNfSFr8m2CqKiY6mwDa3qYqWcClQFc5Qbd+ivl/eLrWqfPTrXQ0t7QQFGqn1p9W72KEV1Xj7AEe/48CIA==
-react-spinners@^0.10.4:
- version "0.10.6"
- resolved "https://registry.yarnpkg.com/react-spinners/-/react-spinners-0.10.6.tgz#7e780144aaf54372231f6168ca075b4cd70e48ef"
- integrity sha512-UPLcaMFhFnLWtS1zVDDT14ssW08gnZ44cjPN6GL27dEGboiCvRSthOilZXRDWESp449GB2iI6gjEbxzaMtA+dg==
- dependencies:
- "@emotion/core" "^10.0.35"
-
-react-spring-bottom-sheet@^3.4.1:
- version "3.4.1"
- resolved "https://registry.yarnpkg.com/react-spring-bottom-sheet/-/react-spring-bottom-sheet-3.4.1.tgz#9a4f90b1c0af17eb4a22a606a5efc5d6e62c7b0c"
- integrity sha512-yDFqiPMm/fjefjnOe6Q9zxccbCl6HMUKsK5bWgfGHJIj4zmXVKio5d4icQvmOLuwpuCA2pwv4J6nGWS6fUZidQ==
- dependencies:
- "@juggle/resize-observer" "^3.2.0"
- "@reach/portal" "^0.13.0"
- "@xstate/react" "^1.2.0"
- body-scroll-lock "^3.1.5"
- focus-trap "^6.2.2"
- react-spring "^8.0.27"
- react-use-gesture "^8.0.1"
- xstate "^4.15.1"
-
-react-spring@^8.0.27:
- version "8.0.27"
- resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-8.0.27.tgz#97d4dee677f41e0b2adcb696f3839680a3aa356a"
- integrity sha512-nDpWBe3ZVezukNRandTeLSPcwwTMjNVu1IDq9qA/AMiUqHuRN4BeSWvKr3eIxxg1vtiYiOLy4FqdfCP5IoP77g==
- dependencies:
- "@babel/runtime" "^7.3.1"
- prop-types "^15.5.8"
-
react-toggle@^4.1.1:
version "4.1.3"
resolved "https://registry.yarnpkg.com/react-toggle/-/react-toggle-4.1.3.tgz#99193392cca8e495710860c49f55e74c4e6cf452"
@@ -5990,11 +5791,6 @@ react-transition-state@^2.3.1:
resolved "https://registry.yarnpkg.com/react-transition-state/-/react-transition-state-2.3.1.tgz#53f5d33a95d1859d1ad2b1673c4466da1518e4ed"
integrity sha512-Z48el73x+7HUEM131dof9YpcQ5IlM4xB+pKWH/lX3FhxGfQaNTZa16zb7pWkC/y5btTZzXfCtglIJEGc57giOw==
-react-use-gesture@^8.0.1:
- version "8.0.1"
- resolved "https://registry.yarnpkg.com/react-use-gesture/-/react-use-gesture-8.0.1.tgz#4360c0f7c9e26baf9fbe58f63fc9de7ef699c17f"
- integrity sha512-CXzUNkulUdgouaAlvAsC5ZVo0fi9KGSBSk81WrE4kOIcJccpANe9zZkAYr5YZZhqpicIFxitsrGVS4wmoMun9A==
-
react@^19.1.0:
version "19.1.0"
resolved "https://registry.yarnpkg.com/react/-/react-19.1.0.tgz#926864b6c48da7627f004795d6cce50e90793b75"
@@ -6100,7 +5896,7 @@ resolve-from@^4.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
-resolve@^1.12.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.22.4:
+resolve@^1.14.2, resolve@^1.19.0, resolve@^1.22.4:
version "1.22.8"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
@@ -6399,11 +6195,6 @@ svg-parser@^2.0.4:
resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5"
integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==
-tabbable@^5.3.3:
- version "5.3.3"
- resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.3.3.tgz#aac0ff88c73b22d6c3c5a50b1586310006b47fbf"
- integrity sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==
-
terser@^5.19.2:
version "5.26.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.26.0.tgz#ee9f05d929f4189a9c28a0feb889d96d50126fe1"
@@ -6679,18 +6470,11 @@ update-browserslist-db@^1.1.3:
escalade "^3.2.0"
picocolors "^1.1.1"
-use-isomorphic-layout-effect@^1.0.0, use-isomorphic-layout-effect@^1.1.2:
+use-isomorphic-layout-effect@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb"
integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==
-use-subscription@^1.3.0:
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.8.0.tgz#f118938c29d263c2bce12fc5585d3fe694d4dbce"
- integrity sha512-LISuG0/TmmoDoCRmV5XAqYkd3UCBNM0ML3gGBndze65WITcsExCD3DTvXXTLyNcOC0heFQZzluW88bN/oC1DQQ==
- dependencies:
- use-sync-external-store "^1.2.0"
-
use-sync-external-store@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
@@ -6883,11 +6667,6 @@ wrap-ansi@^7.0.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
-xstate@^4.15.1:
- version "4.38.3"
- resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.38.3.tgz#4e15e7ad3aa0ca1eea2010548a5379966d8f1075"
- integrity sha512-SH7nAaaPQx57dx6qvfcIgqKRXIh4L0A1iYEqim4s1u7c9VoCgzZc+63FY90AKU4ZzOC2cfJzTnpO4zK7fCUzzw==
-
y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
@@ -6903,7 +6682,7 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
-yaml@^1.10.0, yaml@^1.7.2:
+yaml@^1.10.0:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
From 1898d267307c0f4741d3468b23100f38387ccbda Mon Sep 17 00:00:00 2001
From: John Doe
Date: Wed, 19 Nov 2025 21:25:05 +0100
Subject: [PATCH 74/89] redo spinners dependency
---
package.json | 1 +
yarn.lock | 177 ++++++++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 169 insertions(+), 9 deletions(-)
diff --git a/package.json b/package.json
index 1022c78d..c6cc4e3e 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
"react-select": "^5.0.1",
"react-share": "^4.4.1",
"react-simple-toasts": "^6.1.0",
+ "react-spinners": "^0.10.4",
"react-toggle": "^4.1.1",
"react-tooltip": "^4.2.21",
"timeago.js": "^4.0.2",
diff --git a/yarn.lock b/yarn.lock
index ee059c67..18b71f91 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -277,14 +277,7 @@
dependencies:
"@babel/types" "^7.23.0"
-"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.15":
- version "7.22.15"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0"
- integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==
- dependencies:
- "@babel/types" "^7.22.15"
-
-"@babel/helper-module-imports@^7.27.1":
+"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204"
integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==
@@ -292,6 +285,13 @@
"@babel/traverse" "^7.27.1"
"@babel/types" "^7.27.1"
+"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0"
+ integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==
+ dependencies:
+ "@babel/types" "^7.22.15"
+
"@babel/helper-module-transforms@^7.23.3":
version "7.23.3"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1"
@@ -1273,6 +1273,11 @@
dependencies:
regenerator-runtime "^0.14.0"
+"@babel/runtime@^7.7.2":
+ version "7.28.4"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326"
+ integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==
+
"@babel/template@^7.22.15":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
@@ -1385,6 +1390,16 @@
source-map "^0.5.7"
stylis "4.2.0"
+"@emotion/cache@^10.0.27":
+ version "10.0.29"
+ resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0"
+ integrity sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==
+ dependencies:
+ "@emotion/sheet" "0.9.4"
+ "@emotion/stylis" "0.8.5"
+ "@emotion/utils" "0.11.3"
+ "@emotion/weak-memoize" "0.2.5"
+
"@emotion/cache@^11.11.0", "@emotion/cache@^11.4.0":
version "11.11.0"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff"
@@ -1396,11 +1411,42 @@
"@emotion/weak-memoize" "^0.3.1"
stylis "4.2.0"
+"@emotion/core@^10.0.35":
+ version "10.3.1"
+ resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.3.1.tgz#4021b6d8b33b3304d48b0bb478485e7d7421c69d"
+ integrity sha512-447aUEjPIm0MnE6QYIaFz9VQOHSXf4Iu6EWOIqq11EAPqinkSZmfymPTmlOE3QjLv846lH4JVZBUOtwGbuQoww==
+ dependencies:
+ "@babel/runtime" "^7.5.5"
+ "@emotion/cache" "^10.0.27"
+ "@emotion/css" "^10.0.27"
+ "@emotion/serialize" "^0.11.15"
+ "@emotion/sheet" "0.9.4"
+ "@emotion/utils" "0.11.3"
+
+"@emotion/css@^10.0.27":
+ version "10.0.27"
+ resolved "https://registry.yarnpkg.com/@emotion/css/-/css-10.0.27.tgz#3a7458198fbbebb53b01b2b87f64e5e21241e14c"
+ integrity sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==
+ dependencies:
+ "@emotion/serialize" "^0.11.15"
+ "@emotion/utils" "0.11.3"
+ babel-plugin-emotion "^10.0.27"
+
+"@emotion/hash@0.8.0":
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
+ integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
+
"@emotion/hash@^0.9.1":
version "0.9.1"
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43"
integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==
+"@emotion/memoize@0.7.4":
+ version "0.7.4"
+ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
+ integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
+
"@emotion/memoize@^0.8.1":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17"
@@ -1420,6 +1466,17 @@
"@emotion/weak-memoize" "^0.3.1"
hoist-non-react-statics "^3.3.1"
+"@emotion/serialize@^0.11.15", "@emotion/serialize@^0.11.16":
+ version "0.11.16"
+ resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.11.16.tgz#dee05f9e96ad2fb25a5206b6d759b2d1ed3379ad"
+ integrity sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==
+ dependencies:
+ "@emotion/hash" "0.8.0"
+ "@emotion/memoize" "0.7.4"
+ "@emotion/unitless" "0.7.5"
+ "@emotion/utils" "0.11.3"
+ csstype "^2.5.7"
+
"@emotion/serialize@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.2.tgz#017a6e4c9b8a803bd576ff3d52a0ea6fa5a62b51"
@@ -1431,11 +1488,26 @@
"@emotion/utils" "^1.2.1"
csstype "^3.0.2"
+"@emotion/sheet@0.9.4":
+ version "0.9.4"
+ resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-0.9.4.tgz#894374bea39ec30f489bbfc3438192b9774d32e5"
+ integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==
+
"@emotion/sheet@^1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec"
integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==
+"@emotion/stylis@0.8.5":
+ version "0.8.5"
+ resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04"
+ integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==
+
+"@emotion/unitless@0.7.5":
+ version "0.7.5"
+ resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
+ integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
+
"@emotion/unitless@^0.8.1":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3"
@@ -1446,11 +1518,21 @@
resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963"
integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==
+"@emotion/utils@0.11.3":
+ version "0.11.3"
+ resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.11.3.tgz#a759863867befa7e583400d322652a3f44820924"
+ integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==
+
"@emotion/utils@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4"
integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==
+"@emotion/weak-memoize@0.2.5":
+ version "0.2.5"
+ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
+ integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
+
"@emotion/weak-memoize@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6"
@@ -3132,6 +3214,31 @@ axobject-query@^3.2.1:
dependencies:
dequal "^2.0.3"
+babel-plugin-emotion@^10.0.27:
+ version "10.2.2"
+ resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz#a1fe3503cff80abfd0bdda14abd2e8e57a79d17d"
+ integrity sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==
+ dependencies:
+ "@babel/helper-module-imports" "^7.0.0"
+ "@emotion/hash" "0.8.0"
+ "@emotion/memoize" "0.7.4"
+ "@emotion/serialize" "^0.11.16"
+ babel-plugin-macros "^2.0.0"
+ babel-plugin-syntax-jsx "^6.18.0"
+ convert-source-map "^1.5.0"
+ escape-string-regexp "^1.0.5"
+ find-root "^1.1.0"
+ source-map "^0.5.7"
+
+babel-plugin-macros@^2.0.0:
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138"
+ integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==
+ dependencies:
+ "@babel/runtime" "^7.7.2"
+ cosmiconfig "^6.0.0"
+ resolve "^1.12.0"
+
babel-plugin-macros@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1"
@@ -3165,6 +3272,11 @@ babel-plugin-polyfill-regenerator@^0.5.3:
dependencies:
"@babel/helper-define-polyfill-provider" "^0.4.4"
+babel-plugin-syntax-jsx@^6.18.0:
+ version "6.18.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
+ integrity sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==
+
babel-plugin-transform-react-remove-prop-types@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a"
@@ -3437,6 +3549,17 @@ core-js-compat@^3.31.0, core-js-compat@^3.33.1:
dependencies:
browserslist "^4.22.2"
+cosmiconfig@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982"
+ integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==
+ dependencies:
+ "@types/parse-json" "^4.0.0"
+ import-fresh "^3.1.0"
+ parse-json "^5.0.0"
+ path-type "^4.0.0"
+ yaml "^1.7.2"
+
cosmiconfig@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
@@ -3473,6 +3596,11 @@ css-mediaquery@^0.1.2:
resolved "https://registry.yarnpkg.com/css-mediaquery/-/css-mediaquery-0.1.2.tgz#6a2c37344928618631c54bd33cedd301da18bea0"
integrity sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==
+csstype@^2.5.7:
+ version "2.6.21"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.21.tgz#2efb85b7cc55c80017c66a5ad7cbd931fda3a90e"
+ integrity sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==
+
csstype@^3.0.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
@@ -4509,6 +4637,14 @@ ignore@^5.2.0:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78"
integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==
+import-fresh@^3.1.0:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf"
+ integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==
+ dependencies:
+ parent-module "^1.0.0"
+ resolve-from "^4.0.0"
+
import-fresh@^3.2.1, import-fresh@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
@@ -4596,6 +4732,13 @@ is-core-module@^2.13.0, is-core-module@^2.13.1:
dependencies:
hasown "^2.0.0"
+is-core-module@^2.16.1:
+ version "2.16.1"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4"
+ integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==
+ dependencies:
+ hasown "^2.0.2"
+
is-date-object@^1.0.1, is-date-object@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f"
@@ -5761,6 +5904,13 @@ react-simple-toasts@^6.1.0:
resolved "https://registry.yarnpkg.com/react-simple-toasts/-/react-simple-toasts-6.1.0.tgz#2bc68c4127b7b41e0a850f806e4c2dfa6b0f5bd7"
integrity sha512-6XRkaBNfSFr8m2CqKiY6mwDa3qYqWcClQFc5Qbd+ivl/eLrWqfPTrXQ0t7QQFGqn1p9W72KEV1Xj7AEe/48CIA==
+react-spinners@^0.10.4:
+ version "0.10.6"
+ resolved "https://registry.yarnpkg.com/react-spinners/-/react-spinners-0.10.6.tgz#7e780144aaf54372231f6168ca075b4cd70e48ef"
+ integrity sha512-UPLcaMFhFnLWtS1zVDDT14ssW08gnZ44cjPN6GL27dEGboiCvRSthOilZXRDWESp449GB2iI6gjEbxzaMtA+dg==
+ dependencies:
+ "@emotion/core" "^10.0.35"
+
react-toggle@^4.1.1:
version "4.1.3"
resolved "https://registry.yarnpkg.com/react-toggle/-/react-toggle-4.1.3.tgz#99193392cca8e495710860c49f55e74c4e6cf452"
@@ -5896,6 +6046,15 @@ resolve-from@^4.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+resolve@^1.12.0:
+ version "1.22.11"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262"
+ integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==
+ dependencies:
+ is-core-module "^2.16.1"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
resolve@^1.14.2, resolve@^1.19.0, resolve@^1.22.4:
version "1.22.8"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
@@ -6682,7 +6841,7 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
-yaml@^1.10.0:
+yaml@^1.10.0, yaml@^1.7.2:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
From 94a3cb54eb52b6d7901d36126cf5653978e8e7bd Mon Sep 17 00:00:00 2001
From: John Doe
Date: Wed, 19 Nov 2025 21:28:13 +0100
Subject: [PATCH 75/89] fix: remove unused 'react-spring-bottom-sheet'
dependency from Vite config
---
vite.config.mjs | 1 -
1 file changed, 1 deletion(-)
diff --git a/vite.config.mjs b/vite.config.mjs
index 45655d7c..8202a168 100644
--- a/vite.config.mjs
+++ b/vite.config.mjs
@@ -84,7 +84,6 @@ export default defineConfig(({ mode }) => {
'react-icons',
'react-markdown',
'react-modal',
- 'react-spring-bottom-sheet',
'react-infinite-scroll-hook',
'@dnd-kit/core',
'@dnd-kit/sortable',
From 10498ab02f23a77885b2098a7298bd97c7a588a9 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Wed, 19 Nov 2025 22:29:58 +0100
Subject: [PATCH 76/89] fix: update migration function to include version
parameter and log migration message
---
src/stores/preferences.ts | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/stores/preferences.ts b/src/stores/preferences.ts
index d127fec7..13b364ca 100644
--- a/src/stores/preferences.ts
+++ b/src/stores/preferences.ts
@@ -235,9 +235,12 @@ export const useUserPreferences = create(
name: 'preferences_storage',
version: 1,
storage: createJSONStorage(() => defaultStorage),
- migrate: (persistedState) => {
+ migrate: (persistedState, version) => {
const state = persistedState as unknown as UserPreferencesState &
UserPreferencesStoreActions
+ if (version === 0) {
+ console.log('Migrating preferences_storage to version 1')
+ }
return state
},
}
From 60afc6f33fad385e8c6b2c944a58fb9317d540cb Mon Sep 17 00:00:00 2001
From: John Doe
Date: Thu, 20 Nov 2025 21:25:53 +0100
Subject: [PATCH 77/89] fix: enhance migration logging and update
onboardingCompleted state during preferences migration
---
src/stores/preferences.ts | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/stores/preferences.ts b/src/stores/preferences.ts
index 13b364ca..8bd3dd28 100644
--- a/src/stores/preferences.ts
+++ b/src/stores/preferences.ts
@@ -239,7 +239,12 @@ export const useUserPreferences = create(
const state = persistedState as unknown as UserPreferencesState &
UserPreferencesStoreActions
if (version === 0) {
- console.log('Migrating preferences_storage to version 1')
+ console.log('Migrating preferences_storage to version 1', state)
+
+ return {
+ ...state,
+ onboardingCompleted: true,
+ }
}
return state
},
From 53f53e0900bee9f2401c1432f0a56be71c000e84 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Thu, 20 Nov 2025 22:56:27 +0100
Subject: [PATCH 78/89] fix: update query keys to include version suffix for
conferences, GitHub repos, and Product Hunt products
---
src/features/cards/api/getConferences.ts | 2 +-
src/features/cards/api/getGithubRepos.ts | 2 +-
src/features/cards/api/getProductHuntProducts.ts | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/features/cards/api/getConferences.ts b/src/features/cards/api/getConferences.ts
index c6c0ad33..e56c2c3b 100644
--- a/src/features/cards/api/getConferences.ts
+++ b/src/features/cards/api/getConferences.ts
@@ -21,7 +21,7 @@ type UseGetConferencesOptions = {
export const useGetConferences = ({ config, tags }: UseGetConferencesOptions) => {
return useQuery>({
...config,
- queryKey: ['conferences', ...tags],
+ queryKey: ['conferences_v2', ...tags],
queryFn: () => getConferences(tags),
})
}
diff --git a/src/features/cards/api/getGithubRepos.ts b/src/features/cards/api/getGithubRepos.ts
index 5618db1e..ecbff38b 100644
--- a/src/features/cards/api/getGithubRepos.ts
+++ b/src/features/cards/api/getGithubRepos.ts
@@ -29,7 +29,7 @@ type UseGetReposOptions = {
export const useGetGithubRepos = ({ config, tags, dateRange }: UseGetReposOptions) => {
return useQuery>({
...config,
- queryKey: ['github', ...tags, dateRange],
+ queryKey: ['github_v2', ...tags, dateRange],
queryFn: () => getRepos({ tags, dateRange }),
})
}
diff --git a/src/features/cards/api/getProductHuntProducts.ts b/src/features/cards/api/getProductHuntProducts.ts
index f37e4809..cae9ac03 100644
--- a/src/features/cards/api/getProductHuntProducts.ts
+++ b/src/features/cards/api/getProductHuntProducts.ts
@@ -21,7 +21,7 @@ type UseGetArticlesOptions = {
export const useGeProductHuntProducts = ({ date, config }: UseGetArticlesOptions) => {
return useQuery>({
...config,
- queryKey: ['producthunt', date],
+ queryKey: ['producthunt_v2', date],
queryFn: () => getArticles({ date }),
})
}
From adc4681b8e5c7b7c4c67fc08a07f18a5b5cd36a9 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Thu, 20 Nov 2025 22:56:41 +0100
Subject: [PATCH 79/89] fix: add occupation field to user preferences state and
initialize from onboarding result
---
src/stores/preferences.ts | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/stores/preferences.ts b/src/stores/preferences.ts
index 8bd3dd28..44b4160b 100644
--- a/src/stores/preferences.ts
+++ b/src/stores/preferences.ts
@@ -19,6 +19,11 @@ export type UserPreferencesState = {
theme: Theme
openLinksNewTab: boolean
onboardingCompleted: boolean
+ onboardingResult?: {
+ title: string
+ sources: string[]
+ tags: string[]
+ } | null
occupation: string | null
listingMode: ListingMode
promptEngine: string
@@ -244,6 +249,7 @@ export const useUserPreferences = create(
return {
...state,
onboardingCompleted: true,
+ occupation: state.onboardingResult?.title || '',
}
}
return state
From a37b1d3d953cb308811e938fdaf0022226be2b7b Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sat, 22 Nov 2025 12:27:08 +0100
Subject: [PATCH 80/89] fix: add Hackernews and Lobsters sources to
FeedItemSource and update styles for source display
---
.../feed/components/FeedItemSource.tsx | 49 +++++++++++++++----
src/features/feed/components/feed.css | 13 +++++
.../components/feedItems/ArticleFeedItem.tsx | 4 +-
3 files changed, 55 insertions(+), 11 deletions(-)
diff --git a/src/features/feed/components/FeedItemSource.tsx b/src/features/feed/components/FeedItemSource.tsx
index ac477d51..ac7f2e30 100644
--- a/src/features/feed/components/FeedItemSource.tsx
+++ b/src/features/feed/components/FeedItemSource.tsx
@@ -1,10 +1,11 @@
+import { memo, useState } from 'react'
import { FaDev, FaMediumM, FaReddit } from 'react-icons/fa'
import { GoDotFill } from 'react-icons/go'
-import { SiGithub, SiProducthunt } from 'react-icons/si'
+import { SiGithub, SiProducthunt, SiYcombinator } from 'react-icons/si'
import HashNodeIcon from 'src/assets/icon_hashnode.png'
-const FeedItemKV: {
- [key: string]: React.ReactNode
-} = {
+import LobstersIcon from 'src/assets/icon_lobsters.png'
+
+const SOURCE_MAP: Record = {
producthunt: (
<>
Product hunt
@@ -35,13 +36,43 @@ const FeedItemKV: {
Hashnode
>
),
+ hackernews: (
+ <>
+ Hackernews
+ >
+ ),
+ lobsters: (
+ <>
+ Lobsters
+ >
+ ),
}
-export const FeedItemSource = ({ source }: { source: string }) => {
- return (
- FeedItemKV[source] || (
+export const FeedItemSource = memo(({ source }: { source: string }) => {
+ const [fallback, setFallback] = useState(false)
+
+ if (SOURCE_MAP[source]) {
+ return SOURCE_MAP[source]
+ }
+
+ if (!fallback && source.includes('.')) {
+ return (
<>
- {source}
+ {
+ setFallback(true)
+ }}
+ />{' '}
+ {source}
>
)
+ }
+
+ return (
+ <>
+ {source}
+ >
)
-}
+})
diff --git a/src/features/feed/components/feed.css b/src/features/feed/components/feed.css
index 38824082..1c536659 100644
--- a/src/features/feed/components/feed.css
+++ b/src/features/feed/components/feed.css
@@ -130,3 +130,16 @@
margin: 0;
padding: 0;
}
+
+.sourceName {
+ text-transform: none;
+
+ &::first-letter {
+ text-transform: capitalize;
+ }
+}
+
+.feedItemSourceFallback {
+ width: 12px;
+ height: 12px;
+}
diff --git a/src/features/feed/components/feedItems/ArticleFeedItem.tsx b/src/features/feed/components/feedItems/ArticleFeedItem.tsx
index f284b39a..bb51fc47 100644
--- a/src/features/feed/components/feedItems/ArticleFeedItem.tsx
+++ b/src/features/feed/components/feedItems/ArticleFeedItem.tsx
@@ -20,14 +20,14 @@ export const ArticleFeedItem = (props: BaseItemPropsType) =
{listingMode === 'compact' && (
-
+
)}
{listingMode === 'normal' && (
-
+
From 13fa8754e764951bf324123b326e611a4dab8a15 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sat, 22 Nov 2025 19:34:53 +0100
Subject: [PATCH 81/89] fix: remove unused PostFlair component and its
associated types from ArticleItem
---
.../cards/components/redditCard/ArticleItem.tsx | 16 ----------------
1 file changed, 16 deletions(-)
diff --git a/src/features/cards/components/redditCard/ArticleItem.tsx b/src/features/cards/components/redditCard/ArticleItem.tsx
index 615ae392..1369f3b4 100644
--- a/src/features/cards/components/redditCard/ArticleItem.tsx
+++ b/src/features/cards/components/redditCard/ArticleItem.tsx
@@ -10,22 +10,6 @@ import { useUserPreferences } from 'src/stores/preferences'
import { Article, BaseItemPropsType } from 'src/types'
import { format } from 'timeago.js'
-type PostFlairPropsType = {
- text: string
- textColor?: string
- bgColor?: string
-}
-
-const PostFlair = ({ text, bgColor, textColor }: PostFlairPropsType) => {
- const color = textColor === 'light' ? '#fff' : '#000'
- const backgroundColor = bgColor ? bgColor : '#dadada'
- return (
-
- {text}
-
- )
-}
-
const ArticleItem = ({ item, analyticsTag }: BaseItemPropsType) => {
const { listingMode } = useUserPreferences()
From d6c16c153ee0041e1e1548a77bbe6026b55b1954 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sat, 22 Nov 2025 19:35:02 +0100
Subject: [PATCH 82/89] fix: update GLOBAL_TAG value to an empty string in
HashnodeCard and RedditCard components
---
src/features/cards/components/hashnodeCard/HashnodeCard.tsx | 2 +-
src/features/cards/components/redditCard/RedditCard.tsx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/features/cards/components/hashnodeCard/HashnodeCard.tsx b/src/features/cards/components/hashnodeCard/HashnodeCard.tsx
index 2082aed7..5daddb91 100644
--- a/src/features/cards/components/hashnodeCard/HashnodeCard.tsx
+++ b/src/features/cards/components/hashnodeCard/HashnodeCard.tsx
@@ -11,7 +11,7 @@ import { MemoizedCardHeader } from '../CardHeader'
import { MemoizedCardSettings } from '../CardSettings'
import ArticleItem from './ArticleItem'
-const GLOBAL_TAG = { label: 'Global', value: 'programming' }
+const GLOBAL_TAG = { label: 'Global', value: '' }
export function HashnodeCard(props: CardPropsType) {
const { meta } = props
diff --git a/src/features/cards/components/redditCard/RedditCard.tsx b/src/features/cards/components/redditCard/RedditCard.tsx
index 4f03d69d..96d3b31e 100644
--- a/src/features/cards/components/redditCard/RedditCard.tsx
+++ b/src/features/cards/components/redditCard/RedditCard.tsx
@@ -11,7 +11,7 @@ import { MemoizedCardHeader } from '../CardHeader'
import { MemoizedCardSettings } from '../CardSettings'
import ArticleItem from './ArticleItem'
-const GLOBAL_TAG = { label: 'Global', value: 'global' }
+const GLOBAL_TAG = { label: 'Global', value: '' }
export function RedditCard(props: CardPropsType) {
const { meta } = props
From 7e908eb0eac5496e53ebb61d144477359675be04 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sat, 22 Nov 2025 19:44:11 +0100
Subject: [PATCH 83/89] fix: update GLOBAL_TAG value to an empty string in
FreecodecampCard component
---
.../cards/components/freecodecampCard/FreecodecampCard.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/features/cards/components/freecodecampCard/FreecodecampCard.tsx b/src/features/cards/components/freecodecampCard/FreecodecampCard.tsx
index 34bd637c..83b93fa7 100644
--- a/src/features/cards/components/freecodecampCard/FreecodecampCard.tsx
+++ b/src/features/cards/components/freecodecampCard/FreecodecampCard.tsx
@@ -9,7 +9,7 @@ import { MemoizedCardHeader } from '../CardHeader'
import { MemoizedCardSettings } from '../CardSettings'
import ArticleItem from './ArticleItem'
-const GLOBAL_TAG = { label: 'Global', value: 'global' }
+const GLOBAL_TAG = { label: 'Global', value: '' }
export function FreecodecampCard(props: CardPropsType) {
const { meta } = props
From 7cb791c4ff625bb700b180410c4d7964e02161cb Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sat, 22 Nov 2025 20:30:13 +0100
Subject: [PATCH 84/89] feat: add HackernoonCard component and associated
ArticleItem with icon
---
src/assets/icon_hackernoon.jpeg | Bin 0 -> 7510 bytes
src/config/supportedCards.tsx | 11 +++
.../components/hackernoonCard/ArticleItem.tsx | 47 +++++++++++++
.../hackernoonCard/HackernoonCard.tsx | 64 ++++++++++++++++++
.../cards/components/hackernoonCard/index.ts | 1 +
src/features/cards/index.ts | 1 +
6 files changed, 124 insertions(+)
create mode 100644 src/assets/icon_hackernoon.jpeg
create mode 100644 src/features/cards/components/hackernoonCard/ArticleItem.tsx
create mode 100644 src/features/cards/components/hackernoonCard/HackernoonCard.tsx
create mode 100644 src/features/cards/components/hackernoonCard/index.ts
diff --git a/src/assets/icon_hackernoon.jpeg b/src/assets/icon_hackernoon.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..12b4bd04e1ab7ea2a6dcf41117564aefbe3af737
GIT binary patch
literal 7510
zcmcIo2RxNs{6BXg#J$%wvo}dsM!d)-vXxSll|8SOmDMscqo{0>h7}md+Dv-_x-=)^Z)#h>v?|Xdw%=;&Uv2mZ1!#r1GJX9raFMZ0DwV%U~?3nuBoDO
zO5adVT~k|)GysovvU2u7;sJ1W@gx{(D03b&InD`wY8F-=Zc6(4+CM>mAeUYfwFA(#
zhp6ihga6c}vOeu$1vy9oF-lvx5j-JU5u$Z{Jl%+NEJWk1>@2JyIvb)z2#|vi-9f}H
zx9E37+IEXxA<~4yhAIHSsUe!vc8eAv(w18^F&0iMJAyOB;{?&X&Q4yC52Qwng!;70
z5hLi%O8WNzhCl--15RKGynsD$0-j(Gba#Pr_SbUzNOHQs6_T-nu5Q2=5FiO#Uk54TPVD{+u8VsQt+OkDPkgh!gceAyaDsfEwG}To49;JQaYAi<_HknVXv%
zSpXn<0jN#V}D@0*8!jn1K>f;56m(LfO06uOoMI~1PhWK1ayVl*Z?q}
z2f$7f0O+7xQ=a(0j&J1~(oNJ4vU&hG><@r;D*#uo0l*L0qcz=p15^MCfkYw^DCmMh
zp~%RoDafHlOGUMV8bgc4VrVfKItEroIvfi<2E)X~#KOkL!NEbt$j!^m&dbWq!A?{H
zgG9;6$Z04jXxMQW9Q%K6oAm%g0gHvjB4FGAj)5UCu+1k>LBIe4Mr_0wNDcCk0*QdZ
zp-~pn$j^g-422{|z&HB=H3A0UXapLX#yEE0aZUg*rT&fVx!qFvid%|cl2nq<8r;y|
zpt{;>>YXzcn2>CGvzk`R=NcJh^M=zJDu?RbhylJAX
zh9K&!qtB^_X>Pn{zXA1dCtl&2YO4)qm`YwM9~XoW*!ztQdBLL^rmxujKi+wG$Sm=Q
z`&!ef{J#=4uV=K-tPWCOP1DR4_SUs$6q@@$n_LTka;x%}<*vr&tnw(1Ga?Z12TuXO
zn|(&@HjjraV>>M(e*=KaCc|&-$FA`OhgC1;myvEv#i{HvjuV8Z+4%*FQ!3_6@(6wFDeb)G#gWU}>0yTpfTwsA%VcL`~4-x
zbE*!lyR1az*`9syjN|Ne=lGq@@qW>&kPVb>x}#Gp@#m?v36ei7WbF~9t}Gj?6Hkqs
zU=rW<;`h}F6Jiwf)Q3=U(|d=X*Mh%UVp?i3H$2cEd&Fq2|9LNQ%74+E?PTpWwqP%R
zxScob)#Q$hUh<>&{~qR0k}nWC&4UkuK@#M~$qW@d=`;p9eoZ(U>Ewm5vVN
zF}N+4_-iRR7_1py?oT}1owbz1QOaz;aOokW2#;Ez8~fv+zE!OYTOPxdbh&+F#^5Hf
z{f*l(`)FIgykMo*lNVw%m#%%JUwD04OZ0k?IJHV9f2CT$m#mkOc~065=!x*Vai`?%
z7BfG-p~F8R3AP`H1ik+(=*_jvNV6vGJvs8GjAQ>SO+4?@U2=UwdA5p8(UhljNej6y-CvSQ9UgQ?Y?JZqNVsNrvvD_UPjYN5tJhD
zQ1e|qh4Ng?Qf*C(wfwBI!{%t6nY>`9xGnF@O@V-K>qdbqxodOUEX%1AK|hVkBeEBN
zDDvp|O>b*Z@}jzW%>*hnoxvse_-d+*K!A+ghehtx3AOzl5#~LnZyUZc9oknz74XBH
z>(!EYYBM_vk>@LW7Dzs+%8YIdna~Va%X-C5&5ALw%^6XYY4g@*6%~d0lSzN*Kuh8i
z4%hE7q9~sqm7nW5c<)B9tfdXpMO7Woh}EgE2mBV7JXQZ4JP>x5$Zg3f
zT@(0f*5zK9jMr>1a4BNxIStWb3`z#1RRq0TD8S~D_cTj#8|O8e5Z-zi@;L+?
z`A5Jg?6=(c{3OM`s$Dm4%l?)-c`~uVAtLw4LEilx|V%_TPr@h@U=5pl$>=(
z#9MK7(+XBM{@rfvS1GJgvmZOZVSj{5*Nx=9AKsGtmSU7rF_U3`SAC*YxC7O#n
zB27eybreun2ettZy+0-@X=X7+qd5}#J=aMj9eOM{rvVga;dn5g0+9DHbyj4FgVt8penVeqPQjh{wMdI
zwrv+%nF+)0wJCQRB+T3GWrg|myR>(4Bp=ibq}xO0egDHXFX*p|m`0<!tDkZF#
z0T*em;H0^VH(~7#ESC~~CKBUt;7uyM^%V_C4(Ha0;zK;wEd&lMY_gsBlG#>ysr%24AN6Wtrh5lZhWlsimFL;{;SIXTKAwVD7~8`{g{W%rCO#11)1b*XXsAr#w+r&@!WNvqAc_Z>WZl$9BPO`PCvDYik}?|
zku7G<+U=%_qhpsXakW#bW8C{rD}t3yFl8t`X0oTB|FVG@UWj};Z98&fO=}N>rMJDe
zd5dN8*Ib;A{BNn_fM!AldhWnc6ci`~YWrOqh5&G+ViATDFF^N*6V=hl38Ilt`+WZ`8MUPA7njm%ZCgBdV)P7AuHE{>
zE#|1#>i&D_YXpeu{=njW>@l^vp639KeI;uL(_va_?M=|J=DW`4RFilo`DIe^SHlOV
zw2WImYV`#QYL0fNhuFYhYAz8~`f_FzB#&I0LjI9Y=)~R}SboD138WVNszUS-3UL$)p&C-}`h$)bH_yCu`
zmv>;-OB>CP;=J%zsBsPlIV0vGpPk~8aRmEO^iIAZ%Kr**R)%rcrVc%!^Ir*d4MOW!
zkr}+c8DS+QI+ANsEUF`?3{$|Z|7>K(O5SKnYh;D}B?iL)#UlpEUjU!cD*d;Yx7H&z
zf&JlcYC)CWH@Lt{N_3T1d||#8h+*fraI)2WVJ)}*HzNx+!9z0X
z#+mai+%@gOY3g_Hr%--NU&8fjXgTrZNMT;yJa*>tC7S`Wo7Rx5UsNk-RP$9%Ystt-
zWwfil^~;3&8l|C`&kr&3h+eqjWR^Mq%e<$)l0Fb0@LRkD~Ac}G6~r4AUAWa?lk
zW^;sNb$I2&gLmRxji13Pih7Pcin#nZ{z$|N!$MulBufw6ZMu}4V`+`TPu>e^!rSy=
z7laFIf)?$bVOx#_p*6aHWf3wK5YsA>i9iVDof#SDr%=g{tTO8Rl%-&hs6>J4~xY~8V$2d}k_@~Os2
zUVow$&h03BDG96k+6N=d8(uigeOQyu5#EqWv!}8zQLC7vew{p
zlIc};b$E4CLW5qn)h>5@1r7K*&~ss?dVkvbzHDM_*G-Ft)Uty3_d>O=PW9(e3zWQE*N-Rs&OENWfB
zEJ}7LB-*t?zV^hkFKd>mOah;{{cZi%%fD+-U20oibrtBDif}R?Xqv7r4;-(W5z*#xZJKItULi?|B^3{9$m?B$`IO!Y58j^20VrYWnUYuF$;Rj
z7fDU^!p%$_xMJXOiY0u75*KsWiDo4<)>GvaOQ;?BQ$L<(fhn40vXiXX2EQ(7m
zzn5(eCpdATrVMpMk^4rvr<6W&dqMkEV(2(mtuy{b6=?Co=MQQi0YG6W~Ne3%!vgRGhlKW%7m(kt6lH!(qU
zqb)ziT1%2&)0j{o#W15ie%NWxU}dv<_5^qNCA>bDz&j@zbkgb3-DiRk6m5a~v+Sc!
zrp}cjIDzIpudkOsolOtE#k=}=m*a=lm3~+pQlzIcP-9NP4d}sk&5u|r3PrS0
z3qL(?ry75U=)cdtkd}mpi8Q9ye+iP9rrl`h0gFUXKu6i_b1e*DpvtP7<-{xLL*dhCM;C{13Oan!-lq|J3(
zv71gIMyO^f8(#P)@wIZ$!;9ii@rAySqK?imZwDrpX^WBMOh
zE=qUH)L~ysWHJvVK7bS{xn6PPPd87@$oKe(r{iI5VT!aPzjc9v`?^o}mO?ebTKnvz
zp+d&0d0{~@zDKyeIaIlsIupyX8+S@>`=vcknIxx&)x>meqEE!)9S3S7EK?6~mUEi-
zRE6M(rd$;3?)kulBAix2dz*IROt9Ei_FG-%=C#2ER})5%^k@1{$$ltQ*1-}mPuzzn
zA+|T!BN7Q2`i|m73Vus8m%{F9CLU*JT%{0u*&rs!P;9z$u&RqvotOu6FaP^&uc2Pk
zU-qhs?k?6Bku0+v3G25p!ekv1ZP1bAXWhk#izR1!@6kYS
zj~~HX{^TUN7X-ZXd(=xak^VI0g83KrDg+r4rTXGA1?+bH%he${OYB5K{9T?4ywW&CiMm%g=MBY#;Y1$jQ*xf?HT*@)49T`@^3TyaJ`b#3tT#3C;voFBCX?7V#26*
z3eafk$L?D#u2*&Fxay(R;reN(?Ek04Q
z$^EW)w0oLk)ELBNCC&1I@;oyx=jhko;ub;ZYrRcLP}C;4Y-u|;sr^kp{N^wAfp#
eie~qiF}5bzC?BS2{B7@`#I7IJN-W*YzW)Jsksjv&
literal 0
HcmV?d00001
diff --git a/src/config/supportedCards.tsx b/src/config/supportedCards.tsx
index 4ef1fe82..cd0c1423 100644
--- a/src/config/supportedCards.tsx
+++ b/src/config/supportedCards.tsx
@@ -2,6 +2,7 @@ import { CgIndieHackers } from 'react-icons/cg'
import { FaDev, FaFreeCodeCamp, FaMediumM, FaReddit } from 'react-icons/fa'
import { HiSparkles, HiTicket } from 'react-icons/hi'
import { SiGithub, SiProducthunt, SiYcombinator } from 'react-icons/si'
+import HackernoonIcon from 'src/assets/icon_hackernoon.jpeg'
import HashNodeIcon from 'src/assets/icon_hashnode.png'
import LobstersIcon from 'src/assets/icon_lobsters.png'
import { AICard } from 'src/features/cards/components/aiCard'
@@ -18,6 +19,7 @@ const { IndiehackersCard } = lazyImport(() => import('src/features/cards'), 'Ind
const { LobstersCard } = lazyImport(() => import('src/features/cards'), 'LobstersCard')
const { ProductHuntCard } = lazyImport(() => import('src/features/cards'), 'ProductHuntCard')
const { RedditCard } = lazyImport(() => import('src/features/cards'), 'RedditCard')
+const { HackernoonCard } = lazyImport(() => import('src/features/cards'), 'HackernoonCard')
export const SUPPORTED_CARDS: SupportedCardType[] = [
{
@@ -128,4 +130,13 @@ export const SUPPORTED_CARDS: SupportedCardType[] = [
type: 'supported',
link: 'https://hackertab.dev/',
},
+ {
+ value: 'hackernoon',
+ analyticsTag: 'hackernoon',
+ label: 'Hackernoon',
+ component: HackernoonCard,
+ icon: ,
+ link: 'https://hackernoon.com/',
+ type: 'supported',
+ },
]
diff --git a/src/features/cards/components/hackernoonCard/ArticleItem.tsx b/src/features/cards/components/hackernoonCard/ArticleItem.tsx
new file mode 100644
index 00000000..01262328
--- /dev/null
+++ b/src/features/cards/components/hackernoonCard/ArticleItem.tsx
@@ -0,0 +1,47 @@
+import { MdAccessTime } from 'react-icons/md'
+import { CardItemWithActions, CardLink, ColoredLanguagesBadge } from 'src/components/Elements'
+import { Attributes } from 'src/lib/analytics'
+import { useUserPreferences } from 'src/stores/preferences'
+import { Article, BaseItemPropsType } from 'src/types'
+import { format } from 'timeago.js'
+
+const ArticleItem = (props: BaseItemPropsType) => {
+ const { item, selectedTag, analyticsTag } = props
+ const { listingMode } = useUserPreferences()
+ return (
+
+
+ {item.title}
+
+ {listingMode === 'normal' && (
+ <>
+
+
+
+ {format(new Date(item.published_at))}
+
+
+
+
+
+ >
+ )}
+ >
+ }
+ />
+ )
+}
+
+export default ArticleItem
diff --git a/src/features/cards/components/hackernoonCard/HackernoonCard.tsx b/src/features/cards/components/hackernoonCard/HackernoonCard.tsx
new file mode 100644
index 00000000..060b7c7e
--- /dev/null
+++ b/src/features/cards/components/hackernoonCard/HackernoonCard.tsx
@@ -0,0 +1,64 @@
+import { useCallback } from 'react'
+import { Card } from 'src/components/Elements'
+import { ListPostComponent } from 'src/components/List/ListPostComponent'
+import { Article, CardPropsType } from 'src/types'
+import { useGetSourceArticles } from '../../api/getSourceArticles'
+import { useLazyListLoad } from '../../hooks/useLazyListLoad'
+import { useSelectedTags } from '../../hooks/useSelectedTags'
+import { MemoizedCardHeader } from '../CardHeader'
+import { MemoizedCardSettings } from '../CardSettings'
+import ArticleItem from './ArticleItem'
+
+const GLOBAL_TAG = { label: 'Global', value: '' }
+
+export function HackernoonCard(props: CardPropsType) {
+ const { meta } = props
+ const { ref, isVisible } = useLazyListLoad()
+ const {
+ queryTags,
+ selectedTag,
+ cardSettings: { sortBy, language } = {},
+ } = useSelectedTags({
+ source: meta.value,
+ fallbackTag: GLOBAL_TAG,
+ })
+
+ const { data, isLoading } = useGetSourceArticles({
+ source: 'hackernoon',
+ tags: queryTags,
+ config: {
+ enabled: isVisible,
+ },
+ })
+
+ const renderItem = useCallback(
+ (item: Article) => ,
+ [meta.analyticsTag]
+ )
+
+ return (
+
+ }
+ settingsComponent={
+
+ }
+ {...props}>
+
+
+ )
+}
diff --git a/src/features/cards/components/hackernoonCard/index.ts b/src/features/cards/components/hackernoonCard/index.ts
new file mode 100644
index 00000000..d54a47dc
--- /dev/null
+++ b/src/features/cards/components/hackernoonCard/index.ts
@@ -0,0 +1 @@
+export * from './HackernoonCard'
diff --git a/src/features/cards/index.ts b/src/features/cards/index.ts
index 47e18318..6c9e784e 100644
--- a/src/features/cards/index.ts
+++ b/src/features/cards/index.ts
@@ -6,6 +6,7 @@ export * from './components/devtoCard'
export * from './components/freecodecampCard'
export * from './components/githubCard'
export * from './components/hackernewsCard'
+export * from './components/hackernoonCard'
export * from './components/hashnodeCard'
export * from './components/indiehackersCard'
export * from './components/lobstersCard'
From 603fb48d571267636777e51da769191823afb50c Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 23 Nov 2025 22:24:40 +0100
Subject: [PATCH 85/89] fix: add error handling to source article fetching in
multiple card components
---
.../cards/components/conferencesCard/ConferencesCard.tsx | 7 ++++++-
.../cards/components/freecodecampCard/FreecodecampCard.tsx | 3 ++-
.../cards/components/hackernoonCard/HackernoonCard.tsx | 3 ++-
.../cards/components/hashnodeCard/HashnodeCard.tsx | 3 ++-
src/features/cards/components/mediumCard/MediumCard.tsx | 3 ++-
5 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/src/features/cards/components/conferencesCard/ConferencesCard.tsx b/src/features/cards/components/conferencesCard/ConferencesCard.tsx
index 84173fac..66e21121 100644
--- a/src/features/cards/components/conferencesCard/ConferencesCard.tsx
+++ b/src/features/cards/components/conferencesCard/ConferencesCard.tsx
@@ -21,7 +21,11 @@ export function ConferencesCard(props: CardPropsType) {
source: meta.value,
fallbackTag: GLOBAL_TAG,
})
- const { isLoading, data: results } = useGetConferences({
+ const {
+ isLoading,
+ error,
+ data: results,
+ } = useGetConferences({
tags: queryTags,
config: {
enabled: isVisible,
@@ -59,6 +63,7 @@ export function ConferencesCard(props: CardPropsType) {
diff --git a/src/features/cards/components/freecodecampCard/FreecodecampCard.tsx b/src/features/cards/components/freecodecampCard/FreecodecampCard.tsx
index 83b93fa7..ff11f745 100644
--- a/src/features/cards/components/freecodecampCard/FreecodecampCard.tsx
+++ b/src/features/cards/components/freecodecampCard/FreecodecampCard.tsx
@@ -23,7 +23,7 @@ export function FreecodecampCard(props: CardPropsType) {
fallbackTag: GLOBAL_TAG,
})
- const { data, isLoading } = useGetSourceArticles({
+ const { data, error, isLoading } = useGetSourceArticles({
source: 'freecodecamp',
tags: queryTags,
config: {
@@ -56,6 +56,7 @@ export function FreecodecampCard(props: CardPropsType) {
diff --git a/src/features/cards/components/hackernoonCard/HackernoonCard.tsx b/src/features/cards/components/hackernoonCard/HackernoonCard.tsx
index 060b7c7e..0f5d7cb8 100644
--- a/src/features/cards/components/hackernoonCard/HackernoonCard.tsx
+++ b/src/features/cards/components/hackernoonCard/HackernoonCard.tsx
@@ -23,7 +23,7 @@ export function HackernoonCard(props: CardPropsType) {
fallbackTag: GLOBAL_TAG,
})
- const { data, isLoading } = useGetSourceArticles({
+ const { data, error, isLoading } = useGetSourceArticles({
source: 'hackernoon',
tags: queryTags,
config: {
@@ -55,6 +55,7 @@ export function HackernoonCard(props: CardPropsType) {
{...props}>
sortBy={sortBy as keyof Article}
+ error={error}
items={data}
isLoading={isLoading}
renderItem={renderItem}
diff --git a/src/features/cards/components/mediumCard/MediumCard.tsx b/src/features/cards/components/mediumCard/MediumCard.tsx
index 83d64e66..1e342860 100644
--- a/src/features/cards/components/mediumCard/MediumCard.tsx
+++ b/src/features/cards/components/mediumCard/MediumCard.tsx
@@ -24,7 +24,7 @@ export function MediumCard(props: CardPropsType) {
source: meta.value,
fallbackTag: GLOBAL_TAG,
})
- const { data, isLoading } = useGetSourceArticles({
+ const { data, isLoading, error } = useGetSourceArticles({
source: 'medium',
tags: queryTags,
config: {
@@ -76,6 +76,7 @@ export function MediumCard(props: CardPropsType) {
From 05944a6a026c19c4a61b240e0afdc721fc775f0f Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 23 Nov 2025 22:24:59 +0100
Subject: [PATCH 86/89] feat: add bodyClassName prop to SettingsContentLayout
and apply topicsBottomSpacer style in TopicSettings
---
.../Layout/SettingsContentLayout/SettingsContentLayout.tsx | 5 ++++-
.../Layout/SettingsContentLayout/settingsContentLayout.css | 5 +++++
src/features/settings/components/TopicSettings.tsx | 1 +
3 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/components/Layout/SettingsContentLayout/SettingsContentLayout.tsx b/src/components/Layout/SettingsContentLayout/SettingsContentLayout.tsx
index cd2b596f..c3e15c7d 100644
--- a/src/components/Layout/SettingsContentLayout/SettingsContentLayout.tsx
+++ b/src/components/Layout/SettingsContentLayout/SettingsContentLayout.tsx
@@ -1,3 +1,4 @@
+import clsx from 'clsx'
import './settingsContentLayout.css'
type SettingsContentLayoutProps = {
@@ -5,6 +6,7 @@ type SettingsContentLayoutProps = {
description: string
children: React.ReactNode
actions?: React.ReactNode
+ bodyClassName?: string
}
export const SettingsContentLayout = ({
@@ -12,6 +14,7 @@ export const SettingsContentLayout = ({
description,
actions,
children,
+ bodyClassName,
}: SettingsContentLayoutProps) => {
return (
@@ -23,7 +26,7 @@ export const SettingsContentLayout = ({
{actions &&
{actions}
}
-
{children}
+
{children}
)
}
diff --git a/src/components/Layout/SettingsContentLayout/settingsContentLayout.css b/src/components/Layout/SettingsContentLayout/settingsContentLayout.css
index 6cab9b24..22ce4d59 100644
--- a/src/components/Layout/SettingsContentLayout/settingsContentLayout.css
+++ b/src/components/Layout/SettingsContentLayout/settingsContentLayout.css
@@ -63,6 +63,11 @@
cursor: pointer;
}
}
+
+.topicsBottomSpacer {
+ margin-bottom: 80px;
+ padding-bottom: 24px;
+}
.settingsContent header {
display: flex;
justify-content: space-between;
diff --git a/src/features/settings/components/TopicSettings.tsx b/src/features/settings/components/TopicSettings.tsx
index 0be73258..c6759997 100644
--- a/src/features/settings/components/TopicSettings.tsx
+++ b/src/features/settings/components/TopicSettings.tsx
@@ -59,6 +59,7 @@ export const TopicSettings = () => {
return (
{userSelectedTags.length > 0 ? (
From 530796dd76a169888266e28eed6a0c06a71be1d2 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 23 Nov 2025 22:25:08 +0100
Subject: [PATCH 87/89] fix: update sources for Devops, Data, Security, and ML
Engineer occupations to remove Freecodecamp
---
src/features/onboarding/components/steps/HelloTab.tsx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/features/onboarding/components/steps/HelloTab.tsx b/src/features/onboarding/components/steps/HelloTab.tsx
index ed3529c5..171f2876 100644
--- a/src/features/onboarding/components/steps/HelloTab.tsx
+++ b/src/features/onboarding/components/steps/HelloTab.tsx
@@ -51,28 +51,28 @@ const OCCUPATIONS: Occupation[] = [
title: 'Devops Engineer',
value: 'devops',
icon: FaServer,
- sources: ['freecodecamp', 'github', 'reddit', 'devto'],
+ sources: ['hackernoon', 'github', 'reddit', 'hackernews'],
tags: ['devops', 'kubernetes', 'docker', 'bash'],
},
{
title: 'Data Engineer',
value: 'data',
icon: FaDatabase,
- sources: ['freecodecamp', 'github', 'reddit', 'devto'],
+ sources: ['hackernoon', 'github', 'reddit', 'devto'],
tags: ['data science', 'python', 'artificial intelligence', 'machine learning'],
},
{
title: 'Security Engineer',
value: 'security',
icon: AiFillSecurityScan,
- sources: ['freecodecamp', 'github', 'reddit', 'devto'],
+ sources: ['hackernoon', 'github', 'reddit', 'devto'],
tags: ['security', 'cpp', 'bash', 'python'],
},
{
title: 'ML Engineer',
value: 'ai',
icon: FaRobot,
- sources: ['github', 'freecodecamp', 'hackernews', 'devto'],
+ sources: ['github', 'hackernoon', 'hackernews', 'devto'],
tags: ['machine learning', 'artificial intelligence', 'python'],
},
{
From 6d9188438685e91203a9e6e4059f395426615a05 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Sun, 23 Nov 2025 22:33:16 +0100
Subject: [PATCH 88/89] fix: update occupation tags and improve card settings
handling in HelloTab component
---
.../onboarding/components/steps/HelloTab.tsx | 22 ++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/src/features/onboarding/components/steps/HelloTab.tsx b/src/features/onboarding/components/steps/HelloTab.tsx
index 171f2876..656e9f74 100644
--- a/src/features/onboarding/components/steps/HelloTab.tsx
+++ b/src/features/onboarding/components/steps/HelloTab.tsx
@@ -28,7 +28,7 @@ const OCCUPATIONS: Occupation[] = [
icon: RiDeviceFill,
value: 'fullstack',
sources: ['devto', 'github', 'medium', 'hashnode'],
- tags: ['javascript', 'typescript', 'php', 'ruby', 'rust'],
+ tags: ['webdev', 'javascript', 'typescript', 'php', 'devops'],
},
{
title: 'Mobile',
@@ -36,8 +36,8 @@ const OCCUPATIONS: Occupation[] = [
icon: AiFillMobile,
sources: ['reddit', 'github', 'medium', 'hashnode'],
tags: [
- 'android',
'mobile',
+ 'android',
'kotlin',
'java',
'ios',
@@ -73,7 +73,7 @@ const OCCUPATIONS: Occupation[] = [
value: 'ai',
icon: FaRobot,
sources: ['github', 'hackernoon', 'hackernews', 'devto'],
- tags: ['machine learning', 'artificial intelligence', 'python'],
+ tags: ['artificial intelligence', 'machine learning', 'python'],
},
{
title: 'Other',
@@ -85,8 +85,14 @@ const OCCUPATIONS: Occupation[] = [
]
export const HelloTab = () => {
- const { markOnboardingAsCompleted, setCards, setTags, setOccupation, occupation } =
- useUserPreferences()
+ const {
+ markOnboardingAsCompleted,
+ setCardSettings,
+ setCards,
+ setTags,
+ setOccupation,
+ occupation,
+ } = useUserPreferences()
const { tags } = useRemoteConfigStore()
@@ -108,6 +114,12 @@ export const HelloTab = () => {
.filter(Boolean) as Array
setTags(userTags)
+ for (const source of selectedOccupation.sources) {
+ setCardSettings(source, {
+ language: selectedOccupation.tags[0],
+ sortBy: 'published_at',
+ })
+ }
}
markOnboardingAsCompleted()
From 082c6863a31e824aa6ee6dde52eb593c55ab81c1 Mon Sep 17 00:00:00 2001
From: John Doe
Date: Tue, 25 Nov 2025 22:26:03 +0100
Subject: [PATCH 89/89] feat: add 'Stars today' metric to GitHub cards and feed
items
---
src/features/cards/components/githubCard/GithubCard.tsx | 5 +++++
src/features/cards/components/githubCard/RepoItem.tsx | 6 ++++++
src/features/feed/components/feedItems/RepoFeedItem.tsx | 6 ++++++
src/types/index.ts | 3 ++-
4 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/src/features/cards/components/githubCard/GithubCard.tsx b/src/features/cards/components/githubCard/GithubCard.tsx
index 26f06f63..bca8aeab 100644
--- a/src/features/cards/components/githubCard/GithubCard.tsx
+++ b/src/features/cards/components/githubCard/GithubCard.tsx
@@ -93,6 +93,11 @@ export function GithubCard(props: CardPropsType) {
value: 'stars_count',
icon: ,
},
+ {
+ label: 'Stars today',
+ value: 'stars_in_range',
+ icon: ,
+ },
{
label: 'Forks',
value: 'forks_count',
diff --git a/src/features/cards/components/githubCard/RepoItem.tsx b/src/features/cards/components/githubCard/RepoItem.tsx
index d819bbe8..d915a4e3 100644
--- a/src/features/cards/components/githubCard/RepoItem.tsx
+++ b/src/features/cards/components/githubCard/RepoItem.tsx
@@ -42,6 +42,12 @@ const RepoItem = ({ item, selectedTag, analyticsTag }: BaseItemPropsType {numberWithCommas(item.stars_count)} stars
)}
+ {item.stars_in_range && (
+
+ {' '}
+ {numberWithCommas(item.stars_in_range || 0)} stars today
+
+ )}
{item.forks_count && (
{numberWithCommas(item.forks_count)}{' '}
diff --git a/src/features/feed/components/feedItems/RepoFeedItem.tsx b/src/features/feed/components/feedItems/RepoFeedItem.tsx
index ae7dc72d..14236c18 100644
--- a/src/features/feed/components/feedItems/RepoFeedItem.tsx
+++ b/src/features/feed/components/feedItems/RepoFeedItem.tsx
@@ -54,6 +54,12 @@ export const RepoFeedItem = (props: BaseItemPropsType) => {
stars
)}
+ {item.stars_in_range && (
+
+ {' '}
+ {numberWithCommas(item.stars_in_range || 0)} stars today
+
+ )}
{numberWithCommas(item?.forks || 0)}{' '}
forks
diff --git a/src/types/index.ts b/src/types/index.ts
index 52efb3ab..51f2e4a8 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -75,6 +75,7 @@ export type ProductHuntFeedItemData = FeedItem & {
export type GithubFeedItemData = FeedItem & {
type: 'github'
stars: number
+ stars_in_range: number
forks: number
programmingLanguage: string
description?: string
@@ -98,7 +99,7 @@ export type Repository = BaseEntry & {
description: string
owner: string
forks_count: number
- starsInDateRange?: number
+ stars_in_range: number
name: string
}