Skip to content

Commit

Permalink
FW-1818, FW-3934 list view for join requests and admin approve and ig…
Browse files Browse the repository at this point in the history
…nore (#187)

* remove padding from DashboardTable

* Add reusable infiniteScroll hook

* Use infiniteScroll hook in useJoinRequests

* Increase max char for join message

* FW-1818 Dashboard JoinRequests List

* Add JoinRequest list to Dashboard homepage

* Rename DashboardJoinRequests to List

* Approve/Ignore mvp with button and select

* Styling and copy adjustments

* Add auth check to Join Requests list

* Adjustments for smaller screens and styling tweaks

* Sonar fix
  • Loading branch information
gmcauliffe authored Nov 9, 2023
1 parent 60c3bd6 commit ababdd7
Show file tree
Hide file tree
Showing 14 changed files with 487 additions and 29 deletions.
60 changes: 60 additions & 0 deletions src/common/dataHooks/useInfiniteScroll.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useRef } from 'react'
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()

const pagesDataAdaptor = (pages) =>
pages.map((page, index) => singlePageDataAdaptor(page, index))

const singlePageDataAdaptor = (page, index) => {
const formattedResult = page?.results?.map((result) =>
resultAdaptor ? resultAdaptor(result) : result,
)
return {
...page,
pageNumber: index + 1,
results: formattedResult,
}
}

// Fetch search results
const response = useInfiniteQuery({
queryKey,
queryFn,
enabled: !!site?.sitename,
getNextPageParam: (currentPage) => currentPage.next,
select: (responseData) => ({
pages: pagesDataAdaptor(responseData.pages),
pageParams: responseData.pageParams,
}),
})

const infiniteScroll = {
fetchNextPage: response?.fetchNextPage,
hasNextPage: response?.hasNextPage,
isFetchingNextPage: response?.isFetchingNextPage,
}

const loadRef = useRef(null)
useIntersectionObserver({
target: loadRef,
onIntersect: response?.fetchNextPage,
enabled: response?.hasNextPage,
})

return {
...response,
infiniteScroll,
loadRef,
}
}

export default useInfiniteScroll
26 changes: 10 additions & 16 deletions src/common/dataHooks/useJoinRequests.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
import {
useInfiniteQuery,
useMutation,
useQueryClient,
} from '@tanstack/react-query'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useParams } from 'react-router-dom'

// FPCC
import api from 'services/api'
import { JOIN_REQUESTS, MEMBER } from 'common/constants'
import useMutationWithNotification from 'common/dataHooks/useMutationWithNotification'
import useInfiniteScroll from 'common/dataHooks/useInfiniteScroll'
import { useSiteStore } from 'context/SiteContext'

export function useJoinRequests() {
const { sitename } = useParams()
const { site } = useSiteStore()

const allJoinRequestsResponse = useInfiniteQuery(
[JOIN_REQUESTS, sitename],
({ pageParam = 1 }) =>
const response = useInfiniteScroll({
queryKey: [JOIN_REQUESTS, site?.sitename],
queryFn: ({ pageParam = 1 }) =>
api.joinRequests.getJoinRequests({
sitename,
sitename: site?.sitename,
pageParam,
}),
{
// The query will not execute until the sitename exists
enabled: !!sitename,
},
)
})

return allJoinRequestsResponse
return response
}

export function useJoinRequestCreate(options = {}) {
Expand Down
80 changes: 71 additions & 9 deletions src/components/DashboardHome/DashboardHomePresentation.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Link } from 'react-router-dom'

// FPCC
import DashboardTiles from 'components/DashboardTiles'
import getIcon from 'common/utils/getIcon'
import RequireAuth from 'common/RequireAuth'
import { LANGUAGE_ADMIN, MEMBER } from 'common/constants/roles'
import DashboardLocator from 'components/DashboardLocator'
import DashboardJoinList from 'components/DashboardJoinList'

