diff --git a/components/Author/AuthorList.tsx b/components/Author/AuthorList.tsx index b8d26c96a6..473469c1f3 100644 --- a/components/Author/AuthorList.tsx +++ b/components/Author/AuthorList.tsx @@ -66,7 +66,7 @@ export const CondensedAuthorList = ({ ) : ( {authorName} )} - {idx < primaryAuthors.length - 1 && <>,} + {idx < primaryAuthors.length - 1 && <>, } ))} {showEtAllText && <>, et al.} diff --git a/components/Bounty/api/fetchBountiesAPI.ts b/components/Bounty/api/fetchBountiesAPI.ts index f2c9c99e25..2e720e0605 100644 --- a/components/Bounty/api/fetchBountiesAPI.ts +++ b/components/Bounty/api/fetchBountiesAPI.ts @@ -1,17 +1,20 @@ -import API from "~/config/api"; -import { buildApiUri } from "~/config/utils/buildApiUri"; +import API, { generateApiUrl } from "~/config/api"; import { Helpers } from "@quantfive/js-web-config"; +import { PaginatedApiResponse } from "~/config/types/root_types"; type Args = { - onError: (error: Error) => void; - onSuccess: (response: any) => void; + personalized?: boolean; page?: number; }; -export const fetchBounties = ({ onError, onSuccess, page }: Args): void => { - fetch(buildApiUri({ apiPath: "bounty/get_bounties" }), API.GET_CONFIG()) +export const fetchBounties = ({ personalized = true, page }: Args): Promise => { + const url = generateApiUrl(`bounty`) + (personalized ? `?personalized=true` : ''); + + return fetch(url, API.GET_CONFIG()) .then(Helpers.checkStatus) .then(Helpers.parseJSON) - .then((res: any): void => onSuccess({ res })) - .catch(onError); -}; \ No newline at end of file + .then((res: any) => { + return res; + }) +}; + diff --git a/components/Home/sidebar/RootLeftSidebar.tsx b/components/Home/sidebar/RootLeftSidebar.tsx index ccf49b494b..3cb8429601 100644 --- a/components/Home/sidebar/RootLeftSidebar.tsx +++ b/components/Home/sidebar/RootLeftSidebar.tsx @@ -243,6 +243,7 @@ function RootLeftSidebar({ "hubs", "referral", "search", + "bounties", "user", "author", ].includes(pathname.split("/")[1]) !== true; diff --git a/config/types/contribution.ts b/config/types/contribution.ts index 5a92b426d9..9db148b648 100644 --- a/config/types/contribution.ts +++ b/config/types/contribution.ts @@ -252,7 +252,7 @@ export const parseBountyContributionItem = ( ), id: raw.id, createdDate: raw.created_date, - amount: formatBountyAmount({ amount: raw.item.amount }), + amount: formatBountyAmount({ amount: raw.item.amount || raw.item }), content: raw?.item?.item?.comment_content_json, ...(raw.item.bounty_parent && { parent: new Bounty(raw.item.bounty_parent), diff --git a/config/types/root_types.ts b/config/types/root_types.ts index 1eb5ed3a4a..bc4a10ae35 100644 --- a/config/types/root_types.ts +++ b/config/types/root_types.ts @@ -81,6 +81,7 @@ export type UnifiedDocument = { documentType: RhDocumentType; id: ID; isRemoved: boolean; + authors: Array; }; export type OrcidConnect = { @@ -233,6 +234,7 @@ export const parseOrganization = (raw: any): Organization => { }; export const parseUnifiedDocument = (raw: any): UnifiedDocument => { + if (typeof raw !== "object") { return raw; } @@ -270,6 +272,11 @@ export const parseUnifiedDocument = (raw: any): UnifiedDocument => { parsed.document["body"] = unparsedInnerDoc.renderable_text; } + if (unparsedInnerDoc.authors) { + parsed["authors"] = unparsedInnerDoc.authors.map(parseAuthorProfile); + } + + // @ts-ignore return parsed; }; diff --git a/pages/bounties/index.tsx b/pages/bounties/index.tsx new file mode 100644 index 0000000000..99f5f58c49 --- /dev/null +++ b/pages/bounties/index.tsx @@ -0,0 +1,479 @@ +import { NextPage } from "next"; +import { useRouter } from "next/router"; +import { css, StyleSheet } from "aphrodite"; +import { fetchBounties } from "~/components/Bounty/api/fetchBountiesAPI"; +import { useEffect, useState } from "react"; +import { getPlainText } from "~/components/Comment/lib/quill"; +import { parseAuthorProfile, parseUnifiedDocument, parseUser } from "~/config/types/root_types"; +import Bounty, { formatBountyAmount } from "~/config/types/bounty"; +import UserTooltip from "~/components/Tooltips/User/UserTooltip"; +import ALink from "~/components/ALink"; +import VerifiedBadge from "~/components/Verification/VerifiedBadge"; +import { formatDateStandard, timeSince } from "~/config/utils/dates"; +import { getUrlToUniDoc } from "~/config/utils/routing"; +import { truncateText } from "~/config/utils/string"; +import CommentAvatars from "~/components/Comment/CommentAvatars"; +import CommentReadOnly from "~/components/Comment/CommentReadOnly"; +import { CloseIcon, PaperIcon } from "~/config/themes/icons"; +import { CondensedAuthorList } from "~/components/Author/AuthorList"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faAngleRight } from "@fortawesome/pro-light-svg-icons"; +import Button from "~/components/Form/Button"; +import colors from "~/config/themes/colors"; +import ContentBadge from "~/components/ContentBadge"; +import numeral from "numeral"; +import ResearchCoinIcon from "~/components/Icons/ResearchCoinIcon"; + +type SimpleBounty = { + id: string; + amount: number; + content: any; + createdBy: any; + expirationDate: string; + createdDate: string; + unifiedDocument: any; + bountyType: "REVIEW" | "GENERIC_COMMMENT" | "ANSWER"; +} + +const parseSimpleBounty = (raw: any): SimpleBounty => { + return { + id: raw.id, + amount: raw.total_amount, + content: raw.item.comment_content_json, + bountyType: raw.bounty_type, + createdDate: raw.created_date, + createdBy: parseUser(raw.created_by), + expirationDate: raw.expiration_date, + unifiedDocument: parseUnifiedDocument(raw.unified_document), + } +} + +const BountyCard = ({ bounty }: { bounty: SimpleBounty }) => { + + const { createdBy, unifiedDocument, expirationDate, createdDate } = bounty; + + return ( + +
+ + +
+
+ + + {createdBy?.authorProfile?.firstName}{" "} + {createdBy?.authorProfile?.lastName} + + } + /> + {createdBy?.authorProfile?.isVerified && ( + + )} +
opened a bounty
+
+
+ +
+
+
+ Amount: +
+
+ + + +
+ {numeral( + formatBountyAmount({ + amount: bounty.amount, + }) + ).format("0,0a")}{" "} + RSC +
+
+ } + /> +
+
+
+
+ Bounty type: +
+
+ {bounty.bountyType === "REVIEW" ? "Peer Review" : bounty.bountyType === "ANSWER" ? "Answer to question" : "Other"} +
+
+
+
+ Expiration date: +
+
+ {formatDateStandard(bounty.expirationDate)} +
+
+
+
+ Details: +
+ {bounty.content?.ops && +
+ +
+ } +
+
+ +
+
+ +
+
+
+ {unifiedDocument?.document?.title} +
+
+ {unifiedDocument.authors && + a.firstName + " " + a.lastName)} allowAuthorNameToIncludeHtml={false} /> + } +
+
+
+
+ +
+ + + ) +} + + +const BountiesPage: NextPage = () => { + const router = useRouter(); + const [currentPage, setCurrentPage] = useState(1); + const [currentBounties, setCurrentBounties] = useState([]); + + useEffect(() => { + (async () => { + const bounties: any = await fetchBounties({ personalized: true, page: 1 }); + + const parsedBounties = (bounties?.results || []).map((bounty) => { + try { + return parseSimpleBounty(bounty) + } + catch (e) { + console.error('error parsing bounty', bounty, e); + } + }).filter((bounty) => bounty !== undefined); + + setCurrentBounties(parsedBounties); + })(); + }, [currentPage]); + + return ( +
+ +
+

Bounties

+
Earn ResearchCoin by completing science related bounties.
+
+ + Verify identity to see bounty recommendations relevant to your research interests. +
+ + +
+
+ +
+ {currentBounties.map((bounty) => ( +
+ +
+ ))} +
+
+ +
+
+
+ + About ResearchCoin +
+
+ ResearchHub is a platform for academics to share their research + papers, ask questions, and get feedback. We are a community of + researchers and students driven to build a space for collaboration + and open science. +
+
+ +
+
Doing things with ResearchCoin
+
+
+
+
Fund open science
+
+
+
+
+
+
+
+
Create a bounty
+
+
+
+
+
+
+
+
Reward quality contributions
+
+
+
+
+
+ +
+
Earning ResearchCoin
+
+
+
+
Answer a bounty
+
+
+
+
+
+
+
+
Publish an open access paper
+
+
+
+
+
+
+
+
Get upvotes on your content
+
+
+
+
+
+ +
+ +
+ ); +} + +const styles = StyleSheet.create({ + pageWrapper: { + display: "flex", + justifyContent: "space-between", + paddingTop: 35, + paddingRight: 28, + paddingLeft: 28, + }, + closeBtn: { + ":hover": { + background: "rgba(255, 255, 255, 0.3)", + cursor: "pointer", + } + + }, + bountiesSection: { + maxWidth: 800, + margin: "0 auto", + }, + bounties: { + display: "flex", + flexDirection: "column", + gap: 20, + }, + badge: { + borderRadius: 25, + fontSize: 12, + marginLeft: -8, + lineHeight: "16px", + padding: "3px 10px", + background: "white", + }, + verifyIdentityBanner: { + display: "flex", + alignItems: "center", + gap: 10, + padding: 10, + // border: "1px solid #ccc", + borderRadius: 5, + marginTop: 10, + color: "white", + background: "#6165D7", + }, + verifyActions: { + marginLeft: "auto", + display: "flex", + gap: 10, + alignItems: "center", + justifyContent: "space-between" + }, + title: { + fontWeight: 500, + textOverflow: "ellipsis", + marginBottom: 15, + textTransform: "capitalize", + }, + answerCTA: { + marginTop: 20, + }, + description: { + fontSize: 15, + marginBottom: 20, + maxWidth: 790, + lineHeight: "22px", + }, + bounty: { + + }, + bountyWrapper: { + borderBottom: `1px solid ${colors.LIGHTER_GREY()}`, + paddingBottom: 25, + ":first-child": { + marginTop: 25, + }, + }, + bountyHeader: { + marginBottom: 10, + fontSize: 14, + }, + action: { + color: colors.BLACK(0.6), + }, + lineItems: { + rowGap: 6, + display: "flex", + flexDirection: "column", + }, + lineItem: { + display: "flex", + fontSize: 14, + alignItems: "center", + }, + lineItemLabel: { + color: colors.BLACK(0.7), + width: 120, + }, + lineItemValue: { + + }, + detailsLineItem: { + display: "block", + }, + info: { + maxWidth: 320, + }, + infoSection: { + }, + aboutRSC: { + border: `1px solid ${colors.BLACK(0.2)}`, + borderRadius: 4, + }, + infoBlock: { + border: `1px solid ${colors.BLACK(0.2)}`, + borderRadius: 4, + marginTop: 25, + }, + infoLabel: { + fontWeight: 500, + padding: 15, + display: "flex", + alignItems: "center", + gap: 10, + fontSize: 20, + borderBottom: `1px solid ${colors.BLACK(0.2)}`, + }, + aboutRSCContent: { + padding: 15, + }, + doingThingsWithRSC: { + + }, + collapsable: { + padding: 15, + ":hover": { + background: colors.LIGHTER_GREY(1.0), + cursor: "pointer", + } + // borderBottom: `1px solid ${colors.BLACK(0.2)}`, + }, + collapsableHeader: { + + }, + collapsableHeaderTitle: { + justifyContent: "space-between", + display: "flex" + + }, + collapsableContent: { + display: "none" + }, + collapsableContentOpen: { + display: "block" + }, + paperWrapper: { + display: "flex", + alignItems: "center", + marginTop: 10, + background: "rgba(250, 250, 252, 1)", + borderRadius: 2, + padding: 20, + ":hover": { + transition: "0.2s", + background: colors.LIGHTER_GREY(1.0), + }, + }, + iconWrapper: { + marginRight: 10, + }, + paperDetails: { + + }, + paperTitle: { + fontSize: 16, + + }, + paperAuthors: { + color: colors.BLACK(0.6), + fontSize: 13, + marginTop: 3, + } + + +}) + +export default BountiesPage; \ No newline at end of file