diff --git a/api/models/Conversation.js b/api/models/Conversation.js index 7d59113b90..4403c1e6ab 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -145,10 +145,11 @@ module.exports = { const promises = convoIds.map(convo => { return Conversation.findOne({ user, conversationId: convo.conversationId}).exec(); }); - const results = await Promise.all(promises); + const results = (await Promise.all(promises)).filter(convo => convo); const startIndex = (pageNumber - 1) * pageSize; const convos = results.slice(startIndex, startIndex + pageSize); const totalPages = Math.ceil(results.length / pageSize); + console.log(results.length, totalPages, convos.length); return { conversations: convos, pages: totalPages, pageNumber, pageSize }; } catch (error) { console.log(error); diff --git a/api/server/routes/search.js b/api/server/routes/search.js index 511a97881a..d58c62919f 100644 --- a/api/server/routes/search.js +++ b/api/server/routes/search.js @@ -2,7 +2,7 @@ const express = require('express'); const router = express.Router(); const { Message } = require('../../models/Message'); const { Conversation, getConvosQueried } = require('../../models/Conversation'); -const {reduceMessages, reduceHits} = require('../../lib/utils/reduceHits'); +const { reduceMessages, reduceHits } = require('../../lib/utils/reduceHits'); // const { MeiliSearch } = require('meilisearch'); router.get('/sync', async function (req, res) { @@ -12,19 +12,24 @@ router.get('/sync', async function (req, res) { }); router.get('/', async function (req, res) { - const { q } = req.query; - console.log(req.query); - const pageNumber = req.query.pageNumber || 1; - // const message = await Message.meiliSearch(q, { attributesToHighlight: ['text', 'sender'] }); - const message = await Message.meiliSearch(q); - const title = await Conversation.meiliSearch(q, { attributesToHighlight: ['title'] }); - // console.log('titles', title); - // console.log(sortedHits); - const sortedHits = reduceHits(message.hits, title.hits); - const result = await getConvosQueried(req?.session?.user?.username, sortedHits, pageNumber); - // const sortedHits = reduceMessages(message.hits); - // res.status(200).send(sortedHits || result); - res.status(200).send(result); + try { + const { q } = req.query; + console.log(req.query, req.params); + const pageNumber = req.query.pageNumber || 1; + const message = await Message.meiliSearch(q); + const title = await Conversation.meiliSearch(q, { attributesToHighlight: ['title'] }); + const sortedHits = reduceHits(message.hits, title.hits); + const result = await getConvosQueried( + req?.session?.user?.username, + sortedHits, + pageNumber + ); + console.log('result', result.pageNumber, result.pages, result.pageSize); + res.status(200).send(result); + } catch (error) { + console.log(error); + res.status(500).send({ message: 'Error searching' }); + } }); router.get('/clear', async function (req, res) { diff --git a/client/src/components/Conversations/Pages.jsx b/client/src/components/Conversations/Pages.jsx new file mode 100644 index 0000000000..1001ee8362 --- /dev/null +++ b/client/src/components/Conversations/Pages.jsx @@ -0,0 +1,36 @@ +import React from 'react'; + +export default function Pages({ pageNumber, pages, nextPage, previousPage }) { + const clickHandler = (func) => async (e) => { + e.preventDefault(); + await func(); + }; + + return ( +
+ + + {pageNumber} / {pages} + + +
+ ); +} diff --git a/client/src/components/Conversations/index.jsx b/client/src/components/Conversations/index.jsx index 4142cb8ed1..eaa59b0097 100644 --- a/client/src/components/Conversations/index.jsx +++ b/client/src/components/Conversations/index.jsx @@ -1,11 +1,7 @@ import React from 'react'; import Conversation from './Conversation'; -export default function Conversations({ conversations, conversationId, pageNumber, pages, nextPage, previousPage, moveToTop }) { - const clickHandler = (func) => async (e) => { - e.preventDefault(); - await func(); - }; +export default function Conversations({ conversations, conversationId, moveToTop }) { return ( <> @@ -37,25 +33,6 @@ export default function Conversations({ conversations, conversationId, pageNumbe /> ); })} -
- - - {pageNumber} / {pages} - - -
); } diff --git a/client/src/components/Nav/NavLinks.jsx b/client/src/components/Nav/NavLinks.jsx index 72cd44ce35..97e90ca881 100644 --- a/client/src/components/Nav/NavLinks.jsx +++ b/client/src/components/Nav/NavLinks.jsx @@ -6,10 +6,10 @@ import ClearConvos from './ClearConvos'; import DarkMode from './DarkMode'; import Logout from './Logout'; -export default function NavLinks() { +export default function NavLinks({ onSearchSuccess, clearSearch }) { return ( <> - + diff --git a/client/src/components/Nav/SearchBar.jsx b/client/src/components/Nav/SearchBar.jsx index 16778d22ac..ae556c7dd6 100644 --- a/client/src/components/Nav/SearchBar.jsx +++ b/client/src/components/Nav/SearchBar.jsx @@ -1,18 +1,34 @@ import React, { useState, useCallback } from 'react'; import { debounce } from 'lodash'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; import { Search } from 'lucide-react'; import { setQuery } from '~/store/searchSlice'; -import { setPage, refreshConversation } from '~/store/convoSlice'; +import { setConvos, refreshConversation } from '~/store/convoSlice'; +import axios from 'axios'; -export default function SearchBar() { +const fetch = async (q, pageNumber, callback) => { + const { data } = await axios.get(`/api/search?q=${q}&pageNumber=${pageNumber}`); + console.log(data); + callback(data); +}; + +export default function SearchBar({ onSuccess, clearSearch }) { const dispatch = useDispatch(); const [inputValue, setInputValue] = useState(''); - const { search } = useSelector((state) => state.search); + + // const onSuccess = (data) => { + // const { conversations, pages, pageNumber } = data; + // dispatch(setConvos({ convos: conversations, searchFetch: true })); + // dispatch(setPage(pageNumber)); + // dispatch(setPages(pages)); + // }; const debouncedChangeHandler = useCallback( debounce((q) => { dispatch(setQuery(q)); + if (q.length > 0) { + fetch(q, 1, onSuccess); + } }, 750), [dispatch] ); @@ -22,27 +38,27 @@ export default function SearchBar() { if (e.keyCode === 8 && value === '') { // Value after clearing input: "" console.log(`Value after clearing input: "${value}"`); - dispatch(setPage(1)); dispatch(setQuery('')); - dispatch(refreshConversation()); + clearSearch(); } }; const changeHandler = (e) => { - if (!search) { - console.log('setting page to 1'); - dispatch(setPage(1)); - } + // if (!search) { + // console.log('setting page to 1'); + // dispatch(setPage(1)); + // } let q = e.target.value; setInputValue(q); q = q.trim(); - if (q === '' || !q) { - dispatch(setPage(1)); + if (q === '') { dispatch(setQuery('')); - dispatch(refreshConversation()); + // dispatch(setPage(1)); + // dispatch(refreshConversation()); + clearSearch(); } else { debouncedChangeHandler(q); } diff --git a/client/src/components/Nav/index.jsx b/client/src/components/Nav/index.jsx index 0b6e8e5c49..4903ed8a73 100644 --- a/client/src/components/Nav/index.jsx +++ b/client/src/components/Nav/index.jsx @@ -1,36 +1,63 @@ import React, { useState, useEffect, useRef } from 'react'; +import axios from 'axios'; +import _ from 'lodash'; import NewChat from './NewChat'; import Spinner from '../svg/Spinner'; +import Pages from '../Conversations/Pages'; import Conversations from '../Conversations'; import NavLinks from './NavLinks'; import { swr } from '~/utils/fetchers'; import { useDispatch, useSelector } from 'react-redux'; -import { increasePage, decreasePage, setPage, setConvos, setPages } from '~/store/convoSlice'; +import { setConvos, refreshConversation } from '~/store/convoSlice'; + +const fetch = async (q, pageNumber, callback) => { + const { data } = await axios.get(`/api/search?q=${q}&pageNumber=${pageNumber}`); + console.log(data); + callback(data); +}; export default function Nav({ navVisible, setNavVisible }) { const dispatch = useDispatch(); const [isHovering, setIsHovering] = useState(false); + const [pages, setPages] = useState(1); + const [pageNumber, setPage] = useState(1); const { search, query } = useSelector((state) => state.search); - const { conversationId, convos, pages, pageNumber, refreshConvoHint } = useSelector( - (state) => state.convo - ); - const onSuccess = (data) => { - const { conversations, pages } = data; + const { conversationId, convos, refreshConvoHint } = useSelector((state) => state.convo); + const onSuccess = (data, searchFetch = false) => { + if (search) { + return; + } + const { conversations, pages } = data; if (pageNumber > pages) { - dispatch(setPage(pages)); + setPage(pages); } else { - dispatch(setConvos(conversations)); - dispatch(setPages(pages)); + dispatch(setConvos({ convos: conversations, searchFetch })); + setPages(pages); } }; - const { data, isLoading, mutate } = swr(`/api/${search ? `search?q=${query}&pageNumber=${pageNumber}` : `convos?pageNumber=${pageNumber}`}`, onSuccess, { + const onSearchSuccess = (data, expectedPage) => { + const res = data; + dispatch(setConvos({ convos: res.conversations, searchFetch: true })); + if (expectedPage) { + setPage(expectedPage); + } + setPage(res.pageNumber); + setPages(res.pages); + }; + + const clearSearch = () => { + setPage(1); + dispatch(refreshConversation()); + }; + + const { data, isLoading, mutate } = swr(`/api/convos?pageNumber=${pageNumber}`, onSuccess, { revalidateOnMount: false, - revalidateIfStale: !search, - revalidateOnFocus: !search, - revalidateOnReconnect: !search, - populateCache: !search, + // populateCache: false, + // revalidateIfStale: false, + // revalidateOnFocus: false, + // revalidateOnReconnect : false, }); const containerRef = useRef(null); @@ -46,19 +73,29 @@ export default function Nav({ navVisible, setNavVisible }) { const nextPage = async () => { moveToTop(); - dispatch(increasePage()); - await mutate(); + if (!search) { + setPage((prev) => prev + 1); + await mutate(); + } else { + await fetch(query, +pageNumber + 1, _.partialRight(onSearchSuccess, +pageNumber + 1)); + } }; const previousPage = async () => { moveToTop(); - dispatch(decreasePage()); - await mutate(); + if (!search) { + setPage((prev) => prev - 1); + await mutate(); + } else { + await fetch(query, +pageNumber - 1, _.partialRight(onSearchSuccess, +pageNumber - 1)); + } }; useEffect(() => { - mutate(); + if (!search) { + mutate(); + } }, [pageNumber, conversationId, refreshConvoHint]); useEffect(() => { @@ -114,16 +151,21 @@ export default function Nav({ navVisible, setNavVisible }) { )} + - + diff --git a/client/src/store/convoSlice.js b/client/src/store/convoSlice.js index 3b02bcc31a..2d84e05b38 100644 --- a/client/src/store/convoSlice.js +++ b/client/src/store/convoSlice.js @@ -17,7 +17,7 @@ const initialState = { refreshConvoHint: 0, search: false, latestMessage: null, - convos: [], + convos: [] }; const currentSlice = createSlice({ @@ -57,9 +57,12 @@ const currentSlice = createSlice({ state.latestMessage = null; }, setConvos: (state, action) => { - state.convos = action.payload.sort( - (a, b) => new Date(b.createdAt) - new Date(a.createdAt) - ); + const { convos, searchFetch } = action.payload; + if (searchFetch) { + state.convos = convos; + } else { + state.convos = convos.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + } }, setPages: (state, action) => { state.pages = action.payload; @@ -72,11 +75,23 @@ const currentSlice = createSlice({ }, setLatestMessage: (state, action) => { state.latestMessage = action.payload; - }, + } } }); -export const { refreshConversation, setConversation, setPages, setConvos, setNewConvo, setError, increasePage, decreasePage, setPage, removeConvo, removeAll, setLatestMessage } = - currentSlice.actions; +export const { + refreshConversation, + setConversation, + setPages, + setConvos, + setNewConvo, + setError, + increasePage, + decreasePage, + setPage, + removeConvo, + removeAll, + setLatestMessage +} = currentSlice.actions; export default currentSlice.reducer; diff --git a/client/src/store/searchSlice.js b/client/src/store/searchSlice.js index 2c39b3654f..3769af3af6 100644 --- a/client/src/store/searchSlice.js +++ b/client/src/store/searchSlice.js @@ -16,9 +16,9 @@ const currentSlice = createSlice({ const q = action.payload; state.query = q; - if (!q || q === '') { + if (q === '') { state.search = false; - } else { + } else if (q?.length > 0 && !state.search) { state.search = true; } }, diff --git a/client/src/utils/fetchers.js b/client/src/utils/fetchers.js index 3d9a83413e..abe8f4a9a6 100644 --- a/client/src/utils/fetchers.js +++ b/client/src/utils/fetchers.js @@ -3,21 +3,25 @@ import axios from 'axios'; import useSWR from 'swr'; import useSWRMutation from 'swr/mutation'; -const fetcher = (url) => fetch(url, {credentials: 'include'}).then((res) => res.json()); +const fetcher = (url) => fetch(url, { credentials: 'include' }).then((res) => res.json()); +const axiosFetcher = async (url, params) => { + console.log(params, 'params'); + return axios.get(url, params); +}; const postRequest = async (url, { arg }) => { return await axios.post(url, { withCredentials: true, arg }); }; export const swr = (path, successCallback, options) => { - const _options = {...options}; + const _options = { ...options }; if (successCallback) { _options.onSuccess = successCallback; } return useSWR(path, fetcher, _options); -} +}; export default function manualSWR(path, type, successCallback) { const options = {}; @@ -28,3 +32,16 @@ export default function manualSWR(path, type, successCallback) { const fetchFunction = type === 'get' ? fetcher : postRequest; return useSWRMutation(path, fetchFunction, options); } + +export function useManualSWR({ path, params, type, onSuccess }) { + const options = {}; + + if (onSuccess) { + options.onSuccess = onSuccess; + } + + console.log(params, 'params'); + + const fetchFunction = type === 'get' ? _.partialRight(axiosFetcher, params) : postRequest; + return useSWRMutation(path, fetchFunction, options); +}