diff --git a/CHANGELOG.md b/CHANGELOG.md index db81c1031f..61bae595e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,46 @@ > All notable changes to this project will be documented in this file +## [1.43.0-beta.5](https://github.com/open-sauced/insights/compare/v1.43.0-beta.4...v1.43.0-beta.5) (2023-04-20) + + +### πŸ› Bug Fixes + +* only redirect on contributor profile if login is defined ([#1114](https://github.com/open-sauced/insights/issues/1114)) ([483f38c](https://github.com/open-sauced/insights/commit/483f38c54c6c707873b9e63c78b751bc5a994494)) + +## [1.43.0-beta.4](https://github.com/open-sauced/insights/compare/v1.43.0-beta.3...v1.43.0-beta.4) (2023-04-20) + + +### πŸ• Features + +* connect contributors page to API ([#1113](https://github.com/open-sauced/insights/issues/1113)) ([e51c51e](https://github.com/open-sauced/insights/commit/e51c51e4b4f6bc21fa171495578993325c3427ba)) + +## [1.43.0-beta.3](https://github.com/open-sauced/insights/compare/v1.43.0-beta.2...v1.43.0-beta.3) (2023-04-20) + + +### πŸ› Bug Fixes + +* adds spacing to contributor highlights container ([#1089](https://github.com/open-sauced/insights/issues/1089)) ([83d89bd](https://github.com/open-sauced/insights/commit/83d89bd95212fd6e739343c3b9774217b5433cab)), closes [#1057](https://github.com/open-sauced/insights/issues/1057) + + +### πŸ• Features + +* connect contributor page and graphs to the API ([#1072](https://github.com/open-sauced/insights/issues/1072)) ([#1112](https://github.com/open-sauced/insights/issues/1112)) ([4fc471c](https://github.com/open-sauced/insights/commit/4fc471c3223a6cff4bf8cc3c1a4c8a074a56d576)) + +## [1.43.0-beta.2](https://github.com/open-sauced/insights/compare/v1.43.0-beta.1...v1.43.0-beta.2) (2023-04-18) + + +### πŸ• Features + +* implement insights team feature ([#1080](https://github.com/open-sauced/insights/issues/1080)) ([5186a60](https://github.com/open-sauced/insights/commit/5186a6089c7dd5f306b7854f5a7706015e33e0f0)) + +## [1.43.0-beta.1](https://github.com/open-sauced/insights/compare/v1.42.0...v1.43.0-beta.1) (2023-04-18) + + +### πŸ• Features + +* add page to accept insight member invitation ([#1102](https://github.com/open-sauced/insights/issues/1102)) ([ffe35e2](https://github.com/open-sauced/insights/commit/ffe35e210e6d4115480a0c44478998c73fade875)) + ## [1.42.0](https://github.com/open-sauced/insights/compare/v1.41.1...v1.42.0) (2023-04-18) diff --git a/components/atoms/Search/search.tsx b/components/atoms/Search/search.tsx index 33f4203f24..3d42188c6b 100644 --- a/components/atoms/Search/search.tsx +++ b/components/atoms/Search/search.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { GrClose } from "react-icons/gr"; import { FaSearch } from "react-icons/fa"; import { Spinner } from "../SpinLoader/spin-loader"; @@ -44,6 +44,10 @@ const Search = ({ onChange?.(""); }; + useEffect(() => { + setSearch(value); + }, [value]); + const handleOnSelect = (suggestion: string) => { setSearch(suggestion); onSearch?.(suggestion); diff --git a/components/atoms/Selector/selector.tsx b/components/atoms/Selector/selector.tsx index 427d35e29f..13a36af095 100644 --- a/components/atoms/Selector/selector.tsx +++ b/components/atoms/Selector/selector.tsx @@ -2,46 +2,39 @@ import Radio from "components/atoms/Radio/radio"; import RadioCheck from "../RadioCheck/radio-check"; interface SelectorProps { - filterOptions: string[]; + filterOptions: { name: string; value: string }[]; handleFilterClick: (filter: string) => void; selected?: string; - variation?: "circle" | "check" + variation?: "circle" | "check"; } -const Selector = ({ - filterOptions, - handleFilterClick, - selected, - variation = "circle" -}: SelectorProps) => { +const Selector = ({ filterOptions, handleFilterClick, selected, variation = "circle" }: SelectorProps) => { return ( -
+
{filterOptions.length > 0 && - filterOptions.map((option, index) => { - return ( - variation === "circle" ? ( - { - handleFilterClick(option); - }} - className="!w-full" - checked={selected === option ? true : false} - > - {option} - - ) : ( - { - handleFilterClick(option); - }} - className="!w-full" - checked={selected === option ? true : false} - > - {option} - - ) + filterOptions.map(({ name, value }, index) => { + return variation === "circle" ? ( + { + handleFilterClick(value); + }} + className="!w-full" + checked={selected === value ? true : false} + > + {name} + + ) : ( + { + handleFilterClick(value); + }} + className="!w-full" + checked={selected === value ? true : false} + > + {name} + ); })}
diff --git a/components/molecules/CardHorizontalBarChart/card-horizontal-bar-chart.tsx b/components/molecules/CardHorizontalBarChart/card-horizontal-bar-chart.tsx index 0698fa037b..b8c627ff56 100644 --- a/components/molecules/CardHorizontalBarChart/card-horizontal-bar-chart.tsx +++ b/components/molecules/CardHorizontalBarChart/card-horizontal-bar-chart.tsx @@ -29,13 +29,15 @@ const CardHorizontalBarChart = ({ languageList, withDescription }: CardHorizonta // used this state to calculate thte percentage of each language const [percentage, setPercentage] = useState(0); - const [descriptText, setDescriptText] = useState(sortedLangArray[0].languageName); + const [descriptText, setDescriptText] = useState(sortedLangArray[0]?.languageName || "javascript"); const handleChangeDescriptText = (descriptText: string) => { setDescriptText(descriptText); }; useEffect(() => { + if (sortedLangArray.length === 0) return; + const totalSumOfFirstFivePercentage = sortedLangArray .slice(0, 4) .map((lang) => lang.percentageUsed) diff --git a/components/molecules/FilterCardSelect/filter-card-select.tsx b/components/molecules/FilterCardSelect/filter-card-select.tsx index d8eb969867..c928d9afee 100644 --- a/components/molecules/FilterCardSelect/filter-card-select.tsx +++ b/components/molecules/FilterCardSelect/filter-card-select.tsx @@ -8,10 +8,10 @@ import repoIcon from "../../../img/icons/repo.svg"; import Selector from "../../atoms/Selector/selector"; interface FilterCardSelectProps { - selected: string; - icon?: "topic" | "repo" | "org" | "contributor"; - options: string[]; - handleFilterClick: (filter: string) => void; + selected: string; + icon?: "topic" | "repo" | "org" | "contributor"; + options: string[]; + handleFilterClick: (filter: string) => void; } const icons = { @@ -33,8 +33,12 @@ const icons = { } }; -const FilterCardSelect: React.FC = ({ selected: filterName, icon = "topic", options, handleFilterClick }) => { - +const FilterCardSelect: React.FC = ({ + selected: filterName, + icon = "topic", + options, + handleFilterClick +}) => { const ref = useRef(null); const [isOpen, setIsOpen] = useState(false); const toggleFilter = () => { @@ -62,22 +66,26 @@ const FilterCardSelect: React.FC = ({ selected: filterNam
+ className={ + "inline-block py-1 border border-slate-300 outline-none hover:bg-slate-50 focus:ring-2 bg-slate-100 focus:ring-slate-300 rounded-lg cursor-pointer" + } + > - { isOpen && - } + {isOpen && ( + ({ name: option, value: option }))} + handleFilterClick={handleFilterClick} + selected={filterName} + /> + )}
); diff --git a/components/molecules/PullRequestTable/pull-request-table.tsx b/components/molecules/PullRequestTable/pull-request-table.tsx index 0a7589290b..4451e8c5a3 100644 --- a/components/molecules/PullRequestTable/pull-request-table.tsx +++ b/components/molecules/PullRequestTable/pull-request-table.tsx @@ -19,10 +19,11 @@ interface CardTableProps { repositories?: number[]; limit?: number; isHoverCard?: boolean; + range?: number; } -const PullRequestTable = ({ contributor, topic, repositories, limit, isHoverCard }: CardTableProps): JSX.Element => { - const { data, isLoading } = useContributorPullRequests(contributor, topic, repositories, limit); +const PullRequestTable = ({ contributor, topic, repositories, limit, isHoverCard, range }: CardTableProps): JSX.Element => { + const { data, isLoading } = useContributorPullRequests(contributor, topic, repositories, limit, range); return data.length > 0 ? ( <> diff --git a/components/molecules/RepoRow/repo-row.tsx b/components/molecules/RepoRow/repo-row.tsx index 93909c724f..cf963d52b5 100644 --- a/components/molecules/RepoRow/repo-row.tsx +++ b/components/molecules/RepoRow/repo-row.tsx @@ -24,6 +24,7 @@ import { getAvatarByUsername } from "lib/utils/github"; import useRepositoryPullRequests from "lib/hooks/api/useRepositoryPullRequests"; import getPullRequestsToDays from "lib/utils/get-prs-to-days"; import getPullRequestsContributors from "lib/utils/get-pr-contributors"; +import useStore from "lib/store"; interface RepoProps { repo: RepositoriesRows; @@ -92,7 +93,8 @@ const RepoRow = ({ repo, topic, userPage, selected, handleOnSelectRepo }: RepoPr const ownerAvatar = getAvatarByUsername(fullName.split("/")[0]); const { user } = useSupabaseAuth(); - const { data: repositoryPullRequests } = useRepositoryPullRequests(repo.full_name, 100); + const range = useStore((state) => state.range); + const { data: repositoryPullRequests } = useRepositoryPullRequests(repo.full_name, 100, range); const totalPrs = getTotalPrs(openPrsCount, mergedPrsCount, closedPrsCount, draftPrsCount); const prsMergedPercentage = getPercent(totalPrs, mergedPrsCount || 0); const spamPrsPercentage = getPrsSpam(totalPrs, spamPrsCount || 0); diff --git a/components/molecules/TeamMemberRow/team-member-row.tsx b/components/molecules/TeamMemberRow/team-member-row.tsx index 76593ace33..fdb0c84789 100644 --- a/components/molecules/TeamMemberRow/team-member-row.tsx +++ b/components/molecules/TeamMemberRow/team-member-row.tsx @@ -3,43 +3,76 @@ import { AiOutlineCaretDown } from "react-icons/ai"; import pendingImg from "img/icons/fallback-image-disabled-square.svg"; import { useState } from "react"; import Selector from "components/atoms/Selector/selector"; -import { TeamMemberData } from "../TeamMembersConfig/team-members-config"; +import { MemberAccess, TeamMemberData } from "../TeamMembersConfig/team-members-config"; interface TeamMemberRowProps extends TeamMemberData { className?: string; + onDelete: (memberId: string) => void; + onUpdate: (memberId: string, access: MemberAccess) => Promise | undefined; } -const mapRoleToText: Record = { +const mapRoleToText: Record = { + owner: "Owner", admin: "Admin", - editor: "can edit", - viewer: "can view", + edit: "can edit", + view: "can view", pending: "Pending" }; -const TeamMemberRow = ({ className, name, avatarUrl, role }: TeamMemberRowProps) => { - +const TeamMemberRow = ({ className, name, avatarUrl, access, email, onDelete, onUpdate, id }: TeamMemberRowProps) => { const [isMenuOpen, setIsMenuOpen] = useState(false); - const pending = role == "pending"; + const pending = access == "pending"; + const isOwner = access == "owner"; - const handleRoleChange = (role: string) => {}; + const handleRoleChange = async (role: string) => { + setIsMenuOpen(false); + if (role === "remove") { + onDelete(id); + } else { + onUpdate(id, role as MemberAccess); + } + }; - return( + return (
-
+
-

{name}

+

{name || email}

- {mapRoleToText[role]} {!pending && {setIsMenuOpen(!isMenuOpen);}} />} + {mapRoleToText[access]} + {!pending && ( + <> + {isOwner ? ( + + ) : ( + { + setIsMenuOpen(!isMenuOpen); + }} + /> + )} + + )}
- { !pending && isMenuOpen && ( - + {!pending && isMenuOpen && ( + )}
); - }; export default TeamMemberRow; diff --git a/components/molecules/TeamMembersConfig/team-members-config.tsx b/components/molecules/TeamMembersConfig/team-members-config.tsx index 6804b4ad7b..a54c358e2c 100644 --- a/components/molecules/TeamMembersConfig/team-members-config.tsx +++ b/components/molecules/TeamMembersConfig/team-members-config.tsx @@ -1,30 +1,90 @@ import Button from "components/atoms/Button/button"; import Search from "components/atoms/Search/search"; import TeamMemberRow from "../TeamMemberRow/team-member-row"; +import { useState } from "react"; +import { validateEmail } from "lib/utils/validate-email"; +import { useToast } from "lib/hooks/useToast"; interface TeamMembersConfigProps { className?: string; members: TeamMemberData[]; + onAddMember: (email: string) => Promise | undefined; + onDeleteMember: (memberId: string) => void; + onUpdateMember: (memberId: string, access: MemberAccess) => Promise; } +export type MemberAccess = "owner" | "pending" | "admin" | "edit" | "view"; export interface TeamMemberData { + id: string; + insight_id: number; + user_id?: number; name: string; + access: MemberAccess; avatarUrl: string; - role: "admin" | "editor" | "viewer" | "pending"; + email?: string; } -const TeamMembersConfig = ({ className, members }: TeamMembersConfigProps) => { +const TeamMembersConfig = ({ + className, + members, + onAddMember, + onDeleteMember, + onUpdateMember +}: TeamMembersConfigProps) => { + const [validInput, setValidInput] = useState(false); + const [email, setEmail] = useState(""); + const [loading, setLoading] = useState(false); + const { toast } = useToast(); + + const handleChange = async (value: string) => { + setEmail(value); + + setValidInput(!!validateEmail(value)); + }; + + const handleAddMember = async () => { + const memberExists = members.find((member) => member.email?.toLowerCase() === email.toLowerCase()); + + if (memberExists) { + toast({ description: "Member already exists", variant: "danger" }); + return; + } + setLoading(true); + const res = await onAddMember(email); + setLoading(false); + setEmail(""); + if (res) { + toast({ description: "Member invite sent successfully", variant: "success" }); + } else { + toast({ description: "An error occurred!", variant: "danger" }); + } + }; return ( -
-

Add Team Members

-
- - +
+

Add Team Members

+
+ handleChange(value)} + placeholder="Enter email address" + name="search" + className="flex-1 text-base" + /> +
- {members.map(member => ( - + {members.map((member) => ( + ))}
diff --git a/components/organisms/ContributorCard/contributor-card.tsx b/components/organisms/ContributorCard/contributor-card.tsx index db0beae842..bdcf9aa299 100644 --- a/components/organisms/ContributorCard/contributor-card.tsx +++ b/components/organisms/ContributorCard/contributor-card.tsx @@ -1,7 +1,5 @@ import { useState } from "react"; -import { useTopicContributorCommits } from "lib/hooks/useTopicContributorCommits"; - import Card from "components/atoms/Card/card"; import Text from "components/atoms/Typography/text"; import CardHorizontalBarChart, { @@ -12,19 +10,19 @@ import CardProfile from "components/molecules/CardProfile/card-profile"; import CardRepoList, { RepoList } from "components/molecules/CardRepoList/card-repo-list"; import PullRequestTable from "components/molecules/PullRequestTable/pull-request-table"; -/* - Use this hook in the Contributor Page componenttbecause it has all the mock data: - import useContributorCard from "lib/hooks/useContributorCard"; -*/ +import { useContributorPullRequestsChart } from "lib/hooks/useContributorPullRequestsChart"; +import color from "lib/utils/color.json"; +import { getAvatarByUsername } from "lib/utils/github"; +import useRepositories from "lib/hooks/api/useRepositories"; + +const colorKeys = Object.keys(color); + export interface ContributorObject { profile: { githubAvatar: string; githubName: string; - totalPRs: number; dateOfFirstPR: string; }; - repoList: RepoList[]; - languageList: LanguageObject[]; } interface ContributorCardProps { @@ -32,19 +30,40 @@ interface ContributorCardProps { contributor: ContributorObject; topic: string; repositories?: number[]; + range?: number; } -const ContributorCard = ({ className, contributor, topic, repositories }: ContributorCardProps) => { - const { profile, repoList, languageList } = contributor; +const ContributorCard = ({ className, contributor, topic, repositories, range }: ContributorCardProps) => { + const { profile } = contributor; const [showPRs, setShowPRs] = useState(false); - const { chart } = useTopicContributorCommits(profile.githubName, topic, repositories); + const { chart, data, meta } = useContributorPullRequestsChart(profile.githubName, topic, repositories, range); + const repoList: RepoList[] = Array.from(new Set(data.map(prData => prData.full_name))).map(repo => { + const [repoOwner, repoName] = repo.split("/"); + + return { + repoName: repoName, + repoIcon: getAvatarByUsername(repoOwner) + }; + }); + const repoIds = data.map(pr => pr.repo_id); + const { data: repoData } = useRepositories(repoIds); + const contributorLanguageList = Array.from(new Set(repoData.map(repo => repo.language).filter(language => !!language))); + const languageList: LanguageObject[] = contributorLanguageList + .map((language) => { + const preparedLanguageKey = colorKeys.find((key) => key.toLowerCase() === language.toLowerCase()); + + return { + languageName: preparedLanguageKey ? preparedLanguageKey.toLowerCase() : language, + percentageUsed: Math.round((1 / contributorLanguageList.length) * 100) + }; + }); return (
- +
@@ -52,10 +71,10 @@ const ContributorCard = ({ className, contributor, topic, repositories }: Contri
- + {showPRs ? ( - + ) : null}
diff --git a/components/organisms/ContributorProfilePage/contributor-profile-page.tsx b/components/organisms/ContributorProfilePage/contributor-profile-page.tsx index 087b8bda4b..06098e2794 100644 --- a/components/organisms/ContributorProfilePage/contributor-profile-page.tsx +++ b/components/organisms/ContributorProfilePage/contributor-profile-page.tsx @@ -8,7 +8,7 @@ import PullRequestTable from "components/molecules/PullRequestTable/pull-request import SkeletonWrapper from "components/atoms/SkeletonLoader/skeleton-wrapper"; import color from "lib/utils/color.json"; -import { useTopicContributorCommits } from "lib/hooks/useTopicContributorCommits"; +import { useContributorPullRequestsChart } from "lib/hooks/useContributorPullRequestsChart"; import { getRelativeDays } from "lib/utils/date-utils"; import Pill from "components/atoms/Pill/pill"; import getPercent from "lib/utils/get-percent"; @@ -17,6 +17,7 @@ import ContributorProfileTab from "../ContributorProfileTab/contributor-profile- import ProfileLanguageChart from "components/molecules/ProfileLanguageChart/profile-language-chart"; import useFollowUser from "lib/hooks/useFollowUser"; import useSupabaseAuth from "lib/hooks/useSupabaseAuth"; +import { getAvatarByUsername } from "lib/utils/github"; const colorKeys = Object.keys(color); interface PrObjectType { @@ -39,7 +40,6 @@ interface ContributorProfilePageProps { githubAvatar?: string; githubName: string; langList: string[]; - repoList: RepoList[]; recentContributionCount: number; user?: DbUser; prTotal: number; @@ -57,7 +57,6 @@ const ContributorProfilePage = ({ githubAvatar, githubName, langList, - repoList, openPrs, prTotal, loading, @@ -74,8 +73,15 @@ const ContributorProfilePage = ({ }); const { user: loggedInUser, signIn } = useSupabaseAuth(); + const { chart, data } = useContributorPullRequestsChart(githubName, "*", repositories); + const repoList: RepoList[] = Array.from(new Set(data.map(prData => prData.full_name))).map(repo => { + const [repoOwner, repoName] = repo.split("/"); - const { chart } = useTopicContributorCommits(githubName, "*", repositories); + return { + repoName: repoName, + repoIcon: getAvatarByUsername(repoOwner) + }; + }); const prsMergedPercentage = getPercent(prTotal, prMerged || 0); const { data: Follower, isError: followError, follow, unFollow } = useFollowUser(user?.login || ""); @@ -164,7 +170,7 @@ const ContributorProfilePage = ({
PRs opened - {openPrs ? ( + {openPrs >= 0 ? (
{openPrs} PRs @@ -190,7 +196,7 @@ const ContributorProfilePage = ({
Contributed Repos - {recentContributionCount ? ( + {recentContributionCount >= 0 ? (
{`${recentContributionCount} Repo${recentContributionCount > 1 ? "s" : ""}`} @@ -205,7 +211,7 @@ const ContributorProfilePage = ({
- +
diff --git a/components/organisms/ContributorProfileTab/contributor-profile-tab.tsx b/components/organisms/ContributorProfileTab/contributor-profile-tab.tsx index 057be6b686..8ad2620954 100644 --- a/components/organisms/ContributorProfileTab/contributor-profile-tab.tsx +++ b/components/organisms/ContributorProfileTab/contributor-profile-tab.tsx @@ -69,7 +69,7 @@ const ContributorProfileTab = ({ useEffect(() => { setInputVisible(highlights && highlights.length !== 0 ? true : false); - if (currentPathname) { + if (login && currentPathname) { router.push(`/user/${login}/${currentPathname}`); } }, [highlights]); @@ -121,7 +121,7 @@ const ContributorProfileTab = ({
{/* eslint-disable-next-line camelcase */} {highlights.map(({ id, title, highlight, url, created_at }) => ( -
+

{getFormattedDate(created_at)}

@@ -200,7 +200,7 @@ const ContributorProfileTab = ({
-
)}
-
+
Avg PRs velocity {prVelocity ? (
diff --git a/components/organisms/Contributors/contributors.tsx b/components/organisms/Contributors/contributors.tsx index cdaa167871..08e8ec3321 100644 --- a/components/organisms/Contributors/contributors.tsx +++ b/components/organisms/Contributors/contributors.tsx @@ -7,13 +7,11 @@ import TableHeader from "components/molecules/TableHeader/table-header"; import Select from "components/atoms/Select/custom-select"; import { calcDistanceFromToday } from "lib/utils/date-utils"; -import color from "lib/utils/color.json"; -import { useTopicContributions } from "lib/hooks/useTopicContributions"; import ContributorCard from "../ContributorCard/contributor-card"; import SkeletonWrapper from "components/atoms/SkeletonLoader/skeleton-wrapper"; - -const colorKeys = Object.keys(color); +import useContributors from "lib/hooks/api/useContributors"; +import { getAvatarByUsername } from "lib/utils/github"; interface ContributorProps { repositories?: number[]; @@ -23,41 +21,28 @@ const Contributors = ({ repositories }: ContributorProps): JSX.Element => { const router = useRouter(); const { filterName } = router.query; const topic = filterName as string; - const { data, setLimit, meta, setPage, page, isError, isLoading } = useTopicContributions(10, repositories); - const range = useStore((state) => state.range); const store = useStore(); + const range = useStore((state) => state.range); + const { data, meta, setPage, setLimit, isError, isLoading } = useContributors(10, repositories, range); + + const contributors = data.map(pr => { + return { + host_login: pr.author_login, + first_commit_time: pr.created_at + }; + }); const contributorArray = isError ? [] - : data?.map((contributor) => { - const timeSinceFirstCommit = calcDistanceFromToday(new Date(parseInt(contributor.first_commit_time))); - const contributorLanguageList = (contributor.langs || "").split(","); - const repoList = (contributor.recent_repo_list || "").split(",").map((repo) => { - const [repoOwner, repoName] = repo.split("/"); - - return { - repoName, - repoIcon: `https://www.github.com/${repoOwner ?? "github"}.png?size=460` - }; - }); - const languageList = contributorLanguageList.map((language) => { - const preparedLanguageKey = colorKeys.find((key) => key.toLowerCase() === language.toLowerCase()); - - return { - languageName: preparedLanguageKey ? preparedLanguageKey : language, - percentageUsed: Math.round((1 / contributorLanguageList.length) * 100) - }; - }); + : contributors.map((contributor) => { + const timeSinceFirstCommit = calcDistanceFromToday(new Date(contributor.first_commit_time)); return { profile: { - githubAvatar: `https://www.github.com/${contributor.host_login}.png?size=60`, + githubAvatar: getAvatarByUsername(contributor.host_login), githubName: contributor.host_login, - totalPRs: contributor.recent_pr_total, dateOfFirstPR: timeSinceFirstCommit - }, - languageList, - repoList + } }; }); @@ -83,6 +68,7 @@ const Contributors = ({ repositories }: ContributorProps): JSX.Element => { contributor={{ ...contributor }} topic={topic} repositories={repositories} + range={range} /> ))}
diff --git a/components/organisms/Dashboard/dashboard.tsx b/components/organisms/Dashboard/dashboard.tsx index 6dd5e1fbfa..0443d54d49 100644 --- a/components/organisms/Dashboard/dashboard.tsx +++ b/components/organisms/Dashboard/dashboard.tsx @@ -23,7 +23,7 @@ interface DashboardProps { const Dashboard = ({ repositories }: DashboardProps): JSX.Element => { const { data: insightsData, isLoading } = useInsights(repositories); - const { data: prData, isError: prError } = usePullRequests(undefined, repositories); + const { data: prData, meta: prMeta, isError: prError } = usePullRequests(undefined, repositories); const [showBots, setShowBots] = useState(false); const isMobile = useMediaQuery("(max-width:720px)"); const [prStateFilter, setPrStateFilter] = useState("all"); @@ -99,7 +99,7 @@ const Dashboard = ({ repositories }: DashboardProps): JSX.Element => { metricIncreases={compare1.allContributors - compare2.allContributors >= 0} increased={compare1.allContributors - compare2.allContributors >= 0} numChanged={humanizeNumber(Math.abs(compare1.allContributors - compare2.allContributors), "abbreviation")} - value={humanizeNumber(compare1.allContributors, "comma")} + value={humanizeNumber(prMeta.itemCount, "comma")} contributors={contributorData} isLoading={isLoading} /> diff --git a/components/organisms/InsightPage/InsightPage.tsx b/components/organisms/InsightPage/InsightPage.tsx index fb095e8e60..66a117481d 100644 --- a/components/organisms/InsightPage/InsightPage.tsx +++ b/components/organisms/InsightPage/InsightPage.tsx @@ -13,7 +13,7 @@ import RepoNotIndexed from "components/organisms/Repositories/repository-not-ind import DeleteInsightPageModal from "./DeleteInsightPageModal"; import useSupabaseAuth from "lib/hooks/useSupabaseAuth"; -import { getAvatarByUsername } from "lib/utils/github"; +import { getAvatarById, getAvatarByUsername } from "lib/utils/github"; import useStore from "lib/store"; import Error from "components/atoms/Error/Error"; import Search from "components/atoms/Search/search"; @@ -21,6 +21,8 @@ import { useDebounce } from "rooks"; import SuggestedRepositoriesList from "../SuggestedRepoList/suggested-repo-list"; import { RepoCardProfileProps } from "components/molecules/RepoCardProfile/repo-card-profile"; import { useToast } from "lib/hooks/useToast"; +import TeamMembersConfig, { TeamMemberData } from "components/molecules/TeamMembersConfig/team-members-config"; +import useInsightMembers from "lib/hooks/useInsightMembers"; enum RepoLookupError { Initial = 0, @@ -44,6 +46,25 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { receivedData = JSON.parse(router.query.selectedRepos as string); } + const { data, addMember, deleteMember, updateMember } = useInsightMembers(insight?.id || 0); + + const members = + data && + data.map((member) => ({ + ...member, + email: member.invitation_email, + avatarUrl: !!member.user_id ? getAvatarById(String(member.user_id)) : "" + })); + + const insightOwner: TeamMemberData = { + insight_id: Number(insight?.id), + email: String(insight?.user.email), + id: String(insight?.user.id), + name: String(insight?.user.name || insight?.user.login), + avatarUrl: getAvatarByUsername(String(insight?.user.login)), + access: "owner" + }; + // Loading States const [deleteLoading, setDeleteLoading] = useState(false); const [createLoading, setCreateLoading] = useState(false); @@ -350,7 +371,7 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { {/* insights.opensauced.pizza/pages/{username}/{`{pageId}`}/dashboard */}
-
+
Add Repositories @@ -400,6 +421,17 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => {
+ {edit && ( +
+ updateMember(id, access)} + onDeleteMember={deleteMember} + onAddMember={addMember} + members={[insightOwner, ...members]} + /> +
+ )} + {edit && (
diff --git a/components/organisms/Repositories/repositories.tsx b/components/organisms/Repositories/repositories.tsx index d3e5a87486..3466252313 100644 --- a/components/organisms/Repositories/repositories.tsx +++ b/components/organisms/Repositories/repositories.tsx @@ -10,12 +10,12 @@ import TableHeader from "components/molecules/TableHeader/table-header"; import useRepositories from "lib/hooks/api/useRepositories"; import useSupabaseAuth from "lib/hooks/useSupabaseAuth"; +import useStore from "lib/store"; import RepositoriesTable, { classNames, RepositoriesRows } from "../RepositoriesTable/repositories-table"; import RepoNotIndexed from "./repository-not-indexed"; import Checkbox from "components/atoms/Checkbox/checkbox"; import Button from "components/atoms/Button/button"; -import useStore from "lib/store"; interface RepositoriesProps { repositories?: number[]; @@ -36,7 +36,7 @@ const Repositories = ({ repositories }: RepositoriesProps): JSX.Element => { isLoading: repoListIsLoading, setPage, setLimit - } = useRepositories(repositories); + } = useRepositories(repositories, range); const filteredRepoNotIndexed = selectedFilter && !repoListIsLoading && !repoListIsError && repoListData.length === 0; const [selectedRepos, setSelectedRepos] = useState<DbRepo[]>([]); diff --git a/lib/hooks/api/useContributorPullRequests.ts b/lib/hooks/api/useContributorPullRequests.ts index 8f34507b4e..08d6514401 100644 --- a/lib/hooks/api/useContributorPullRequests.ts +++ b/lib/hooks/api/useContributorPullRequests.ts @@ -9,7 +9,7 @@ interface PaginatedResponse { readonly meta: Meta; } -const useContributorPullRequests = (contributor: string, topic: string, repoIds: number[] = [], limit = 8) => { +const useContributorPullRequests = (contributor: string, topic: string, repoIds: number[] = [], limit = 8, range = 30) => { const router = useRouter(); const { selectedFilter } = router.query; const filterQuery = getFilterQuery(selectedFilter); @@ -27,6 +27,8 @@ const useContributorPullRequests = (contributor: string, topic: string, repoIds: query.set("repoIds", repoIds.join(",")); } + query.set("range", `${range}`); + const baseEndpoint = `users/${contributor}/prs`; const endpointString = `${baseEndpoint}?${query.toString()}`; diff --git a/lib/hooks/api/useContributors.ts b/lib/hooks/api/useContributors.ts new file mode 100644 index 0000000000..336d593759 --- /dev/null +++ b/lib/hooks/api/useContributors.ts @@ -0,0 +1,69 @@ +import { useState } from "react"; +import useSWR, { Fetcher } from "swr"; +import { useRouter } from "next/router"; + +import publicApiFetcher from "lib/utils/public-api-fetcher"; +import getFilterQuery from "lib/utils/get-filter-query"; + +interface PaginatedResponse { + readonly data: DbRepoPR[]; + readonly meta: Meta; +} + +/** + * Fetch contributors based on pull requests. + * Replace with contributors API endpoint when available. + * + * @param intialLimit + * @param repoIds + * @param range + * @returns + */ +const useContributors = (intialLimit = 10, repoIds: number[] = [], range = 30) => { + const router = useRouter(); + const [page, setPage] = useState(1); + const [limit, setLimit] = useState(intialLimit); + const { filterName, selectedFilter } = router.query; + const topic = filterName as string; + const filterQuery = getFilterQuery(selectedFilter); + const query = new URLSearchParams(filterQuery); + + if (Number.isNaN(Number(topic))) { + query.set("topic", topic); + } + + if (page) { + query.set("page", `${page}`); + } + + if (limit) { + query.set("limit", `${limit}`); + } + + if (repoIds?.length > 0) { + query.set("repoIds", repoIds.join(",")); + } + + query.set("range", `${range}`); + + const baseEndpoint = "prs/search"; + const endpointString = `${baseEndpoint}?${query.toString()}`; + + const { data, error, mutate } = useSWR<PaginatedResponse, Error>( + endpointString, + publicApiFetcher as Fetcher<PaginatedResponse, Error> + ); + + return { + data: data?.data ?? [], + meta: data?.meta ?? { itemCount: 0, limit: 0, page: 0, hasNextPage: false, hasPreviousPage: false, pageCount: 0 }, + isLoading: !error && !data, + isError: !!error, + mutate, + page, + setPage, + setLimit + }; +}; + +export default useContributors; diff --git a/lib/hooks/api/usePullRequests.ts b/lib/hooks/api/usePullRequests.ts index 30a4f008bd..b17c104022 100644 --- a/lib/hooks/api/usePullRequests.ts +++ b/lib/hooks/api/usePullRequests.ts @@ -10,9 +10,10 @@ interface PaginatedResponse { readonly meta: Meta; } -const usePullRequests = (limit = 1000, repoIds: number[] = []) => { +const usePullRequests = (intialLimit = 1000, repoIds: number[] = [], range = 30) => { const router = useRouter(); const [page, setPage] = useState(1); + const [limit, setLimit] = useState(intialLimit); const { filterName, selectedFilter } = router.query; const topic = filterName as string; const filterQuery = getFilterQuery(selectedFilter); @@ -34,6 +35,8 @@ const usePullRequests = (limit = 1000, repoIds: number[] = []) => { query.set("repoIds", repoIds.join(",")); } + query.set("range", `${range}`); + const baseEndpoint = "prs/search"; const endpointString = `${baseEndpoint}?${query.toString()}`; @@ -49,7 +52,8 @@ const usePullRequests = (limit = 1000, repoIds: number[] = []) => { isError: !!error, mutate, page, - setPage + setPage, + setLimit }; }; diff --git a/lib/hooks/api/useRepositories.ts b/lib/hooks/api/useRepositories.ts index e8ab070d8c..376e3db55b 100644 --- a/lib/hooks/api/useRepositories.ts +++ b/lib/hooks/api/useRepositories.ts @@ -10,7 +10,7 @@ interface PaginatedResponse { readonly meta: Meta; } -const useRepositories = (repoIds: number[] = []) => { +const useRepositories = (repoIds: number[] = [], range = 30) => { const router = useRouter(); const [page, setPage] = useState(1); const [limit, setLimit] = useState(10); @@ -31,6 +31,10 @@ const useRepositories = (repoIds: number[] = []) => { query.set("limit", `${limit}`); } + if (range) { + query.set("range", `${range}`); + } + if (repoIds?.length > 0) { query.set("repoIds", repoIds.join(",")); } diff --git a/lib/hooks/api/useRepositoryPullRequests.ts b/lib/hooks/api/useRepositoryPullRequests.ts index beaf81d378..4cb033c1b4 100644 --- a/lib/hooks/api/useRepositoryPullRequests.ts +++ b/lib/hooks/api/useRepositoryPullRequests.ts @@ -8,7 +8,7 @@ interface PaginatedResponse { readonly meta: Meta; } -const useRepositoryPullRequests = (fullName: string, limit = 10) => { +const useRepositoryPullRequests = (fullName: string, limit = 10, range = 30) => { const router = useRouter(); const { filterName } = router.query; const topic = filterName as string; @@ -21,6 +21,7 @@ const useRepositoryPullRequests = (fullName: string, limit = 10) => { query.set("repo", fullName); query.set("page", "1"); query.set("limit", `${limit}`); + query.set("range", `${range}`); const baseEndpoint = "prs/search"; const endpointString = `${baseEndpoint}?${query.toString()}`; diff --git a/lib/hooks/useContributionsList.ts b/lib/hooks/useContributionsList.ts deleted file mode 100644 index c9a5289782..0000000000 --- a/lib/hooks/useContributionsList.ts +++ /dev/null @@ -1,31 +0,0 @@ -import useSWR from "swr"; -import useStore from "lib/store"; - -interface PaginatedContributorsResponse { - readonly data: DbContribution[]; - readonly meta: Meta; -} - -const useContributionsList = (repoId: string, limit = "", orderBy = "") => { - const range = useStore(state => state.range); - - //The endpoint for all Hacktoberfest contributions doesn't exist yet so will substitute this for now - const baseEndpoint = `repos/${repoId}/contributions`; - const limitQuery = `orderBy=last_commit_time${limit === "" ? limit : `&limit=${limit}`}`; - const orderByQuery = orderBy ? `&updated_at=${orderBy}` : ""; - const rangeQuery = range ? `&range=${range}` : ""; - const endpointString = `${baseEndpoint}?${limitQuery}${orderByQuery}${rangeQuery}`; - - - const { data, error, mutate } = useSWR<PaginatedContributorsResponse, Error>(repoId ? endpointString : null); - - return { - data: data?.data ?? [], - meta: data?.meta ?? { itemCount: 0 }, - isLoading: !error && !data, - isError: !!error, - mutate - }; -}; - -export {useContributionsList}; diff --git a/lib/hooks/useTopicContributorCommits.ts b/lib/hooks/useContributorPullRequestsChart.ts similarity index 52% rename from lib/hooks/useTopicContributorCommits.ts rename to lib/hooks/useContributorPullRequestsChart.ts index 498ba346df..d0baa81753 100644 --- a/lib/hooks/useTopicContributorCommits.ts +++ b/lib/hooks/useContributorPullRequestsChart.ts @@ -1,14 +1,8 @@ import { useEffect, useState } from "react"; -import useSWR from "swr"; -import { getCommitsLast30Days } from "lib/utils/get-recent-commits"; -import { useRouter } from "next/router"; -import getFilterQuery from "lib/utils/get-filter-query"; +import getPullRequestsToDays from "lib/utils/get-prs-to-days"; +import useContributorPullRequests from "./api/useContributorPullRequests"; -interface PaginatedTopicCommitResponse { - readonly data: DbRepoCommit[]; - readonly meta: Meta; -} -const useTopicContributorCommits = (contributor: string, topic: string, repoIds: number[] = []) => { +const useContributorPullRequestsChart = (contributor: string, topic: string, repoIds: number[] = [], range = 30) => { const lineChart = { xAxis: { type: "category", @@ -49,18 +43,11 @@ const useTopicContributorCommits = (contributor: string, topic: string, repoIds: }; const [chart, setChart] = useState(lineChart); - const router = useRouter(); - const baseEndpoint = `${topic}/${contributor}/commits`; - const { selectedFilter } = router.query; - const filterQuery = getFilterQuery(selectedFilter); - const reposQuery = repoIds.length > 0 ? `repoIds=${repoIds.join(",")}`: ""; - const endpointString = `${baseEndpoint}?${filterQuery.replace("&", "")}${reposQuery}`; - - const { data } = useSWR<PaginatedTopicCommitResponse, Error>(contributor ? endpointString : null); + const { data, meta } = useContributorPullRequests(contributor, topic, repoIds, 100, range); useEffect(() => { - if (data && Array.isArray(data.data)) { - const graphData = getCommitsLast30Days(data.data); + if (data && Array.isArray(data)) { + const graphData = getPullRequestsToDays(data); setChart((prevChart) => ({ ...prevChart, @@ -77,8 +64,10 @@ const useTopicContributorCommits = (contributor: string, topic: string, repoIds: }, [data]); return { - chart + chart, + data, + meta }; }; -export { useTopicContributorCommits }; +export { useContributorPullRequestsChart }; diff --git a/lib/hooks/useInsightMembers.ts b/lib/hooks/useInsightMembers.ts new file mode 100644 index 0000000000..66126acab3 --- /dev/null +++ b/lib/hooks/useInsightMembers.ts @@ -0,0 +1,82 @@ +import useSWR, { Fetcher } from "swr"; +import useSupabaseAuth from "./useSupabaseAuth"; +import publicApiFetcher from "lib/utils/public-api-fetcher"; +import { MemberAccess } from "components/molecules/TeamMembersConfig/team-members-config"; +interface PaginatedInsightMembers { + data: DbInsightMember[]; + meta: Meta; +} + +const useInsightMembers = (insightId: number) => { + const { sessionToken } = useSupabaseAuth(); + + const { data, error, mutate } = useSWR<PaginatedInsightMembers, Error>( + `user/insights/${insightId}/members`, + publicApiFetcher as Fetcher<PaginatedInsightMembers, Error> + ); + + const addMember = async (email: string) => { + const req = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/insights/${insightId}/members`, { + method: "POST", + headers: { + "Content-type": "application/json", + Authorization: `Bearer ${sessionToken}` + }, + body: JSON.stringify({ email }) + }); + + if (!req.ok) { + console.log(req.status, req.statusText); + return undefined; + } else { + mutate(); + return req.json(); + } + }; + + const updateMember = async (memberId: string, access: MemberAccess) => { + const req = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/insights/${insightId}/members/${memberId}`, { + method: "PATCH", + headers: { + "Content-type": "application/json", + Authorization: `Bearer ${sessionToken}` + }, + body: JSON.stringify({ access }) + }); + + if (!req.ok) { + console.log(req.status, req.statusText); + + return undefined; + } else { + mutate(); + return req.json(); + } + }; + + const deleteMember = async (memberId: string) => { + const req = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/insights/${insightId}/members/${memberId}`, { + method: "DELETE", + headers: { + "Content-type": "application/json", + Authorization: `Bearer ${sessionToken}` + } + }); + + if (req.ok) { + mutate(); + } + }; + + return { + data: data?.data ?? [], + isLoading: !error && !data, + isError: !!error, + mutate, + addMember, + updateMember, + deleteMember + }; +}; + +export default useInsightMembers; diff --git a/lib/hooks/useNav.ts b/lib/hooks/useNav.ts index 70fc357570..95c09e3411 100644 --- a/lib/hooks/useNav.ts +++ b/lib/hooks/useNav.ts @@ -1,12 +1,13 @@ import { useRouter } from "next/router"; +import useStore from "lib/store"; import useRepositories from "./api/useRepositories"; -import { useTopicContributions } from "./useTopicContributions"; - +import useContributors from "./api/useContributors"; const useNav = (repositories: number[] = []) => { const router = useRouter(); - const { meta: repoMetaData } = useRepositories(repositories); - const { meta: conMetaData } = useTopicContributions(10, repositories); + const range = useStore(state => state.range); + const { meta: repoMetaData } = useRepositories(repositories, range); + const { meta: conMetaData } = useContributors(10, repositories, range); const defaultTools = [ { diff --git a/lib/hooks/useRepoList.ts b/lib/hooks/useRepoList.ts index 9d73b5c8c5..6abb73cb99 100644 --- a/lib/hooks/useRepoList.ts +++ b/lib/hooks/useRepoList.ts @@ -1,5 +1,5 @@ const useRepoList = (repos: string) => { - return repos.split(",").map((repo) => { + return repos.split(",").filter(rpo => !!rpo).map((repo) => { const [repoOwner, repoName] = repo.split("/"); return { diff --git a/lib/hooks/useTopicContributions.ts b/lib/hooks/useTopicContributions.ts deleted file mode 100644 index 9129a97e67..0000000000 --- a/lib/hooks/useTopicContributions.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { useRouter } from "next/router"; -import { useState } from "react"; -import useSWR from "swr"; -import useStore from "lib/store"; -import getFilterQuery from "lib/utils/get-filter-query"; - -interface PaginatedContributorsResponse { - readonly data: DbContribution[]; - readonly meta: Meta; -} - -const useTopicContributions = (initialLimit = 10, repoIds: number[] = []) => { - const router = useRouter(); - const range = useStore(state => state.range); - const { filterName, selectedFilter } = router.query; - const topic = filterName as string; - const [page, setPage] = useState(1); - const [limit, setLimit] = useState(initialLimit); - - const baseEndpoint = `${topic}/contributions`; - const pageQuery = page ? `page=${page}` : ""; - const filterQuery = getFilterQuery(selectedFilter); - const limitQuery = limit ? `&limit=${limit}` : ""; - const reposQuery = repoIds.length > 0 ? `&repoIds=${repoIds.join(",")}`: ""; - const rangeQuery = range ? `&range=${range}` : ""; - const endpointString = `${baseEndpoint}?${pageQuery}${filterQuery}${limitQuery}${reposQuery}${rangeQuery}`; - - const { data, error, mutate } = useSWR<PaginatedContributorsResponse, Error>(topic ? endpointString : null); - - return { - data: data?.data ?? [], - meta: data?.meta ?? { itemCount: 0, limit: 0, page: 0, hasNextPage: false, hasPreviousPage: false, pageCount: 0 }, - isLoading: !error && !data, - isError: !!error, - mutate, - page, - setPage, - setLimit - }; -}; - -export { useTopicContributions }; diff --git a/lib/utils/get-prs-to-days.ts b/lib/utils/get-prs-to-days.ts index 512bb1c133..f3ae9b4d7a 100644 --- a/lib/utils/get-prs-to-days.ts +++ b/lib/utils/get-prs-to-days.ts @@ -5,7 +5,7 @@ interface GraphData { y: number; } -const getPullRequestsToDays = (pull_requests: DbRepoPR[]) => { +const getPullRequestsToDays = (pull_requests: DbRepoPR[], range = 30) => { const graphDays = pull_requests.reduce((days: { [name: string]: number }, curr: DbRepoPR) => { const day = differenceInDays(new Date(), new Date(curr.updated_at)); @@ -19,7 +19,7 @@ const getPullRequestsToDays = (pull_requests: DbRepoPR[]) => { }, {}); const days: GraphData[] = []; - for(let d=30;d>=0;d--) { + for(let d=range;d>=0;d--) { days.push({ x: d, y: graphDays[d] || 0 }); } diff --git a/next-types.d.ts b/next-types.d.ts index 3edb74d72b..dade5250a5 100644 --- a/next-types.d.ts +++ b/next-types.d.ts @@ -15,6 +15,7 @@ interface DbRepo { readonly spam_prs_count?: number; readonly pr_velocity_count?: number; readonly churnTotalCount?: number; + readonly language: string; } interface DbRepoPR { @@ -33,6 +34,7 @@ interface DbRepoPR { readonly additions: number; readonly deletions: number; readonly changed_files: number; + readonly repo_id: number; } interface DbFollowUser { @@ -98,6 +100,18 @@ interface DbInsight { readonly accepted_repo_total: number; } +interface DbInsightMember { + readonly id: string; + readonly insight_id: number; + readonly user_id: number; + readonly name: string; + readonly access: "pending" | "admin" | "edit" | "view"; + readonly created_at: string; + readonly updated_at: string; + readonly deleted_at: string; + readonly invitation_emailed_at: string; + readonly invitation_email: string; +} interface DbUserInsight { readonly id: number; readonly user_id: number; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 6ca77a83db..22fb7a58ad 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "@open-sauced/insights", - "version": "1.42.0", + "version": "1.43.0-beta.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@open-sauced/insights", - "version": "1.42.0", + "version": "1.43.0-beta.5", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 2e8d6a2b14..7aa1d52b6c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@open-sauced/insights", "description": "πŸ•The dashboard for open source discovery.", "keywords": [], - "version": "1.42.0", + "version": "1.43.0-beta.5", "author": "TED Vortex <ted.vortex@gmail.com>", "private": true, "license": "MIT", diff --git a/pages/hub/insights/[insightId]/accept.tsx b/pages/hub/insights/[insightId]/accept.tsx new file mode 100644 index 0000000000..2b9f0f2fbd --- /dev/null +++ b/pages/hub/insights/[insightId]/accept.tsx @@ -0,0 +1,85 @@ +import { useRouter } from "next/router"; + +import GitHubIcon from "img/icons/github-icon.svg"; + +import HubLayout from "layouts/hub"; +import Button from "components/atoms/Button/button"; +import Title from "components/atoms/Typography/title"; +import Text from "components/atoms/Typography/text"; + +import useSupabaseAuth from "lib/hooks/useSupabaseAuth"; +import useInsight from "lib/hooks/useInsight"; +import { useToast } from "lib/hooks/useToast"; +import Icon from "components/atoms/Icon/icon"; + +const AcceptMemberInvitePage = () => { + const { user, sessionToken, signIn } = useSupabaseAuth(); + const router = useRouter(); + const { toast } = useToast(); + const { insightId, id: inviteId } = router.query; + const id = insightId as string; + const { data: insight, isLoading: insightLoading, isError: insightError } = useInsight(id); + + async function acceptInvite() { + const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/insights/${insightId}/members/${inviteId}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${sessionToken}` + }, + body: JSON.stringify({ access: "view" }) + }); + + if (response.ok) { + router.push(`/pages/${user?.user_metadata.user_name}/${insightId}/dashboard`); + } else { + toast({ description: "An error occurred!", variant: "danger" }); + } + } + + if (insightLoading) { + return <>Loading</>; + } + + if (insightError) { + return <>An error occurred while loading this page</>; + } + + return ( + <section className="flex flex-col w-full py-4"> + <div className="flex flex-col justify-center gap-8"> + <div className="pb-6 border-b border-light-slate-8"> + <Title className="!text-2xl !leading-none mb-4" level={1}> + Insight Member Invitation - {insight?.name} + + + + An insight is a dashboard containing selected repositories that you and your team can get insights from. + +
+ +
+
+ {user ? ( + + ) : ( + + )} +
+
+
+ + ); +}; + +AcceptMemberInvitePage.PageLayout = HubLayout; +AcceptMemberInvitePage.SEO = { + title: "Accept Insight Member Invitation | Open Sauced Insights", + noindex: true +}; + +export default AcceptMemberInvitePage; diff --git a/pages/hub/insights/[insightId]/edit.tsx b/pages/hub/insights/[insightId]/edit.tsx index f3ab7388ef..7fa3db4c29 100644 --- a/pages/hub/insights/[insightId]/edit.tsx +++ b/pages/hub/insights/[insightId]/edit.tsx @@ -14,7 +14,7 @@ const EditInsightPage: WithPageLayout = () => { const { insightId } = router.query; const id = insightId as string; const { data: insight, isLoading: insightLoading, isError: insightError } = useInsight(id); - const insightRepos = insight?.repos.map(repo => repo.repo_id); + const insightRepos = insight?.repos.map((repo) => repo.repo_id); const { data: repos } = useRepositories(insightRepos); if (insightLoading) { @@ -29,13 +29,7 @@ const EditInsightPage: WithPageLayout = () => { return <>Unauthorized; } - return ( - - ); + return ; }; EditInsightPage.PageLayout = HubLayout; diff --git a/pages/user/[username]/index.tsx b/pages/user/[username]/index.tsx index e4bc1c6650..cfce7e3d0a 100644 --- a/pages/user/[username]/index.tsx +++ b/pages/user/[username]/index.tsx @@ -10,6 +10,7 @@ import SEO from "layouts/SEO/SEO"; import { supabase } from "lib/utils/supabase"; import dynamic from "next/dynamic"; import { getAvatarByUsername } from "lib/utils/github"; +import useContributorPullRequests from "lib/hooks/api/useContributorPullRequests"; // A quick fix to the hydration issue. Should be replaced with a real solution. // Slows down the page's initial client rendering as the component won't be loaded on the server. @@ -28,8 +29,10 @@ const Contributor: WithPageLayout = ({ username, user, ogIm const { data: contributor, isError: contributorError } = useSingleContributor(username); + const { data: contributorPRData, meta: contributorPRMeta } = useContributorPullRequests(username, "*", [], 100); const isError = contributorError; - const repoList = useRepoList(contributor[0]?.recent_repo_list || ""); + const repoList = useRepoList(Array.from(new Set(contributorPRData.map(prData => prData.full_name))).join(",")); + const mergedPrs = contributorPRData.filter(prData => prData.merged); const contributorLanguageList = (contributor[0]?.langs || "").split(","); const githubAvatar = getAvatarByUsername(username, 300); @@ -62,17 +65,16 @@ const Contributor: WithPageLayout = ({ username, user, ogIm
diff --git a/stories/atoms/selector.stories.tsx b/stories/atoms/selector.stories.tsx index c0e80237cc..c5ca9c70c9 100644 --- a/stories/atoms/selector.stories.tsx +++ b/stories/atoms/selector.stories.tsx @@ -8,6 +8,13 @@ const storyConfig = { export default storyConfig; +const options = [ + { name: "option1", value: "option1" }, + { name: "option2", value: "option2" }, + { name: "option3", value: "option3" }, + { name: "option4", value: "option4" } +]; + //Select Template const SelectTemplate: ComponentStory = (args) => ; @@ -15,12 +22,12 @@ export const Default = SelectTemplate.bind({}); export const CheckMarks = SelectTemplate.bind({}); Default.args = { - filterOptions: ["option1", "option2", "option3"], + filterOptions: options, selected: "option1" }; CheckMarks.args = { - filterOptions: ["option1", "option2", "option3"], + filterOptions: options, selected: "option1", variation: "check" }; diff --git a/stories/atoms/team-member-row.stories.tsx b/stories/atoms/team-member-row.stories.tsx index bb19612492..c5d7e700cb 100644 --- a/stories/atoms/team-member-row.stories.tsx +++ b/stories/atoms/team-member-row.stories.tsx @@ -20,23 +20,23 @@ Default.args = { className: "max-w-2xl", name: "John Doe", avatarUrl: "https://avatars.githubusercontent.com/u/7252105?v=4", - role: "admin" + access: "admin" }; Editor.args = { className: "max-w-2xl", name: "John Doe", avatarUrl: "https://avatars.githubusercontent.com/u/7252105?v=4", - role: "editor" + access: "edit" }; Viewer.args = { className: "max-w-2xl", name: "John Doe", avatarUrl: "https://avatars.githubusercontent.com/u/7252105?v=4", - role: "viewer" + access: "view" }; Pending.args = { className: "max-w-2xl", name: "John Doe", avatarUrl: "https://avatars.githubusercontent.com/u/7252105?v=4", - role: "pending" + access: "pending" }; diff --git a/stories/atoms/team-members-config.stories.tsx b/stories/atoms/team-members-config.stories.tsx index 38e11cda97..658627ac22 100644 --- a/stories/atoms/team-members-config.stories.tsx +++ b/stories/atoms/team-members-config.stories.tsx @@ -18,17 +18,23 @@ Default.args = { { name: "John Doe", avatarUrl: "https://avatars.githubusercontent.com/u/7252105?v=4", - role: "admin" + access: "admin", + id: "3", + insight_id: 3 }, { name: "John Cena", avatarUrl: "https://avatars.githubusercontent.com/u/7252105?v=4", - role: "editor" + access: "edit", + id: "4", + insight_id: 4 }, { name: "John Wick", avatarUrl: "https://avatars.githubusercontent.com/u/7252105?v=4", - role: "viewer" + access: "view", + id: "5", + insight_id: 5 } ] }; diff --git a/stories/organisms/contributor-card.stories.tsx b/stories/organisms/contributor-card.stories.tsx index 2328e00a90..2f369922b0 100644 --- a/stories/organisms/contributor-card.stories.tsx +++ b/stories/organisms/contributor-card.stories.tsx @@ -141,8 +141,6 @@ export const Default = ContributorCardTemplate.bind({}); Default.args = { contributor: { - profile: profile, - languageList: languageList, - repoList: repoList + profile: profile } };