+
{!isOwner && (
{
+ const [selectListOpen, setSelectListOpen] = useState(false);
+ const [selectedList, setSelectedList] = useState([]);
+ const { data } = useFetchAllLists();
+ const { data: contributor } = useFetchUser(username ?? "");
+ const { toast } = useToast();
+
+ const listOptions = data ? data.map((list) => ({ label: list.name, value: list.id })) : [];
+
+ const handleSelectList = (value: OptionKeys) => {
+ const isOptionSelected = selectedList.some((s) => s.value === value.value);
+ if (isOptionSelected) {
+ setSelectedList((prev) => prev.filter((s) => s.value !== value.value));
+ } else {
+ setSelectedList((prev) => [...prev, value]);
+ }
+ };
+
+ const handleAddToList = async () => {
+ if (selectedList.length > 0 && contributor) {
+ const listIds = selectedList.map((list) => list.value);
+ const response = Promise.all(listIds.map((listIds) => addListContributor(listIds, [contributor.id])));
+
+ response
+ .then((res) => {
+ toast({
+ description: `
+ You've added ${username} to ${selectedList.length} list${selectedList.length > 1 ? "s" : ""}!`,
+ variant: "success",
+ });
+ })
+ .catch((res) => {
+ const failedList = listOptions.filter((list) => res.some((r: any) => r.error?.list_id === list.value));
+ toast({
+ description: `
+ Failed to add ${username} to ${failedList[0].label} ${
+ failedList.length > 1 && `and ${failedList.length - 1} other lists`
+ } !
+ `,
+ variant: "danger",
+ });
+ });
+ }
+ };
+
+ useEffect(() => {
+ if (!selectListOpen && selectedList.length > 0) {
+ handleAddToList();
+ setSelectedList([]);
+ }
+ }, [selectListOpen]);
+
+ return (
+
+ You have no lists.
+
+ Create a list
+
+
+ }
+ className="w-10 px-4"
+ placeholder="Add to list"
+ options={listOptions}
+ selected={selectedList}
+ setSelected={setSelectedList}
+ handleSelect={(option) => handleSelectList(option)}
+ />
+ );
+};
+
export default ContributorProfileHeader;
diff --git a/components/molecules/HubContributorsHeader/hub-contributors-header.tsx b/components/molecules/HubContributorsHeader/hub-contributors-header.tsx
index 5e94ff3d3a..2fff8ac15e 100644
--- a/components/molecules/HubContributorsHeader/hub-contributors-header.tsx
+++ b/components/molecules/HubContributorsHeader/hub-contributors-header.tsx
@@ -3,6 +3,7 @@ import { FaPlus } from "react-icons/fa";
import clsx from "clsx";
import { FiGlobe } from "react-icons/fi";
+import { useEffect, useState } from "react";
import { useToast } from "lib/hooks/useToast";
import ListNameHeader from "components/atoms/ListNameHeader/list-name-header";
@@ -11,6 +12,8 @@ import SingleSelect from "components/atoms/Select/single-select";
import ToggleSwitch from "components/atoms/ToggleSwitch/toggle-switch";
import Button from "components/atoms/Button/button";
import Text from "components/atoms/Typography/text";
+import Search from "components/atoms/Search/search";
+import useDebounceTerm from "lib/hooks/useDebounceTerm";
// import Search from "components/atoms/Search/search";
interface ListHeaderProps {
@@ -25,6 +28,8 @@ interface ListHeaderProps {
onAddToList?: () => void;
onTitleChange?: (title: string) => void;
loading?: boolean;
+ onSearch: (searchTerm: string | undefined) => void;
+ searchResults?: DbUser[];
}
const HubContributorsHeader = ({
@@ -39,9 +44,17 @@ const HubContributorsHeader = ({
timezone,
setTimezoneFilter,
timezoneOptions,
+ onSearch,
}: ListHeaderProps): JSX.Element => {
const { toast } = useToast();
+ const [contributorSearch, setContributorSearch] = useState("");
+ const debouncedSearchTerm = useDebounceTerm(contributorSearch, 300);
+
+ useEffect(() => {
+ onSearch(contributorSearch);
+ }, [debouncedSearchTerm]);
+
return (
@@ -86,7 +99,15 @@ const HubContributorsHeader = ({
-
+
+
+ setContributorSearch(value)}
+ />
+
{
-
-
-
-
-
+ {(user?.user_metadata.sub && Number(user?.user_metadata.sub) === Number(insight.user_id)) ||
+ (!insight.is_featured && (
+
+
+
+
+
+ ))}
diff --git a/components/molecules/ListCard/list-card.tsx b/components/molecules/ListCard/list-card.tsx
index ed0b363dc1..925c6ee8bf 100644
--- a/components/molecules/ListCard/list-card.tsx
+++ b/components/molecules/ListCard/list-card.tsx
@@ -1,10 +1,8 @@
import React from "react";
import Link from "next/link";
import { MdOutlineArrowForwardIos } from "react-icons/md";
-import { FiPlus } from "react-icons/fi";
import { RiDeleteBinLine } from "react-icons/ri";
import Text from "components/atoms/Typography/text";
-import Tooltip from "components/atoms/Tooltip/tooltip";
import { useFetchListContributors } from "lib/hooks/useList";
import StackedAvatar, { Contributor } from "../StackedAvatar/stacked-avatar";
@@ -41,16 +39,6 @@ const ListCard = ({ list, handleOnDeleteClick }: ListCardProps) => {
-
-
-
{/* Delete button */}
diff --git a/components/molecules/RecommendedRepoCard/recommended-repo-card.tsx b/components/molecules/RecommendedRepoCard/recommended-repo-card.tsx
index 50e59fbaba..63108d3d58 100644
--- a/components/molecules/RecommendedRepoCard/recommended-repo-card.tsx
+++ b/components/molecules/RecommendedRepoCard/recommended-repo-card.tsx
@@ -52,7 +52,7 @@ const RecommendedRepoCard = ({ fullName, className }: RecommendedRepoCardProps):
diff --git a/components/molecules/TableRepositoryName/table-repository-name.tsx b/components/molecules/TableRepositoryName/table-repository-name.tsx
index 22a801bf66..6c962bc06d 100644
--- a/components/molecules/TableRepositoryName/table-repository-name.tsx
+++ b/components/molecules/TableRepositoryName/table-repository-name.tsx
@@ -1,7 +1,6 @@
import { StaticImageData } from "next/image";
import Avatar from "components/atoms/Avatar/avatar";
-import { truncateString } from "lib/utils/truncate-string";
-
+import Tooltip from "components/atoms/Tooltip/tooltip";
interface TableRepositoryNameProps {
avatarURL?: string | StaticImageData;
fullName: string;
@@ -26,12 +25,15 @@ const TableRepositoryName = ({ avatarURL, fullName }: TableRepositoryNameProps):
{/* Text */}
+
-
+
+
+
diff --git a/components/organisms/TopNav/top-nav.tsx b/components/organisms/TopNav/top-nav.tsx
index d8dfe30618..6e5a9dc14d 100644
--- a/components/organisms/TopNav/top-nav.tsx
+++ b/components/organisms/TopNav/top-nav.tsx
@@ -36,11 +36,18 @@ const Nav = ({ className, name = "Main" }: { className?: string; name?: string }
{router.pathname.split("/")[2] === "insights" ? (
) : (
)}
diff --git a/lib/hooks/api/useContributionsByProject.ts b/lib/hooks/api/useContributionsByProject.ts
new file mode 100644
index 0000000000..bc44b9f62d
--- /dev/null
+++ b/lib/hooks/api/useContributionsByProject.ts
@@ -0,0 +1,27 @@
+import useSWR, { Fetcher } from "swr";
+import publicApiFetcher from "lib/utils/public-api-fetcher";
+
+export const useContributionsByProject = ({
+ listId,
+ range,
+ initialData,
+}: {
+ listId: string;
+ range: number;
+ initialData?: DbProjectContributions[];
+}) => {
+ const { data, error } = useSWR
(
+ `lists/${listId}/stats/contributions-by-project?range=${range}`,
+ publicApiFetcher as Fetcher
+ // {
+ // fallbackData: {
+ // `lists/${listId}/stats/contributions-by-project?range=${range}`: initialData
+ // },
+ // }
+ );
+
+ return {
+ data,
+ error,
+ };
+};
diff --git a/lib/hooks/api/useContributorsByProject.ts b/lib/hooks/api/useContributorsByProject.ts
new file mode 100644
index 0000000000..18677a0214
--- /dev/null
+++ b/lib/hooks/api/useContributorsByProject.ts
@@ -0,0 +1,18 @@
+import useSWR, { Fetcher } from "swr";
+import { useState } from "react";
+import publicApiFetcher from "lib/utils/public-api-fetcher";
+
+export const useContributorsByProject = (listId: string, range: number) => {
+ const [repoId, setRepoId] = useState(null);
+ const { data, error } = useSWR(
+ `lists/${listId}/stats/top-project-contributions-by-contributor?repo_id=${repoId}&range=${range}`,
+ publicApiFetcher as Fetcher
+ );
+
+ return {
+ data,
+ error,
+ setRepoId,
+ repoId,
+ };
+};
diff --git a/lib/hooks/api/useMostActiveContributors.ts b/lib/hooks/api/useMostActiveContributors.ts
index 38efc5a2cb..d3f998e407 100644
--- a/lib/hooks/api/useMostActiveContributors.ts
+++ b/lib/hooks/api/useMostActiveContributors.ts
@@ -20,16 +20,15 @@ const useMostActiveContributors = ({
listId,
initData,
intialLimit = 20,
- initialRange = 30,
+ range = 30,
defaultContributorType = "all",
}: {
listId: string;
initData?: ContributorStat[];
intialLimit?: number;
- initialRange?: number;
+ range?: number;
defaultContributorType?: ContributorType;
}) => {
- const [range, setRange] = useState(initialRange);
const [limit, setLimit] = useState(intialLimit);
const [contributorType, setContributorType] = useState(defaultContributorType);
@@ -52,7 +51,6 @@ const useMostActiveContributors = ({
data,
isLoading: !error && !data,
isError: !!error,
- setRange,
contributorType,
setContributorType,
};
diff --git a/lib/hooks/useFetchAllContributors.ts b/lib/hooks/useFetchAllContributors.ts
index 19b6ab0938..6fcf77feb3 100644
--- a/lib/hooks/useFetchAllContributors.ts
+++ b/lib/hooks/useFetchAllContributors.ts
@@ -15,6 +15,7 @@ type queryObj = {
pr_velocity?: string;
timezone?: string;
initialLimit?: number;
+ contributor?: string;
};
const useFetchAllContributors = (query: queryObj, config?: SWRConfiguration) => {
@@ -37,6 +38,9 @@ const useFetchAllContributors = (query: queryObj, config?: SWRConfiguration) =>
if (query.timezone) {
urlQuery.set("timezone", `${query.timezone}`);
}
+ if (query.contributor) {
+ urlQuery.set("contributor", `${query.contributor}`);
+ }
if (limit) {
urlQuery.set("limit", `${limit}`);
}
diff --git a/lib/hooks/useList.ts b/lib/hooks/useList.ts
index c1cb56cdb6..83caeb60a9 100644
--- a/lib/hooks/useList.ts
+++ b/lib/hooks/useList.ts
@@ -2,6 +2,7 @@ import useSWR, { Fetcher } from "swr";
import { useState } from "react";
import publicApiFetcher from "lib/utils/public-api-fetcher";
+import { supabase } from "lib/utils/supabase";
interface PaginatedListResponse {
data: DbUserList[];
@@ -69,6 +70,42 @@ const useFetchListContributors = (id: string, range = 30) => {
};
};
+const addListContributor = async (listId: string, contributors: number[]) => {
+ const sessionResponse = await supabase.auth.getSession();
+ const sessionToken = sessionResponse?.data.session?.access_token;
+
+ try {
+ const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/lists/${listId}/contributors`, {
+ method: "POST",
+ body: JSON.stringify({ contributors }),
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${sessionToken}`,
+ },
+ });
+
+ if (response.ok) {
+ const data = await response.json();
+ return {
+ data,
+ error: null,
+ };
+ } else {
+ const error = await response.json();
+ return {
+ data: null,
+ error: { message: error.message, listId },
+ };
+ }
+ } catch (error: any) {
+ console.log(error);
+ return {
+ data: null,
+ error: { message: error.message, listId },
+ };
+ }
+};
+
const useList = (listId: string) => {
const { data, error, mutate } = useSWR(`lists/${listId}`, publicApiFetcher as Fetcher);
@@ -80,4 +117,4 @@ const useList = (listId: string) => {
};
};
-export { useList, useFetchAllLists, useFetchListContributors };
+export { useList, useFetchAllLists, useFetchListContributors, addListContributor };
diff --git a/lib/store/index.ts b/lib/store/index.ts
index 7beeabc61a..c6059aec23 100644
--- a/lib/store/index.ts
+++ b/lib/store/index.ts
@@ -12,6 +12,7 @@ const initialState: GlobalStateInterface = {
userId: null,
hasReports: false,
openSearch: false,
+ dismissFeaturedInsights: false,
};
interface AppStore extends GlobalStateInterface {
@@ -33,6 +34,7 @@ interface AppStore extends GlobalStateInterface {
setUserId: (userId?: number | null) => void;
setHasReports: (hasReports: boolean) => void;
setOpenSearch: (openSearch: boolean) => void;
+ setDismissFeaturedInsights: () => void;
}
const store = create()((set) => ({
@@ -54,7 +56,8 @@ const store = create()((set) => ({
setProviderToken: (providerToken?: string | null) => set((state) => ({ ...state, providerToken })),
setUserId: (userId?: number | null) => set((state) => ({ ...state, userId })),
setHasReports: (hasReports: boolean) => set((state) => ({ ...state, hasReports })),
- setOpenSearch: (openSearch: boolean) => set(() => ({ openSearch: openSearch })),
+ setOpenSearch: (openSearch: boolean) => set((state) => ({ ...state, openSearch: openSearch })),
+ setDismissFeaturedInsights: () => set((state) => ({ ...state, dismissFeaturedInsights: true })),
}));
export default store;
diff --git a/lib/utils/color-utils.ts b/lib/utils/color-utils.ts
new file mode 100644
index 0000000000..083e0bb1d7
--- /dev/null
+++ b/lib/utils/color-utils.ts
@@ -0,0 +1,26 @@
+export function stringToHSLAColor({
+ id,
+ saturation = 90,
+ lightness = 48,
+ alpha = 1,
+}: {
+ id: string;
+ saturation?: number;
+ lightness?: number;
+ alpha?: number;
+}) {
+ // Ensure valid values for saturation and lightness (0-100) and alpha (0-1)
+ saturation = Math.min(Math.max(saturation, 0), 100);
+ lightness = Math.min(Math.max(lightness, 0), 100);
+ alpha = Math.min(Math.max(alpha, 0), 1);
+
+ // Use a simple hashing algorithm to generate H, S, and L values
+ let hash = 0;
+ for (let i = 0; i < id.length; i++) {
+ hash = id.charCodeAt(i) + ((hash << 5) - hash);
+ }
+
+ const h = ((hash % 360) + 360) % 360; // Ensure H value is between 0 and 360
+
+ return `hsla(${h}, ${saturation}%, ${lightness}%, ${alpha})`;
+}
diff --git a/lib/utils/nivo-utils.ts b/lib/utils/nivo-utils.ts
new file mode 100644
index 0000000000..a0029df93a
--- /dev/null
+++ b/lib/utils/nivo-utils.ts
@@ -0,0 +1,18 @@
+import { SpringValue, to } from "@react-spring/web";
+
+/**
+ There are several utility functions in the @nico/* packages that are ESM only. If you are
+ unable to dynamically import them for usage in your project, you can copy the source code
+here and use this instead.
+
+You'll know if you need to do this if you see an error like this:
+
+ Error: require() of ES Module /Users/nicktaylor/dev/work/app/node_modules/@nivo/treemap/node_modules/d3-color/src/index.js from /Users/nicktaylor/dev/work/app/node_modules/@nivo/treemap/node_modules/@nivo/colors/dist/nivo-colors.cjs.js not supported.
+Instead change the require of index.js in /Users/nicktaylor/dev/work/app/node_modules/@nivo/treemap/node_modules/@nivo/colors/dist/nivo-colors.cjs.js to a dynamic import() which is available in all CommonJS modules.
+
+**/
+
+// See https://github.com/plouc/nivo/blob/master/packages/treemap/src/transitions.ts#L6-L7
+export function htmlNodeTransform(x: SpringValue, y: SpringValue) {
+ return to([x, y], (x, y) => `translate(${x}px, ${y}px)`);
+}
diff --git a/netlify.toml b/netlify.toml
index 5726d8995a..9ba4c89c67 100644
--- a/netlify.toml
+++ b/netlify.toml
@@ -26,3 +26,9 @@ deno_import_map = "./netlify/edge-functions/deno.json"
to = "https://app.opensauced.pizza/:splat"
status = 301
force = true
+
+[[redirects]]
+ from = "https://beta.insights.opensauced.pizza/*"
+ to = "https://beta.app.opensauced.pizza/:splat"
+ status = 301
+ force = true
diff --git a/next-types.d.ts b/next-types.d.ts
index 32c25bee7f..0399ae57dc 100644
--- a/next-types.d.ts
+++ b/next-types.d.ts
@@ -190,6 +190,7 @@ interface DbUserInsight {
readonly name: string;
readonly is_public: boolean;
readonly is_favorite: boolean;
+ readonly is_featured: boolean;
readonly short_code: string;
readonly created_at: string;
readonly updated_at: string;
@@ -231,6 +232,7 @@ interface DbUser {
readonly linkedin_url: string;
readonly discord_url: string;
readonly notification_count: number;
+ readonly insights_count: number;
readonly languages: { [lang]: number };
readonly first_opened_pr_at: string;
readonly followers_count: number;
@@ -367,3 +369,18 @@ interface DbListContributorStat {
commits: number;
prsCreated: number;
}
+interface DbProjectContributions {
+ org_id: string;
+ project_id: string;
+ repo_id: number;
+ contributions: number;
+}
+
+interface DBProjectContributor {
+ login: string;
+ commits: number;
+ prs_created: number;
+ prs_reviewed: number;
+ issues_created: number;
+ comments: number;
+}
diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json
index 1638919b9a..fde1f70287 100644
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -1,12 +1,12 @@
{
"name": "@open-sauced/insights",
- "version": "1.68.0",
+ "version": "1.69.0-beta.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@open-sauced/insights",
- "version": "1.68.0",
+ "version": "1.69.0-beta.8",
"hasInstallScript": true,
"license": "Apache 2.0",
"dependencies": {
@@ -54,7 +54,7 @@
"next": "^13.4.7",
"octokit": "^2.0.14",
"path-to-regexp": "^6.2.1",
- "posthog-js": "^1.57.2",
+ "posthog-js": "^1.83.0",
"react": "^18.2.0",
"react-csv": "^2.2.2",
"react-day-picker": "^8.7.1",
@@ -43427,12 +43427,11 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
"node_modules/posthog-js": {
- "version": "1.57.2",
- "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.57.2.tgz",
- "integrity": "sha512-ER4gkYZasrd2Zwmt/yLeZ5G/nZJ6tpaYBCpx3CvocDx+3F16WdawJlYMT0IyLKHXDniC5+AsjzFd6fi8uyYlJA==",
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.83.0.tgz",
+ "integrity": "sha512-3dp/yNbRCYsOgvJovFUMCLv9/KxnwmGBy5Ft27Q7/rbW++iJXVR64liX7i0NrXkudjoL9j1GW1LGh84rV7kv8Q==",
"dependencies": {
- "fflate": "^0.4.1",
- "rrweb-snapshot": "^1.1.14"
+ "fflate": "^0.4.1"
}
},
"node_modules/prelude-ls": {
@@ -44717,11 +44716,6 @@
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
- "node_modules/rrweb-snapshot": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-1.1.14.tgz",
- "integrity": "sha512-eP5pirNjP5+GewQfcOQY4uBiDnpqxNRc65yKPW0eSoU1XamDfc4M8oqpXGMyUyvLyxFDB0q0+DChuxxiU2FXBQ=="
- },
"node_modules/rtl-css-js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz",
diff --git a/package.json b/package.json
index 06c9f22020..8b64244618 100644
--- a/package.json
+++ b/package.json
@@ -2,14 +2,14 @@
"name": "@open-sauced/insights",
"description": "πThe dashboard for open source discovery.",
"keywords": [],
- "version": "1.68.0",
+ "version": "1.69.0-beta.8",
"author": "Brian Douglas ",
"private": true,
"license": "Apache 2.0",
"files": [
"build/**/*"
],
- "homepage": "https://insights.opensauced.pizza",
+ "homepage": "https://app.opensauced.pizza",
"bugs": {
"url": "https://github.com/open-sauced/insights/issues"
},
@@ -90,7 +90,7 @@
"next": "^13.4.7",
"octokit": "^2.0.14",
"path-to-regexp": "^6.2.1",
- "posthog-js": "^1.57.2",
+ "posthog-js": "^1.83.0",
"react": "^18.2.0",
"react-csv": "^2.2.2",
"react-day-picker": "^8.7.1",
diff --git a/pages/_app.tsx b/pages/_app.tsx
index cd174f52c8..4a3827e9dd 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -64,7 +64,7 @@ function MyApp({ Component, pageProps }: ComponentWithPageLayout) {
const interval = setInterval(() => {
chatButton = document.getElementById("sitegpt-chat-icon");
if (chatButton) {
- if (hostname !== "insights.opensauced.pizza") {
+ if (hostname !== "app.opensauced.pizza") {
chatButton.style.display = "none";
}
if (router.asPath === "/feed" && isMobile) {
diff --git a/pages/hub/insights/index.tsx b/pages/hub/insights/index.tsx
index 7593dc7937..a7dd79d666 100644
--- a/pages/hub/insights/index.tsx
+++ b/pages/hub/insights/index.tsx
@@ -1,4 +1,6 @@
+import { useEffect } from "react";
import Link from "next/link";
+import { useRouter } from "next/router";
import clsx from "clsx";
import InsightRow from "components/molecules/InsightRow/insight-row";
@@ -10,12 +12,67 @@ import HubLayout from "layouts/hub";
import { WithPageLayout } from "interfaces/with-page-layout";
import useUserInsights from "lib/hooks/useUserInsights";
import useSupabaseAuth from "lib/hooks/useSupabaseAuth";
+import useStore from "lib/store";
+import useSession from "lib/hooks/useSession";
+import { useToast } from "lib/hooks/useToast";
+import Text from "components/atoms/Typography/text";
const InsightsHub: WithPageLayout = () => {
+ const router = useRouter();
const { user } = useSupabaseAuth();
-
+ const store = useStore();
+ const dismissFeaturedInsights = useStore((store) => store.dismissFeaturedInsights);
+ const { toast } = useToast();
+ const { session } = useSession(true);
const { data, meta, isError, isLoading, setPage } = useUserInsights();
+ function handleView() {
+ const insight = data.slice(0, 1).shift();
+ router.push(`/pages/${user?.user_metadata.user_name}/${insight!.id}/dashboard`);
+ }
+
+ function openInsightToast() {
+ const toaster = toast({
+ description: "Welcome to your Insights Hub!",
+ variant: "success",
+ action: (
+
+
+
+ We've included a featured Insight Page for you to test out. You can also create your own to get
+ insights on repositories.
+
+
+
+
+
+
+
+ ),
+ });
+
+ store.setDismissFeaturedInsights();
+ }
+
+ useEffect(() => {
+ if (session && session.insights_count === 0 && !dismissFeaturedInsights) {
+ openInsightToast();
+ }
+ }, [session, user]);
+
return (
<>
diff --git a/pages/hub/lists/find.tsx b/pages/hub/lists/find.tsx
index 3a93af6fc8..aaa5e87a5e 100644
--- a/pages/hub/lists/find.tsx
+++ b/pages/hub/lists/find.tsx
@@ -87,10 +87,12 @@ const NewListCreationPage = ({ initialData, timezoneOption }: NewListCreationPag
const [title, setTitle] = useState("");
const [selectedContributors, setSelectedContributors] = useState([]);
const [selectedTimezone, setSelectedTimezone] = useState(undefined);
+ const [contributor, setContributor] = useState(undefined);
const [isPublic, setIsPublic] = useState(false);
const { data, meta, isLoading, setLimit, setPage } = useFetchAllContributors(
{
timezone: selectedTimezone,
+ contributor,
},
{
fallbackData: initialData,
@@ -217,6 +219,12 @@ const NewListCreationPage = ({ initialData, timezoneOption }: NewListCreationPag
setSelectedTimezone(selected);
};
+ function onSearch(searchTerm: string | undefined) {
+ if (!searchTerm || searchTerm.length >= 3) {
+ setContributor(searchTerm);
+ }
+ }
+
return (
-
+
0 && selectedContributors.length === meta.limit}
handleOnSelectAllContributor={handleOnSelectAllChecked}
diff --git a/pages/lists/[listId]/activity.tsx b/pages/lists/[listId]/activity.tsx
index f1a1d97602..c76106b34d 100644
--- a/pages/lists/[listId]/activity.tsx
+++ b/pages/lists/[listId]/activity.tsx
@@ -1,14 +1,18 @@
import { createPagesServerClient } from "@supabase/auth-helpers-nextjs";
import { GetServerSidePropsContext } from "next";
+import { useState } from "react";
+import { NodeMouseEventHandler } from "@nivo/treemap";
import Error from "components/atoms/Error/Error";
import { fetchApiData, validateListPath } from "helpers/fetchApiData";
import ListPageLayout from "layouts/lists";
import MostActiveContributorsCard, {
ContributorStat,
} from "components/molecules/MostActiveContributorsCard/most-active-contributors-card";
-
import useMostActiveContributors from "lib/hooks/api/useMostActiveContributors";
import ClientOnly from "components/atoms/ClientOnly/client-only";
+import { ContributionsTreemap } from "components/molecules/ContributionsTreemap/contributions-treemap";
+import { useContributorsByProject } from "lib/hooks/api/useContributorsByProject";
+import { useContributionsByProject } from "lib/hooks/api/useContributionsByProject";
interface ContributorListPageProps {
list?: DBList;
@@ -17,6 +21,7 @@ interface ContributorListPageProps {
activityData: {
contributorStats: { data: ContributorStat[]; meta: Meta };
topContributor: ContributorStat;
+ projectData: DbProjectContributions[];
};
}
@@ -33,6 +38,7 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
{ data, error: contributorListError },
{ data: list, error },
{ data: mostActiveData, error: mostActiveError },
+ { data: projectData, error: projectError },
] = await Promise.all([
fetchApiData>({
path: `lists/${listId}/contributors?limit=1`,
@@ -45,6 +51,11 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
bearerToken,
pathValidator: validateListPath,
}),
+ fetchApiData({
+ path: `lists/${listId}/stats/contributions-by-project?range=${range}`,
+ bearerToken,
+ pathValidator: validateListPath,
+ }),
]);
if (error?.status === 404 || error?.status === 401) {
@@ -61,21 +72,53 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
activityData: {
contributorStats: mostActiveData,
topContributor: mostActiveData?.data?.length ? mostActiveData.data[0] : null,
+ projectData: projectData ?? [],
},
},
};
};
const ListActivityPage = ({ list, numberOfContributors, isError, activityData }: ContributorListPageProps) => {
+ const [range, setRange] = useState(30);
const isOwner = false;
const {
data: contributorStats,
isLoading,
isError: isMostActiveError,
- setRange,
setContributorType,
contributorType,
- } = useMostActiveContributors({ listId: list!.id, initData: activityData.contributorStats.data });
+ } = useMostActiveContributors({ listId: list!.id, initData: activityData.contributorStats.data, range });
+
+ const { setRepoId, error, data: projectContributionsByUser, repoId } = useContributorsByProject(list!.id, range);
+
+ const { data: projectData, error: projectDataError } = useContributionsByProject({
+ listId: list!.id,
+ range,
+ initialData: activityData.projectData,
+ });
+
+ const onHandleClick: NodeMouseEventHandler
)}
diff --git a/stories/atoms/text-input.stories.tsx b/stories/atoms/text-input.stories.tsx
index c26a648b5a..e09b6d6a3b 100644
--- a/stories/atoms/text-input.stories.tsx
+++ b/stories/atoms/text-input.stories.tsx
@@ -35,7 +35,7 @@ WithDescriptionText.args = {
disabled: false,
autoFocus: true,
borderless: false,
- descriptionText: "insights.opensauced.pizza/statelyai/slug",
+ descriptionText: "app.opensauced.pizza/statelyai/slug",
};
IsInvalid.args = {
diff --git a/stories/atoms/typography-wrapper.stories.tsx b/stories/atoms/typography-wrapper.stories.tsx
index b1aec7df18..bed4633d36 100644
--- a/stories/atoms/typography-wrapper.stories.tsx
+++ b/stories/atoms/typography-wrapper.stories.tsx
@@ -137,8 +137,8 @@ const TypographyTemplate: StoryFn
= (args) => (
There are other elements we need to style
I almost forgot to mention links, like{" "}
- this link to the OpenSauced Insights website. We almost made them
- blue but that's so yesterday, so we went with dark gray, feels edgier.
+ this link to the OpenSauced Insights website. We almost made them blue
+ but that's so yesterday, so we went with dark gray, feels edgier.
We even included table styles, check it out:
diff --git a/stories/molecules/treemap-prototype/contributor-node.tsx b/stories/molecules/treemap-prototype/contributor-node.tsx
index ca5ffc1098..4cad726a36 100644
--- a/stories/molecules/treemap-prototype/contributor-node.tsx
+++ b/stories/molecules/treemap-prototype/contributor-node.tsx
@@ -1,7 +1,9 @@
import { memo } from "react";
import { animated } from "@react-spring/web";
-import { NodeProps, htmlNodeTransform } from "@nivo/treemap";
import { getAvatarByUsername } from "lib/utils/github";
+import { htmlNodeTransform } from "lib/utils/nivo-utils";
+import { stringToHSLAColor } from "lib/utils/color-utils";
+import type { NodeProps } from "@nivo/treemap";
const NonMemoizedContributorNode = ({
node,
@@ -14,44 +16,35 @@ const NonMemoizedContributorNode = labelSkipSize);
const avatarURL = getAvatarByUsername(node.id);
+ const color = stringToHSLAColor({ id: node.id });
return (
{showLabel && (
({
node,
@@ -11,34 +13,28 @@ const NonMemoizedSpecialNode = ({
}: NodeProps) => {
const showLabel =
enableLabel && node.isLeaf && (labelSkipSize === 0 || Math.min(node.width, node.height) > labelSkipSize);
+ const [fullRepoName] = node.id.split(":");
+ node.color = stringToHSLAColor({ id: node.id });
return (
({
/>
{showLabel && (
({
}}
>
-
{node.id}
+
{fullRepoName}
{node.label}
diff --git a/tests/lib/utils/github.test.ts b/tests/lib/utils/github.test.ts
index 4bdb27b7b6..204b46db86 100644
--- a/tests/lib/utils/github.test.ts
+++ b/tests/lib/utils/github.test.ts
@@ -77,7 +77,7 @@ describe("[lib] github methods", () => {
});
});
it("Should return an object with isValidUrl set to false", () => {
- const result = generateRepoParts("https://insights.opensauced.pizza/hub/insights/new");
+ const result = generateRepoParts("https://app.opensauced.pizza/hub/insights/new");
expect(result.isValidUrl).toBeFalsy();
});
it("Should return an object with isValidUrl set to false", () => {