function DashboardHomePresentation({ site, tiles, currentUser }) {
return (
<main id="DashboardHome">
<div className="mx-auto p-4 sm:p-6 lg:p-8">
<h1 className="sr-only">Dashboard Landing Page</h1>

<section className="mb-6">
<h1 className="sr-only">Dashboard Landing Page</h1>
<div className="mx-auto p-4 sm:p-6 lg:p-8 space-y-8">
<section>
<div className="rounded-lg bg-white overflow-hidden shadow">
<h2 className="sr-only" id="profile-overview-title">
Dashboard Overview
</h2>
<div className="bg-white p-6">
<div className="sm:flex sm:items-center sm:justify-between">
<div className="flex space-x-5">
Expand All @@ -41,7 +41,69 @@ function DashboardHomePresentation({ site, tiles, currentUser }) {
</div>
</div>
</section>
<DashboardTiles.Presentation tileContent={tiles} />
<section>
<h2 className="mb-3 mx-2 text-sm font-medium text-fv-charcoal-light">
Quick Links
</h2>
<ul className="grid grid-cols-3 lg:grid-cols-4 gap-3">
{tiles.map((tile) => (
<RequireAuth
key={tile.name}
siteMembership={tile?.auth ? tile?.auth : MEMBER}
>
<li
key={tile?.name}
className="relative group col-span-1 flex rounded-lg"
>
<div
className={`bg-gray-100 text-${tile?.iconColor} flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-l-md font-medium`}
>
{getIcon(tile.icon, 'h-6 w-6 fill-current')}
</div>
<div className="flex flex-1 items-center justify-between truncate rounded-r-md border-b border-r border-t border-gray-200 bg-white">
<div className="flex-1 truncate p-1 ">
{tile?.externalLink ? (
<a
href={tile?.href}
className="text-sm font-medium text-fv-charcoal group-hover:text-fv-charcoal-light"
target="_blank"
rel="noreferrer noopener"
>
{/* Extend touch target to entire panel */}
<span
className="absolute inset-0"
aria-hidden="true"
/>
{tile?.name}
</a>
) : (
<Link
to={tile?.href}
className="text-sm font-medium text-fv-charcoal group-hover:text-fv-charcoal-light"
>
{/* Extend touch target to entire panel */}
<span
className="absolute inset-0"
aria-hidden="true"
/>
{tile?.name}
</Link>
)}
</div>
</div>
</li>
</RequireAuth>
))}
</ul>
</section>
<RequireAuth siteMembership={LANGUAGE_ADMIN}>
<section className="rounded-lg overflow-hidden">
<h2 className="mb-3 mx-2 text-sm font-medium text-fv-charcoal-light">
Requests to join {site?.title}
</h2>
<DashboardJoinList.Container />
</section>
</RequireAuth>
</div>
</main>
)
Expand Down
25 changes: 25 additions & 0 deletions src/components/DashboardJoinCard/DashboardJoinCardContainer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react'
import PropTypes from 'prop-types'

// FPCC
import DashboardJoinCardData from 'components/DashboardJoinCard/DashboardJoinCardData'
import DashboardJoinCardPresentation from 'components/DashboardJoinCard/DashboardJoinCardPresentation'

function DashboardJoinCardContainer({ joinRequest }) {
const { handleIgnore, handleApprove } = DashboardJoinCardData({ joinRequest })
return (
<DashboardJoinCardPresentation
joinRequest={joinRequest}
handleIgnore={handleIgnore}
handleApprove={handleApprove}
/>
)
}

// PROPTYPES
const { object } = PropTypes
DashboardJoinCardContainer.propTypes = {
joinRequest: object,
}

export default DashboardJoinCardContainer
21 changes: 21 additions & 0 deletions src/components/DashboardJoinCard/DashboardJoinCardData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// FPCC
import {
useJoinRequestApprove,
useJoinRequestIgnore,
} from 'common/dataHooks/useJoinRequests'

function DashboardJoinCardData({ joinRequest }) {
const { onSubmit: ignoreRequest } = useJoinRequestIgnore()
const { onSubmit: approveRequest } = useJoinRequestApprove()

const handleApprove = (roleToAssign) => {
approveRequest({ id: joinRequest.id, role: roleToAssign })
}

return {
handleIgnore: () => ignoreRequest(joinRequest?.id),
handleApprove,
}
}

export default DashboardJoinCardData
Loading

0 comments on commit ababdd7

Please sign in to comment.