diff --git a/src/constants.ts b/src/constants.ts index 6c0e6105..25dedb79 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -8,6 +8,7 @@ export const OPEN_SAUCED_INSIGHTS_DOMAIN = "insights.opensauced.pizza"; export const OPEN_SAUCED_USERS_ENDPOINT = "https://api.opensauced.pizza/v1/users"; export const OPEN_SAUCED_REPOS_ENDPOINT = "https://api.opensauced.pizza/v1/repos"; export const OPEN_SAUCED_SESSION_ENDPOINT = "https://api.opensauced.pizza/v1/auth/session"; +export const OPEN_SAUCED_USER_INSIGHTS_ENDPOINT = "https://api.opensauced.pizza/v1/user/insights"; // GitHub constants/selectors export const GITHUB_PROFILE_MENU_SELECTOR = ".p-nickname.vcard-username.d-block"; diff --git a/src/content-scripts/components/InsightsSelectDropdown/InsightsSelectDropdown.ts b/src/content-scripts/components/InsightsSelectDropdown/InsightsSelectDropdown.ts new file mode 100644 index 00000000..d5fce61f --- /dev/null +++ b/src/content-scripts/components/InsightsSelectDropdown/InsightsSelectDropdown.ts @@ -0,0 +1,115 @@ +import "../../content-scripts.css"; +import { createHtmlElement } from "../../../utils/createHtmlElement"; +import { getAuthToken } from "../../../utils/checkAuthentication"; +import { getUserInsightsData, getRepoData, updateInsight } from "../../../utils/fetchOpenSaucedApiData"; +import { IInsight } from "../../../ts/InsightDto"; + +export const InsightsSelectDropdown = async (ownerName: string, repoName: string) => { + const insightsDropdown = createHtmlElement("div", { + className: "SelectMenu cursor-default right-0 mt-1 hidden text-inherit", + innerHTML: ` +
+
+
Add Repo To Insights
+ + +
+
+
+ + `, + }); + + const userToken = await getAuthToken(); + const userInsightsData = await getUserInsightsData(userToken); + const currentRepoData = await getRepoData(ownerName, repoName); + + const userInsights = userInsightsData.data as any[]; + + const closeButton = insightsDropdown.querySelector( + ".SelectMenu-closeButton", + )!; + + closeButton.addEventListener("click", () => { + insightsDropdown.classList.add("hidden"); + }); + + const insightsList = insightsDropdown.querySelector( + ".SelectMenu-list", + )!; + + userInsights.forEach((insight: IInsight) => { + const insightLabel = createHtmlElement("div", { + className: "form-checkbox mt-1 mb-0 p-1", + innerHTML: ` + + `, + }); + + const insightCheckbox = insightLabel.querySelector( + "input", + )!; + + if (insight.repos.some((repo: any) => repo.repo_id === currentRepoData.id)) { + insightCheckbox.setAttribute("checked", "true"); + } + + insightCheckbox.addEventListener("click", async () => { + const currentRepoId = currentRepoData.id; + const { checked } = insightCheckbox; + + const updatedInsight = await updateInsight( + userToken, + currentRepoId, + checked, + `${ownerName}/${repoName}`, + insight, + ); + + if (!updatedInsight) { + console.log("Something went wrong"); + } + }); + + insightsList.appendChild(insightLabel); + }); + + + document.addEventListener("click", () => { + insightsDropdown.classList.add("hidden"); + }); + + insightsDropdown.addEventListener("click", e => { + e.stopPropagation(); + }); + + const addInsightBtn = insightsDropdown.querySelector( + ".user-lists-menu-action", + )!; + + addInsightBtn.addEventListener("click", () => { + window.open( + "https://insights.opensauced.pizza/hub/insights/new", + "_blank", + ); + }); + + return insightsDropdown; + }; + diff --git a/src/content-scripts/components/RepoVoting/RepoUnvoteButton.ts b/src/content-scripts/components/RepoVoting/RepoUnvoteButton.ts index aed7ba10..022e1905 100644 --- a/src/content-scripts/components/RepoVoting/RepoUnvoteButton.ts +++ b/src/content-scripts/components/RepoVoting/RepoUnvoteButton.ts @@ -3,6 +3,7 @@ import { createHtmlElement } from "../../../utils/createHtmlElement"; import { getAuthToken } from "../../../utils/checkAuthentication"; import { VoteRepoButton } from "./RepoVoteButton"; import { voteOrUnvoteRepo } from "../../../utils/fetchOpenSaucedApiData"; +import { InsightsSelectDropdown } from "../InsightsSelectDropdown/InsightsSelectDropdown"; export const RepoUnvoteButton = (ownerName: string, repoName: string) => { const repoUnvoteButton = createHtmlElement("li", { @@ -11,6 +12,9 @@ export const RepoUnvoteButton = (ownerName: string, repoName: string) => { innerHTML: ` Unvote + `, }); @@ -27,9 +31,23 @@ export const RepoUnvoteButton = (ownerName: string, repoName: string) => { repoUnvoteButton.replaceWith(VoteRepoButton(ownerName, repoName)); } else { console.log("Something went wrong"); + repoUnvoteButton.innerHTML = ` + Unvote + `; } }); + const insightsDropdownBtn = repoUnvoteButton.querySelector( + "#insights-dropdown-btn", + )!; + + insightsDropdownBtn.addEventListener("click", async e => { + e.stopPropagation(); + const insightsDropdown = await InsightsSelectDropdown(ownerName, repoName) as HTMLDivElement; + + repoUnvoteButton.appendChild(insightsDropdown); + insightsDropdown.classList.toggle("hidden"); + }); return repoUnvoteButton; }; diff --git a/src/content-scripts/components/RepoVoting/RepoVoteButton.ts b/src/content-scripts/components/RepoVoting/RepoVoteButton.ts index b22dfd34..c2246261 100644 --- a/src/content-scripts/components/RepoVoting/RepoVoteButton.ts +++ b/src/content-scripts/components/RepoVoting/RepoVoteButton.ts @@ -2,6 +2,7 @@ import "../../content-scripts.css"; import { createHtmlElement } from "../../../utils/createHtmlElement"; import { getAuthToken } from "../../../utils/checkAuthentication"; import { voteOrUnvoteRepo } from "../../../utils/fetchOpenSaucedApiData"; +import { InsightsSelectDropdown } from "../InsightsSelectDropdown/InsightsSelectDropdown"; export const VoteRepoButton = (ownerName: string, repoName: string) => { const voteRepoButton = createHtmlElement("li", { @@ -10,6 +11,9 @@ export const VoteRepoButton = (ownerName: string, repoName: string) => { innerHTML: ` Upvote + `, }); @@ -26,8 +30,23 @@ export const VoteRepoButton = (ownerName: string, repoName: string) => { voteRepoButton.replaceWith(RepoUnvoteButton(ownerName, repoName)); } else { console.log("Something went wrong"); + voteRepoButton.innerHTML = ` + Upvote + `; } }); + const insightsDropdownBtn = voteRepoButton.querySelector( + "#insights-dropdown-btn", + )!; + + insightsDropdownBtn.addEventListener("click", async e => { + e.stopPropagation(); + const insightsDropdown = await InsightsSelectDropdown(ownerName, repoName) as HTMLDivElement; + + voteRepoButton.appendChild(insightsDropdown); + insightsDropdown.classList.toggle("hidden"); + }); + return voteRepoButton; }; diff --git a/src/content-scripts/content-scripts.css b/src/content-scripts/content-scripts.css index 591a2cd8..87822a00 100644 --- a/src/content-scripts/content-scripts.css +++ b/src/content-scripts/content-scripts.css @@ -25,3 +25,9 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } + +.SelectMenu-modal { + font-size: 12px; + color: var(--color-fg-default)!important; + line-height: 1.5!important; +} \ No newline at end of file diff --git a/src/ts/InsightDto.ts b/src/ts/InsightDto.ts new file mode 100644 index 00000000..5e80b0aa --- /dev/null +++ b/src/ts/InsightDto.ts @@ -0,0 +1,6 @@ +export interface IInsight { + id: number; + name: string; + is_public: boolean; + repos: any[]; +} diff --git a/src/utils/fetchOpenSaucedApiData.ts b/src/utils/fetchOpenSaucedApiData.ts index c1de589a..b3cefa75 100644 --- a/src/utils/fetchOpenSaucedApiData.ts +++ b/src/utils/fetchOpenSaucedApiData.ts @@ -1,5 +1,6 @@ import { cachedFetch } from "./cache"; -import { OPEN_SAUCED_USERS_ENDPOINT, OPEN_SAUCED_SESSION_ENDPOINT, OPEN_SAUCED_REPOS_ENDPOINT } from "../constants"; +import { OPEN_SAUCED_USERS_ENDPOINT, OPEN_SAUCED_SESSION_ENDPOINT, OPEN_SAUCED_REPOS_ENDPOINT, OPEN_SAUCED_USER_INSIGHTS_ENDPOINT } from "../constants"; +import { IInsight } from "../ts/InsightDto"; export const isOpenSaucedUser = async (username: string) => { try { @@ -100,6 +101,18 @@ export const repoExistsOnOpenSauced = async (ownerName: string, repoName: string return response.status === 200; }; +export const getRepoData = async (ownerName: string, repoName: string, forceRefresh: boolean = false) => cachedFetch(`${OPEN_SAUCED_REPOS_ENDPOINT}/${ownerName}/${repoName}`, { + expireInSeconds: 2 * 60 * 60, + forceRefresh, + headers: { Accept: "application/json" }, +}).then(async resp => { + if (!resp?.ok) { + console.log("error getting repo info"); + } + return resp?.json(); +}) + .then(json => json); + export const voteOrUnvoteRepo = async (userToken: string, ownerName: string, repoName: string, method: "PUT" | "DELETE") => { const response = await fetch( `${OPEN_SAUCED_REPOS_ENDPOINT}/${ownerName}/${repoName}/vote`, @@ -111,3 +124,50 @@ export const voteOrUnvoteRepo = async (userToken: string, ownerName: string, rep return response.status === 200; }; + +export const getUserInsightsData = async (userToken: string) => { + const response = await fetch( + `${OPEN_SAUCED_USER_INSIGHTS_ENDPOINT}`, + { + method: "GET", + headers: { Authorization: `Bearer ${userToken}` }, + }, + ); + + return response.json(); +}; + +export const updateInsight = async (userToken: string, repoId: string, checked: boolean, repoFullName: string, insight: IInsight) => { + insight.repos = insight.repos.map((repo: any) => ({ + id: repo.repo_id, + fullName: repo.full_name, + })); + + const response = await fetch( + `${OPEN_SAUCED_USER_INSIGHTS_ENDPOINT}/${insight.id}`, + { + method: "PATCH", + headers: { + Authorization: `Bearer ${userToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: insight.name, + is_public: insight.is_public, + repos: checked + ? [ + ...insight.repos, + { + id: repoId, + fullName: `${repoFullName}`, + }, + ] + : insight.repos.filter( + (repo: any) => repo.id !== repoId, + ), + }), + }, + ); + + return response.status === 200; +};