diff --git a/api/allWakaValues.js b/api/allWakaValues.js index 5cb9f15..06e1563 100644 --- a/api/allWakaValues.js +++ b/api/allWakaValues.js @@ -14,9 +14,9 @@ import connectToDb from "../src/server/db"; async function handle(req, res) { const allNcItemNamesAndIdsPromise = loadAllNcItemNamesAndIds(); - let itemValuesByName; + let itemValuesByIdOrName; try { - itemValuesByName = await loadWakaValuesByName(); + itemValuesByIdOrName = await loadWakaValuesByIdOrName(); } catch (e) { console.error(e); res.setHeader("Content-Type", "text/plain"); @@ -28,8 +28,10 @@ async function handle(req, res) { const allNcItemNamesAndIds = await allNcItemNamesAndIdsPromise; const itemValues = {}; for (const { name, id } of allNcItemNamesAndIds) { - if (name in itemValuesByName) { - itemValues[id] = itemValuesByName[name]; + if (id in itemValuesByIdOrName) { + itemValues[id] = itemValuesByIdOrName[id]; + } else if (name in itemValuesByIdOrName) { + itemValues[id] = itemValuesByIdOrName[name]; } } @@ -56,10 +58,15 @@ async function loadAllNcItemNamesAndIds() { AND item_translations.locale = "en" `); - return rows; + return rows.map(({ id, name }) => ({ id, name: normalizeItemName(name) })); } -async function loadWakaValuesByName() { +/** + * Load all Waka values from the spreadsheet. Returns an object keyed by ID or + * name - that is, if the item ID is provided in the sheet, we use that as the + * key; or if not, we use the name as the key. + */ +async function loadWakaValuesByIdOrName() { if (!process.env["GOOGLE_API_KEY"]) { throw new Error(`GOOGLE_API_KEY environment variable must be provided`); } @@ -92,14 +99,35 @@ async function loadWakaValuesByName() { // the spreadsheet columns that we don't use on DTI, like Notes. // // NOTE: The Sheets API only returns the first non-empty cells of the row. - // So, when there's no value specified, it only returns one cell. - // That's why we set `""` as the default `value`. - const itemValuesByName = {}; - for (const [itemName, value = ""] of rows) { - itemValuesByName[itemName] = { value }; + // That's why we set `""` as the defaults, in case the value/notes/etc + // aren't provided. + const itemValuesByIdOrName = {}; + for (const [ + itemName, + value = "", + notes = "", + marks = "", + itemId = "", + ] of rows) { + const normalizedItemName = normalizeItemName(itemName); + itemValuesByIdOrName[itemId || normalizedItemName] = { value }; } - return itemValuesByName; + return itemValuesByIdOrName; +} + +function normalizeItemName(name) { + return ( + name + // Remove all spaces, they're a common source of inconsistency + .replace(/\s+/g, "") + // Lower case, because capitalization is another common source + .toLowerCase() + // Remove diacritics: https://stackoverflow.com/a/37511463/107415 + // Waka has some stray ones in item names, not sure why! + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") + ); } export default async (req, res) => { diff --git a/src/app/ItemPage.js b/src/app/ItemPage.js index 6b4c165..7384e12 100644 --- a/src/app/ItemPage.js +++ b/src/app/ItemPage.js @@ -74,6 +74,7 @@ export function ItemPageContent({ itemId, isEmbedded }) { thumbnailUrl description createdAt + wakaValueText # For Support users. rarityIndex diff --git a/src/app/ItemPageLayout.js b/src/app/ItemPageLayout.js index 9caad8c..c0f1068 100644 --- a/src/app/ItemPageLayout.js +++ b/src/app/ItemPageLayout.js @@ -5,6 +5,7 @@ import { Flex, Popover, PopoverArrow, + PopoverBody, PopoverContent, PopoverTrigger, Portal, @@ -15,7 +16,11 @@ import { useToast, VStack, } from "@chakra-ui/react"; -import { ExternalLinkIcon, ChevronRightIcon } from "@chakra-ui/icons"; +import { + ExternalLinkIcon, + ChevronRightIcon, + QuestionIcon, +} from "@chakra-ui/icons"; import { gql, useMutation } from "@apollo/client"; import { @@ -150,6 +155,42 @@ function ItemPageBadges({ item, isEmbedded }) { Jellyneo + {item.isNc && ( + + {item.wakaValueText && ( + <> + {/* For hover-y devices, use a hover popover over the badge. */} + + + + Waka: {item.wakaValueText} + + + + {/* For touch-y devices, use a tappable help icon. */} + + + Waka: {item.wakaValueText} + + + + + + + + + )} + + )} {!item?.isNc && !item?.isPb && ( ; } -function LinkBadge({ children, href, isEmbedded }) { - return ( - - {children} - { - // We also change the icon to signal whether this will launch in a new - // window or not! - isEmbedded ? : - } - - ); -} +const LinkBadge = React.forwardRef( + ({ children, href, isEmbedded, ...props }, ref) => { + return ( + + {children} + { + // We also change the icon to signal whether this will launch in a new + // window or not! + isEmbedded ? ( + + ) : ( + + ) + } + + ); + } +); const fullDateFormatter = new Intl.DateTimeFormat("en-US", { dateStyle: "long", @@ -380,4 +430,30 @@ function ShortTimestamp({ when }) { ); } +function WakaPopover({ children, ...props }) { + return ( + + {children} + + + + +

+ Waka is a community resource that tracks the approximate value of + NC items, based on real-world trades. +

+ +

+ The Waka Team aims to maintain the accuracy of the guide as fully + as possible, but please remember values are often changing and + with certain difficult-to-find or popular items it doesn't hurt to + make a value check! +

+
+
+
+
+ ); +} + export default ItemPageLayout; diff --git a/src/app/ItemTradesPage.js b/src/app/ItemTradesPage.js index 75c4e5c..5120f98 100644 --- a/src/app/ItemTradesPage.js +++ b/src/app/ItemTradesPage.js @@ -102,6 +102,7 @@ function ItemTradesPage({ thumbnailUrl description createdAt + wakaValueText } } `, diff --git a/src/server/loaders.js b/src/server/loaders.js index 0f1b734..88a811f 100644 --- a/src/server/loaders.js +++ b/src/server/loaders.js @@ -608,10 +608,11 @@ const buildItemWakaValueLoader = () => // API would, and avoid putting pressure on our Google Sheets API quotas. // (Some kind of internal memcache or process-level cache would be a more // idiomatic solution in a monolith server environment!) - const url = - process.env.NODE_ENV === "production" - ? "https://impress-2020.openneo.net/api/allWakaValues" - : "http://localhost:3000/api/allWakaValues"; + const url = process.env.VERCEL_URL + ? `https://${process.env.VERCEL_URL}/api/allWakaValues` + : process.env.NODE_ENV === "production" + ? "https://impress-2020.openneo.net/api/allWakaValues" + : "http://localhost:3000/api/allWakaValues"; const res = await fetch(url); if (!res.ok) { throw new Error(