diff --git a/pages/lists/[listId]/activity.tsx b/pages/lists/[listId]/activity.tsx deleted file mode 100644 index f6cf4260ca..0000000000 --- a/pages/lists/[listId]/activity.tsx +++ /dev/null @@ -1,252 +0,0 @@ -import { createPagesServerClient } from "@supabase/auth-helpers-nextjs"; -import { GetServerSidePropsContext } from "next"; -import { useRouter } from "next/router"; -import { useState } from "react"; -import { NodeMouseEventHandler } from "@nivo/treemap"; -import { useRef } from "react"; -import ContributionsEvolutionByType from "components/molecules/ContributionsEvolutionByTypeCard/contributions-evolution-by-type-card"; -import { ContributionsTreemap } from "components/Graphs/ContributionsTreemap/contributions-treemap"; -import Error from "components/atoms/Error/Error"; -import { fetchApiData, validateListPath } from "helpers/fetchApiData"; -import ListPageLayout from "layouts/lists"; -import MostActiveContributorsCard from "components/molecules/MostActiveContributorsCard/most-active-contributors-card"; -import useMostActiveContributors from "lib/hooks/api/useMostActiveContributors"; -import { useContributorsByProject } from "lib/hooks/api/useContributorsByProject"; -import { useContributionsByProject } from "lib/hooks/api/useContributionsByProject"; -import { getGraphColorPalette } from "lib/utils/color-utils"; -import useContributionsEvolutionByType from "lib/hooks/api/useContributionsByEvolutionType"; -import { FeatureFlagged } from "components/shared/feature-flagged"; -import { FeatureFlag, getAllFeatureFlags } from "lib/utils/server/feature-flags"; -import { OnToggleResizeEventType } from "components/Graphs/shared/graph-resizer"; - -export interface ContributorListPageProps { - list?: DBList; - numberOfContributors: number; - isError: boolean; - isOwner: boolean; - featureFlags: Record; -} - -export type FilterParams = { - listId: string; - range?: string; - limit?: string; -}; - -export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { - const supabase = createPagesServerClient(ctx); - - const { - data: { session }, - } = await supabase.auth.getSession(); - const bearerToken = session ? session.access_token : ""; - const { listId, range: rawRange, limit: rawLimit } = ctx.params as FilterParams; - - const range = rawRange ? Number(rawRange) : 30; - const limit = rawLimit ? Number(rawLimit) : 20; - const [{ data, error: contributorListError }, { data: list, error }] = await Promise.all([ - fetchApiData>({ - path: `lists/${listId}/contributors?limit=1`, - bearerToken, - pathValidator: validateListPath, - }), - fetchApiData({ path: `lists/${listId}`, bearerToken, pathValidator: validateListPath }), - ]); - - if (error?.status === 404 || error?.status === 401) { - return { - notFound: true, - }; - } - - const userId = Number(session?.user.user_metadata.sub); - const featureFlags = await getAllFeatureFlags(userId); - - return { - props: { - list, - numberOfContributors: data?.meta.itemCount, - isError: error || contributorListError, - isOwner: list && list.user_id === userId, - featureFlags, - }, - }; -}; - -// if no repoName is set, want the id - -const getTreemapData = ({ - currentOrgName, - repoName, - projectData = [], - projectContributionsByUser = [], -}: { - currentOrgName: string | null; - repoName: string | null; - projectData: DbProjectContributions[]; - projectContributionsByUser: DBProjectContributor[] | undefined; -}) => { - let children; - - switch (true) { - case currentOrgName === null: - children = Object.values( - projectData.reduce((acc, { repo_name, total_contributions }) => { - const [orgName] = repo_name.split("/"); - - if (!acc[orgName]) { - acc[orgName] = { id: orgName, value: 0, orgName }; - } - acc[orgName].value += total_contributions; - return acc; - }, {} as any) - ); - break; - case currentOrgName !== null && repoName === null: - children = projectData - .filter(({ repo_name }) => { - const [org_id] = repo_name.split("/"); - - return org_id === currentOrgName; - }) - .map(({ repo_name, total_contributions }) => { - return { - id: repo_name, - value: total_contributions, - repoName: `${repo_name}`, - }; - }); - break; - - case repoName !== null: - children = projectContributionsByUser.map( - ({ login, commits, prs_created, prs_reviewed, issues_created, comments }) => { - return { - id: login, - value: commits + prs_created, // Coming soon + prs_reviewed + issues_created + comments, - }; - } - ); - break; - } - - return { - id: "root", - children, - }; -}; - -const ListActivityPage = ({ list, numberOfContributors, isError, isOwner, featureFlags }: ContributorListPageProps) => { - const router = useRouter(); - const range = router.query.range as string; - const { - data: contributorStats, - isLoading, - isError: isMostActiveError, - setContributorType, - contributorType, - } = useMostActiveContributors({ listId: list!.id }); - const [currentOrgName, setCurrentOrgName] = useState(null); - const { - setRepoName, - error, - data: projectContributionsByUser, - repoName, - isLoading: isLoadingProjectContributionsByUser, - } = useContributorsByProject(list!.id, Number(range ?? "30")); - const [projectId, setProjectId] = useState(null); - - const { - data: projectData = [], - error: projectDataError, - isLoading: isTreemapLoading, - } = useContributionsByProject({ - listId: list!.id, - range: Number(range ?? "30"), - }); - - const onDrillDown: NodeMouseEventHandler<{ orgName: null; repoName: null; id: string | null }> = (node) => { - if (currentOrgName === null) { - setCurrentOrgName(node.data.orgName); - return; - } else { - setRepoName(node.data.repoName); - setProjectId(node.data.id); - } - }; - - const onDrillUp = () => { - if (repoName !== null) { - setRepoName(null); - setProjectId(null); - } else { - setCurrentOrgName(null); - } - }; - - const treemapData = getTreemapData({ currentOrgName, repoName, projectData, projectContributionsByUser }); - - const { - data: evolutionData, - isError: evolutionError, - isLoading: isLoadingEvolution, - } = useContributionsEvolutionByType({ listId: list!.id, range: Number(range ?? "30") }); - const treemapRef = useRef(null); - const mostActiveRef = useRef(null); - const graphResizerLookup = new Map(); - - if (treemapRef.current) { - graphResizerLookup.set(treemapRef.current, true); - } - - if (mostActiveRef.current) { - graphResizerLookup.set(mostActiveRef.current, true); - } - - const onToggleResize: OnToggleResizeEventType = (checked) => { - const treemap = treemapRef.current; - - if (!treemap) { - return; - } - - treemap.style.gridColumn = checked ? "1 / span 2" : ""; - treemap.style.gridRow = checked ? "1 / span 2" : ""; - }; - - return ( - - {isError ? ( - - ) : ( -
- - - } - onDrillUp={onDrillUp} - data={treemapData} - color={getGraphColorPalette()} - isLoading={isLoadingProjectContributionsByUser || isTreemapLoading} - onToggleResize={onToggleResize} - /> - - - - -
- )} -
- ); -}; - -export default ListActivityPage; diff --git a/pages/lists/[listId]/add-contributors.tsx b/pages/lists/[listId]/add-contributors.tsx deleted file mode 100644 index 05fbb1d163..0000000000 --- a/pages/lists/[listId]/add-contributors.tsx +++ /dev/null @@ -1,321 +0,0 @@ -import React, { useState } from "react"; -import { GetServerSidePropsContext } from "next"; -import { createPagesServerClient } from "@supabase/auth-helpers-nextjs"; - -import Image from "next/image"; -import { FiAlertOctagon, FiCheckCircle } from "react-icons/fi"; -import useFetchAllContributors from "lib/hooks/useFetchAllContributors"; -import { fetchApiData, validateListPath } from "helpers/fetchApiData"; - -import ContributorListTableHeaders from "components/molecules/ContributorListTableHeader/contributor-list-table-header"; -import HubContributorsPageLayout from "layouts/hub-contributors"; -import ContributorTable from "components/organisms/ContributorsTable/contributors-table"; -import Header from "components/organisms/Header/header"; -import AddContributorsHeader from "components/AddContributorsHeader/add-contributors-header"; -import useSupabaseAuth from "lib/hooks/useSupabaseAuth"; -import { Dialog, DialogContent } from "components/molecules/Dialog/dialog"; -import Title from "components/atoms/Typography/title"; -import Text from "components/atoms/Typography/text"; -import Button from "components/shared/Button/button"; -import { searchUsers } from "lib/hooks/search-users"; - -// TODO: Move to a shared file -export function isListId(listId: string) { - const regex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/; - - return regex.test(listId); -} - -interface AddContributorsPageProps { - list: DbUserList; - initialData: { - meta: Meta; - data: DbPRContributor[]; - }; - timezoneOption: { timezone: string }[]; -} - -export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { - const { listId } = ctx.params as { listId: string }; - const supabase = createPagesServerClient(ctx); - - const { - data: { session }, - } = await supabase.auth.getSession(); - const bearerToken = session ? session.access_token : ""; - const userId = Number(session?.user.user_metadata.sub); - - if (!isListId(listId)) { - return { - notFound: true, - }; - } - - const { data: list, error: listError } = await fetchApiData({ - path: `lists/${listId}`, - bearerToken, - // TODO: remove this in another PR for cleaning up fetchApiData - pathValidator: () => true, - }); - - // Only the list owner should be allowed to add contributors - if (listError?.status === 404 || (list && list.user_id !== userId)) { - return { - notFound: true, - }; - } - - return { - props: { - list, - }, - }; -}; - -interface ContributorsAddedModalProps { - list: DbUserList; - contributorCount: number; - isOpen: boolean; - onClose: () => void; -} - -interface ErrorModalProps { - list: DbUserList; - isOpen: boolean; - onRetry: () => void; - onClose: () => void; -} - -const ErrorModal = ({ list, isOpen, onRetry, onClose }: ErrorModalProps) => { - return ( - - onClose()} className="grid place-content-center"> -
-
- - - - - - - - Something went wrong - - - - We couldn't add the new contributors to your list. Please try again. - -
-
- - -
-
-
-
- ); -}; - -const ContributorsAddedModal = ({ list, contributorCount, isOpen, onClose }: ContributorsAddedModalProps) => { - return ( - - onClose()} className="grid place-content-center"> -
-
- - - - - - - - You've added {contributorCount} new {contributorCount > 1 ? "contributors" : "contributor"} - - - - You can now get insights into their activity from your list dashboard. - -
-
- - -
-
-
-
- ); -}; - -const useContributorSearch = (makeRequest: boolean) => { - const [contributorSearchTerm, setContributorSearchTerm] = useState(); - - const { data, meta, isLoading, setPage } = useFetchAllContributors( - { - contributor: contributorSearchTerm, - }, - { - revalidateOnFocus: false, - }, - makeRequest - ); - - return { - setContributorSearchTerm, - data, - meta, - isLoading, - setPage, - }; -}; - -const EmptyState = () => ( -
- -

Search for contributors to add to your list

-
-); - -const AddContributorsToList = ({ list, timezoneOption }: AddContributorsPageProps) => { - const [selectedContributors, setSelectedContributors] = useState([]); - - const { sessionToken, providerToken } = useSupabaseAuth(); - const [contributorsAdded, setContributorsAdded] = useState(false); - const [contributorsAddedError, setContributorsAddedError] = useState(false); - const [contributors, setContributors] = useState([]); - const [suggestions, setSuggestions] = useState([]); - const [searchResults, setSearchResults] = useState([]); - - const addContributorsToList = async () => { - const { error } = await fetchApiData({ - path: `lists/${list.id}/contributors`, - body: { - contributors: selectedContributors.map(({ user_id, author_login }) => ({ id: user_id, login: author_login })), - }, - method: "POST", - bearerToken: sessionToken!, - pathValidator: validateListPath, - }); - - if (error) { - setContributorsAddedError(true); - } else { - setContributorsAdded(true); - } - }; - - const onAllChecked = (state: boolean) => { - if (state) { - setSelectedContributors(contributors); - } else { - setSelectedContributors([]); - } - }; - - const onChecked = (state: boolean, contributor: DbPRContributor) => { - if (state) { - setSelectedContributors((prev) => [...prev, contributor]); - } else { - setSelectedContributors(selectedContributors.filter((selected) => selected.user_id !== contributor.user_id)); - } - }; - - async function onSearch(searchTerm: string | undefined) { - if (searchTerm && searchTerm.length >= 3) { - await updateSuggestions(searchTerm); - } - } - - async function updateSuggestions(contributor: string) { - setSuggestions([]); - - const response = await searchUsers(contributor, providerToken); - - if (response) { - const suggestions = response.data.map((item) => item.login); - setSuggestions(suggestions); - setSearchResults(response.data); - } - } - - const onSearchSelect = (username: string) => { - const contributorInfo = searchResults.find((item) => item.login === username); - - if (contributorInfo) { - const contributor: DbPRContributor = { - user_id: contributorInfo.id, - author_login: contributorInfo.login, - username: contributorInfo.login, - updated_at: "", - }; - onChecked(true, contributor); - - setContributors((prev) => [...prev, contributor]); - } - }; - - return ( - -
-
- user_id)} - onAddToList={addContributorsToList} - onSearch={onSearch} - searchSuggestions={suggestions} - onSearchSelect={onSearchSelect} - /> -
- 0 && selectedContributors.length === contributors.length} - handleOnSelectAllContributor={onAllChecked} - /> - {contributors.length > 0 ? ( - - ) : ( - - )} -
- {contributorsAdded && ( - { - setContributorsAdded(false); - setContributors([]); - setSelectedContributors([]); - }} - /> - )} - {contributorsAddedError && ( - { - setContributorsAddedError(false); - addContributorsToList(); - }} - onClose={() => { - setContributorsAddedError(false); - }} - /> - )} -
- ); -}; - -export default AddContributorsToList; diff --git a/pages/lists/[listId]/edit.tsx b/pages/lists/[listId]/edit.tsx deleted file mode 100644 index f3eb2826d0..0000000000 --- a/pages/lists/[listId]/edit.tsx +++ /dev/null @@ -1,409 +0,0 @@ -import dynamic from "next/dynamic"; -import { useRouter } from "next/router"; - -import { createPagesServerClient } from "@supabase/auth-helpers-nextjs"; -import { GetServerSidePropsContext } from "next"; -import { UserGroupIcon } from "@heroicons/react/24/solid"; -import { ComponentProps, useState } from "react"; -import { FaUserPlus } from "react-icons/fa6"; -import Link from "next/link"; -import { MdOutlineArrowBackIos } from "react-icons/md"; -import { fetchApiData } from "helpers/fetchApiData"; -import HubContributorsPageLayout from "layouts/hub-contributors"; -import Title from "components/atoms/Typography/title"; -import Text from "components/atoms/Typography/text"; -import ToggleSwitch from "components/atoms/ToggleSwitch/toggle-switch"; -import Button from "components/shared/Button/button"; -import TextInput from "components/atoms/TextInput/text-input"; -import useSupabaseAuth from "lib/hooks/useSupabaseAuth"; -import { useToast } from "lib/hooks/useToast"; -import Search from "components/atoms/Search/search"; -import Pagination from "components/molecules/Pagination/pagination"; -import { Avatar } from "components/atoms/Avatar/avatar-hover-card"; -import useFetchAllListContributors from "lib/hooks/useFetchAllListContributors"; - -const DeleteListPageModal = dynamic(() => import("components/organisms/ListPage/DeleteListPageModal")); - -// TODO: put into shared utilities once https://github.com/open-sauced/app/pull/2016 is merged -function isListId(listId: string) { - const regex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/; - - return regex.test(listId); -} - -export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { - const { listId } = ctx.params as { listId: string }; - const supabase = createPagesServerClient(ctx); - - const { - data: { session }, - } = await supabase.auth.getSession(); - const bearerToken = session ? session.access_token : ""; - const userId = Number(session?.user.user_metadata.sub); - - if (!isListId(listId)) { - return { - notFound: true, - }; - } - - const [{ data: list, error }, { data: initialContributors, error: contributorsError }] = await Promise.all([ - fetchApiData({ - path: `lists/${listId}`, - bearerToken, - // TODO: remove this in another PR for cleaning up fetchApiData - pathValidator: () => true, - }), - fetchApiData({ - path: `lists/${listId}/contributors?limit=10`, - bearerToken, - // TODO: remove this in another PR for cleaning up fetchApiData - pathValidator: () => true, - }), - ]); - - // Only the list owner should be allowed to add contributors - if (error?.status === 404 || (list && list.user_id !== userId)) { - return { - notFound: true, - }; - } - - return { - props: { - list, - initialContributors, - }, - }; -}; - -interface EditListPageProps { - list: DBList; - initialContributors: PagedData; -} - -interface UpdateListPayload { - name: string; - is_public: boolean; - contributors: number[]; -} - -const ListContributors = ({ - contributors, - onRemoveContributor, -}: { - contributors: DbListContributor[]; - onRemoveContributor: ComponentProps["onClick"]; -}) => { - return ( -
    - {contributors?.map((contributor) => ( -
  • -
    - - {contributor.login} -
    - -
  • - ))} -
