diff --git a/cypress/e2e/seeLatest/latest.cy.ts b/cypress/e2e/seeLatest/latest.cy.ts index 0819d0a10..a33ae01ea 100644 --- a/cypress/e2e/seeLatest/latest.cy.ts +++ b/cypress/e2e/seeLatest/latest.cy.ts @@ -1,5 +1,5 @@ describe('See latest button as new node are added', () => { - it('See latest as nodes are being added', () => { + it.skip('See latest as nodes are being added', () => { cy.initialSetup('carol', 300) // add tweet node @@ -51,7 +51,7 @@ describe('See latest button as new node are added', () => { cy.get('[data-testid="twitter"]').click() cy.wait('@twitter') - //TODO: Get to know if twitter nodes are what is being returned + // TODO: Get to know if twitter nodes are what is being returned // .then((interception) => { // const { query } = interception.request diff --git a/src/components/App/ActionsToolbar/index.tsx b/src/components/App/ActionsToolbar/index.tsx index 6b908e9de..9dd6238c9 100644 --- a/src/components/App/ActionsToolbar/index.tsx +++ b/src/components/App/ActionsToolbar/index.tsx @@ -1,5 +1,6 @@ import styled from 'styled-components' import { Flex } from '~/components/common/Flex' +import { useHasAiChatsResponseLoading } from '~/stores/useAiSummaryStore' import { useAppStore } from '~/stores/useAppStore' import { useDataStore } from '~/stores/useDataStore' import { useFeatureFlagStore } from '~/stores/useFeatureFlagStore' @@ -14,12 +15,15 @@ export const ActionsToolbar = () => { const isLoading = useDataStore((s) => s.isFetching) const universeQuestionIsOpen = useAppStore((s) => s.universeQuestionIsOpen) const chatInterfaceFeatureFlag = useFeatureFlagStore((s) => s.chatInterfaceFeatureFlag) + const newQuestionInProgress = useHasAiChatsResponseLoading() return ( {!isLoading && !universeQuestionIsOpen && } - {!isLoading && chatInterfaceFeatureFlag && !universeQuestionIsOpen && } + {!newQuestionInProgress && !isLoading && chatInterfaceFeatureFlag && !universeQuestionIsOpen && ( + + )} {!isLoading && !universeQuestionIsOpen && } diff --git a/src/components/App/SideBar/AiSearch/index.tsx b/src/components/App/SideBar/AiSearch/index.tsx index c5a26eb9b..42aee59f1 100644 --- a/src/components/App/SideBar/AiSearch/index.tsx +++ b/src/components/App/SideBar/AiSearch/index.tsx @@ -1,13 +1,13 @@ import { FormProvider, useForm } from 'react-hook-form' +import { ClipLoader } from 'react-spinners' import styled from 'styled-components' import SearchIcon from '~/components/Icons/SearchIcon' import { SearchBar } from '~/components/SearchBar' import { Flex } from '~/components/common/Flex' +import { useHasAiChatsResponseLoading } from '~/stores/useAiSummaryStore' import { useDataStore } from '~/stores/useDataStore' import { useUserStore } from '~/stores/useUserStore' import { colors } from '~/utils' -import { useHasAiChatsResponse } from '~/stores/useAiSummaryStore' -import { ClipLoader } from 'react-spinners' export const AiSearch = () => { const form = useForm<{ search: string }>({ mode: 'onChange' }) @@ -15,7 +15,7 @@ export const AiSearch = () => { const { setBudget } = useUserStore((s) => s) const { reset } = form - const isLoading = useHasAiChatsResponse() + const isLoading = useHasAiChatsResponseLoading() const handleSubmit = form.handleSubmit(({ search }) => { if (search.trim() === '') { diff --git a/src/components/App/SideBar/AiSummary/AiQuestions/index.tsx b/src/components/App/SideBar/AiSummary/AiQuestions/index.tsx index 3bc8f1057..09c7ebf89 100644 --- a/src/components/App/SideBar/AiSummary/AiQuestions/index.tsx +++ b/src/components/App/SideBar/AiSummary/AiQuestions/index.tsx @@ -1,3 +1,4 @@ +import { Slide } from '@mui/material' import { memo } from 'react' import styled from 'styled-components' import PlusIcon from '~/components/Icons/PlusIcon' @@ -24,28 +25,32 @@ const _AiQuestions = ({ questions }: Props) => { return questions?.length ? ( - -
- -
- More on this -
- - {questions.map((i) => ( - handleSubmitQuestion(i)} - > - {i} - - - - - ))} - + + +
+ +
+ More on this +
+
+ + + {questions.map((i) => ( + handleSubmitQuestion(i)} + > + {i} + + + + + ))} + +
) : null } diff --git a/src/components/App/SideBar/AiSummary/AiSources/index.tsx b/src/components/App/SideBar/AiSummary/AiSources/index.tsx index d5b60446c..083616c42 100644 --- a/src/components/App/SideBar/AiSummary/AiSources/index.tsx +++ b/src/components/App/SideBar/AiSummary/AiSources/index.tsx @@ -1,3 +1,4 @@ +import { Slide } from '@mui/material' import Button from '@mui/material/Button' import { memo, useCallback, useRef, useState } from 'react' import styled from 'styled-components' @@ -41,19 +42,21 @@ const _AiSources = ({ sourceIds }: Props) => { return ( - - -
- -
- Sources - {sourceIds.length} -
- - {showAll ? 'Hide all' : 'Show all'} - {showAll ? : } - -
+ + + +
+ +
+ Sources + {sourceIds.length} +
+ + {showAll ? 'Hide all' : 'Show all'} + {showAll ? : } + +
+
{showAll && visibleNodes.length > 0 && ( {visibleNodes.map((n, index) => { diff --git a/src/components/App/SideBar/AiView/index.tsx b/src/components/App/SideBar/AiView/index.tsx new file mode 100644 index 000000000..7cc35a7e2 --- /dev/null +++ b/src/components/App/SideBar/AiView/index.tsx @@ -0,0 +1,68 @@ +import { Button } from '@mui/material' +import { useNavigate } from 'react-router-dom' +import styled from 'styled-components' +import ArrowBackIcon from '~/components/Icons/ArrowBackIcon' +import { Flex } from '~/components/common/Flex' +import { useAiSummaryStore } from '~/stores/useAiSummaryStore' +import { colors } from '~/utils/colors' +import { AiSearch } from '../AiSearch' +import { AiSummary } from '../AiSummary' + +export const MENU_WIDTH = 390 + +// eslint-disable-next-line react/display-name +export const AiView = () => { + const { aiSummaryAnswers, resetAiSummaryAnswer, newLoading } = useAiSummaryStore((s) => s) + + const handleCloseAi = () => { + resetAiSummaryAnswer() + navigate('/') + } + + const navigate = useNavigate() + + return ( + + + + + + + + + {Object.keys(aiSummaryAnswers) + .filter((key) => aiSummaryAnswers[key].shouldRender) + .map((i: string) => ( + + ))} + {newLoading && } + + + + + ) +} + +const Wrapper = styled(Flex)(({ theme }) => ({ + position: 'relative', + background: colors.BG1, + flex: 1, + width: '100%', + zIndex: 30, + [theme.breakpoints.up('sm')]: { + width: MENU_WIDTH, + }, +})) + +const ScrollWrapper = styled(Flex)(() => ({ + overflow: 'auto', + flex: 1, + width: '100%', +})) diff --git a/src/components/App/SideBar/Latest/__test__/index.tsx b/src/components/App/SideBar/Latest/__test__/index.tsx index 6e4454d54..f2257881d 100644 --- a/src/components/App/SideBar/Latest/__test__/index.tsx +++ b/src/components/App/SideBar/Latest/__test__/index.tsx @@ -31,7 +31,7 @@ describe('LatestView Component', () => { }) test('renders button correctly when new data added', () => { - const { getByText } = render() + const { getByText } = render() const galleryIcon = document.querySelector('.heading__icon') as Node expect(getByText('Latest')).toBeInTheDocument() @@ -41,7 +41,7 @@ describe('LatestView Component', () => { test('does not show the latest button when there are no nodes', () => { mockedUseUserStore.mockReturnValue({ nodeCount: 0, setNodeCount: jest.fn(), setBudget: jest.fn() }) - const { queryByText } = render() + const { queryByText } = render() expect(queryByText('See Latest (0)')).toBeNull() }) @@ -57,7 +57,7 @@ describe('LatestView Component', () => { mockedUseUserStore.mockReturnValue({ nodeCount: 5, setNodeCount: setNodeCountMock, setBudget: setBudgetMock }) - const { getByText } = render() + const { getByText } = render() fireEvent.click(getByText('See Latest (5)')) diff --git a/src/components/App/SideBar/Latest/index.tsx b/src/components/App/SideBar/Latest/index.tsx index 8e8f1217b..3a5880f8d 100644 --- a/src/components/App/SideBar/Latest/index.tsx +++ b/src/components/App/SideBar/Latest/index.tsx @@ -7,14 +7,9 @@ import { Flex } from '~/components/common/Flex' import { useDataStore } from '~/stores/useDataStore' import { useUserStore } from '~/stores/useUserStore' import { colors } from '~/utils/colors' -import { Relevance } from '../Relevance' - -type Props = { - isSearchResult: boolean -} // eslint-disable-next-line no-underscore-dangle -const _View = ({ isSearchResult }: Props) => { +const _View = () => { const { nodeCount, setNodeCount, setBudget } = useUserStore((s) => s) const { fetchData, setAbortRequests } = useDataStore((s) => s) @@ -29,29 +24,26 @@ const _View = ({ isSearchResult }: Props) => { return ( - {!isSearchResult && ( -
-
- Latest - - - -
- {nodeCount ? ( -
- } - > - {`See Latest (${nodeCount})`} - -
- ) : null} +
+
+ Latest + + +
- )} - + {nodeCount ? ( +
+ } + > + {`See Latest (${nodeCount})`} + +
+ ) : null} +
) } diff --git a/src/components/App/SideBar/RegularView/index.tsx b/src/components/App/SideBar/RegularView/index.tsx new file mode 100644 index 000000000..c9fa79d74 --- /dev/null +++ b/src/components/App/SideBar/RegularView/index.tsx @@ -0,0 +1,285 @@ +import clsx from 'clsx' +import React, { useEffect, useRef, useState } from 'react' +import { useFormContext } from 'react-hook-form' +import { useNavigate } from 'react-router-dom' +import { ClipLoader } from 'react-spinners' +import styled from 'styled-components' +import { SelectWithPopover } from '~/components/App/SideBar/Dropdown' +import { FilterSearch } from '~/components/App/SideBar/FilterSearch' +import ClearIcon from '~/components/Icons/ClearIcon' +import SearchFilterCloseIcon from '~/components/Icons/SearchFilterCloseIcon' +import SearchFilterIcon from '~/components/Icons/SearchFilterIcon' +import SearchIcon from '~/components/Icons/SearchIcon' +import { SearchBar } from '~/components/SearchBar' +import { Flex } from '~/components/common/Flex' +import { FetchLoaderText } from '~/components/common/Loader' +import { getSchemaAll } from '~/network/fetchSourcesData' +import { useAppStore } from '~/stores/useAppStore' +import { useDataStore, useFilteredNodes } from '~/stores/useDataStore' +import { useFeatureFlagStore } from '~/stores/useFeatureFlagStore' +import { useUpdateSelectedNode } from '~/stores/useGraphStore' +import { useSchemaStore } from '~/stores/useSchemaStore' +import { colors } from '~/utils/colors' +import { LatestView } from '../Latest' +import { Relevance } from '../Relevance' +import { EpisodeSkeleton } from '../Relevance/EpisodeSkeleton' +import { Trending } from '../Trending' + +export const MENU_WIDTH = 390 + +// eslint-disable-next-line react/display-name +export const RegularView = () => { + const { isFetching: isLoading, setSidebarFilter } = useDataStore((s) => s) + const [schemaAll, setSchemaAll] = useSchemaStore((s) => [s.schemas, s.setSchemas]) + + const setSelectedNode = useUpdateSelectedNode() + + const filteredNodes = useFilteredNodes() + + const { currentSearch: searchTerm, clearSearch, searchFormValue } = useAppStore((s) => s) + + const [trendingTopicsFeatureFlag] = useFeatureFlagStore((s) => [s.trendingTopicsFeatureFlag]) + + const { setValue, watch } = useFormContext() + const componentRef = useRef(null) + const [isScrolled, setIsScrolled] = useState(false) + const [isFilterOpen, setIsFilterOpen] = useState(false) + const [anchorEl, setAnchorEl] = useState(null) + const [showAllSchemas, setShowAllSchemas] = useState(false) + + useEffect(() => { + setValue('search', searchFormValue) + }, [setValue, searchFormValue]) + + useEffect(() => { + const component = componentRef.current + + if (!component) { + return + } + + const handleScroll = () => { + setIsScrolled(component?.scrollTop > 0) + } + + component.addEventListener('scroll', handleScroll) + }, []) + + const typing = watch('search') + + useEffect(() => { + const fetchSchemaData = async () => { + try { + const response = await getSchemaAll() + + setSchemaAll(response.schemas.filter((schema) => !schema.is_deleted)) + } catch (error) { + console.error('Error fetching schema:', error) + } + } + + fetchSchemaData() + }, [setSchemaAll]) + + const handleFilterIconClick = (event: React.MouseEvent) => { + if (isFilterOpen) { + setAnchorEl(null) + } else { + setAnchorEl(event.currentTarget) + } + + setIsFilterOpen((prev) => !prev) + setShowAllSchemas(false) + } + + const navigate = useNavigate() + + return ( + <> + + + + + { + if (searchTerm) { + setValue('search', '') + clearSearch() + setSidebarFilter('all') + setSelectedNode(null) + navigate(`/`) + + return + } + + if (typing.trim() === '') { + return + } + + const encodedQuery = typing.replace(/\s+/g, '+') + + navigate(`/search?q=${encodedQuery}`) + }} + > + {!isLoading ? ( + <>{searchTerm?.trim() ? : } + ) : ( + + )} + + + + + {isFilterOpen ? : } + + + + + {searchTerm && ( + + {isLoading ? ( + + ) : ( + <> +
+ {filteredNodes.length} + results +
+
+ +
+ + )} +
+ )} +
+ + {!searchTerm && trendingTopicsFeatureFlag && ( + + + + )} + {!searchTerm && } + {isLoading ? : } + + + ) +} + +const SearchWrapper = styled(Flex).attrs({ + direction: 'column', + justify: 'center', + align: 'stretch', +})(({ theme }) => ({ + padding: theme.spacing(3.75, 2), + [theme.breakpoints.up('sm')]: { + padding: '12px', + }, + + '&.has-shadow': { + borderBottom: '1px solid rgba(0, 0, 0, 0.25)', + background: colors.BG1, + boxShadow: '0px 1px 6px 0px rgba(0, 0, 0, 0.20)', + }, +})) + +const Search = styled(Flex).attrs({ + direction: 'row', + justify: 'center', + align: 'center', +})` + flex-grow: 1; +` + +const SearchDetails = styled(Flex).attrs({ + direction: 'row', + justify: 'space-between', + align: 'center', +})` + flex-grow: 1; + color: ${colors.GRAY6}; + font-family: Barlow; + font-size: 13px; + font-style: normal; + font-weight: 400; + line-height: 18px; + margin-top: 10px; + padding: 0 8px; + .count { + color: ${colors.white}; + } + + .right { + display: flex; + } +` + +const InputButton = styled(Flex).attrs({ + align: 'center', + justify: 'center', + p: 5, +})` + font-size: 32px; + color: ${colors.mainBottomIcons}; + cursor: pointer; + transition-duration: 0.2s; + margin-left: -42px; + z-index: 2; + + &:hover { + /* background-color: ${colors.gray200}; */ + } + + ${SearchWrapper} input:focus + & { + color: ${colors.primaryBlue}; + } +` + +const ScrollWrapper = styled(Flex)(() => ({ + overflow: 'auto', + flex: 1, + width: '100%', +})) + +const TrendingWrapper = styled(Flex)` + padding: 0; + margin-bottom: 36px; + margin-top: 20px; +` + +const SearchFilterIconWrapper = styled(Flex)` + align-items: center; + justify-content: space-between; + flex-direction: row; + gap: 10px; +` + +const IconWrapper = styled.div<{ isFilterOpen: boolean }>` + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.3s; + margin: 1px 2px 0 0; + border-radius: 8px; + width: 32px; + height: 32px; + background-color: ${({ isFilterOpen }) => (isFilterOpen ? colors.white : 'transparent')}; + + &:hover { + background-color: ${({ isFilterOpen }) => + isFilterOpen ? 'rgba(255, 255, 255, 0.85)' : 'rgba(255, 255, 255, 0.2)'}; + } + + svg { + width: 15px; + height: ${({ isFilterOpen }) => (isFilterOpen ? '11px' : '24px')}; + color: ${({ isFilterOpen }) => (isFilterOpen ? colors.black : colors.GRAY7)}; + fill: none; + } +` diff --git a/src/components/App/SideBar/Relevance/index.tsx b/src/components/App/SideBar/Relevance/index.tsx index 1329849e4..db5a18197 100644 --- a/src/components/App/SideBar/Relevance/index.tsx +++ b/src/components/App/SideBar/Relevance/index.tsx @@ -1,7 +1,6 @@ import { Button } from '@mui/material' -import { memo, useCallback, useMemo, useRef, useState } from 'react' +import { memo, useCallback, useMemo, useState } from 'react' import styled from 'styled-components' -import { ScrollView } from '~/components/ScrollView' import { Flex } from '~/components/common/Flex' import { useAppStore } from '~/stores/useAppStore' import { useDataStore, useFilteredNodes } from '~/stores/useDataStore' @@ -9,9 +8,9 @@ import { useUpdateSelectedNode } from '~/stores/useGraphStore' import { NodeExtended } from '~/types' import { formatDescription } from '~/utils/formatDescription' import { saveConsumedContent } from '~/utils/relayHelper' +import { adaptTweetNode } from '~/utils/twitterAdapter' import { useIsMatchBreakpoint } from '~/utils/useIsMatchBreakpoint' import { Episode } from './Episode' -import { adaptTweetNode } from '~/utils/twitterAdapter' type Props = { isSearchResult: boolean @@ -19,8 +18,6 @@ type Props = { // eslint-disable-next-line no-underscore-dangle const _Relevance = ({ isSearchResult }: Props) => { - const scrollViewRef = useRef(null) - const pageSize = !isSearchResult ? 10 : 80 const { setSelectedTimestamp, nextPage } = useDataStore((s) => s) @@ -81,57 +78,55 @@ const _Relevance = ({ isSearchResult }: Props) => { return ( <> - - {(currentNodes ?? []).map((n, index) => { - const adaptedNode = adaptTweetNode(n) - - const { - image_url: imageUrl, - date, - boost, - type, - episode_title: episodeTitle, - show_title: showTitle, - node_type: nodeType, - text, - source_link: sourceLink, - link, - name, - verified = false, - twitter_handle: twitterHandle, - } = adaptedNode || {} - - return ( - { - handleNodeClick(n) - }} - showTitle={formatDescription(showTitle)} - sourceLink={sourceLink} - text={text || ''} - twitterHandle={twitterHandle} - type={nodeType || type} - verified={verified} - /> - ) - })} - - - {hasNext && ( - - )} - - + {(currentNodes ?? []).map((n, index) => { + const adaptedNode = adaptTweetNode(n) + + const { + image_url: imageUrl, + date, + boost, + type, + episode_title: episodeTitle, + show_title: showTitle, + node_type: nodeType, + text, + source_link: sourceLink, + link, + name, + verified = false, + twitter_handle: twitterHandle, + } = adaptedNode || {} + + return ( + { + handleNodeClick(n) + }} + showTitle={formatDescription(showTitle)} + sourceLink={sourceLink} + text={text || ''} + twitterHandle={twitterHandle} + type={nodeType || type} + verified={verified} + /> + ) + })} + + + {hasNext && ( + + )} + ) } diff --git a/src/components/App/SideBar/index.tsx b/src/components/App/SideBar/index.tsx index e5f4cd143..e9fdf2bbb 100644 --- a/src/components/App/SideBar/index.tsx +++ b/src/components/App/SideBar/index.tsx @@ -1,37 +1,16 @@ -import { Button, Slide } from '@mui/material' -import clsx from 'clsx' -import React, { forwardRef, useEffect, useRef, useState } from 'react' -import { useFormContext } from 'react-hook-form' -import { useNavigate } from 'react-router-dom' -import { ClipLoader } from 'react-spinners' +import { Slide } from '@mui/material' +import { forwardRef } from 'react' import styled from 'styled-components' -import { SelectWithPopover } from '~/components/App/SideBar/Dropdown' -import { FilterSearch } from '~/components/App/SideBar/FilterSearch' -import ArrowBackIcon from '~/components/Icons/ArrowBackIcon' import ChevronLeftIcon from '~/components/Icons/ChevronLeftIcon' -import ClearIcon from '~/components/Icons/ClearIcon' -import SearchFilterCloseIcon from '~/components/Icons/SearchFilterCloseIcon' -import SearchFilterIcon from '~/components/Icons/SearchFilterIcon' -import SearchIcon from '~/components/Icons/SearchIcon' -import { SearchBar } from '~/components/SearchBar' import { Flex } from '~/components/common/Flex' -import { FetchLoaderText } from '~/components/common/Loader' -import { getSchemaAll } from '~/network/fetchSourcesData' -import { useAiSummaryStore, useHasAiChats } from '~/stores/useAiSummaryStore' +import { useHasAiChats } from '~/stores/useAiSummaryStore' import { useAppStore } from '~/stores/useAppStore' -import { useDataStore, useFilteredNodes } from '~/stores/useDataStore' -import { useFeatureFlagStore } from '~/stores/useFeatureFlagStore' -import { useSelectedNode, useUpdateSelectedNode } from '~/stores/useGraphStore' -import { useSchemaStore } from '~/stores/useSchemaStore' +import { useSelectedNode } from '~/stores/useGraphStore' import { colors } from '~/utils/colors' -import { AiSearch } from './AiSearch' -import { AiSummary } from './AiSummary' -import { LatestView } from './Latest' -import { Relevance } from './Relevance' -import { EpisodeSkeleton } from './Relevance/EpisodeSkeleton' +import { AiView } from './AiView' +import { RegularView } from './RegularView' import { SideBarSubView } from './SidebarSubView' import { Tab } from './Tab' -import { Trending } from './Trending' export const MENU_WIDTH = 390 @@ -41,147 +20,14 @@ type ContentProp = { // eslint-disable-next-line react/display-name const Content = forwardRef(({ subViewOpen }, ref) => { - const { isFetching: isLoading, setSidebarFilter } = useDataStore((s) => s) - const [schemaAll, setSchemaAll] = useSchemaStore((s) => [s.schemas, s.setSchemas]) - - const { aiSummaryAnswers, resetAiSummaryAnswer } = useAiSummaryStore((s) => s) - const setSelectedNode = useUpdateSelectedNode() - - const filteredNodes = useFilteredNodes() - - const { setSidebarOpen, currentSearch: searchTerm, clearSearch, searchFormValue } = useAppStore((s) => s) - - const [trendingTopicsFeatureFlag] = useFeatureFlagStore((s) => [s.trendingTopicsFeatureFlag]) - - const { setValue, watch } = useFormContext() - const componentRef = useRef(null) - const [isScrolled, setIsScrolled] = useState(false) - const [isFilterOpen, setIsFilterOpen] = useState(false) - const [anchorEl, setAnchorEl] = useState(null) - const [showAllSchemas, setShowAllSchemas] = useState(false) - - useEffect(() => { - setValue('search', searchFormValue) - }, [setValue, searchFormValue]) - - useEffect(() => { - const component = componentRef.current - - if (!component) { - return - } - - const handleScroll = () => { - setIsScrolled(component?.scrollTop > 0) - } - - component.addEventListener('scroll', handleScroll) - }, []) - - const typing = watch('search') - - useEffect(() => { - const fetchSchemaData = async () => { - try { - const response = await getSchemaAll() - - setSchemaAll(response.schemas.filter((schema) => !schema.is_deleted)) - } catch (error) { - console.error('Error fetching schema:', error) - } - } - - fetchSchemaData() - }, [setSchemaAll]) - - const handleFilterIconClick = (event: React.MouseEvent) => { - if (isFilterOpen) { - setAnchorEl(null) - } else { - setAnchorEl(event.currentTarget) - } - - setIsFilterOpen((prev) => !prev) - setShowAllSchemas(false) - } - - const handleCloseAi = () => { - resetAiSummaryAnswer() - navigate('/') - } - - const navigate = useNavigate() + const { setSidebarOpen } = useAppStore((s) => s) const hasAiChats = useHasAiChats() return ( - {!hasAiChats && ( - - - - - { - if (searchTerm) { - setValue('search', '') - clearSearch() - setSidebarFilter('all') - setSelectedNode(null) - navigate(`/`) - - return - } - - if (typing.trim() === '') { - return - } - - const encodedQuery = typing.replace(/\s+/g, '+') - - navigate(`/search?q=${encodedQuery}`) - }} - > - {!isLoading ? ( - <>{searchTerm?.trim() ? : } - ) : ( - - )} - - - - - {isFilterOpen ? : } - - - - - {searchTerm && ( - - {isLoading ? ( - - ) : ( - <> -
- {filteredNodes.length} - results -
-
- -
- - )} -
- )} -
- )} + {!hasAiChats ? : } {!subViewOpen && ( { @@ -191,38 +37,6 @@ const Content = forwardRef(({ subViewOpen }, ref) = )} - - {hasAiChats ? ( - - - - - - ) : null} - {!searchTerm && !hasAiChats && trendingTopicsFeatureFlag && ( - - - - )} - - {Object.keys(aiSummaryAnswers) - .filter((key) => aiSummaryAnswers[key].shouldRender) - .map((i: string) => ( - - ))} - - {isLoading ? : !hasAiChats && } - - {!hasAiChats && } - - {hasAiChats ? : null}
) }) @@ -257,75 +71,6 @@ const Wrapper = styled(Flex)(({ theme }) => ({ }, })) -const SearchWrapper = styled(Flex).attrs({ - direction: 'column', - justify: 'center', - align: 'stretch', -})(({ theme }) => ({ - padding: theme.spacing(3.75, 2), - [theme.breakpoints.up('sm')]: { - padding: '12px', - }, - - '&.has-shadow': { - borderBottom: '1px solid rgba(0, 0, 0, 0.25)', - background: colors.BG1, - boxShadow: '0px 1px 6px 0px rgba(0, 0, 0, 0.20)', - }, -})) - -const Search = styled(Flex).attrs({ - direction: 'row', - justify: 'center', - align: 'center', -})` - flex-grow: 1; -` - -const SearchDetails = styled(Flex).attrs({ - direction: 'row', - justify: 'space-between', - align: 'center', -})` - flex-grow: 1; - color: ${colors.GRAY6}; - font-family: Barlow; - font-size: 13px; - font-style: normal; - font-weight: 400; - line-height: 18px; - margin-top: 10px; - padding: 0 8px; - .count { - color: ${colors.white}; - } - - .right { - display: flex; - } -` - -const InputButton = styled(Flex).attrs({ - align: 'center', - justify: 'center', - p: 5, -})` - font-size: 32px; - color: ${colors.mainBottomIcons}; - cursor: pointer; - transition-duration: 0.2s; - margin-left: -42px; - z-index: 2; - - &:hover { - /* background-color: ${colors.gray200}; */ - } - - ${SearchWrapper} input:focus + & { - color: ${colors.primaryBlue}; - } -` - const CollapseButton = styled(Flex).attrs({ align: 'center', justify: 'center', @@ -357,52 +102,9 @@ const CollapseButton = styled(Flex).attrs({ }, })) -const ScrollWrapper = styled(Flex)(() => ({ - overflow: 'auto', - flex: 1, - width: '100%', -})) - const TitlePlaceholder = styled(Flex)` - height: 64px; + flex: 0 0 64px; background: ${colors.BG2}; ` -const TrendingWrapper = styled(Flex)` - padding: 0; - margin-bottom: 36px; - margin-top: 20px; -` - -const SearchFilterIconWrapper = styled(Flex)` - align-items: center; - justify-content: space-between; - flex-direction: row; - gap: 10px; -` - -const IconWrapper = styled.div<{ isFilterOpen: boolean }>` - display: flex; - align-items: center; - justify-content: center; - transition: background-color 0.3s; - margin: 1px 2px 0 0; - border-radius: 8px; - width: 32px; - height: 32px; - background-color: ${({ isFilterOpen }) => (isFilterOpen ? colors.white : 'transparent')}; - - &:hover { - background-color: ${({ isFilterOpen }) => - isFilterOpen ? 'rgba(255, 255, 255, 0.85)' : 'rgba(255, 255, 255, 0.2)'}; - } - - svg { - width: 15px; - height: ${({ isFilterOpen }) => (isFilterOpen ? '11px' : '24px')}; - color: ${({ isFilterOpen }) => (isFilterOpen ? colors.black : colors.GRAY7)}; - fill: none; - } -` - SideBar.displayName = 'Sidebar' diff --git a/src/stores/useAiSummaryStore/index.ts b/src/stores/useAiSummaryStore/index.ts index 566e77171..e082e13d5 100644 --- a/src/stores/useAiSummaryStore/index.ts +++ b/src/stores/useAiSummaryStore/index.ts @@ -11,15 +11,18 @@ export type AiSummaryStore = { aiSummaryAnswers: AIAnswer aiRefId: string setAiSummaryAnswer: (key: string, answer: AIEntity) => void + setNewLoading: (answer: AIEntity | null) => void resetAiSummaryAnswer: () => void getAiSummaryAnswer: (key: string) => string getKeyExist: (key: string) => boolean setAiRefId: (aiRefId: string) => void + newLoading: AIEntity | null } const defaultData = { aiSummaryAnswers: {}, aiRefId: '', + newLoading: null, } export const useAiSummaryStore = create()( @@ -34,6 +37,9 @@ export const useAiSummaryStore = create()( set({ aiSummaryAnswers: clone }) }, + setNewLoading: (newLoading) => { + set({ newLoading }) + }, resetAiSummaryAnswer: () => { set({ aiSummaryAnswers: {}, aiRefId: '' }) }, @@ -55,11 +61,11 @@ export const useAiSummaryStore = create()( })), ) -export const useHasAiChats = () => useAiSummaryStore((s) => !isEmpty(s.aiSummaryAnswers)) +export const useHasAiChats = () => useAiSummaryStore((s) => !isEmpty(s.aiSummaryAnswers) || !!s.newLoading) -export const useHasAiChatsResponse = () => +export const useHasAiChatsResponseLoading = () => useAiSummaryStore((s) => { const answers = s.aiSummaryAnswers - return Object.values(answers).at(-1)?.answerLoading + return !!s.newLoading || Object.values(answers).at(-1)?.answerLoading }) diff --git a/src/stores/useDataStore/index.ts b/src/stores/useDataStore/index.ts index bf43989e2..869b48a8d 100644 --- a/src/stores/useDataStore/index.ts +++ b/src/stores/useDataStore/index.ts @@ -137,7 +137,7 @@ export const useDataStore = create()( fetchData: async (setBudget, setAbortRequests, AISearchQuery = '') => { const { currentPage, itemsPerPage, dataInitial: existingData, filters } = get() const { currentSearch } = useAppStore.getState() - const { setAiSummaryAnswer, aiRefId } = useAiSummaryStore.getState() + const { setAiSummaryAnswer, setNewLoading, aiRefId } = useAiSummaryStore.getState() let ai = { ai_summary: String(!!AISearchQuery) } if (!AISearchQuery) { @@ -150,6 +150,7 @@ export const useDataStore = create()( if (AISearchQuery) { ai = { ...ai, ai_summary: String(true) } + setNewLoading({ question: AISearchQuery, answerLoading: true }) } if (abortController) { @@ -195,6 +196,8 @@ export const useDataStore = create()( sourcesLoading: !answer, shouldRender: true, }) + + setNewLoading(null) } const currentNodes = currentPage === 0 && !aiRefId ? [] : [...(existingData?.nodes || [])]