Skip to content

Commit

Permalink
Merge pull request #1823 from ResearchHub/tiers
Browse files Browse the repository at this point in the history
Achievements with tiers
  • Loading branch information
yattias authored Aug 28, 2024
2 parents 2676aad + e8a2ddc commit 6834682
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 86 deletions.
122 changes: 65 additions & 57 deletions components/Author/Profile/AuthorHeaderAchievements.tsx
Original file line number Diff line number Diff line change
@@ -1,69 +1,60 @@
import colors from "~/config/themes/colors";
import { Achievement, AuthorSummaryStats, FullAuthorProfile } from "../lib/types";
import { Achievement, AuthorSummaryStats, FullAuthorProfile, TIER_COLORS, TIER_INDICES } from "../lib/types";
import { getAchievmentDetails } from "../lib/utils";
import { css, StyleSheet } from "aphrodite";
import { Tooltip } from "@mui/material";
import { faCircleCheck, faTrophy, faTrophyStar } from "@fortawesome/pro-light-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ReactElement } from "react";


const AuthorHeaderAchievements = ({ summaryStats, achievements }: { summaryStats: AuthorSummaryStats, achievements: Array<Achievement> }) => {
const getTooltipContent = (achievement: Achievement, achivementDetails: { icon: ReactElement, title: string } ) => {
const filledColor = TIER_COLORS[achievement.currentMilestoneIndex + 1]
const isTopTier = achievement.currentMilestoneIndex === achievement.milestones.length - 1

let value = achievement.value;
let displayValue = value.toLocaleString();
let nextTierValue = achievement.milestones[achievement.currentMilestoneIndex + 1] || 0
let nextTierDisplayValue = nextTierValue.toLocaleString();

if (achievement.type === "OPEN_ACCESS") {
value = Math.round(achievement.value * 100);
nextTierValue = nextTierValue * 100;
displayValue = value + "%";
nextTierDisplayValue = nextTierValue + "%";
}

return (
<div className={css(styles.tooltip)}>
<div style={{ display: "flex", alignItems: "center", columnGap: 10, }}>
{achivementDetails.icon} {achivementDetails.title} ({TIER_INDICES[achievement.currentMilestoneIndex]})
</div>
<div style={{ fontSize: 13, fontWeight: 400, marginBottom: 10 }}>
{achievement.type === "OPEN_SCIENCE_SUPPORTER" ? `Funded open science using ${displayValue} RSC.` : achievement.type === "EXPERT_PEER_REVIEWER" ? `Peer reviewed ${displayValue} publications.` : achievement.type === "HIGHLY_UPVOTED" ? ` Received ${displayValue} upvotes.` : achievement.type === "CITED_AUTHOR" ? `Author's publications Cited ${displayValue} times.` : achievement.type === "OPEN_ACCESS" ? `Published ${displayValue} of works as open access.` : ""}
</div>
{!isTopTier && (
<>
<div style={{ display: "flex", justifyContent: "space-between" }}>
Next tier:

<div style={{ fontSize: 12 }}>
{displayValue} / {nextTierDisplayValue}
{achievement.type === "OPEN_SCIENCE_SUPPORTER" ? " RSC" : achievement.type === "EXPERT_PEER_REVIEWER" ? " reviews" : achievement.type === "HIGHLY_UPVOTED" ? " upvotes" : achievement.type === "CITED_AUTHOR" ? " citations" : ""}
</div>

const getTooltipContent = (achievement: string) => {
if (achievement === "OPEN_ACCESS") {
return (
<div className={css(styles.tooltip)}>
<div className={css(styles.tooltipLineItem)}>
<FontAwesomeIcon icon={faCircleCheck} color={colors.GREEN()} />
Published at least 2 papers
</div>
<div className={css(styles.tooltipLineItem)}>
<FontAwesomeIcon icon={faCircleCheck} color={colors.GREEN()} />
51% or papers or more are open access
</div>
</div>
)
}
else if (achievement === "CITED_AUTHOR") {
return (
<div className={css(styles.tooltip)}>
<div className={css(styles.tooltipLineItem)}>
<FontAwesomeIcon icon={faCircleCheck} color={colors.GREEN()} />
Author on paper that received at least 1 citation
</div>
</div>
)
}
else if (achievement === "OPEN_SCIENCE_SUPPORTER") {
return (
<div className={css(styles.tooltip)}>
<div className={css(styles.tooltipLineItem)}>
<FontAwesomeIcon icon={faCircleCheck} color={colors.GREEN()} />
Funded open science using RSC
</div>
</div>
)
}
else if (achievement.includes("HIGHLY_UPVOTED")) {
return (
<div className={css(styles.tooltip)}>
<div className={css(styles.tooltipLineItem)}>
<FontAwesomeIcon icon={faCircleCheck} color={"#B9F2FF"} />
Received at least 10 upvotes
</div>
</div>
)
}
else if (achievement.includes("EXPERT_PEER_REVIEWER")) {
return (
<div className={css(styles.tooltip)}>
<div className={css(styles.tooltipLineItem)}>
<FontAwesomeIcon icon={faCircleCheck} color={"#B9F2FF"} />
Peer reviewed at least 1 publication
<div className={css(styles.progressBar)}>
<div style={{ position: "absolute", top: 2, paddingLeft: 5 }}>
{TIER_INDICES[achievement.currentMilestoneIndex + 1]}
</div>
<div style={{ borderRadius: "4px 0 0 4px", background: filledColor, height: "100%", width: (achievement.pctProgress * 100) + "%" }}>
</div>
</div>
</div>
)
}
</>
)}
</div>
)
}

return (
Expand All @@ -80,8 +71,17 @@ const AuthorHeaderAchievements = ({ summaryStats, achievements }: { summaryStats
const achivementDetails = getAchievmentDetails({ achievement, summaryStats, })

return (
<Tooltip title={getTooltipContent(achievement)}>
<div key={achievement} className={css(styles.achievement)}>
<Tooltip
title={getTooltipContent(achievement, achivementDetails)}
componentsProps={{
tooltip: {
sx: {
bgcolor: "rgba(36, 31, 58, 1)",
},
},
}}
>
<div key={achievement.type} className={css(styles.achievement)}>
<div>{achivementDetails.icon}</div>
<div>{achivementDetails.title}</div>
</div>
Expand All @@ -98,6 +98,13 @@ const AuthorHeaderAchievements = ({ summaryStats, achievements }: { summaryStats
}

const styles = StyleSheet.create({
progressBar: {
borderRadius: 4,
width: "100%",
height: 20,
background: "rgba(124, 121, 137, 1)",
position: "relative"
},
rootWrapper: {
rowGap: 5,
display: "flex",
Expand All @@ -107,11 +114,12 @@ const styles = StyleSheet.create({
},
tooltip: {
padding: 6,
fontSize: 14,
fontSize: 13,
display: "flex",
flexDirection: "column",
alignContent: "center",
gap: 5,
width: 250,
},
tooltipLineItem: {
display: "flex",
Expand Down
4 changes: 4 additions & 0 deletions components/Author/lib/AuthorReputationSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@ const getTooltipText = (percentile) => {
const AuthorReputationSection = ({
reputationList,
limit = -1,
hideBelowValue = 0,
}: {
reputationList: Reputation[];
limit?: number;
hideBelowValue?: number;
}) => {
let repListToRender = reputationList;
repListToRender = repListToRender.filter((rep) => rep.percentile > hideBelowValue);

if (limit > 0) {
repListToRender = reputationList.slice(0, limit);
}
Expand Down
2 changes: 1 addition & 1 deletion components/Author/lib/ExpertiseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const ExpertiseModal = ({
Learn more about our reputation algorithm
</ALink>
</div>
<AuthorReputationSection reputationList={profile.reputationList} />
<AuthorReputationSection hideBelowValue={1} reputationList={profile.reputationList} />
</div>
</BaseModal>
);
Expand Down
54 changes: 49 additions & 5 deletions components/Author/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,23 @@ import {
parseEducation,
} from "~/config/types/root_types";

export type Achievement = "CITED_AUTHOR" | "OPEN_ACCESS" | "OPEN_SCIENCE_SUPPORTER" | "EXPERT_PEER_REVIEWER" | "HIGHLY_UPVOTED";
export type AchievementType = "CITED_AUTHOR" | "OPEN_ACCESS" | "OPEN_SCIENCE_SUPPORTER" | "EXPERT_PEER_REVIEWER" | "HIGHLY_UPVOTED";

export const TIER_INDICES = ["Bronze", "Silver", "Gold"];
export const TIER_COLORS = [
"rgba(209, 166, 132, 1)", // Bronze
"rgba(180, 184, 188, 1)", // Silver
"rgba(255, 204, 1, 1)" // Gold
];

export type Achievement = {
type: AchievementType;
value: number;
currentMilestoneIndex: number;
milestones: Array<number>;
pctProgress: number;
}

export type FullAuthorProfile = {
id: ID;
firstName: string;
Expand Down Expand Up @@ -109,15 +125,40 @@ function ensureSufficientYears(activityData: YearlyActivity[]): YearlyActivity[]
}

export const parseAuthorAchievements = (raw: any): Array<Achievement> => {

let achievements: Achievement[] = [];
for (const key in raw.achievements) {
if (raw.achievements.hasOwnProperty(key)) {
const achievement = raw.achievements[key];
if (achievement["value"] >= achievement["milestones"][0]) {
achievements.push(key as Achievement);
const rawAchievement = raw.achievements[key];
const hasAchievementUnlocked = rawAchievement["value"] >= rawAchievement["milestones"][0];

if (hasAchievementUnlocked) {
const achievement:any = {};
achievement.type = key;
achievement.value = rawAchievement["value"];
achievement.milestones = rawAchievement["milestones"];
achievement.currentMilestoneIndex = 0;

// Find current milestone user is in
for (let i = 0; i < rawAchievement["milestones"].length; i++) {
if (achievement.value >= rawAchievement["milestones"][i]) {
achievement.currentMilestoneIndex = i;
}
}

achievement.pctProgress = achievement.value / achievement.milestones[achievement.currentMilestoneIndex + 1]

achievements.push(achievement as Achievement);
}
}

// Sort achievements by highest tier (currentMilestoneIndex) first, then by percentage progress
achievements.sort((a, b) => {
if (b.currentMilestoneIndex !== a.currentMilestoneIndex) {
return b.currentMilestoneIndex - a.currentMilestoneIndex;
}
return b.pctProgress - a.pctProgress;
});

return achievements;
}

Expand Down Expand Up @@ -209,6 +250,7 @@ export const DEMO_BINS: Array<Reputation> = [
name: "Biology",
relevancyScore: 0,
description: "",
isUsedForRep: true,
},
percentile: 0,
},
Expand All @@ -226,6 +268,7 @@ export const DEMO_BINS: Array<Reputation> = [
name: "Chemistry",
relevancyScore: 0,
description: "",
isUsedForRep: true,
},
percentile: 0,
},
Expand All @@ -243,6 +286,7 @@ export const DEMO_BINS: Array<Reputation> = [
name: "Economics",
relevancyScore: 0,
description: "",
isUsedForRep: true,
},
percentile: 0,
},
Expand Down
57 changes: 34 additions & 23 deletions components/Author/lib/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,64 +2,75 @@ import {
CitedAuthorAchievementIcon,
OpenAccessAchievementIcon,
} from "~/config/icons/AchievementIcons";
import { Achievement, AuthorSummaryStats, FullAuthorProfile } from "./types";
import { Achievement, AuthorSummaryStats, FullAuthorProfile, TIER_COLORS, TIER_INDICES } from "./types";
import { ReactElement } from "react";
import { Tab } from "~/components/HorizontalTabBar";
import colors from "~/config/themes/colors";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faQuestion } from "@fortawesome/pro-solid-svg-icons";
import { faCircleStar, faCircleUp, faHandHoldingDollar } from "@fortawesome/pro-light-svg-icons";
import { faCircleUp, faQuestion, faHandHoldingDollar, faStar, faStarCircle, faUnlock, faLockOpen, faQuoteRight, faHandsHoldingDollar } from "@fortawesome/pro-solid-svg-icons";
import { StyleSheet, css } from "aphrodite";
import Tooltip from "@mui/material/Tooltip";
import ResearchCoinIcon from "~/components/Icons/ResearchCoinIcon";


type Tier = "bronze" | "silver" | "gold" | "platinum" | "diamond";

export const getAchievmentDetails = ({
achievement,
summaryStats,
}: {
achievement: Achievement;
summaryStats: AuthorSummaryStats;
}): { icon: ReactElement; title: string; details: string } => {
const tier = parseInt(achievement.charAt(achievement.length - 1).toUpperCase()) || 0;
}): { icon: ReactElement; title: string } => {
const tierColor = TIER_COLORS[achievement.currentMilestoneIndex]

if (achievement === "OPEN_SCIENCE_SUPPORTER") {
if (achievement.type === "OPEN_SCIENCE_SUPPORTER") {
return {
icon: <FontAwesomeIcon style={{ color: "black" }} icon={faHandHoldingDollar} fontSize={24} />,
icon: (
<ResearchCoinIcon version={4} color={tierColor} height={25} width={25} />
),
title: "Open Science Supporter",
details: `Provided funding for open science`,
};
} else if (achievement === "CITED_AUTHOR") {
} else if (achievement.type === "CITED_AUTHOR") {
return {
icon: <CitedAuthorAchievementIcon height={24} width={24} />,
icon: (
<div style={{ background: tierColor, borderRadius: "50%", padding: 3, width: 19, height: 19, display: "flex", alignContent: "center", flexDirection: "column", justifyContent: "center" }}>
<FontAwesomeIcon style={{ color: "white" }} icon={faQuoteRight} fontSize={13} />
</div>
),
title: "Cited Author",
details: `Cited ${summaryStats.citationCount} times`,
};
} else if (achievement.includes("EXPERT_PEER_REVIEWER")) {
} else if (achievement.type === "EXPERT_PEER_REVIEWER") {
return {
icon: <FontAwesomeIcon style={{ color: "black" }} icon={faCircleStar} fontSize={24} />,
icon: (
<div style={{ background: "white", borderRadius: "50%", }}>
<FontAwesomeIcon style={{ color: tierColor }} icon={faStarCircle} fontSize={25} />
</div>
),
title: "Peer Reviewer",
details: `Peer reviewed at least 1 publication`,
};
} else if (achievement.includes("HIGHLY_UPVOTED")) {
} else if (achievement.type === "HIGHLY_UPVOTED") {
return {
icon: <FontAwesomeIcon style={{ color: "black" }} icon={faCircleUp} fontSize={24} />,
icon: (
<div style={{ background: "white", borderRadius: "50%", }}>
<FontAwesomeIcon style={{ color: tierColor }} icon={faCircleUp} fontSize={25} />
</div>
),
title: "Active user",
details: `Received at least five upvotes on the platform`,
};
} else if (achievement.includes("OPEN_ACCESS")) {
} else if (achievement.type === "OPEN_ACCESS") {
return {
icon: <OpenAccessAchievementIcon height={24} width={24} />,
icon: (
<div style={{ background: tierColor, borderRadius: "50%", padding: 3, width: 19, height: 19, display: "flex", alignContent: "center", flexDirection: "column", justifyContent: "center" }}>
<FontAwesomeIcon style={{ color: "white" }} icon={faLockOpen} fontSize={12} />
</div>
),

title: "Open Access Advocate",
details: `At least 50% of papers are open access`,
};
}

return {
icon: <></>,
title: "",
details: "",
};
};

Expand Down

0 comments on commit 6834682

Please sign in to comment.