- ); -}; - -export default function EditListPage({ list, initialContributors }: EditListPageProps) { - const router = useRouter(); - - const [isPublic, setIsPublic] = useState(list.is_public); - const { sessionToken } = useSupabaseAuth(); - const { toast } = useToast(); - async function updateList(payload: UpdateListPayload) { - const { data, error } = await fetchApiData({ - path: `lists/${list.id}`, - method: "PATCH", - body: payload, - bearerToken: sessionToken!, - // TODO: remove this in another PR for cleaning up fetchApiData - pathValidator: () => true, - }); - - return { data, error }; - } - const [contributorSearchTerm, setContributorSearchTerm] = useState(""); - const { - setPage, - meta, - data: contributors, - isLoading, - } = useFetchAllListContributors( - { - listId: list.id, - contributor: contributorSearchTerm, - }, - { - fallbackData: initialContributors, - revalidateOnFocus: false, - } - ); - - async function onRemoveContributor(event: React.MouseEvent) { - const { userId, userName } = (event.target as HTMLButtonElement).dataset; - const undoId = setTimeout(async () => { - const { error } = await fetchApiData({ - path: `lists/${list.id}/contributors/${userId}`, - method: "DELETE", - bearerToken: sessionToken!, - }); - - if (error) { - setRemovedContributorIds((prev) => prev.filter((id) => id !== userId)); - toast({ description: `Error removing ${userName} from your list`, variant: "danger" }); - } - }, 3000); - - setRemovedContributorIds((prev) => [...prev, userId!]); - - toast({ - description: ( -
-

- {userName} - was removed from your list -

- -
- ), - variant: "success", - }); - } - - const [removedContributorIds, setRemovedContributorIds] = useState([]); - - const GraphLoading = ({ rows = 10 }: { rows?: number }) => { - return ( -
- {new Array(rows).fill(0).map((_, index) => ( - <> -
-
- - ))} -
- ); - }; - - const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const [submitted, setSubmitted] = useState(false); - const [deleteLoading, setDeleteLoading] = useState(false); - - const handleOnDelete = () => { - setIsDeleteModalOpen(true); - }; - - const handleDeleteModalClose = () => { - setIsDeleteModalOpen(false); - }; - - const handleOnDeleteConfirm = async () => { - setSubmitted(true); - setDeleteLoading(true); - - const { error } = await fetchApiData({ - path: "lists/" + list.id, - method: "DELETE", - bearerToken: sessionToken as string, - pathValidator: () => true, - }); - - if (!error) { - setIsDeleteModalOpen(false); - setDeleteLoading(false); - toast({ description: "List deleted successfully", variant: "success" }); - router.push("/hub/lists"); - } else { - setIsDeleteModalOpen(false); - setDeleteLoading(false); - // eslint-disable-next-line no-console - console.log(error); - toast({ description: "An error occurred while deleting the list", variant: "danger" }); - } - }; - - return ( - <> - -
-
{ - event.preventDefault(); - // FormData was being funky because of the way our ToggleSwitch works - // so went this way instead. - const form = event.target as HTMLFormElement; - const listUpdates = { - name: form["list_name"].value, - is_public: form["is_public"].checked, - contributors: [], - } satisfies UpdateListPayload; - - const { data, error } = await updateList(listUpdates); - - if (!error) { - toast({ description: "List updated successfully!", variant: "success" }); - router.push(`/lists/${list.id}/overview`); - } else { - toast({ description: "Error updating list. Please try again", variant: "danger" }); - } - }} - className="flex flex-col gap-8" - > -
-

- - - {" "} - Edit List -

- -
-

- A list is a collection of contributors that you and your team can get insights for. -

- -
- -
-
- - - Make this page publicly visible - -
-
- Make Public - setIsPublic((isPublic: boolean) => !isPublic)} - /> -
-
-
-
-

Add Contributors

- -
-
-
-

Remove Contributors

-
- -
- <> - {isLoading ? ( - - ) : ( - <> - {contributors && contributors.length > 0 ? ( - <> - { - return !removedContributorIds.includes(id); - })} - onRemoveContributor={onRemoveContributor} - /> -
- index + 1)} - pageSize={5} - hasNextPage={meta.hasNextPage} - hasPreviousPage={meta.hasPreviousPage} - totalPage={meta.pageCount} - page={meta.page} - onPageChange={function (page: number): void { - setPage(page); - }} - showTotalPages={false} - /> -
- - ) : ( -

No contributors to remove found.

- )} - - )} - -
-
- -
- - Delete List - - Once you delete a list, you're past the point of no return. - - -
-
-
- -
- - ); -} diff --git a/pages/lists/[listId]/highlights.tsx b/pages/lists/[listId]/highlights.tsx deleted file mode 100644 index 3b6c35e78b..0000000000 --- a/pages/lists/[listId]/highlights.tsx +++ /dev/null @@ -1,262 +0,0 @@ -import React, { useEffect, useMemo, useRef, useState } from "react"; -import { createPagesServerClient } from "@supabase/auth-helpers-nextjs"; -import { GetServerSidePropsContext } from "next"; -import { formatDistanceToNowStrict } from "date-fns"; -import Link from "next/link"; -import { useRouter } from "next/router"; -import ListPageLayout from "layouts/lists"; -import { fetchApiData, validateListPath } from "helpers/fetchApiData"; -import Search from "components/atoms/Search/search"; - -import ContributorHighlightCard from "components/molecules/ContributorHighlight/contributor-highlight-card"; -import Avatar from "components/atoms/Avatar/avatar"; -import ClientOnly from "components/atoms/ClientOnly/client-only"; -import useFetchAllEmojis from "lib/hooks/useFetchAllEmojis"; -import { - PaginatedListContributorsHighlightsResponse, - useFetchListContributorsHighlights, - useListHighlightsTaggedRepos, -} from "lib/hooks/useList"; -import useDebounceTerm from "lib/hooks/useDebounceTerm"; -import { setQueryParams } from "lib/utils/query-params"; - -import SkeletonWrapper from "components/atoms/SkeletonLoader/skeleton-wrapper"; -import DashContainer from "components/atoms/DashedContainer/DashContainer"; -import PaginationResults from "components/molecules/PaginationResults/pagination-result"; -import Pagination from "components/molecules/Pagination/pagination"; - -import Icon from "components/atoms/Icon/icon"; -import repoTofilterList from "lib/utils/repo-to-filter-list"; -import { ContributorListPageProps } from "./activity"; - -interface HighlightsPageProps extends ContributorListPageProps { - highlights: PaginatedListContributorsHighlightsResponse; -} - -export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { - const supabase = createPagesServerClient(ctx); - - const { - data: { session }, - } = await supabase.auth.getSession(); - const bearerToken = session ? session.access_token : ""; - - const { listId, repo } = ctx.params as { listId: string; repo?: string }; - const limit = 10; // Can pull this from the querystring in the future - - const query = new URLSearchParams(); - - if (repo) { - query.append("repo", repo); - } - query.append("limit", "10"); - - const [{ data, error: contributorListError }, { data: list, error }, { data: highlights, error: highlightError }] = - await Promise.all([ - fetchApiData>({ - path: `lists/${listId}/contributors?limit=${limit}`, - bearerToken, - pathValidator: validateListPath, - }), - fetchApiData({ path: `lists/${listId}`, bearerToken, pathValidator: validateListPath }), - fetchApiData>({ - path: `lists/${listId}/contributors/highlights?${query}`, - bearerToken, - pathValidator: validateListPath, - }), - ]); - - if (error?.status === 404) { - return { - notFound: true, - }; - } - - const userId = Number(session?.user.user_metadata.sub); - - return { - props: { - list, - numberOfContributors: data?.meta.itemCount || 0, - isOwner: list && list.user_id === userId, - highlights: { - data: highlights?.data || [], - meta: highlights?.meta || {}, - }, - }, - }; -}; - -const Highlights = ({ list, numberOfContributors, isOwner, highlights }: HighlightsPageProps) => { - const router = useRouter(); - const repo = router.query.repo as string; - - const { limit = 10, range = 30 } = router.query; - const topRef = useRef(null); - - const { data: emojis } = useFetchAllEmojis(); - const { data: taggedRepos } = useListHighlightsTaggedRepos(list?.id ?? ""); - const [selectedFilter, setSelectedFilter] = useState(null); - - const filterOptions = useMemo(() => { - return repoTofilterList(taggedRepos); - }, [taggedRepos]); - - const handleRepoFilter = (name: string) => { - setSelectedFilter(selectedFilter === name ? "" : name); - setQueryParams({ repo: name }); - }; - - const [contributor, setContributor] = useState(""); - const debouncedSearchTerm = useDebounceTerm(contributor, 300); - - const { data, isLoading, meta } = useFetchListContributorsHighlights({ - listId: list?.id ?? "", - initialData: highlights, - repo, - range: range as number, - contributor, - limit: limit as number, - }); - - function onSearch(searchTerm: string) { - if (!searchTerm || searchTerm.length >= 3) { - setContributor(searchTerm); - } - } - - useEffect(() => { - onSearch(contributor); - }, [debouncedSearchTerm]); - - return ( - -
-
-
- { - onSearch(value); - }} - isDisabled={data.length === 0 && !contributor} - placeholder="Search contributors" - className="!w-full" - name="helo" - /> - - {taggedRepos && taggedRepos.length > 0 ? ( - -
- Tagged Repositories - {filterOptions && - filterOptions.length > 0 && - filterOptions.map(({ full_name, repoIcon, repoName }) => ( -
handleRepoFilter(full_name)} - key={full_name as string} - className={`${ - selectedFilter === full_name ? "border-orange-600 bg-orange-200" : "" - } flex hover:border-orange-600 hover:bg-orange-200 cursor-pointer gap-1 w-max p-1 pr-2 border-[1px] border-light-slate-6 rounded-lg text-light-slate-12`} - > - - {repoName} -
- ))} -
-
- ) : null} -
-
-
- {isLoading ? ( - -
-
- -
- -
-
- ) : null} - - {!isLoading && data && data.length === 0 ? ( - -

No highlights published in the past {range} days

-
- ) : null} - - {data && data.length > 0 - ? data.map(({ id, url, title, created_at, highlight, shipped_at, login, type, tagged_repos }) => ( -
-
- - - - {login} - - - - {formatDistanceToNowStrict(new Date(created_at), { addSuffix: true })} - - - -
-
- - - -
-
- )) - : null} - - {meta.pageCount > 1 && ( -
-
- -
- -
- )} -
-
-
- ); -}; - -export default Highlights; diff --git a/pages/lists/[listId]/overview.tsx b/pages/lists/[listId]/overview.tsx deleted file mode 100644 index 67d16eeb5f..0000000000 --- a/pages/lists/[listId]/overview.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import { useRouter } from "next/router"; -import { ErrorBoundary } from "react-error-boundary"; -import { createPagesServerClient } from "@supabase/auth-helpers-nextjs"; -import { GetServerSidePropsContext } from "next"; -import ListPageLayout from "layouts/lists"; - -import Error from "components/atoms/Error/Error"; -import useListStats from "lib/hooks/useListStats"; -import HighlightCard from "components/molecules/HighlightCard/highlight-card"; -import { fetchApiData, validateListPath } from "helpers/fetchApiData"; -import ClientOnly from "components/atoms/ClientOnly/client-only"; -import { useContributorsList } from "lib/hooks/api/useContributorList"; -import ContributorsList from "components/organisms/ContributorsList/contributors-list"; - -interface ListsOverviewProps { - list: DBList; - numberOfContributors: number; - isOwner: boolean; - isError: boolean; -} - -export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { - const supabase = createPagesServerClient(ctx); - - const { - data: { session }, - } = await supabase.auth.getSession(); - const bearerToken = session ? session.access_token : ""; - - const { listId } = ctx.params as { listId: string }; - const limit = 10; // Can pull this from the querystring in the future - const [{ data, error: contributorListError }, { data: list, error }] = await Promise.all([ - fetchApiData>({ - path: `lists/${listId}/contributors?limit=${limit}`, - bearerToken, - pathValidator: validateListPath, - }), - fetchApiData({ path: `lists/${listId}`, bearerToken, pathValidator: validateListPath }), - ]); - - if (error?.status === 404) { - return { - notFound: true, - }; - } - - const userId = Number(session?.user.user_metadata.sub); - - return { - props: { - list, - numberOfContributors: data?.meta.itemCount || 0, - isOwner: list && list.user_id === userId, - isError: error || contributorListError, - }, - }; -}; - -const ListsOverview = ({ list, numberOfContributors, isOwner, isError }: ListsOverviewProps): JSX.Element => { - const router = useRouter(); - const { listId, range, limit } = router.query; - - const { - isLoading, - setPage, - data: { data: contributors, meta }, - } = useContributorsList({ - listId: list?.id, - defaultRange: range ? (range as string) : "30", - defaultLimit: limit ? (limit as unknown as number) : 10, - }); - - const { - data: prevAllContributorStats, - meta: prevAllContributorMeta, - isLoading: prevAllContributorStatsLoading, - } = useListStats(listId as string, "all", 30); - const { - data: allContributorStats, - meta: allContributorMeta, - isLoading: allContributorStatsLoading, - } = useListStats(listId as string, "all"); - - const { meta: prevNewContributorMeta, isLoading: prevNewContributorStatsLoading } = useListStats( - listId as string, - "new", - 30 - ); - const { meta: newContributorMeta, isLoading: newContributorStatsLoading } = useListStats(listId as string, "new"); - - const { meta: prevAlumniContributorMeta, isLoading: prevAlumniContributorStatsLoading } = useListStats( - listId as string, - "alumni", - 30 - ); - const { meta: alumniContributorMeta, isLoading: alumniContributorStatsLoading } = useListStats( - listId as string, - "alumni" - ); - - const allContributorCommits = allContributorStats?.reduce((acc, curr) => acc + curr.commits, 0) || 0; - const prevAllContributorCommits = prevAllContributorStats?.reduce((acc, curr) => acc + curr.commits, 0) || 0; - - return ( - -
- -
- prevAllContributorCommits} - increased={allContributorCommits > prevAllContributorCommits} - value={allContributorCommits} - valueLabel="in the last 30d" - tooltip="" - /> - prevAllContributorMeta.itemCount} - increased={allContributorMeta.itemCount > prevAllContributorMeta.itemCount} - numChanged={allContributorMeta.itemCount - prevAllContributorMeta.itemCount} - value={allContributorMeta.itemCount} - isLoading={prevAllContributorStatsLoading || allContributorStatsLoading} - tooltip={`A contributor who has been active in the last ${range ?? 30} days`} - /> - prevNewContributorMeta.itemCount} - increased={newContributorMeta.itemCount > prevNewContributorMeta.itemCount} - numChanged={newContributorMeta.itemCount - prevNewContributorMeta.itemCount} - value={newContributorMeta.itemCount} - isLoading={prevNewContributorStatsLoading || newContributorStatsLoading} - tooltip="A contributor who has recently made their first contribution to a project." - /> - prevAlumniContributorMeta.itemCount} - increased={alumniContributorMeta.itemCount > prevAlumniContributorMeta.itemCount} - numChanged={alumniContributorMeta.itemCount - prevAlumniContributorMeta.itemCount} - value={alumniContributorMeta.itemCount} - isLoading={prevAlumniContributorStatsLoading || alumniContributorStatsLoading} - tooltip={`A contributor who has not been active on a project in the last ${range ?? 30} days.`} - /> -
-
-
- {isError ? ( - - ) : ( - Error loading the list of contributors
} - > - - - )} -
- - -
- - ); -}; - -export default ListsOverview; diff --git a/pages/lists/index.tsx b/pages/lists/index.tsx deleted file mode 100644 index ffc59e3a2d..0000000000 --- a/pages/lists/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -const ListsHub = () => { - return
Redirect to Lists Overview
; -}; - -export default ListsHub;