Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: react/unreact on highlights #200

Merged
merged 4 commits into from
Jul 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 120 additions & 22 deletions src/popup/components/HighlightSlide.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import { getHighlightReactions } from "../../utils/fetchOpenSaucedApiData";
import { getHighlightReactions, getUserHighlightReactions, reactOnHighlight, removeReactionOnHighlight } from "../../utils/fetchOpenSaucedApiData";
import { getAuthToken } from "../../utils/checkAuthentication";

interface HighlightSlideProps {
highlight: {
Expand All @@ -12,26 +13,39 @@ interface HighlightSlideProps {
emojis: Record<string, string>[];
}

interface HighlightReaction {
url: string;
reaction_count: string;
reactedByUser: boolean;
emojiId: string;
}

export const HighlightSlide = ({ highlight, emojis }: HighlightSlideProps) => {
const [highlightReactions, setHighlightReactions] = useState<Record<string, string>[]>([]);
const [highlightReactions, setHighlightReactions] = useState<HighlightReaction[]>([]);
const [reactingDivOpen, setReactingDivOpen] = useState(false);

useEffect(() => {
async function fetchHighlightReactions () {
const highlightReactionData = await getHighlightReactions(highlight.id);
async function fetchHighlightReactions () {
const highlightReactionData = await getHighlightReactions(highlight.id);
const userHighlightReactionData = await getUserHighlightReactions( await getAuthToken(), highlight.id);

const highlightReactionsWithEmojiUrls = emojis.filter(emoji => highlightReactionData.some(highlightReaction => highlightReaction.emoji_id === emoji.id)).map(emoji => {
const highlightReaction = highlightReactionData.find(highlightReaction => highlightReaction.emoji_id === emoji.id)!;

const highlightReactionsWithEmojiUrls = emojis.filter(emoji => highlightReactionData.some(highlightReaction => highlightReaction.emoji_id === emoji.id)).map(emoji => {
const highlightReaction = highlightReactionData.find(highlightReaction => highlightReaction.emoji_id === emoji.id)!;
const reactedByUser = userHighlightReactionData.some(userHighlightReaction => userHighlightReaction.emoji_id === emoji.id);

return {
url: emoji.url,
reaction_count: highlightReaction.reaction_count,
highlight_url: highlight.url,
};
});
return {
url: emoji.url,
reaction_count: highlightReaction.reaction_count,
reactedByUser,
emojiId: emoji.id,
} as HighlightReaction;
});


setHighlightReactions(highlightReactionsWithEmojiUrls);
}
setHighlightReactions(highlightReactionsWithEmojiUrls);
}

useEffect(() => {
void fetchHighlightReactions();
}, []);

Expand All @@ -41,6 +55,17 @@ export const HighlightSlide = ({ highlight, emojis }: HighlightSlideProps) => {
.join("/");
const openGraphUrl = `https://opengraph.githubassets.com/1/${openGraphSearchParameter}`;

const addReactionToHighlight = async (highlightId: string, emojiId: string) => {
await reactOnHighlight(await getAuthToken(), highlightId, emojiId);
setReactingDivOpen(false);
await fetchHighlightReactions();
};

const removeReactionFromHighlight = async (highlightId: string, emojiId: string) => {
await removeReactionOnHighlight(await getAuthToken(), highlightId, emojiId);
await fetchHighlightReactions();
};

return (
<div className="border border-white/40 rounded-md p-3 mt-2 bg-white">
{/* fixed height, content ellipsis */}
Expand Down Expand Up @@ -92,17 +117,94 @@ export const HighlightSlide = ({ highlight, emojis }: HighlightSlideProps) => {
/>

<div className="flex gap-2 mt-2 h-6 items-center">
<div className="flex gap-1 items-center">
<button
aria-haspopup="menu"
className="p-0.5 rounded-full bg-lightOrange"
type="button"
onClick={() => setReactingDivOpen(!reactingDivOpen)}
>
<svg
className="w-5 h-5"
fill="hsla(19, 100%, 50%, .3)"
height="1em"
stroke="currentColor"
strokeWidth="0"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
</svg>
</button>
</div>

<div
aria-labelledby="options-menu"
aria-orientation="vertical"
className={`${reactingDivOpen ? "block" : "hidden"} rounded-full shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-100 focus:outline-none flex`}
id="reacting-div"
role="menu"
>
{emojis.map(emoji => (
<div
key={emoji.name}
className="p-2 text-sm hover:bg-gray-100 flex gap-2 items-center cursor-pointer"
role="menuitem"
tabIndex={-1}
onClick={async () => {
await addReactionToHighlight( highlight.id, emoji.id);
}}
onKeyDown={async () => {
await addReactionToHighlight( highlight.id, emoji.id);
}}
>
<img
alt="Emoji"
className="rounded-full w-6 aspect-square border border-orange p-1"
src={emoji.url}
/>
</div>
))}
</div>


{
highlightReactions.length > 0
highlightReactions.length > 0 && !reactingDivOpen
? (
highlightReactions.map(highlightReaction => (
<div
key={highlightReaction.url}
className="flex gap-1 items-center"
role="button"
tabIndex={0}
onClick={
highlightReaction.reactedByUser
? async () => {
await removeReactionFromHighlight(highlight.id, highlightReaction.emojiId);
}
: async () => {
await addReactionToHighlight(highlight.id, highlightReaction.emojiId);
}
}
onKeyDown={
highlightReaction.reactedByUser
? async () => {
await removeReactionFromHighlight(highlight.id, highlightReaction.emojiId);
}
: async () => {
await addReactionToHighlight(highlight.id, highlightReaction.emojiId);
}
}
>
<img
alt="Emoji"
className="rounded-full w-6 aspect-square border border-orange p-1"
className={`rounded-full w-6 aspect-square border border-orange p-1 ${highlightReaction.reactedByUser ? "bg-lightOrange" : ""}`}
src={highlightReaction.url}
/>

Expand All @@ -111,11 +213,7 @@ export const HighlightSlide = ({ highlight, emojis }: HighlightSlideProps) => {
</span>
</div>
)))
: (
<span className="text-slate-500">
No reactions yet.
</span>
)
: null
}
</div>

Expand Down
1 change: 0 additions & 1 deletion src/popup/pages/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ const Home = () => {

return;
}
console.log(inputFields);
inputFields[0].value = data.name;
inputFields[1].value = data.description;
}
Expand Down
3 changes: 0 additions & 3 deletions src/popup/pages/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { FaChevronLeft } from "react-icons/fa";
import OpenSaucedLogo from "../../assets/opensauced-logo.svg";
import { goBack } from "react-chrome-extension-router";
import Toggle from "../components/ToggleSwitch";
import AIPRDescription from "./aiprdescription";
import { useEffect, useState } from "react";
Expand Down
19 changes: 19 additions & 0 deletions src/utils/fetchOpenSaucedApiData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,27 @@ export const getHighlightReactions = async (highlightId: string):Promise<Record<
return response.json();
};

export const getUserHighlightReactions = async (userToken: string, highlightId: string):Promise<Record<string, string>[]> => {
const response = await fetch(`${OPEN_SAUCED_USER_HIGHLIGHTS_ENDPOINT}/${highlightId}/reactions`, {
headers: { Authorization: `Bearer ${userToken}` },
method: "GET",
});

return response.json();
};

export const getEmojis = async ():Promise<GeneralAPIResponse> => {
const response = await fetch(`${OPEN_SAUCED_EMOJIS_ENDPOINT}`, { method: "GET" });

return response.json();
};

export const reactOnHighlight = async (userToken: string, highlightId: string, emojiId: string) => fetch(`${OPEN_SAUCED_USER_HIGHLIGHTS_ENDPOINT}/${highlightId}/reactions/${emojiId}`, {
headers: { Authorization: `Bearer ${userToken}` },
method: "POST",
});

export const removeReactionOnHighlight = async (userToken: string, highlightId: string, emojiId: string) => fetch(`${OPEN_SAUCED_USER_HIGHLIGHTS_ENDPOINT}/${highlightId}/reactions/${emojiId}`, {
headers: { Authorization: `Bearer ${userToken}` },
method: "DELETE",
});
1 change: 1 addition & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
extend: {
colors: {
orange: "hsla(19, 100%, 50%, 1)",
lightOrange: "hsla(19, 100%, 50%, 0.5)"
},
boxShadow: {
button: "0 0 0.2rem 0.2rem rgb(245, 131, 106, 0.2)",
Expand Down
2 changes: 1 addition & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { crx } from "@crxjs/vite-plugin";
import * as manifest from "./manifest.json";
import manifest from "./manifest.json" assert { type: "json" };

// https://vitejs.dev/config/
export default defineConfig({
Expand Down