diff --git a/src/common/dataAdaptors/mediaAdaptors.js b/src/common/dataAdaptors/mediaAdaptors.js index 9f08d14f..64e56ed5 100644 --- a/src/common/dataAdaptors/mediaAdaptors.js +++ b/src/common/dataAdaptors/mediaAdaptors.js @@ -21,7 +21,6 @@ export const mediaSearchAdaptor = ({ type, data }) => { downloadLink: data?.original?.path || '', height: data?.original?.height, width: data?.original?.width, - thumbnail: data?.small?.path, } default: return { ...data, message: 'NOT a media document' } diff --git a/src/common/dataAdaptors/searchResponseAdaptors.js b/src/common/dataAdaptors/searchResponseAdaptors.js index bdb2859a..6d2a9d3b 100644 --- a/src/common/dataAdaptors/searchResponseAdaptors.js +++ b/src/common/dataAdaptors/searchResponseAdaptors.js @@ -13,23 +13,7 @@ import { storySummaryAdaptor } from 'common/dataAdaptors/storyAdaptors' import { songSummaryAdaptor } from 'common/dataAdaptors/songAdaptors' import { mediaSearchAdaptor } from 'common/dataAdaptors/mediaAdaptors' -export function searchResponseAdaptor(response) { - return pagesDataAdaptor(response.pages) -} - -const pagesDataAdaptor = (pages) => - pages.map((page, index) => singlePageDataAdaptor(page, index)) - -const singlePageDataAdaptor = (page, index) => { - const formattedEntries = page?.results?.map((result) => resultAdaptor(result)) - return { - ...page, - pageNumber: index + 1, - results: formattedEntries, - } -} - -const resultAdaptor = (result) => { +export function searchResultAdaptor(result) { const baseObject = { id: result?.entry?.id, title: result?.entry?.title, diff --git a/src/common/dataHooks/useGamesSearch.js b/src/common/dataHooks/useGamesSearch.js index ea561c5b..414c2115 100644 --- a/src/common/dataHooks/useGamesSearch.js +++ b/src/common/dataHooks/useGamesSearch.js @@ -13,7 +13,11 @@ import { GAMES, SORT, HAS_UNRECOGNIZED_CHARS, + TYPE_PHRASE, + MINWORDS, + WORDSY, } from 'common/constants' +import { getOrthographyPattern } from 'components/Game/Wordsy/Utils/helpers' export function useParachuteSearch({ perPage, kids }) { const { sitename } = useParams() @@ -42,7 +46,6 @@ export function useParachuteSearch({ perPage, kids }) { searchParams: searchParamString, perPage, }), - ...{ enabled: !!sitename }, }) const getPuzzles = () => { @@ -76,3 +79,54 @@ export function useParachuteSearch({ perPage, kids }) { puzzles: getPuzzles(), } } + +export function usePhraseScramblerSearch({ kids }) { + const { sitename } = useParams() + const _searchParams = new URLSearchParams({ + [TYPES]: TYPE_PHRASE, + [GAMES]: true, + [HAS_TRANSLATION]: true, + [SORT]: 'random', + [MINWORDS]: 2, + }) + if (kids) { + _searchParams.append(KIDS, kids) + } + + const queryResponse = useQuery({ + queryKey: [SEARCH, sitename], + queryFn: () => + api.search.get({ + sitename, + searchParams: _searchParams.toString(), + pageParam: 1, + perPage: 1, // Fetching one phrase at a time + }), + }) + return queryResponse +} + +export function useWordsySearch({ kids }) { + const { sitename } = useParams() + const _searchParams = new URLSearchParams() + if (kids) { + _searchParams.append(KIDS, kids) + } + + const queryResponse = useQuery({ + queryKey: [WORDSY, sitename], + queryFn: () => + api.gameContent.getWordsyConfig({ + sitename, + searchParams: _searchParams.toString(), + }), + }) + + const languageConfig = { + orthography: queryResponse?.data?.orthography, + orthographyPattern: getOrthographyPattern(queryResponse?.data?.orthography), + words: queryResponse?.data?.words, + validGuesses: queryResponse?.data?.validGuesses, + } + return { ...queryResponse, languageConfig } +} diff --git a/src/common/dataHooks/useInfiniteScroll.js b/src/common/dataHooks/useInfiniteScroll.js index cd13d84a..ddb4b61b 100644 --- a/src/common/dataHooks/useInfiniteScroll.js +++ b/src/common/dataHooks/useInfiniteScroll.js @@ -3,14 +3,16 @@ import { useInfiniteQuery } from '@tanstack/react-query' // FPCC import useIntersectionObserver from 'common/hooks/useIntersectionObserver' -import { useSiteStore } from 'context/SiteContext' /** * Calls API and provides results and infinite scroll info. */ -function useInfiniteScroll({ queryKey, queryFn, resultAdaptor }) { - const { site } = useSiteStore() - +function useInfiniteScroll({ + queryKey, + queryFn, + enabled = true, + resultAdaptor, +}) { const pagesDataAdaptor = (pages) => pages.map((page, index) => singlePageDataAdaptor(page, index)) @@ -29,7 +31,7 @@ function useInfiniteScroll({ queryKey, queryFn, resultAdaptor }) { const response = useInfiniteQuery({ queryKey, queryFn, - enabled: !!site?.sitename, + enabled, getNextPageParam: (currentPage) => currentPage.next, select: (responseData) => ({ pages: pagesDataAdaptor(responseData.pages), @@ -37,10 +39,21 @@ function useInfiniteScroll({ queryKey, queryFn, resultAdaptor }) { }), }) + const getLoadLabel = () => { + if (response?.isFetchingNextPage) { + return 'Loading more...' + } + if (response?.hasNextPage) { + return 'Load more' + } + return 'End of results.' + } + const infiniteScroll = { fetchNextPage: response?.fetchNextPage, hasNextPage: response?.hasNextPage, isFetchingNextPage: response?.isFetchingNextPage, + loadLabel: getLoadLabel(), } const loadRef = useRef(null) @@ -53,6 +66,7 @@ function useInfiniteScroll({ queryKey, queryFn, resultAdaptor }) { return { ...response, infiniteScroll, + loadLabel: getLoadLabel(), loadRef, } } diff --git a/src/common/dataHooks/useJoinRequests.js b/src/common/dataHooks/useJoinRequests.js index 6769d79e..7d2d1405 100644 --- a/src/common/dataHooks/useJoinRequests.js +++ b/src/common/dataHooks/useJoinRequests.js @@ -11,7 +11,7 @@ import { useSiteStore } from 'context/SiteContext' export function useJoinRequests() { const { site } = useSiteStore() - const response = useInfiniteScroll({ + const infiniteQueryResponse = useInfiniteScroll({ queryKey: [JOIN_REQUESTS, site?.sitename], queryFn: ({ pageParam = 1 }) => api.joinRequests.getAll({ @@ -20,7 +20,7 @@ export function useJoinRequests() { }), }) - return response + return infiniteQueryResponse } export function useJoinRequestCreate(options = {}) { diff --git a/src/common/dataHooks/useMediaSearch.js b/src/common/dataHooks/useMediaSearch.js index a4525247..2f7f81ce 100644 --- a/src/common/dataHooks/useMediaSearch.js +++ b/src/common/dataHooks/useMediaSearch.js @@ -47,7 +47,7 @@ function useMediaSearch({ type, searchSharedMedia }) { enabled: searchSharedMedia, }) - const response = searchSharedMedia + const infiniteQueryResponse = searchSharedMedia ? sharedMediaSearchResponse : siteSearchResponse @@ -67,21 +67,17 @@ function useMediaSearch({ type, searchSharedMedia }) { } useEffect(() => { - if (!currentFile && response?.data?.pages?.[0]?.results) { - const firstFile = response?.data?.pages?.[0]?.results?.[0] + if (!currentFile && infiniteQueryResponse?.data?.pages?.[0]?.results) { + const firstFile = infiniteQueryResponse?.data?.pages?.[0]?.results?.[0] setCurrentFile(firstFile) } - }, [currentFile, response?.data, type]) + }, [currentFile, infiniteQueryResponse?.data, type]) return { + ...infiniteQueryResponse, handleSearchSubmit, handleSearchSubmitWithUrlSync, handleTextFieldChange, - infiniteScroll: response?.infiniteScroll, - isLoadingEntries: response?.isLoading, - loadLabel: response?.infiniteScroll?.loadLabel, - loadRef: response?.loadRef, - media: response?.data, searchValue: searchInputValue, currentFile, setCurrentFile, diff --git a/src/common/dataHooks/useSearchAllSitesLoader.js b/src/common/dataHooks/useSearchAllSitesLoader.js index 5a0cda9d..acacd845 100644 --- a/src/common/dataHooks/useSearchAllSitesLoader.js +++ b/src/common/dataHooks/useSearchAllSitesLoader.js @@ -1,43 +1,26 @@ -import { useInfiniteQuery } from '@tanstack/react-query' import PropTypes from 'prop-types' // FPCC -import useLoader from 'common/hooks/useLoader' +import useInfiniteScroll from 'common/dataHooks/useInfiniteScroll' import api from 'services/api' import { SEARCH } from 'common/constants' -import { searchResponseAdaptor } from 'common/dataAdaptors' +import { searchResultAdaptor } from 'common/dataAdaptors' -/** - * Calls search API and provides search results and infinite scroll info. - */ function useSearchAllSitesLoader({ enabled, searchParams }) { const searchParamString = searchParams.toString() - // Fetch search results - const response = useInfiniteQuery({ + const infiniteQueryResponse = useInfiniteScroll({ queryKey: [SEARCH, 'cross-site', searchParamString], queryFn: ({ pageParam = 1 }) => api.search.getFVWideSearch({ searchParams: searchParamString, pageParam, }), - getNextPageParam: (currentPage) => currentPage.next, enabled: !!enabled, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - select: (responseData) => ({ - pages: searchResponseAdaptor(responseData), - pageParams: responseData.pageParams, - }), + resultAdaptor: searchResultAdaptor, }) - const { infiniteScroll, loadRef } = useLoader({ response }) - - return { - ...response, - infiniteScroll, - loadRef, - } + return infiniteQueryResponse } // PROPTYPES diff --git a/src/common/dataHooks/useSearchLoader.js b/src/common/dataHooks/useSearchLoader.js index 252ec2c6..e211e2bb 100644 --- a/src/common/dataHooks/useSearchLoader.js +++ b/src/common/dataHooks/useSearchLoader.js @@ -1,41 +1,24 @@ -import { useInfiniteQuery } from '@tanstack/react-query' import { useParams } from 'react-router-dom' import PropTypes from 'prop-types' // FPCC -import useLoader from 'common/hooks/useLoader' +import useInfiniteScroll from 'common/dataHooks/useInfiniteScroll' import api from 'services/api' import { SEARCH } from 'common/constants' -import { searchResponseAdaptor } from 'common/dataAdaptors' +import { searchResultAdaptor } from 'common/dataAdaptors' -/** - * Calls search API and provides search results and infinite scroll info. - */ function useSearchLoader({ searchParams }) { const { sitename } = useParams() const searchParamString = searchParams.toString() - // Fetch search results - const response = useInfiniteQuery({ + const infiniteQueryResponse = useInfiniteScroll({ queryKey: [SEARCH, sitename, searchParamString], queryFn: ({ pageParam = 1 }) => api.search.get({ sitename, searchParams: searchParamString, pageParam }), - getNextPageParam: (currentPage) => currentPage.next, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - select: (responseData) => ({ - pages: searchResponseAdaptor(responseData), - pageParams: responseData.pageParams, - }), + resultAdaptor: searchResultAdaptor, }) - const { infiniteScroll, loadRef } = useLoader({ response }) - - return { - ...response, - infiniteScroll, - loadRef, - } + return infiniteQueryResponse } // PROPTYPES diff --git a/src/common/hooks/useLoader.js b/src/common/hooks/useLoader.js deleted file mode 100644 index 3c3cafd7..00000000 --- a/src/common/hooks/useLoader.js +++ /dev/null @@ -1,44 +0,0 @@ -import { useRef } from 'react' -import PropTypes from 'prop-types' - -// FPCC -import useIntersectionObserver from 'common/hooks/useIntersectionObserver' - -function useLoader({ response }) { - const getLoadLabel = () => { - if (response?.isFetchingNextPage) { - return 'Loading more...' - } - if (response?.hasNextPage) { - return 'Load more' - } - return 'End of results.' - } - - const infiniteScroll = { - fetchNextPage: response?.fetchNextPage, - hasNextPage: response?.hasNextPage, - isFetchingNextPage: response?.isFetchingNextPage, - loadLabel: getLoadLabel(), - } - - const loadRef = useRef(null) - useIntersectionObserver({ - target: loadRef, - onIntersect: response?.fetchNextPage, - enabled: response?.hasNextPage, - }) - - return { - infiniteScroll, - loadRef, - } -} - -// PROPTYPES -const { obj } = PropTypes -useLoader.propTypes = { - searchParams: obj, -} - -export default useLoader diff --git a/src/components/ByAlphabet/ByAlphabetContainer.js b/src/components/ByAlphabet/ByAlphabetContainer.js index b2a23722..c16b0367 100644 --- a/src/components/ByAlphabet/ByAlphabetContainer.js +++ b/src/components/ByAlphabet/ByAlphabetContainer.js @@ -9,14 +9,12 @@ import SiteDocHead from 'components/SiteDocHead' function ByAlphabetContainer({ kids = null }) { const { - actions, characterQueryResponse, currentCharacter, searchType, setSearchType, entryLabel, searchInfiniteQueryResponse, - moreActions, selectedTab, sitename, tabs, @@ -28,7 +26,6 @@ function ByAlphabetContainer({ kids = null }) { description={`Dictionary entries that start with ${currentCharacter?.title}.`} /> characters.map(({ title, id }) => (
@@ -197,14 +179,13 @@ function ByAlphabetPresentation({
)}
-
+
) } // PROPTYPES const { array, bool, func, object, string } = PropTypes ByAlphabetPresentation.propTypes = { - actions: array, characters: array, currentCharacter: object, searchInfiniteQueryResponse: object, @@ -212,7 +193,6 @@ ByAlphabetPresentation.propTypes = { setSearchType: func, entryLabel: string, kids: bool, - moreActions: array, sitename: string, } diff --git a/src/components/ByCategory/ByCategoryContainer.js b/src/components/ByCategory/ByCategoryContainer.js index 8a173130..877eafed 100644 --- a/src/components/ByCategory/ByCategoryContainer.js +++ b/src/components/ByCategory/ByCategoryContainer.js @@ -8,7 +8,6 @@ import LoadOrError from 'components/LoadOrError' import SiteDocHead from 'components/SiteDocHead' function ByCategoryContainer({ kids = null }) { const { - actions, categoriesQueryResponse, currentCategory, currentParentCategory, @@ -16,7 +15,6 @@ function ByCategoryContainer({ kids = null }) { setSearchType, entryLabel, searchInfiniteQueryResponse, - moreActions, selectedTab, sitename, tabs, @@ -25,7 +23,6 @@ function ByCategoryContainer({ kids = null }) { categories.map((category) => (
  • )) + const getNoResultsMessage = () => { let typeLabel = '' switch (searchType) { @@ -194,11 +189,7 @@ function ByCategoryPresentation({
    @@ -244,14 +227,13 @@ function ByCategoryPresentation({
    )}
    -
    +
    ) } // PROPTYPES const { array, bool, func, object, string } = PropTypes ByCategoryPresentation.propTypes = { - actions: array, categories: array, currentCategory: object, currentParentCategory: object, @@ -260,7 +242,6 @@ ByCategoryPresentation.propTypes = { setSearchType: func, searchInfiniteQueryResponse: object, kids: bool, - moreActions: array, sitename: string, } diff --git a/src/components/CategoriesBrowser/CategoriesBrowserContainer.js b/src/components/CategoriesBrowser/CategoriesBrowserContainer.js index de7b4381..ec93a29b 100644 --- a/src/components/CategoriesBrowser/CategoriesBrowserContainer.js +++ b/src/components/CategoriesBrowser/CategoriesBrowserContainer.js @@ -6,7 +6,7 @@ import CategoriesBrowserData from 'components/CategoriesBrowser/CategoriesBrowse import CategoriesBrowserPresentation from 'components/CategoriesBrowser/CategoriesBrowserPresentation' function CategoriesBrowserContainer({ chooseDocHandler }) { const { - isLoading, + categoriesQueryResponse, site, sitename, currentCategory, @@ -16,7 +16,7 @@ function CategoriesBrowserContainer({ chooseDocHandler }) { } = CategoriesBrowserData() return ( - category.title - .toLowerCase() - .replace(/\s+/g, '') - .includes(query.toLowerCase().replace(/\s+/g, '')), + ? categoriesQueryResponse?.allCategories + : categoriesQueryResponse?.allCategories?.filter( + (category) => + category.title + .toLowerCase() + .replace(/\s+/g, '') + .includes(query.toLowerCase().replace(/\s+/g, '')) || + category?.parentTitle + ?.toLowerCase() + ?.replace(/\s+/g, '') + ?.includes(query.toLowerCase().replace(/\s+/g, '')), ) return { - isLoading: isInitialLoading, + categoriesQueryResponse, site, sitename, currentCategory, diff --git a/src/components/CategoriesBrowser/CategoriesBrowserPresentation.js b/src/components/CategoriesBrowser/CategoriesBrowserPresentation.js index 35bc1813..e6153361 100644 --- a/src/components/CategoriesBrowser/CategoriesBrowserPresentation.js +++ b/src/components/CategoriesBrowser/CategoriesBrowserPresentation.js @@ -2,10 +2,10 @@ import React from 'react' import PropTypes from 'prop-types' // FPCC -import Loading from 'components/Loading' +import LoadOrError from 'components/LoadOrError' import getIcon from 'common/utils/getIcon' function CategoriesBrowserPresentation({ - isLoading, + categoriesQueryResponse, chooseDocHandler, currentCategory, setCurrentCategory, @@ -49,7 +49,7 @@ function CategoriesBrowserPresentation({ )} - + {filteredCategories && (
    @@ -81,16 +81,16 @@ function CategoriesBrowserPresentation({ {!filteredCategories && (

    You have not created any categories yet

    )} - +
    ) } // PROPTYPES -const { array, bool, object, func } = PropTypes +const { array, object, func } = PropTypes CategoriesBrowserPresentation.propTypes = { - isLoading: bool, + categoriesQueryResponse: object, chooseDocHandler: func, currentCategory: object, setCurrentCategory: func, diff --git a/src/components/CategoryCrud/CategoryCrudContainer.js b/src/components/CategoryCrud/CategoryCrudContainer.js index f9f10f09..d09d826f 100644 --- a/src/components/CategoryCrud/CategoryCrudContainer.js +++ b/src/components/CategoryCrud/CategoryCrudContainer.js @@ -3,20 +3,20 @@ import React from 'react' // FPCC import CategoryCrudPresentation from 'components/CategoryCrud/CategoryCrudPresentation' import CategoryCrudData from 'components/CategoryCrud/CategoryCrudData' -import Loading from 'components/Loading' +import LoadOrError from 'components/LoadOrError' function CategoryCrudContainer() { const { backHandler, dataToEdit, deleteHandler, - isLoading, + categoriesQueryResponse, submitHandler, parentCategoryOptions, } = CategoryCrudData() return ( - + - +
    ) } diff --git a/src/components/CategoryCrud/CategoryCrudData.js b/src/components/CategoryCrud/CategoryCrudData.js index 2137eda2..381bcfb0 100644 --- a/src/components/CategoryCrud/CategoryCrudData.js +++ b/src/components/CategoryCrud/CategoryCrudData.js @@ -18,15 +18,14 @@ function CategoryCrudData() { const categoryId = searchParams.get('id') // Fetch Data - const { data, isInitialLoading } = useCategories() - const { data: categoryData, isInitialLoading: categoryIsInitialLoading } = - useCategory({ id: categoryId }) + const categoriesQueryResponse = useCategories() + const categoryQueryResponse = useCategory({ id: categoryId }) const getParentCategoryOptions = () => { // If a category with no children or a new category allow assigning/changing a parent - if (categoryData?.children?.length < 1 || !categoryId) { - const categories = data?.results - ? data?.results?.map((result) => { + if (categoryQueryResponse?.data?.children?.length < 1 || !categoryId) { + const categories = categoriesQueryResponse?.data?.results + ? categoriesQueryResponse?.data?.results?.map((result) => { const category = { label: result?.title, value: result?.id } return category }) @@ -41,7 +40,7 @@ function CategoryCrudData() { const { onSubmit: deleteCategory } = useCategoryDelete() const submitHandler = (formData) => { - if (categoryId && categoryData) { + if (categoryId && categoryQueryResponse?.data) { updateCategory(formData) } else { createCategory(formData) @@ -52,9 +51,9 @@ function CategoryCrudData() { parentCategoryOptions: getParentCategoryOptions(), submitHandler, backHandler, - dataToEdit: categoryData, - deleteHandler: () => deleteCategory(categoryData?.id), - isLoading: isInitialLoading || categoryIsInitialLoading, + dataToEdit: categoryQueryResponse?.data, + deleteHandler: () => deleteCategory(categoryQueryResponse?.data?.id), + categoriesQueryResponse, } } diff --git a/src/components/CharacterCrud/CharacterCrudContainer.js b/src/components/CharacterCrud/CharacterCrudContainer.js index 20c7fa55..01dae7e0 100644 --- a/src/components/CharacterCrud/CharacterCrudContainer.js +++ b/src/components/CharacterCrud/CharacterCrudContainer.js @@ -3,20 +3,20 @@ import React from 'react' // FPCC import CharacterCrudPresentation from 'components/CharacterCrud/CharacterCrudPresentation' import CharacterCrudData from 'components/CharacterCrud/CharacterCrudData' -import Loading from 'components/Loading' +import LoadOrError from 'components/LoadOrError' function CharacterCrudContainer() { - const { backHandler, dataToEdit, isLoading, submitHandler } = + const { backHandler, characterQueryResponse, submitHandler } = CharacterCrudData() return ( - + - + ) } diff --git a/src/components/CharacterCrud/CharacterCrudData.js b/src/components/CharacterCrud/CharacterCrudData.js index 2a753ef1..9fe521e4 100644 --- a/src/components/CharacterCrud/CharacterCrudData.js +++ b/src/components/CharacterCrud/CharacterCrudData.js @@ -16,7 +16,7 @@ function CharacterCrudData() { window.location.href = `/${sitename}/dashboard/edit/alphabet` } - const { isInitialLoading, data } = useCharacter({ + const characterQueryResponse = useCharacter({ id: characterId, edit: true, }) @@ -24,7 +24,7 @@ function CharacterCrudData() { const { onSubmit } = useCharacterPartialUpdate() const submitHandler = (formData) => { - if (characterId && data) { + if (characterId && characterQueryResponse?.data) { onSubmit(formData) } } @@ -34,8 +34,7 @@ function CharacterCrudData() { return { submitHandler, backHandler, - dataToEdit: data || null, - isLoading: isInitialLoading, + characterQueryResponse, } } diff --git a/src/components/Dashboard/DashboardContainer.js b/src/components/Dashboard/DashboardContainer.js index 142668b7..8d80e513 100644 --- a/src/components/Dashboard/DashboardContainer.js +++ b/src/components/Dashboard/DashboardContainer.js @@ -11,96 +11,93 @@ import DashboardEntries from 'components/DashboardEntries' import DashboardCreate from 'components/DashboardCreate' import DashboardMedia from 'components/DashboardMedia' import DashboardReports from 'components/DashboardReports' -import Loading from 'components/Loading' import { ASSISTANT } from 'common/constants/roles' import SiteDocHead from 'components/SiteDocHead' function DashboardContainer() { - const { currentUser, site, homeTiles, isLoading, logout } = DashboardData() + const { currentUser, site, homeTiles, logout } = DashboardData() return ( - + - - - User Profile placeholder
    - } - /> - - - Go to the FirstVoices support portal. - -
    - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - } - /> - - - + + User Profile placeholder
    + } + /> + + + Go to the FirstVoices support portal. + +
    + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + } + /> + + ) } diff --git a/src/components/Dashboard/DashboardData.js b/src/components/Dashboard/DashboardData.js index 65aa2afb..c0b8bf21 100644 --- a/src/components/Dashboard/DashboardData.js +++ b/src/components/Dashboard/DashboardData.js @@ -8,13 +8,10 @@ import { TYPE_WORD, TYPE_PHRASE, ASSISTANT, - LANGUAGE_ADMIN, MEMBER, VISIBILITY, VISIBILITY_TEAM, - atLeastAssistant, } from 'common/constants' -import { useMySites } from 'common/dataHooks/useMySites' import useLoginLogout from 'common/hooks/useLoginLogout' import DashboardEditData from 'components/DashboardEdit/DashboardEditData' import DashboardCreateData from 'components/DashboardCreate/DashboardCreateData' @@ -25,26 +22,6 @@ function DashboardData() { const { sitename } = useParams() const { logout } = useLoginLogout() - // -------------------------------- - // Get user sites - // -------------------------------- - - const { isInitialLoading: userSitesIsLoading, mySitesData: userSitesData } = - useMySites() - - const { roles } = user - - const teamMemberSites = userSitesData.filter((s) => - s?.role.match(atLeastAssistant), - ) - - const currentUser = { - ...user, - isAdmin: !!(roles?.[sitename] === LANGUAGE_ADMIN || user?.isSuperAdmin), - role: `${site?.title} ${roles?.[sitename] ? roles?.[sitename] : ''}`, - sites: teamMemberSites, - } - const { createTiles } = DashboardCreateData({ urlPrefix: 'create' }) const { editTiles } = DashboardEditData({ urlPrefix: 'edit' }) @@ -56,7 +33,7 @@ function DashboardData() { name: 'Edit words and phrases', description: 'Edit the words and phrases in your dictionary', href: `edit/entries?${TYPES}=${TYPE_WORD},${TYPE_PHRASE}${ - roles?.[sitename] === ASSISTANT + user?.roles?.[sitename] === ASSISTANT ? `&${VISIBILITY}=${VISIBILITY_TEAM}` : '' }`, @@ -87,11 +64,17 @@ function DashboardData() { }, ] + const currentUser = { + ...user, + role: `${site?.title} ${ + user?.roles?.[sitename] ? user?.roles?.[sitename] : '' + }`, + } + return { currentUser, site, homeTiles, - isLoading: userSitesIsLoading || !site, logout, } } diff --git a/src/components/Dashboard/DashboardPresentation.js b/src/components/Dashboard/DashboardPresentation.js index 9dd94a77..6d43bb96 100644 --- a/src/components/Dashboard/DashboardPresentation.js +++ b/src/components/Dashboard/DashboardPresentation.js @@ -33,18 +33,19 @@ function DashboardPresentation({ children, currentUser, site, logout }) {
    -
    + + FirstVoices Logo {getIcon('FVLogo', 'h-10 w-auto fill-current text-white')} -
    +
    diff --git a/src/components/DashboardEntries/DashboardEntriesContainer.js b/src/components/DashboardEntries/DashboardEntriesContainer.js index 6cfcab6b..d5a95148 100644 --- a/src/components/DashboardEntries/DashboardEntriesContainer.js +++ b/src/components/DashboardEntries/DashboardEntriesContainer.js @@ -9,12 +9,9 @@ function DashboardEntriesContainer({ advancedSearch = false }) { const { emptyListMessage, entryLabel, - infiniteScroll, + searchInfiniteQueryResponse, initialSearchType, isDictionary, - isLoadingEntries, - items, - loadRef, removeFilters, searchType, setSearchType, @@ -27,12 +24,9 @@ function DashboardEntriesContainer({ advancedSearch = false }) { {showAdvancedSearch && (
    - +
    )}
    -
    +
    ) @@ -82,12 +82,9 @@ const { bool, func, object, string } = PropTypes DashboardEntriesPresentation.propTypes = { entryLabel: string, emptyListMessage: string, - infiniteScroll: object, + searchInfiniteQueryResponse: object, initialSearchType: string, isDictionary: bool, - isLoadingEntries: bool, - items: object, - loadRef: object, removeFilters: func, searchType: string, setSearchType: func, diff --git a/src/components/DashboardEntries/DashboardEntriesPresentationList.js b/src/components/DashboardEntries/DashboardEntriesPresentationList.js index 2f090c3d..e9fca86a 100644 --- a/src/components/DashboardEntries/DashboardEntriesPresentationList.js +++ b/src/components/DashboardEntries/DashboardEntriesPresentationList.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import { Link, useParams } from 'react-router-dom' // FPCC -import Loading from 'components/Loading' +import LoadOrError from 'components/LoadOrError' import EntryDetail from 'components/EntryDetail' import getIcon from 'common/utils/getIcon' import Drawer from 'components/Drawer' @@ -20,16 +20,12 @@ import { } from 'common/constants' function DashboardEntriesPresentationList({ - infiniteScroll, - isLoading, - items, + searchInfiniteQueryResponse, emptyListMessage, entryLabel = 'Language Entry', }) { const [drawerOpen, setDrawerOpen] = useState(false) const [selectedItem, setSelectedItem] = useState({}) - const { isFetchingNextPage, fetchNextPage, hasNextPage, loadLabel } = - infiniteScroll const { sitename } = useParams() const { checkIfUserCanEdit } = useAuthCheck() @@ -42,13 +38,13 @@ function DashboardEntriesPresentationList({ 'p-4 text-left text-xs font-medium text-charcoal-900 uppercase tracking-wider' return ( - +
    - {items?.pages !== undefined && - items?.pages?.[0]?.results?.length > 0 ? ( + {searchInfiniteQueryResponse?.data?.pages !== undefined && + searchInfiniteQueryResponse?.data?.pages?.[0]?.results?.length > 0 ? (
    @@ -105,7 +101,7 @@ function DashboardEntriesPresentationList({ - {items.pages.map((page) => ( + {searchInfiniteQueryResponse?.data?.pages.map((page) => ( {page.results.map((entry) => ( fetchNextPage()} - disabled={!hasNextPage || isFetchingNextPage} + className={ + !searchInfiniteQueryResponse?.hasNextPage ? 'cursor-text' : '' + } + onClick={() => searchInfiniteQueryResponse?.fetchNextPage()} + disabled={ + !searchInfiniteQueryResponse?.hasNextPage || + searchInfiniteQueryResponse?.isFetchingNextPage + } > - {loadLabel} + {searchInfiniteQueryResponse?.loadLabel} @@ -273,16 +274,14 @@ function DashboardEntriesPresentationList({ )} - + ) } // PROPTYPES -const { bool, object, string } = PropTypes +const { object, string } = PropTypes DashboardEntriesPresentationList.propTypes = { - infiniteScroll: object, - isLoading: bool, - items: object, + searchInfiniteQueryResponse: object, emptyListMessage: string, entryLabel: string, } diff --git a/src/components/DashboardGalleries/DashboardGalleriesContainer.js b/src/components/DashboardGalleries/DashboardGalleriesContainer.js index 8765f2dc..b7a3be23 100644 --- a/src/components/DashboardGalleries/DashboardGalleriesContainer.js +++ b/src/components/DashboardGalleries/DashboardGalleriesContainer.js @@ -6,20 +6,18 @@ import DashboardGalleriesPresentation from 'components/DashboardGalleries/Dashbo import DashboardLanding from 'components/DashboardLanding' function DashboardGalleriesContainer() { - const { galleries, headerContent, isLoading, site, sitename, tileContent } = + const { galleriesQueryResponse, headerContent, site, tileContent } = DashboardGalleriesData() return (
    diff --git a/src/components/DashboardGalleries/DashboardGalleriesData.js b/src/components/DashboardGalleries/DashboardGalleriesData.js index b2fcfd51..293c9501 100644 --- a/src/components/DashboardGalleries/DashboardGalleriesData.js +++ b/src/components/DashboardGalleries/DashboardGalleriesData.js @@ -10,7 +10,7 @@ function DashboardGalleriesData() { const { sitename } = useParams() // Data fetch - const { data, isInitialLoading } = useGalleries() + const galleriesQueryResponse = useGalleries() const tileContent = [ { @@ -30,11 +30,10 @@ function DashboardGalleriesData() { } return { + galleriesQueryResponse, headerContent, - isLoading: isInitialLoading, site, tileContent, - galleries: data?.results || [], } } diff --git a/src/components/DashboardGalleries/DashboardGalleriesPresentation.js b/src/components/DashboardGalleries/DashboardGalleriesPresentation.js index 6c11ed74..fc868ac9 100644 --- a/src/components/DashboardGalleries/DashboardGalleriesPresentation.js +++ b/src/components/DashboardGalleries/DashboardGalleriesPresentation.js @@ -6,18 +6,19 @@ import { Link } from 'react-router-dom' // import getIcon from 'common/utils/getIcon' import { SMALL, IMAGE } from 'common/constants' import { getMediaPath } from 'common/utils/mediaHelpers' -import Loading from 'components/Loading' +import LoadOrError from 'components/LoadOrError' +import getIcon from 'common/utils/getIcon' -function DashboardGalleriesPresentation({ isLoading, galleries, site }) { +function DashboardGalleriesPresentation({ galleriesQueryResponse, site }) { return (
    - +
      - {galleries?.length > 0 && - galleries?.map((item) => { + {galleriesQueryResponse?.data?.results?.length > 0 && + galleriesQueryResponse?.data?.results?.map((item) => { const imageUrl = getMediaPath({ type: IMAGE, mediaObject: item?.coverImage, @@ -32,13 +33,17 @@ function DashboardGalleriesPresentation({ isLoading, galleries, site }) { : 'text-charcoal-500 bg-charcoal-50' return ( -
    • +
    • + {getIcon( + 'Pencil', + 'absolute top-3 right-3 btn-icon text-white opacity-0 group-hover:opacity-100', + )}
      {item.title}
      {item.titleTranslation}
      @@ -49,15 +54,14 @@ function DashboardGalleriesPresentation({ isLoading, galleries, site }) { ) })}
    -
    +
    ) } // PROPTYPES -const { array, bool, object } = PropTypes +const { object } = PropTypes DashboardGalleriesPresentation.propTypes = { - galleries: array, - isLoading: bool, + galleriesQueryResponse: object, site: object, } diff --git a/src/components/DashboardJoinList/DashboardJoinListContainer.js b/src/components/DashboardJoinList/DashboardJoinListContainer.js index 876a18d4..bf3f1f05 100644 --- a/src/components/DashboardJoinList/DashboardJoinListContainer.js +++ b/src/components/DashboardJoinList/DashboardJoinListContainer.js @@ -1,20 +1,15 @@ import React from 'react' // FPCC -import DashboardJoinListData from 'components/DashboardJoinList/DashboardJoinListData' import DashboardJoinListPresentation from 'components/DashboardJoinList/DashboardJoinListPresentation' +import { useJoinRequests } from 'common/dataHooks/useJoinRequests' function DashboardJoinListContainer() { - const { joinRequests, infiniteScroll, loadRef, isLoading, site } = - DashboardJoinListData() + const joinRequestsInfiniteQueryResponse = useJoinRequests() return (
    ) diff --git a/src/components/DashboardJoinList/DashboardJoinListData.js b/src/components/DashboardJoinList/DashboardJoinListData.js deleted file mode 100644 index e4d4c9a9..00000000 --- a/src/components/DashboardJoinList/DashboardJoinListData.js +++ /dev/null @@ -1,23 +0,0 @@ -import { useParams } from 'react-router-dom' - -// FPCC -import { useSiteStore } from 'context/SiteContext' -import { useJoinRequests } from 'common/dataHooks/useJoinRequests' - -function DashboardJoinListData() { - const { site } = useSiteStore() - const { sitename } = useParams() - - const { data, isError, infiniteScroll, loadRef, isInitialLoading } = - useJoinRequests() - return { - isLoading: isInitialLoading || isError, - infiniteScroll, - loadRef, - site, - sitename, - joinRequests: data || {}, - } -} - -export default DashboardJoinListData diff --git a/src/components/DashboardJoinList/DashboardJoinListPresentation.js b/src/components/DashboardJoinList/DashboardJoinListPresentation.js index 60808342..c1c4b97e 100644 --- a/src/components/DashboardJoinList/DashboardJoinListPresentation.js +++ b/src/components/DashboardJoinList/DashboardJoinListPresentation.js @@ -2,78 +2,67 @@ import React, { Fragment } from 'react' import PropTypes from 'prop-types' // FPCC -import Loading from 'components/Loading' import DashboardJoinCard from 'components/DashboardJoinCard' -function DashboardJoinListPresentation({ - isLoading, - infiniteScroll, - joinRequests, - loadRef, -}) { - const { isFetchingNextPage, fetchNextPage, hasNextPage } = infiniteScroll - - const getLoadLabel = () => { - if (infiniteScroll?.isFetchingNextPage) { - return 'Loading more...' - } - if (infiniteScroll?.hasNextPage) { - return 'Load more' - } - return '' - } +function DashboardJoinListPresentation({ joinRequestsInfiniteQueryResponse }) { return ( - -
    -
    - {joinRequests?.pages?.[0]?.count > 0 ? ( -
    -
    - {joinRequests?.pages.map((page) => ( - - {page.results.map((request) => ( - - ))} - - ))} - -
    +
    +
    + {joinRequestsInfiniteQueryResponse?.data?.pages?.[0]?.count > 0 ? ( +
    +
    + {joinRequestsInfiniteQueryResponse?.data?.pages.map((page) => ( + + {page.results.map((request) => ( + + ))} + + ))} +
    - ) : ( -
    -
    - There are currently no pending join requests for your site. -
    +
    + ) : ( +
    +
    + There are currently no pending join requests for your site.
    - )} -
    -
    +
    + )}
    - +
    +
    ) } // PROPTYPES -const { bool, object } = PropTypes +const { object } = PropTypes DashboardJoinListPresentation.propTypes = { - joinRequests: object, - isLoading: bool, - loadRef: object, - infiniteScroll: object, + joinRequestsInfiniteQueryResponse: object, } export default DashboardJoinListPresentation diff --git a/src/components/DashboardJoinList/index.js b/src/components/DashboardJoinList/index.js index 6862567c..c205a7ee 100644 --- a/src/components/DashboardJoinList/index.js +++ b/src/components/DashboardJoinList/index.js @@ -1,9 +1,7 @@ import DashboardJoinListContainer from 'components/DashboardJoinList/DashboardJoinListContainer' -import DashboardJoinListData from 'components/DashboardJoinList/DashboardJoinListData' import DashboardJoinListPresentation from 'components/DashboardJoinList/DashboardJoinListPresentation' export default { Container: DashboardJoinListContainer, - Data: DashboardJoinListData, Presentation: DashboardJoinListPresentation, } diff --git a/src/components/DashboardMediaAudio/DashboardMediaAudioContainer.js b/src/components/DashboardMediaAudio/DashboardMediaAudioContainer.js index 52742d07..95289813 100644 --- a/src/components/DashboardMediaAudio/DashboardMediaAudioContainer.js +++ b/src/components/DashboardMediaAudio/DashboardMediaAudioContainer.js @@ -9,20 +9,20 @@ import SelectorResultsWrapper from 'components/SelectorResultsWrapper' function DashboardMediaAudioContainer() { const { - media, + data, searchValue, currentFile, setCurrentFile, handleSearchSubmitWithUrlSync, handleTextFieldChange, infiniteScroll, - isLoadingEntries, + isPending, loadRef, loadLabel, } = useMediaSearch({ type: TYPE_AUDIO }) const hasResults = !!( - media?.pages !== undefined && media?.pages?.[0]?.results?.length > 0 + data?.pages !== undefined && data?.pages?.[0]?.results?.length > 0 ) return ( @@ -42,11 +42,11 @@ function DashboardMediaAudioContainer() {
    0 + data?.pages !== undefined && data?.pages?.[0]?.results?.length > 0 ) return ( @@ -44,11 +44,11 @@ function DashboardMediaVisualContainer({ type }) {
    0 && data?.pages?.map((page) => ( - {page.results.map((doc) => ( -
  • -
    - {doc?.title} { + const src = getMediaPath({ + type: IMAGE, + mediaObject, + size: SMALL, + }) + return ( +
  • +
    - -
    -

    - {doc?.title} -

    - {doc?.width && doc?.height && ( -

    {`${doc?.width}x${doc?.height}`}

    - )} -
  • - ))} + {mediaObject?.title} + +
    +

    + {mediaObject?.title} +

    + {mediaObject?.width && mediaObject?.height && ( +

    {`${mediaObject?.width}x${mediaObject?.height}`}

    + )} + + ) + })} ))} diff --git a/src/components/Dictionary/DictionaryContainer.js b/src/components/Dictionary/DictionaryContainer.js index 88b28d17..3d3a07d6 100644 --- a/src/components/Dictionary/DictionaryContainer.js +++ b/src/components/Dictionary/DictionaryContainer.js @@ -4,40 +4,25 @@ import PropTypes from 'prop-types' // FPCC import DictionaryPresentation from 'components/Dictionary/DictionaryPresentation' import DictionaryData from 'components/Dictionary/DictionaryData' -import Loading from 'components/Loading' import { TYPE_DICTIONARY } from 'common/constants' import SiteDocHead from 'components/SiteDocHead' function DictionaryContainer({ searchType = TYPE_DICTIONARY, kids = null }) { - const { - actions, - infiniteScroll, - isLoading, - isLoadingEntries, - items, - labels, - moreActions, - sitename, - loadRef, - count, - } = DictionaryData({ searchType, kids }) + const { infiniteQueryResponse, labels, sitename } = DictionaryData({ + searchType, + kids, + }) return ( - + <> - + ) } diff --git a/src/components/Dictionary/DictionaryData.js b/src/components/Dictionary/DictionaryData.js index 5aaac347..25e237a7 100644 --- a/src/components/Dictionary/DictionaryData.js +++ b/src/components/Dictionary/DictionaryData.js @@ -2,14 +2,12 @@ import { useParams, useSearchParams } from 'react-router-dom' import PropTypes from 'prop-types' // FPCC -import { useSiteStore } from 'context/SiteContext' import useSearchLoader from 'common/dataHooks/useSearchLoader' import { getPresentationPropertiesForType } from 'common/utils/stringHelpers' import { DOMAIN, DOMAIN_BOTH, TYPES, KIDS } from 'common/constants' function DictionaryDataSearch({ searchType, kids }) { const { sitename } = useParams() - const { site } = useSiteStore() const [searchParams] = useSearchParams() const domain = searchParams.get(DOMAIN) || DOMAIN_BOTH @@ -23,25 +21,16 @@ function DictionaryDataSearch({ searchType, kids }) { }) // Search fetch - const { data, infiniteScroll, loadRef, isInitialLoading } = useSearchLoader({ + const infiniteQueryResponse = useSearchLoader({ searchParams: _searchParams, }) - const count = data?.pages[0]?.count - const labels = getPresentationPropertiesForType(searchType) return { - isLoading: !site?.title, - isLoadingEntries: isInitialLoading, - items: data, - infiniteScroll, + infiniteQueryResponse, labels, - loadRef, sitename, - actions: ['copy'], - moreActions: ['share', 'qrcode'], - count, } } diff --git a/src/components/Dictionary/DictionaryPresentation.js b/src/components/Dictionary/DictionaryPresentation.js index 88066bcd..7dd57aab 100644 --- a/src/components/Dictionary/DictionaryPresentation.js +++ b/src/components/Dictionary/DictionaryPresentation.js @@ -14,17 +14,11 @@ import { } from 'common/constants' function DictionaryPresentation({ - actions, searchType, - infiniteScroll, - loadRef, - isLoadingEntries, - items, + infiniteQueryResponse, kids, labels, - moreActions, sitename, - count, }) { const linkStyle = { li: 'block transition duration-500 ease-in-out ml-5 xl:ml-8', @@ -32,10 +26,10 @@ function DictionaryPresentation({ icon: 'inline-flex fill-current w-6 xl:w-8 mr-2 xl:mr-5', } - let countStr = count - if (count >= 10000) { - countStr = '10000+' - } + const count = + infiniteQueryResponse?.data?.pages?.[0]?.count < 10000 + ? infiniteQueryResponse?.data?.pages?.[0]?.count + : '10000+' return ( <> @@ -53,9 +47,7 @@ function DictionaryPresentation({ {kids ? (
    - Results: {countStr} + Results: {count}
    @@ -120,11 +112,7 @@ function DictionaryPresentation({
    @@ -144,24 +128,18 @@ function DictionaryPresentation({
    )} -
    +
    ) } // PROPTYPES -const { array, bool, object, string, number } = PropTypes +const { bool, object, string } = PropTypes DictionaryPresentation.propTypes = { - actions: array, searchType: string, - infiniteScroll: object, - loadRef: object, - isLoadingEntries: bool, - items: object, + infiniteQueryResponse: object, kids: bool, labels: object, - moreActions: array, sitename: string, - count: number, } export default DictionaryPresentation diff --git a/src/components/DictionaryGrid/DictionaryGridPresentation.js b/src/components/DictionaryGrid/DictionaryGridPresentation.js index cf95eb0a..51ff2819 100644 --- a/src/components/DictionaryGrid/DictionaryGridPresentation.js +++ b/src/components/DictionaryGrid/DictionaryGridPresentation.js @@ -2,23 +2,19 @@ import React, { useState } from 'react' import PropTypes from 'prop-types' // FPCC -import Loading from 'components/Loading' +import LoadOrError from 'components/LoadOrError' import getIcon from 'common/utils/getIcon' import DictionaryGridTile from 'components/DictionaryGridTile' import LazyLoader from 'components/LazyLoader' function DictionaryGridPresentation({ - actions = [], - moreActions = [], + infiniteQueryResponse, + actions = ['copy'], + moreActions = ['share', 'qrcode'], hasSideNav, - infiniteScroll, - isLoading, - items, kids = null, noResultsMessage = 'Sorry, no results were found for this search.', }) { - const { isFetchingNextPage, fetchNextPage, hasNextPage, loadLabel } = - infiniteScroll const [loadAll, setLoadAll] = useState(false) const printBtn = () => { @@ -29,8 +25,9 @@ function DictionaryGridPresentation({ } return ( - - {items?.pages !== undefined && items?.pages?.[0]?.results?.length > 0 ? ( + + {infiniteQueryResponse?.data?.pages !== undefined && + infiniteQueryResponse?.data?.pages?.[0]?.results?.length > 0 ? (
    {/* Hiding print button until custom print view has been created */} @@ -42,7 +39,7 @@ function DictionaryGridPresentation({ > {getIcon('Print', 'fill-current w-8 h-auto')} - {items.pages.map((page) => ( + {infiniteQueryResponse?.data?.pages.map((page) => (
    fetchNextPage()} - disabled={!hasNextPage || isFetchingNextPage} + className={ + !infiniteQueryResponse?.hasNextPage ? 'cursor-text' : '' + } + onClick={() => infiniteQueryResponse?.fetchNextPage()} + disabled={ + !infiniteQueryResponse?.hasNextPage || + infiniteQueryResponse?.isFetchingNextPage + } > - {loadLabel} + {infiniteQueryResponse?.loadLabel}
    @@ -81,7 +83,7 @@ function DictionaryGridPresentation({
    {noResultsMessage}
    )} -
    + ) } @@ -89,9 +91,7 @@ function DictionaryGridPresentation({ const { array, bool, node, object } = PropTypes DictionaryGridPresentation.propTypes = { hasSideNav: bool, - infiniteScroll: object, - isLoading: bool, - items: object, + infiniteQueryResponse: object, kids: bool, noResultsMessage: node, actions: array, diff --git a/src/components/DictionaryList/DictionaryListPresentation.js b/src/components/DictionaryList/DictionaryListPresentation.js index da5f8dad..2c700485 100644 --- a/src/components/DictionaryList/DictionaryListPresentation.js +++ b/src/components/DictionaryList/DictionaryListPresentation.js @@ -5,7 +5,7 @@ import { Link, useNavigate } from 'react-router-dom' // FPCC import getIcon from 'common/utils/getIcon' import { makePlural } from 'common/utils/urlHelpers' -import Loading from 'components/Loading' +import LoadOrError from 'components/LoadOrError' import ActionsMenu from 'components/ActionsMenu' import Drawer from 'components/Drawer' import EntryDetail from 'components/EntryDetail' @@ -13,11 +13,9 @@ import AudioButton from 'components/AudioButton' import { useAudiobar } from 'context/AudiobarContext' function DictionaryListPresentation({ - actions = [], - infiniteScroll, - isLoading, - items, - moreActions = [], + infiniteQueryResponse, + actions = ['copy'], + moreActions = ['share', 'qrcode'], noResultsMessage = 'Sorry, no results were found for this search.', onSortByClick, showType, @@ -27,8 +25,6 @@ function DictionaryListPresentation({ }) { const [drawerOpen, setDrawerOpen] = useState(false) const [selectedItem, setSelectedItem] = useState({}) - const { isFetchingNextPage, fetchNextPage, hasNextPage, loadLabel } = - infiniteScroll const navigate = useNavigate() const { setCurrentAudio } = useAudiobar() @@ -59,8 +55,9 @@ function DictionaryListPresentation({ 'px-6 py-3 text-left text-xs font-medium text-charcoal-500 uppercase tracking-wider' return ( - - {items?.pages !== undefined && items?.pages?.[0]?.results?.length > 0 ? ( + + {infiniteQueryResponse?.data?.pages !== undefined && + infiniteQueryResponse?.data?.pages?.[0]?.results?.length > 0 ? (
    @@ -111,7 +108,7 @@ function DictionaryListPresentation({
    - {items?.pages?.map((page) => ( + {infiniteQueryResponse?.data?.pages?.map((page) => ( {page.results.map((entry) => ( @@ -200,11 +197,16 @@ function DictionaryListPresentation({ @@ -233,16 +235,14 @@ function DictionaryListPresentation({ isDrawer /> - + ) } // PROPTYPES const { array, bool, func, node, object, string } = PropTypes DictionaryListPresentation.propTypes = { - infiniteScroll: object, - isLoading: bool, - items: object, + infiniteQueryResponse: object, actions: array, moreActions: array, noResultsMessage: node, diff --git a/src/components/Galleries/GalleriesContainer.js b/src/components/Galleries/GalleriesContainer.js index b7439258..4ad1c65c 100644 --- a/src/components/Galleries/GalleriesContainer.js +++ b/src/components/Galleries/GalleriesContainer.js @@ -2,16 +2,12 @@ import React from 'react' // FPCC import GalleriesPresentation from 'components/Galleries/GalleriesPresentation' -import GalleriesData from 'components/Galleries/GalleriesData' +import { useGalleries } from 'common/dataHooks/useGalleries' function GalleriesContainer() { - const { galleries, isLoading, sitename } = GalleriesData() + const galleriesQueryResponse = useGalleries() return ( - + ) } diff --git a/src/components/Galleries/GalleriesData.js b/src/components/Galleries/GalleriesData.js deleted file mode 100644 index 3225883c..00000000 --- a/src/components/Galleries/GalleriesData.js +++ /dev/null @@ -1,17 +0,0 @@ -import { useParams } from 'react-router-dom' - -// FPCC -import { useGalleries } from 'common/dataHooks/useGalleries' - -function GalleriesData() { - const { sitename } = useParams() - const { data, isInitialLoading } = useGalleries() - - return { - galleries: data?.results || [], - isLoading: isInitialLoading, - sitename, - } -} - -export default GalleriesData diff --git a/src/components/Galleries/GalleriesPresentation.js b/src/components/Galleries/GalleriesPresentation.js index b052d402..aaae8303 100644 --- a/src/components/Galleries/GalleriesPresentation.js +++ b/src/components/Galleries/GalleriesPresentation.js @@ -1,15 +1,16 @@ import React from 'react' import PropTypes from 'prop-types' -import { Link } from 'react-router-dom' +import { Link, useParams } from 'react-router-dom' // FPCC import { getMediaPath } from 'common/utils/mediaHelpers' -import Loading from 'components/Loading' +import LoadOrError from 'components/LoadOrError' import SectionTitle from 'components/SectionTitle' import { SMALL, IMAGE } from 'common/constants' import SiteDocHead from 'components/SiteDocHead' -function GalleriesPresentation({ isLoading, galleries, sitename }) { +function GalleriesPresentation({ galleriesQueryResponse }) { + const { sitename } = useParams() return (
    - +
      - {galleries?.length > 0 ? ( - galleries?.map((item) => { + {galleriesQueryResponse?.data?.results?.length > 0 ? ( + galleriesQueryResponse?.data?.results?.map((item) => { const imageUrl = getMediaPath({ type: IMAGE, mediaObject: item?.coverImage, @@ -70,7 +71,7 @@ function GalleriesPresentation({ isLoading, galleries, sitename }) {
    )} - + @@ -80,11 +81,9 @@ function GalleriesPresentation({ isLoading, galleries, sitename }) { ) } // PROPTYPES -const { bool, array, string } = PropTypes +const { object } = PropTypes GalleriesPresentation.propTypes = { - isLoading: bool, - galleries: array, - sitename: string, + galleriesQueryResponse: object, } export default GalleriesPresentation diff --git a/src/components/Galleries/index.js b/src/components/Galleries/index.js index 399eee62..6ef99e75 100644 --- a/src/components/Galleries/index.js +++ b/src/components/Galleries/index.js @@ -1,9 +1,7 @@ import GalleriesContainer from 'components/Galleries/GalleriesContainer' import GalleriesPresentation from 'components/Galleries/GalleriesPresentation' -import GalleriesData from 'components/Galleries/GalleriesData' export default { Container: GalleriesContainer, Presentation: GalleriesPresentation, - Data: GalleriesData, } diff --git a/src/components/Game/Parachute/ParachuteContainer.js b/src/components/Game/Parachute/ParachuteContainer.js index ffda5d2f..1c214a92 100644 --- a/src/components/Game/Parachute/ParachuteContainer.js +++ b/src/components/Game/Parachute/ParachuteContainer.js @@ -4,23 +4,31 @@ import PropTypes from 'prop-types' // FPCC import ParachuteData from 'components/Game/Parachute/ParachuteData' import ParachutePresentation from 'components/Game/Parachute/ParachutePresentation' -import Loading from 'components/Loading' +import LoadOrError from 'components/LoadOrError' import SiteDocHead from 'components/SiteDocHead' function ParachuteContainer({ kids }) { - const { isLoading, puzzle, translation, audio, alphabet, newPuzzle } = - ParachuteData({ kids }) + const { + parachuteQueryResponse, + puzzle, + translation, + audio, + alphabet, + newPuzzle, + } = ParachuteData({ kids }) return ( - + <> - - + + + + ) } diff --git a/src/components/Game/Parachute/ParachuteData.js b/src/components/Game/Parachute/ParachuteData.js index 9697ead7..2468b120 100644 --- a/src/components/Game/Parachute/ParachuteData.js +++ b/src/components/Game/Parachute/ParachuteData.js @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect, useCallback } from 'react' import PropTypes from 'prop-types' // FPCC @@ -13,12 +13,7 @@ function ParachuteData({ kids }) { const [currentPuzzle, setCurrentPuzzle] = useState() const [pagesWithoutUsablePuzzle, setPagesWithoutUsablePuzzle] = useState(1) - const { - data, - isInitialLoading, - puzzles, - refetch: getNextPage, - } = useParachuteSearch({ + const parachuteQueryResponse = useParachuteSearch({ perPage: PUZZLES_PER_PAGE, kids, }) @@ -26,64 +21,59 @@ function ParachuteData({ kids }) { const { data: characterData } = useCharacters() const characters = characterData?.characters?.map((item) => item?.title) - const nextWord = () => { + const nextWord = useCallback(() => { if ( - currentWordIndex < Math.min(PUZZLES_PER_PAGE, (data?.count || {}) - 1) + currentWordIndex < + Math.min( + PUZZLES_PER_PAGE, + (parachuteQueryResponse?.data?.count || {}) - 1, + ) ) { setCurrentWordIndex(currentWordIndex + 1) } else { // If we run out of puzzles trigger a fetch for another page - getNextPage() + parachuteQueryResponse?.refetch() setCurrentWordIndex(0) setPagesWithoutUsablePuzzle(pagesWithoutUsablePuzzle + 1) } - } + }, [currentWordIndex, pagesWithoutUsablePuzzle, parachuteQueryResponse]) useEffect(() => { const getPuzzle = () => { - if (puzzles[currentWordIndex]?.length > 0) { + if (parachuteQueryResponse?.puzzles[currentWordIndex]?.length > 0) { setPagesWithoutUsablePuzzle(0) - return puzzles[currentWordIndex] + return parachuteQueryResponse?.puzzles[currentWordIndex] } - // If we have already fetched the max number of pages and still not found a usable puzzle return an empty array to prevent possible infinite requests if (pagesWithoutUsablePuzzle < MAX_PAGES_WITHOUT_USABLE_PUZZLE) { // If the puzzle pieces array is empty then go to the next word nextWord() } + // If we have already fetched the max number of pages and still not found a usable puzzle return an empty array to prevent possible infinite requests return [] } - if (data?.results) { + if (parachuteQueryResponse?.data?.results) { setCurrentPuzzle(getPuzzle()) } - }, [currentWordIndex, data]) - - let isLoading = true - - // After data has been fetched, and if we still don't have a puzzle, - // we will be showing the error message. - if (!isInitialLoading && !currentPuzzle) { - isLoading = false - } + }, [ + currentWordIndex, + nextWord, + pagesWithoutUsablePuzzle, + parachuteQueryResponse, + ]) - let gameData = { - isLoading, - puzzle: [], + return { + parachuteQueryResponse, + puzzle: currentPuzzle, + translation: + parachuteQueryResponse?.data?.results?.[currentWordIndex]?.entry + ?.translations?.[0]?.text, + audio: + parachuteQueryResponse?.data?.results?.[currentWordIndex]?.entry + ?.relatedAudio?.[0], + alphabet: characters, + newPuzzle: nextWord, } - - if (!!currentPuzzle && characters) { - gameData = { - isLoading: false, - puzzle: currentPuzzle, - translation: - data?.results?.[currentWordIndex]?.entry?.translations?.[0]?.text, - audio: data?.results?.[currentWordIndex]?.entry?.relatedAudio?.[0], - alphabet: characters, - newPuzzle: nextWord, - } - } - - return gameData } const { bool } = PropTypes diff --git a/src/components/Game/PhraseScrambler/PhraseScramblerContainer.js b/src/components/Game/PhraseScrambler/PhraseScramblerContainer.js index 854cf15c..a321d41f 100644 --- a/src/components/Game/PhraseScrambler/PhraseScramblerContainer.js +++ b/src/components/Game/PhraseScrambler/PhraseScramblerContainer.js @@ -2,14 +2,14 @@ import React from 'react' import PropTypes from 'prop-types' // FPCC -import Loading from 'components/Loading' +import LoadOrError from 'components/LoadOrError' import PhraseScramblerPresentation from 'components/Game/PhraseScrambler/PhraseScramblerPresentation' import PhraseScramblerData from 'components/Game/PhraseScrambler/PhraseScramblerData' import SiteDocHead from 'components/SiteDocHead' function PhraseScramblerContainer({ kids }) { const { - isLoading, + queryResponse, translations, relatedAudio, jumbledWords, @@ -23,21 +23,23 @@ function PhraseScramblerContainer({ kids }) { } = PhraseScramblerData({ kids }) return ( - + <> - - + + + + ) } diff --git a/src/components/Game/PhraseScrambler/PhraseScramblerData.js b/src/components/Game/PhraseScrambler/PhraseScramblerData.js index 7a42b930..a429ca79 100644 --- a/src/components/Game/PhraseScrambler/PhraseScramblerData.js +++ b/src/components/Game/PhraseScrambler/PhraseScramblerData.js @@ -1,27 +1,14 @@ -import { useQuery } from '@tanstack/react-query' -import { useState, useEffect } from 'react' -import { useParams } from 'react-router-dom' +import { useCallback, useState, useEffect } from 'react' import PropTypes from 'prop-types' // FPCC -import api from 'services/api' -import { - SEARCH, - TYPES, - KIDS, - GAMES, - TYPE_PHRASE, - HAS_TRANSLATION, - SORT, - MINWORDS, -} from 'common/constants' +import { usePhraseScramblerSearch } from 'common/dataHooks/useGamesSearch' import { arrayShuffle, partitionArray } from 'common/utils/functionHelpers' import { normalizeSpaces } from 'common/utils/stringHelpers' const MAX_ROW_LENGTH = 6 // max number of buttons to display in one row function PhraseScramblerData({ kids }) { - const { sitename } = useParams() const [jumbledWords, setJumbledWords] = useState([]) const [selectedWords, setSelectedWords] = useState([]) const [gameCompleted, setGameCompleted] = useState(false) @@ -32,6 +19,29 @@ function PhraseScramblerData({ kids }) { relatedAudio: [], }) + const queryResponse = usePhraseScramblerSearch({ kids }) + + const generateInputData = useCallback(() => { + const newPhrase = queryResponse?.data?.results?.[0]?.entry + const translations = newPhrase?.translations?.map((translation) => + normalizeSpaces(translation?.text), + ) + const phraseTitle = normalizeSpaces(newPhrase?.title) + setInputData({ + translations, + title: phraseTitle, + relatedAudio: newPhrase?.relatedAudio.slice(0, 3), // take at max 3 audio files for hints + }) + let correctAnswer = phraseTitle.split(' ') + correctAnswer = correctAnswer.map((text, index) => ({ + id: index, + text, + })) + let shuffledWords = arrayShuffle([...correctAnswer]) + shuffledWords = partitionArray(shuffledWords, MAX_ROW_LENGTH) + setJumbledWords(shuffledWords) + }, [queryResponse?.data?.results]) + const wordClicked = (wordObj) => { if (gameCompleted && !validAnswer) { setGameCompleted(false) @@ -64,63 +74,19 @@ function PhraseScramblerData({ kids }) { } const newGame = () => { - refetch() + queryResponse?.refetch() generateInputData() resetGame() } - const _searchParams = new URLSearchParams({ - [TYPES]: TYPE_PHRASE, - [GAMES]: true, - [HAS_TRANSLATION]: true, - [SORT]: 'random', - [MINWORDS]: 2, - }) - if (kids) { - _searchParams.append(KIDS, kids) - } - - const { data, isFetching, refetch } = useQuery({ - queryKey: [SEARCH, sitename], - queryFn: () => - api.search.get({ - sitename, - searchParams: _searchParams.toString(), - pageParam: 1, - perPage: 1, // Fetching one phrase at a time - }), - ...{ enabled: !!sitename }, - }) - - const generateInputData = () => { - const newPhrase = data?.results?.[0]?.entry - const translations = newPhrase?.translations?.map((translation) => - normalizeSpaces(translation?.text), - ) - const phraseTitle = normalizeSpaces(newPhrase?.title) - setInputData({ - translations, - title: phraseTitle, - relatedAudio: newPhrase?.relatedAudio.slice(0, 3), // take at max 3 audio files for hints - }) - let correctAnswer = phraseTitle.split(' ') - correctAnswer = correctAnswer.map((text, index) => ({ - id: index, - text, - })) - let shuffledWords = arrayShuffle([...correctAnswer]) - shuffledWords = partitionArray(shuffledWords, MAX_ROW_LENGTH) - setJumbledWords(shuffledWords) - } - useEffect(() => { - if (data?.count > 0) { + if (queryResponse?.data?.count > 0 && !inputData?.title) { generateInputData() } - }, [data]) + }, [queryResponse, generateInputData, inputData]) return { - isLoading: isFetching, + queryResponse, translations: inputData?.translations, relatedAudio: inputData?.relatedAudio, jumbledWords, diff --git a/src/components/Game/Wordsy/WordsyContainer.js b/src/components/Game/Wordsy/WordsyContainer.js index 6566f206..6abd2570 100644 --- a/src/components/Game/Wordsy/WordsyContainer.js +++ b/src/components/Game/Wordsy/WordsyContainer.js @@ -2,17 +2,15 @@ import React from 'react' import PropTypes from 'prop-types' // FPCC -import Loading from 'components/Loading' +import LoadOrError from 'components/LoadOrError' import WordsyData from 'components/Game/Wordsy/WordsyData' import WordsyPresentation from 'components/Game/Wordsy/WordsyPresentation' import SiteDocHead from 'components/SiteDocHead' function WordsyContainer({ kids }) { const { - isFetching, + queryResponse, tries, - solution, - languageConfig, guesses, currentGuess, wordLength, @@ -25,24 +23,26 @@ function WordsyContainer({ kids }) { setModalData, } = WordsyData({ kids }) return ( - + <> - - + + + + ) } diff --git a/src/components/Game/Wordsy/WordsyData.js b/src/components/Game/Wordsy/WordsyData.js index 020982d9..d59a5345 100644 --- a/src/components/Game/Wordsy/WordsyData.js +++ b/src/components/Game/Wordsy/WordsyData.js @@ -1,56 +1,15 @@ -import { useState, useEffect } from 'react' -import { useParams } from 'react-router-dom' -import { useQuery } from '@tanstack/react-query' +import { useState } from 'react' import PropTypes from 'prop-types' // FPCC -import api from 'services/api' -import { KIDS, WORDSY } from 'common/constants' -import { - getOrthographyPattern, - isWordInWordList, -} from 'components/Game/Wordsy/Utils/helpers' +import { useWordsySearch } from 'common/dataHooks/useGamesSearch' +import { isWordInWordList } from 'components/Game/Wordsy/Utils/helpers' const MAX_TRIES = 7 const WORD_LENGTH = 5 function WordsyData({ kids }) { - const [languageConfig, setLanguageConfig] = useState({ - orthography: [], - orthographyPattern: [], - words: [], - validGuesses: [], - }) - const [solution, setSolution] = useState('') - - const { sitename } = useParams() - - const _searchParams = new URLSearchParams() - if (kids) { - _searchParams.append(KIDS, kids) - } - - const { data, isFetching } = useQuery({ - queryKey: [WORDSY, sitename], - queryFn: () => - api.gameContent.getWordsyConfig({ - sitename, - searchParams: _searchParams.toString(), - }), - ...{ enabled: !!sitename }, - }) - - useEffect(() => { - const updatedLanguageConfig = { - orthography: data?.orthography, - orthographyPattern: getOrthographyPattern(data?.orthography), - words: data?.words, - validGuesses: data?.validGuesses, - } - setLanguageConfig(updatedLanguageConfig) - setSolution(data?.solution) - }, [data]) - + const queryResponse = useWordsySearch({ kids }) // Game controls const [isGameOver, setIsGameOver] = useState(false) const [guesses, setGuesses] = useState([]) @@ -66,8 +25,8 @@ function WordsyData({ kids }) { const isValidGuess = () => isWordInWordList( - languageConfig.words, - languageConfig.validGuesses, + queryResponse?.languageConfig?.words, + queryResponse?.languageConfig?.validGuesses, currentGuess.join(''), ) @@ -89,7 +48,7 @@ function WordsyData({ kids }) { setGuesses([...guesses, currentGuess]) setCurrentGuess([]) - if (currentGuess.join('') === solution) { + if (currentGuess.join('') === queryResponse?.data?.solution) { setIsGameOver(true) setModalData({ status: 'win', @@ -135,10 +94,8 @@ function WordsyData({ kids }) { } return { - isFetching, + queryResponse, tries: MAX_TRIES, - solution, - languageConfig, guesses, currentGuess, wordLength: WORD_LENGTH, diff --git a/src/components/Immersion/ImmersionContainer.js b/src/components/Immersion/ImmersionContainer.js index 8dbf794c..4d805e06 100644 --- a/src/components/Immersion/ImmersionContainer.js +++ b/src/components/Immersion/ImmersionContainer.js @@ -2,21 +2,16 @@ import React from 'react' // FPCC import ImmersionPresentation from 'components/Immersion/ImmersionPresentation' -import ImmersionData from 'components/Immersion/ImmersionData' -import Loading from 'components/Loading' +import { useImmersionLabels } from 'common/dataHooks/useImmersionLabels' import SiteDocHead from 'components/SiteDocHead' function ImmersionContainer() { - const { actions, isLoading, isLoadingEntries, items } = ImmersionData() + const queryResponse = useImmersionLabels() return ( - + <> - - + + ) } diff --git a/src/components/Immersion/ImmersionData.js b/src/components/Immersion/ImmersionData.js deleted file mode 100644 index 55e1eaa8..00000000 --- a/src/components/Immersion/ImmersionData.js +++ /dev/null @@ -1,17 +0,0 @@ -// FPCC -import { useSiteStore } from 'context/SiteContext' -import { useImmersionLabels } from 'common/dataHooks/useImmersionLabels' - -function ImmersionData() { - const { site } = useSiteStore() - const { isInitialLoading, isError, labels } = useImmersionLabels() - - return { - isLoading: !site?.id, - isLoadingEntries: isInitialLoading || isError, - items: labels || [], - actions: ['copy'], - } -} - -export default ImmersionData diff --git a/src/components/Immersion/ImmersionPresentation.js b/src/components/Immersion/ImmersionPresentation.js index c2851e12..493d8feb 100644 --- a/src/components/Immersion/ImmersionPresentation.js +++ b/src/components/Immersion/ImmersionPresentation.js @@ -4,43 +4,47 @@ import PropTypes from 'prop-types' // FPCC import ImmersionPresentationList from 'components/Immersion/ImmersionPresentationList' import SectionTitle from 'components/SectionTitle' +import LoadOrError from 'components/LoadOrError' -function ImmersionPresentation({ actions, isLoadingEntries, items }) { +function ImmersionPresentation({ queryResponse }) { return ( -
    -
    - -
    - The language team for this site has added translations for the buttons - and headers on FirstVoices, so that you can use Immersion Mode to - navigate through their site in the language. When Immersion Mode is - turned on, many of the English labels will be replaced by labels in - the language. -
    -
    -
    -
    -

    - Immersion Labels -

    - + +
    + {queryResponse?.data?.count > 0 ? ( +
    + +
    + The language team for this site has added translations for the + buttons and headers on FirstVoices, so that you can use Immersion + Mode to navigate through their site in the language. When + Immersion Mode is turned on, many of the English labels will be + replaced by labels in the language.
    -
    -
    -
    -
    +
    +
    +
    +

    + Immersion Labels +

    + +
    +
    +
    + + ) : ( +
    + No immersion labels have been set for this site yet. Please check + back later. +
    + )} + + ) } // PROPTYPES -const { array, bool } = PropTypes +const { object } = PropTypes ImmersionPresentation.propTypes = { - actions: array, - isLoadingEntries: bool, - items: array, + queryResponse: object, } export default ImmersionPresentation diff --git a/src/components/Immersion/ImmersionPresentationList.js b/src/components/Immersion/ImmersionPresentationList.js index 24dce11c..d58d8266 100644 --- a/src/components/Immersion/ImmersionPresentationList.js +++ b/src/components/Immersion/ImmersionPresentationList.js @@ -4,96 +4,83 @@ import { Link } from 'react-router-dom' // FPCC import AudioButton from 'components/AudioButton' -import Loading from 'components/Loading' import ActionsMenu from 'components/ActionsMenu' -function ImmersionPresentationList({ actions = [], isLoading, items }) { +function ImmersionPresentationList({ labels }) { return ( - - {items?.length > 0 ? ( -
    -
    -
    -
    - - - - - - - - - {items.map( - ({ - immersionLabel, - english, - transKey, - relatedAudio, - link, - dictionaryEntry, - }) => - immersionLabel ? ( - - - - - - ) : null, - )} - -
    -
    - LABEL -
    -
    -
    - ENGLISH LABEL -
    -
    - Actions -
    - - {immersionLabel} - - {relatedAudio?.length > 0 && ( - - )} - - {english} - - -
    -
    -
    +
    +
    +
    + + + + + + + + + + {labels?.map( + ({ + immersionLabel, + english, + transKey, + relatedAudio, + link, + dictionaryEntry, + }) => + immersionLabel ? ( + + + + + + ) : null, + )} + +
    +
    + LABEL +
    +
    +
    + ENGLISH LABEL +
    +
    + Actions +
    + + {immersionLabel} + + {relatedAudio?.length > 0 && ( + + )} + {english} + +
    - ) : ( -
    -
    NO RESULTS
    -
    - )} - +
    +
    ) } // PROPTYPES -const { array, bool } = PropTypes +const { array } = PropTypes ImmersionPresentationList.propTypes = { - actions: array, - isLoading: bool, - items: array, + labels: array, } export default ImmersionPresentationList diff --git a/src/components/Immersion/index.js b/src/components/Immersion/index.js index 71fef4ee..36d6303a 100644 --- a/src/components/Immersion/index.js +++ b/src/components/Immersion/index.js @@ -1,9 +1,7 @@ import ImmersionContainer from 'components/Immersion/ImmersionContainer' import ImmersionPresentation from 'components/Immersion/ImmersionPresentation' -import ImmersionData from 'components/Immersion/ImmersionData' export default { Container: ImmersionContainer, Presentation: ImmersionPresentation, - Data: ImmersionData, } diff --git a/src/components/Languages/LanguagesContainer.js b/src/components/Languages/LanguagesContainer.js index 4d78b46c..55cba8ee 100644 --- a/src/components/Languages/LanguagesContainer.js +++ b/src/components/Languages/LanguagesContainer.js @@ -6,15 +6,13 @@ import LanguagesData from 'components/Languages/LanguagesData' import DocHead from 'components/DocHead' function LanguagesContainer() { - const { allSitesList, userSitesList, isLoading, user } = LanguagesData() + const { languagesQueryResponse, user } = LanguagesData() return ( <> ) diff --git a/src/components/Languages/LanguagesData.js b/src/components/Languages/LanguagesData.js index 84c9f648..ef82798a 100644 --- a/src/components/Languages/LanguagesData.js +++ b/src/components/Languages/LanguagesData.js @@ -2,7 +2,6 @@ import { useSearchParams } from 'react-router-dom' // FPCC import { useLanguages } from 'common/dataHooks/useLanguages' -import { useMySites } from 'common/dataHooks/useMySites' import { useUserStore } from 'context/UserContext' function LanguagesData() { @@ -11,19 +10,12 @@ function LanguagesData() { const [searchParams] = useSearchParams() const query = searchParams.get('q') || '' - const { languagesData, isInitialLoading } = useLanguages({ + const languagesQueryResponse = useLanguages({ query, }) - const { mySitesData, isInitialLoading: mySitesIsInitialLoading } = - useMySites() - return { - isLoading: isInitialLoading || mySitesIsInitialLoading, - // sites - allSitesList: languagesData, - // mySites - userSitesList: mySitesData, + languagesQueryResponse, user, } } diff --git a/src/components/Languages/LanguagesPresentation.js b/src/components/Languages/LanguagesPresentation.js index 69c098a9..cb97fc7e 100644 --- a/src/components/Languages/LanguagesPresentation.js +++ b/src/components/Languages/LanguagesPresentation.js @@ -10,27 +10,23 @@ import { PUBLIC } from 'common/constants' import { atLeastMember } from 'common/constants/roles' import { isAtLeastRole } from 'common/utils/membershipHelpers' import SearchLanguagesForm from 'components/SearchLanguagesForm' -import Loading from 'components/Loading' +import LoadOrError from 'components/LoadOrError' -function LanguagesPresentation({ - allSitesList, - isLoading, - userSitesList, - user, -}) { +function LanguagesPresentation({ languagesQueryResponse, user }) { + const languagesList = languagesQueryResponse?.languagesData return (
    {/* USER SITES LANGUAGES SECTION */} - {userSitesList?.length > 0 && ( + {user?.sites?.length > 0 && (
    - {userSitesList?.map((site) => ( + {user?.sites?.map((site) => ( ))}
    @@ -44,10 +40,10 @@ function LanguagesPresentation({
    - +
    - {allSitesList?.length > 0 ? ( - allSitesList.map((language) => { + {languagesList?.length > 0 ? ( + languagesList.map((language) => { // Generating class for border color const borderColor = languageColors?.[language?.languageCode] ? `border-[${languageColors[language?.languageCode]}]` @@ -93,18 +89,16 @@ function LanguagesPresentation({
    )}
    -
    +
    ) } // PROPTYPES -const { array, bool, object } = PropTypes +const { object } = PropTypes LanguagesPresentation.propTypes = { - allSitesList: array, - isLoading: bool, - userSitesList: array, + languagesQueryResponse: object, user: object, } diff --git a/src/components/Search/SearchPresentation.js b/src/components/Search/SearchPresentation.js index 251ff412..38ba2223 100644 --- a/src/components/Search/SearchPresentation.js +++ b/src/components/Search/SearchPresentation.js @@ -11,12 +11,7 @@ function SearchPresentation({ searchType, filters, handleFilter, - isLoading, - items, - infiniteScroll, - loadRef, - actions, - moreActions, + infiniteQueryResponse, sitename, entryLabel, }) { @@ -80,11 +75,7 @@ function SearchPresentation({
    -
    +
    ) } // PROPTYPES -const { array, bool, func, object, string } = PropTypes +const { array, func, object, string } = PropTypes SearchPresentation.propTypes = { - actions: array, searchType: string, filters: array, handleFilter: func, - infiniteScroll: object, - isLoading: bool, - items: object, - loadRef: object, - moreActions: array, + infiniteQueryResponse: object, sitename: string, entryLabel: string, } diff --git a/src/components/SearchAllSites/SearchAllSitesContainer.js b/src/components/SearchAllSites/SearchAllSitesContainer.js index 06c3df1b..f4aa1481 100644 --- a/src/components/SearchAllSites/SearchAllSitesContainer.js +++ b/src/components/SearchAllSites/SearchAllSitesContainer.js @@ -22,27 +22,21 @@ function SearchAllSitesContainer() { }) // fetch results - const { data, infiniteScroll, loadRef, isInitialLoading } = - useSearchAllSitesLoader({ - enabled: true, - searchParams, - }) + const infiniteQueryResponse = useSearchAllSitesLoader({ + enabled: true, + searchParams, + }) return ( <> { setSearchTypeInUrl(filter) }} - infiniteScroll={infiniteScroll} - isLoading={isInitialLoading} - items={data} - loadRef={loadRef} - moreActions={['share', 'qrcode']} + infiniteQueryResponse={infiniteQueryResponse} siteTitle="FirstVoices" entryLabel={getSearchTypeLabel({ searchTypeInUrl })} /> diff --git a/src/components/SearchSite/SearchSiteContainer.js b/src/components/SearchSite/SearchSiteContainer.js index 4e5af4e9..01d49b3b 100644 --- a/src/components/SearchSite/SearchSiteContainer.js +++ b/src/components/SearchSite/SearchSiteContainer.js @@ -24,7 +24,7 @@ function SearchSiteContainer() { }) // fetch results - const { data, infiniteScroll, loadRef, isInitialLoading } = useSearchLoader({ + const infiniteQueryResponse = useSearchLoader({ searchParams, }) @@ -32,17 +32,12 @@ function SearchSiteContainer() { <> { setSearchTypeInUrl(filter) }} - infiniteScroll={infiniteScroll} - isLoading={isInitialLoading} - items={data} - loadRef={loadRef} - moreActions={['share', 'qrcode']} + infiniteQueryResponse={infiniteQueryResponse} sitename={sitename} siteTitle={site?.title} entryLabel={getSearchTypeLabel({ searchTypeInUrl })} diff --git a/src/components/SelectorAudio/SelectorAudioContainer.js b/src/components/SelectorAudio/SelectorAudioContainer.js index 1373b54c..a38cbfca 100644 --- a/src/components/SelectorAudio/SelectorAudioContainer.js +++ b/src/components/SelectorAudio/SelectorAudioContainer.js @@ -14,18 +14,18 @@ function SelectorAudioContainer({ mediaSelectHandler, }) { const { - media, + data, searchValue, handleSearchSubmit, handleTextFieldChange, infiniteScroll, - isLoadingEntries, + isPending, loadRef, loadLabel, } = useMediaSearch({ type: TYPE_AUDIO }) const hasResults = !!( - media?.pages !== undefined && media?.pages?.[0]?.results?.length > 0 + data?.pages !== undefined && data?.pages?.[0]?.results?.length > 0 ) return ( @@ -42,11 +42,11 @@ function SelectorAudioContainer({
    0 + data?.pages !== undefined && data?.pages?.[0]?.results?.length > 0 ) const sharedMediaOptions = [ { value: 'true', label: 'Shared Images' }, @@ -66,7 +66,7 @@ function SelectorImagesContainer({
    @@ -86,7 +86,7 @@ function SelectorImagesContainer({

    0 + data?.pages !== undefined && data?.pages?.[0]?.results?.length > 0 ) return ( @@ -45,7 +45,7 @@ function SelectorVideosContainer({
    @@ -53,7 +53,7 @@ function SelectorVideosContainer({ Videos diff --git a/src/components/SongsAndStories/SongsAndStoriesContainer.js b/src/components/SongsAndStories/SongsAndStoriesContainer.js index b8025114..cb3d60c0 100644 --- a/src/components/SongsAndStories/SongsAndStoriesContainer.js +++ b/src/components/SongsAndStories/SongsAndStoriesContainer.js @@ -3,34 +3,34 @@ import PropTypes from 'prop-types' // FPCC import SongsAndStoriesPresentation from 'components/SongsAndStories/SongsAndStoriesPresentation' -import SongsAndStoriesData from 'components/SongsAndStories/SongsAndStoriesData' -import Loading from 'components/Loading' +import useSearchLoader from 'common/dataHooks/useSearchLoader' import SiteDocHead from 'components/SiteDocHead' import { getPresentationPropertiesForType } from 'common/utils/stringHelpers' +import { TYPE_SONG, TYPE_STORY } from 'common/constants' function SongsAndStoriesContainer({ searchType, kids = null }) { - const { infiniteScroll, items, isLoading, loadRef, sitename } = - SongsAndStoriesData({ searchType, kids }) + const infiniteQueryResponse = useSearchLoader({ + searchParams: `types=${searchType}&kids=${kids}`, + }) + const labels = getPresentationPropertiesForType(searchType) + return ( - + <> - + ) } // PROPTYPES -const { bool, string } = PropTypes +const { bool, oneOf } = PropTypes SongsAndStoriesContainer.propTypes = { - searchType: string.isRequired, + searchType: oneOf([TYPE_SONG, TYPE_STORY]).isRequired, kids: bool, } diff --git a/src/components/SongsAndStories/SongsAndStoriesData.js b/src/components/SongsAndStories/SongsAndStoriesData.js deleted file mode 100644 index 7442e712..00000000 --- a/src/components/SongsAndStories/SongsAndStoriesData.js +++ /dev/null @@ -1,33 +0,0 @@ -import { useParams } from 'react-router-dom' -import PropTypes from 'prop-types' - -// FPCC - -import useSearchLoader from 'common/dataHooks/useSearchLoader' - -function SongsAndStoriesData({ searchType, kids }) { - const { sitename } = useParams() - - const _searchParams = `types=${searchType}&kids=${kids}` - - const { data, infiniteScroll, loadRef, isInitialLoading } = useSearchLoader({ - searchParams: _searchParams, - }) - - return { - items: data || {}, - isLoading: isInitialLoading, - infiniteScroll, - sitename, - loadRef, - } -} - -// PROPTYPES -const { string, bool } = PropTypes -SongsAndStoriesData.propTypes = { - searchType: string.isRequired, - kids: bool, -} - -export default SongsAndStoriesData diff --git a/src/components/SongsAndStories/SongsAndStoriesGrid.js b/src/components/SongsAndStories/SongsAndStoriesGrid.js index e11c9655..9cc581f2 100644 --- a/src/components/SongsAndStories/SongsAndStoriesGrid.js +++ b/src/components/SongsAndStories/SongsAndStoriesGrid.js @@ -44,20 +44,15 @@ function getOpacityClass({ item }) { return 'group-hover:opacity-75' } -function SongsAndStoriesGrid({ - labels, - items, - showNoResultsMessage, - handleItemClick, -}) { +function SongsAndStoriesGrid({ labels, data, handleItemClick }) { return ( -
    +
      - {items?.pages?.map((page) => ( + {data?.pages?.map((page) => ( {page?.results?.map((item) => (
    • @@ -92,7 +87,6 @@ function SongsAndStoriesGrid({
    • ))} - {showNoResultsMessage(page)}
      ))}
    @@ -103,9 +97,8 @@ function SongsAndStoriesGrid({ const { func, object } = PropTypes SongsAndStoriesGrid.propTypes = { labels: object, - items: object, + data: object, handleItemClick: func, - showNoResultsMessage: func, } export default SongsAndStoriesGrid diff --git a/src/components/SongsAndStories/SongsAndStoriesList.js b/src/components/SongsAndStories/SongsAndStoriesList.js index 3bd33d02..f4c2c1d0 100644 --- a/src/components/SongsAndStories/SongsAndStoriesList.js +++ b/src/components/SongsAndStories/SongsAndStoriesList.js @@ -1,20 +1,15 @@ import React from 'react' import PropTypes from 'prop-types' -function SongsAndStoriesList({ - labels, - items, - handleItemClick, - showNoResultsMessage, -}) { +function SongsAndStoriesList({ labels, data, handleItemClick }) { return ( -
    +
    - {items?.pages?.map((page) => ( + {data?.pages?.map((page) => ( {page.results.map((item, index) => (
    @@ -49,9 +42,8 @@ function SongsAndStoriesList({ const { func, object } = PropTypes SongsAndStoriesList.propTypes = { labels: object, - items: object, + data: object, handleItemClick: func, - showNoResultsMessage: func, } export default SongsAndStoriesList diff --git a/src/components/SongsAndStories/SongsAndStoriesPresentation.js b/src/components/SongsAndStories/SongsAndStoriesPresentation.js index 7787f70c..6d783f55 100644 --- a/src/components/SongsAndStories/SongsAndStoriesPresentation.js +++ b/src/components/SongsAndStories/SongsAndStoriesPresentation.js @@ -1,6 +1,6 @@ import React, { useState } from 'react' import PropTypes from 'prop-types' -import { useNavigate } from 'react-router-dom' +import { useNavigate, useParams } from 'react-router-dom' // FPCC import Drawer from 'components/Drawer' @@ -10,21 +10,14 @@ import Song from 'components/Song' import Story from 'components/Story' import SongsAndStoriesGrid from 'components/SongsAndStories/SongsAndStoriesGrid' import SongsAndStoriesList from 'components/SongsAndStories/SongsAndStoriesList' +import LoadOrError from 'components/LoadOrError' -function SongsAndStoriesPresentation({ - infiniteScroll, - items, - kids, - labels, - loadRef, - sitename, -}) { +function SongsAndStoriesPresentation({ infiniteQueryResponse, kids, labels }) { + const { sitename } = useParams() const accentColor = labels?.textColor const [isGridView, setIsGridView] = useState(true) const [drawerOpen, setDrawerOpen] = useState(false) const [selectedItem, setSelectedItem] = useState({}) - const { isFetchingNextPage, fetchNextPage, hasNextPage, loadButtonLabel } = - infiniteScroll const navigate = useNavigate() function getDrawerContents() { @@ -38,7 +31,7 @@ function SongsAndStoriesPresentation({ } } - function handleItemClick(item) { + const handleItemClick = (item) => { if (window.innerWidth < 768 || kids) { navigate(`/${sitename}/${kids ? 'kids/' : ''}${labels?.slug}/${item?.id}`) } @@ -46,18 +39,6 @@ function SongsAndStoriesPresentation({ setDrawerOpen(true) } - function showNoResultsMessage(page) { - return ( - !page.results?.length && ( -
    -
    - No {labels?.lowercase} have been added to this site yet! -
    -
    - ) - ) - } - return ( <>
    -
    -
    -
    + +
    +
    {!kids && (
    @@ -83,42 +64,55 @@ function SongsAndStoriesPresentation({
    )} - {(!Object.hasOwn(items, 'pages') || - items?.pages?.length === 0) && ( -
    -
    - Sorry, no results were found for this search. + {infiniteQueryResponse?.data?.pages?.[0]?.count > 0 ? ( +
    + {isGridView ? ( + + ) : ( + + )} + +
    +
    + +
    +
    + ) : ( +
    +
    + No {labels?.lowercase} have been added to this site yet!
    )} - {isGridView - ? SongsAndStoriesGrid({ - labels, - items, - showNoResultsMessage, - handleItemClick, - }) - : SongsAndStoriesList({ - labels, - items, - showNoResultsMessage, - handleItemClick, - })} -
    -
    -
    - -
    -
    -
    +
    +
    +
    + - + ) } // PROPTYPES diff --git a/src/components/WidgetBrowser/WidgetBrowserData.js b/src/components/WidgetBrowser/WidgetBrowserData.js index d29715c7..5d154a0e 100644 --- a/src/components/WidgetBrowser/WidgetBrowserData.js +++ b/src/components/WidgetBrowser/WidgetBrowserData.js @@ -7,10 +7,10 @@ function WidgetBrowserData({ isHomepage, currentWidgets }) { const { site } = useSiteStore() // Fetch all widgets for this site - const { widgets, isInitialLoading } = useWidgets() + const queryResponse = useWidgets() // Don't include widgets that are already active on the page - const widgetsNotOnThisPage = widgets?.filter( + const widgetsNotOnThisPage = queryResponse?.widgets?.filter( (widget) => !currentWidgets?.includes(widget?.id), ) @@ -20,7 +20,7 @@ function WidgetBrowserData({ isHomepage, currentWidgets }) { : widgetsNotOnThisPage return { - isLoading: isInitialLoading, + queryResponse, site, widgets: widgetsToShow || [], } diff --git a/src/components/WidgetWordOfTheDay/WidgetWordOfTheDayContainer.js b/src/components/WidgetWordOfTheDay/WidgetWordOfTheDayContainer.js index 70f612ee..104bd397 100644 --- a/src/components/WidgetWordOfTheDay/WidgetWordOfTheDayContainer.js +++ b/src/components/WidgetWordOfTheDay/WidgetWordOfTheDayContainer.js @@ -12,11 +12,11 @@ function WidgetWordOfTheDayContainer() { relativeUrl, title, url, - isError, + queryResponse, sitename, entry, } = WidgetWordOfTheDayData() - return isError ? null : ( + return ( ) } diff --git a/src/components/WidgetWordOfTheDay/WidgetWordOfTheDayData.js b/src/components/WidgetWordOfTheDay/WidgetWordOfTheDayData.js index 6c805238..db5d0e47 100644 --- a/src/components/WidgetWordOfTheDay/WidgetWordOfTheDayData.js +++ b/src/components/WidgetWordOfTheDay/WidgetWordOfTheDayData.js @@ -8,18 +8,18 @@ import { WORD_OF_THE_DAY } from 'common/constants/paths' function WidgetWordOfTheDayData() { const { sitename } = useParams() - const { data, error, isError } = useQuery({ + const queryResponse = useQuery({ queryKey: [WORD_OF_THE_DAY, sitename], queryFn: () => api.wordOfTheDay.get({ sitename }), ...{ enabled: !!sitename }, }) - const word = data?.[0]?.dictionaryEntry + const word = queryResponse?.data?.[0]?.dictionaryEntry const translationArray = word?.translations?.map((trans) => `${trans?.text}`) const partOfSpeech = word?.translations?.[0]?.partOfSpeech?.title return { audio: word?.relatedAudio, wordTitle: word?.title, - isError: isError && error && error?.response?.status === 404, + queryResponse, translations: translationArray?.join('; '), partOfSpeech, title: 'WORD OF THE DAY', diff --git a/src/components/WidgetWordOfTheDay/WidgetWordOfTheDayPresentation.js b/src/components/WidgetWordOfTheDay/WidgetWordOfTheDayPresentation.js index de625be2..3d7176ca 100644 --- a/src/components/WidgetWordOfTheDay/WidgetWordOfTheDayPresentation.js +++ b/src/components/WidgetWordOfTheDay/WidgetWordOfTheDayPresentation.js @@ -6,7 +6,6 @@ import { Link } from 'react-router-dom' import ShareLinks from 'components/ShareLinks' import SectionTitle from 'components/SectionTitle' import AudioButton from 'components/AudioButton' -import Loading from 'components/Loading' function WidgetWordOfTheDayPresentation({ audio, @@ -18,15 +17,17 @@ function WidgetWordOfTheDayPresentation({ url, sitename, entry, + queryResponse, }) { return (
    -
    - -
    + + {!queryResponse?.isError ? ( +
    +
    {wordTitle} {audio && ( @@ -38,23 +39,24 @@ function WidgetWordOfTheDayPresentation({ )}
    -

    - {translations} -

    -

    - {partOfSpeech} -

    - -

    - Share on: -

    - -
    +

    {translations}

    +

    {partOfSpeech}

    +

    + Share on: +

    + +
    + ) : ( +
    + Oops! We seem to be having trouble finding a word for the day.
    + If this problem persists please contact hello@firstvoices.com +
    + )}
    ) } @@ -70,6 +72,7 @@ WidgetWordOfTheDayPresentation.propTypes = { partOfSpeech: string, entry: object, sitename: string, + queryResponse: object, } export default WidgetWordOfTheDayPresentation diff --git a/src/context/UserContext.js b/src/context/UserContext.js index d1c9c703..f776be7d 100644 --- a/src/context/UserContext.js +++ b/src/context/UserContext.js @@ -1,5 +1,10 @@ import makeStore from 'context/makeStore' -import { LANGUAGE_ADMIN, EDITOR, ASSISTANT } from 'common/constants/roles' +import { + LANGUAGE_ADMIN, + EDITOR, + ASSISTANT, + atLeastAssistant, +} from 'common/constants' const anonymousUser = { isAnonymous: true, @@ -85,6 +90,9 @@ const userDataAdaptor = (data) => { userProfile?.preferred_username || fullName || userProfile?.email + const teamMemberSites = data?.memberships?.filter((s) => + s?.role.match(atLeastAssistant), + ) return { isAnonymous: false, @@ -97,6 +105,8 @@ const userDataAdaptor = (data) => { isSuperAdmin: false, // until fw-4694 isStaff: false, // until fw-4694 roles, + sites: data?.memberships, + teamMemberSites, } }