diff --git a/app/components/Common/Banner.tsx b/app/components/Common/Banner.tsx index 583da138..0131883d 100644 --- a/app/components/Common/Banner.tsx +++ b/app/components/Common/Banner.tsx @@ -16,7 +16,7 @@ export default function Banner() {

Discord diff --git a/app/components/FFXIVResults/UndercutAlert/Results.tsx b/app/components/FFXIVResults/UndercutAlert/Results.tsx index 915299ed..fda87f2c 100644 --- a/app/components/FFXIVResults/UndercutAlert/Results.tsx +++ b/app/components/FFXIVResults/UndercutAlert/Results.tsx @@ -333,7 +333,7 @@ export const UndercutDescription = () => ( Copy this to your clipboard and use it in our{' '} discord server @@ -354,7 +354,7 @@ export const SalesAlertDescription = () => ( To use Sales Alerts copy this to your clipboard and use it in our{' '} discord server diff --git a/app/components/WoWResults/FullScan/Results.tsx b/app/components/WoWResults/FullScan/Results.tsx index be8880ed..2bfe8009 100644 --- a/app/components/WoWResults/FullScan/Results.tsx +++ b/app/components/WoWResults/FullScan/Results.tsx @@ -143,54 +143,54 @@ export const Results = ({ data }: { data: WoWScanResponseWithPayload }) => {

<>
diff --git a/app/components/navigation/sidebar/Sidebar.tsx b/app/components/navigation/sidebar/Sidebar.tsx index 7d00e7d9..4b91a7e9 100644 --- a/app/components/navigation/sidebar/Sidebar.tsx +++ b/app/components/navigation/sidebar/Sidebar.tsx @@ -95,7 +95,7 @@ const WikiLink = () => ( const DiscordLink = () => ( diff --git a/app/requests/WoW/ShortagePredictor.ts b/app/requests/WoW/ShortagePredictor.ts index de5aeda2..abfdba3b 100644 --- a/app/requests/WoW/ShortagePredictor.ts +++ b/app/requests/WoW/ShortagePredictor.ts @@ -11,6 +11,7 @@ export interface ShortagePredictorProps { homeRealmName: string desiredPriceVsAvgPercent: number desiredQuantityVsAvgPercent: number + expansionNumber: number } export interface Prediction { @@ -60,7 +61,8 @@ const WoWShortagePredictor: ( region, homeRealmName, desiredPriceVsAvgPercent, - desiredQuantityVsAvgPercent + desiredQuantityVsAvgPercent, + expansionNumber }) => { return fetch(`${address}/api/wow/commodityfutures`, { method: 'POST', @@ -77,7 +79,8 @@ const WoWShortagePredictor: ( region, homeRealmName, desired_price_vs_avg_percent: desiredPriceVsAvgPercent, - desired_quantity_vs_avg_percent: desiredQuantityVsAvgPercent + desired_quantity_vs_avg_percent: desiredQuantityVsAvgPercent, + expansionNumber: expansionNumber }) }) } diff --git a/app/routes/[robots.txt].tsx b/app/routes/[robots.txt].tsx index 5ed49726..16651eee 100644 --- a/app/routes/[robots.txt].tsx +++ b/app/routes/[robots.txt].tsx @@ -26,7 +26,6 @@ export const loader: LoaderFunction = () => { Allow: /allagan-data Allow: /wow/best-deals/recommended Allow: /wow/upload-timers - Allow: /wow/shopping-list Allow: /wow/marketshare/recommended Allow: /wow/shortage-predictor Allow: /wow/legacy-marketshare @@ -43,10 +42,11 @@ export const loader: LoaderFunction = () => { Disallow: /build/ Disallow: /queries/full-scan Disallow: /ffxiv/marketshare - Disallow: /ffxiv/craftsim/queries + Disallow: /ffxiv/craftsim Disallow: /wow/best-deals Disallow: /wow/export-search Disallow: /wow/marketshare + Disallow: /wow/shopping-list Sitemap: https://saddlebagexchange.com/sitemap.xml ` diff --git a/app/routes/[sitemap.xml].tsx b/app/routes/[sitemap.xml].tsx index 8fbc817f..929acc79 100644 --- a/app/routes/[sitemap.xml].tsx +++ b/app/routes/[sitemap.xml].tsx @@ -111,21 +111,21 @@ export const loader: LoaderFunction = async () => { 2024-05-07T00:27:48+00:00 0.80 - - https://saddlebagexchange.com/wow/shopping-list - 2024-05-07T00:27:48+00:00 - 0.80 - - - https://saddlebagexchange.com/wow/export-search - 2024-05-07T00:27:48+00:00 - 0.80 - - - https://saddlebagexchange.com/wow/marketshare - 2024-05-07T00:27:48+00:00 - 0.80 - +// +// https://saddlebagexchange.com/wow/shopping-list +// 2024-05-07T00:27:48+00:00 +// 0.80 +// +// +// https://saddlebagexchange.com/wow/export-search +// 2024-05-07T00:27:48+00:00 +// 0.80 +// +// +// https://saddlebagexchange.com/wow/marketshare +// 2024-05-07T00:27:48+00:00 +// 0.80 +// https://saddlebagexchange.com/wow/marketshare/recommended 2024-06-21T00:27:48+00:00 @@ -181,26 +181,26 @@ export const loader: LoaderFunction = async () => { 2024-05-07T00:27:48+00:00 0.80 - - https://saddlebagexchange.com/queries/full-scan - 2024-05-07T00:27:48+00:00 - 0.80 - - - https://saddlebagexchange.com/ffxiv/marketshare - 2024-05-07T00:27:48+00:00 - 0.80 - - - https://saddlebagexchange.com/ffxiv/craftsim - 2024-05-07T00:27:48+00:00 - 0.80 - - - https://saddlebagexchange.com/wow/best-deals - 2024-05-07T00:27:48+00:00 - 0.80 - +// +// https://saddlebagexchange.com/queries/full-scan +// 2024-05-07T00:27:48+00:00 +// 0.80 +// +// +// https://saddlebagexchange.com/ffxiv/marketshare +// 2024-05-07T00:27:48+00:00 +// 0.80 +// +// +// https://saddlebagexchange.com/ffxiv/craftsim +// 2024-05-07T00:27:48+00:00 +// 0.80 +// +// +// https://saddlebagexchange.com/wow/best-deals +// 2024-05-07T00:27:48+00:00 +// 0.80 +// https://saddlebagexchange.com/wow/pet-marketshare 2024-05-11T00:27:48+00:00 diff --git a/app/routes/_public._index.tsx b/app/routes/_public._index.tsx index bd293860..e730eb27 100644 --- a/app/routes/_public._index.tsx +++ b/app/routes/_public._index.tsx @@ -81,6 +81,7 @@ export default function Index() { + {/* Features Section */}
@@ -279,7 +280,8 @@ export default function Index() {
Get Started diff --git a/app/routes/_public.allagan-data.tsx b/app/routes/_public.allagan-data.tsx index f02819ff..fb5e14f2 100644 --- a/app/routes/_public.allagan-data.tsx +++ b/app/routes/_public.allagan-data.tsx @@ -13,6 +13,7 @@ import UniversalisBadgedLink from '~/components/utilities/UniversalisBadgedLink' import type { AllaganResults, InBagsReport } from '~/requests/FFXIV/allagan' import AllaganRequest from '~/requests/FFXIV/allagan' import { getUserSessionData } from '~/sessions' +import Banner from '~/components/Common/Banner' const formName = 'allaganData' @@ -126,6 +127,7 @@ const Index = () => { return ( + discord server diff --git a/app/routes/_public.options.tsx b/app/routes/_public.options.tsx index 34c8b1ae..2ac66bfc 100644 --- a/app/routes/_public.options.tsx +++ b/app/routes/_public.options.tsx @@ -35,6 +35,7 @@ import SelectDCandWorld from '~/components/form/select/SelectWorld' import type { WoWServerData, WoWServerRegion } from '~/requests/WoW/types' import { PageWrapper } from '~/components/Common' import { setCookie } from '~/utils/cookies' +import Banner from '~/components/Common/Banner' // Overwrite default meta in the root.tsx export const meta: MetaFunction = () => { @@ -203,6 +204,7 @@ export default function Options() { return ( +
{ diff --git a/app/routes/_public.price-sniper.tsx b/app/routes/_public.price-sniper.tsx index 61abca81..2787e028 100644 --- a/app/routes/_public.price-sniper.tsx +++ b/app/routes/_public.price-sniper.tsx @@ -16,6 +16,7 @@ import HQCheckbox from '~/components/form/HQCheckbox' import CodeBlock from '~/components/Common/CodeBlock' import ItemSelect from '~/components/form/select/ItemSelect' import CheckBox from '~/components/form/CheckBox' +import Banner from '~/components/Common/Banner' // Overwrite default meta in the root.tsx export const meta: MetaFunction = () => { @@ -101,6 +102,7 @@ const Index = () => { return ( <> + { @@ -141,6 +142,7 @@ const Index = () => { return ( +

- Shit blog post so google with index us, the good guides are on - github + Blogs - WIP

diff --git a/app/routes/blog.ffxiv.bs1.tsx b/app/routes/blog.ffxiv.bs1.tsx index 51333d78..f38e596a 100644 --- a/app/routes/blog.ffxiv.bs1.tsx +++ b/app/routes/blog.ffxiv.bs1.tsx @@ -1,3 +1,5 @@ +import Banner from '~/components/Common/Banner' + // Overwrite default meta in the root.tsx export const meta: MetaFunction = () => { return { @@ -17,6 +19,7 @@ export const links: LinksFunction = () => [ const HowtoCrossServerTradeinFFXIV = () => { return (
+

Mastering Gil Earning on the FFXIV Marketboard diff --git a/app/routes/blog.ffxiv.bs10.tsx b/app/routes/blog.ffxiv.bs10.tsx index 653f73cc..330c2d84 100644 --- a/app/routes/blog.ffxiv.bs10.tsx +++ b/app/routes/blog.ffxiv.bs10.tsx @@ -1,3 +1,5 @@ +import Banner from '~/components/Common/Banner' + // Overwrite default meta in the root.tsx export const meta: MetaFunction = () => { return { @@ -17,6 +19,7 @@ export const links: LinksFunction = () => [ const howtocrossservertradeinffxiv = () => { return (
+

Diversification and Risk Management in Gil Making and Gold Making diff --git a/app/routes/blog.ffxiv.bs11.tsx b/app/routes/blog.ffxiv.bs11.tsx index 9626879f..cd104a98 100644 --- a/app/routes/blog.ffxiv.bs11.tsx +++ b/app/routes/blog.ffxiv.bs11.tsx @@ -1,3 +1,5 @@ +import Banner from '~/components/Common/Banner' + // Overwrite default meta in the root.tsx export const meta: MetaFunction = () => { return { @@ -18,6 +20,7 @@ export const links: LinksFunction = () => [ const howtocrossservertradeinffxiv = () => { return (
+

Leveraging Social Trading Platforms diff --git a/app/routes/blog.ffxiv.howtoresell.tsx b/app/routes/blog.ffxiv.howtoresell.tsx index 04400c31..6fdf34a1 100644 --- a/app/routes/blog.ffxiv.howtoresell.tsx +++ b/app/routes/blog.ffxiv.howtoresell.tsx @@ -20,20 +20,22 @@ export const links: LinksFunction = () => [ const HowtoCrossServerTradeinFFXIV = () => { return ( -
-
-

+
+
+

How to Import, Trade and Flip items on the FFXIV Marketboard using Saddlebag Exchange Reselling Searches : FFXIV Mods

-

- Let's say you want to earn gil by flipping vendor items or trading +

+ Let's say you want to earn gil by flipping vendor items or trading items server to server.

-

+

What items should you actually buy and sell?

-

+

This is the difficult part of trading items or crafting in general and this is why Saddlebag Exchange was founded! You can find the list of all our trading searches here which all use the same search function @@ -42,183 +44,240 @@ const HowtoCrossServerTradeinFFXIV = () => { Saddlebag Exchange Import Search is designed to fit any and every trading need!

-

+

Link:{' '} - + https://saddlebagexchange.com/queries

-

TLDR Gil Earning : FFXIV Mods

-
    -
  1. +

    + TLDR Gil Earning : FFXIV Mods +

    +
      +
    1. Pick a search from the list at:{' '} - + https://saddlebagexchange.com/queries
    2. -
    3. +
    4. Hit search and bookmark a search if you want to change the default values into a custom search.
    5. -
    6. +
    7. Look at items on the results table. Use the{' '} - + Item Data {' '} link to figure out if the item is a good one to sell and find out just how much gil you can realistically earn from trading it!
    8. -
    9. Decide on some items you want to trade.
    10. -
    11. +
    12. Decide on some items you want to trade.
    13. +
    14. Go in game search for the price on your home server to make sure the website is accurate.
    15. -
    16. +
    17. Travel to the server with the lowest prices and buy your items.
    18. -
    19. +
    20. Go to your retainers and post it on your home servers marketboard.
    21. -
    22. Undercut until it sells.
    23. -
    24. $$$ Profit $$$
    25. -
    26. Go back to 1 and repeat.
    27. +
    28. Undercut until it sells.
    29. +
    30. $$$ Profit $$$
    31. +
    32. Go back to 1 and repeat.
    -

    +

    Starter Search Recommendations:

    -
      -
    • - + -

      +

      DO NOT USE THE DEMAND HEART ICON!

      -

      +

      If you have not used Saddlebag Exchange before you may have traded using the Demand heart icon that appears next to some items.

      - {/*

      Screen Shot 2023-02-15 at 2 41 56 PM

      */} -

      +

      + Screen Shot 2023-02-15 at 2 41 56 PM +

      +

      This is not a good metric to use! This icon appears next to items people add the item to their favorites list and does not reflect the actual sales performance of an item.

      -

      +

      Trading is a numbers game and you need raw statistics not a mysterious icon unrelated to actual sales in order to trade properly.

      -

      +

      How Saddlebag Exchange Import Search Works!

      -

      +

      Our search looks at sales per hour and average sale prices to find the best items to trade! We then add various filters to show different markets and provide many trading options.

      -

      +

      We take many statistics into account, the main values we look for in each search come from them values you provide in the search form. We may find items that exceed these values, but we will filter out any - items that don't meet your minimum requirements leaving only the - best items in the search results: + items that don't meet your minimum requirements leaving only the best + items in the search results: +

      +

      + image

      - {/*

      image

      */} -
        -
      • - Sale Rate: Calculated from the values in Sale Amount{' '} - divided by the Scan Hours -
      • -
      • - Average Sale Price: Calculated directly from the{' '} +
          +
        • + Sale Rate: Calculated from the values in{' '} + Sale Amount divided by the Scan Hours +
        • +
        • + Average Sale Price: Calculated directly from the{' '} Average Price Per Unit
        • -
        • - Potential Profit: Calculated by taking your home servers current - lowest price minus the price of the lowest listing available on - other servers. This is then filtered out using the ROI percent (i.e.{' '} - Return on Investment) and the{' '} +
        • + Potential Profit: Calculated by taking your home + servers current lowest price minus the price of the lowest listing + available on other servers. This is then filtered out using the ROI + percent (i.e. Return on Investment) and the{' '} Minimum Profit Amount.
        -

        +

        You can change the search with any numbers you like to find a search that fits your needs! You can save a custom search by bookmarking it in your browser or clicking the Share this search icon to copy the url link to your clipboard and share with others.

        -

        - +

        + There are many advanced options that are described in this section of our guide.

        -

        How to read Search Results

        -

        +

        + How to read Search Results +

        +

        After hitting search you will be given a big table with lots of information. You can rearrange the table by dragging columns and you can sort each column by clicking on it:

        - {/*

        image

        */} -

        Going through the columns we have:

        -
          -
        • +

          + image +

          +

          Going through the columns we have:

          +
            +
          • Item Name: Showing the english name for an item
          • -
          • +
          • Item Data: This essentially tells you if this is a good item to trade! If you are considering trading an item clicking on this link is the very first thing you should do. It links to the Saddlebag exchange searches on that single items{' '} - - 'Listings Comparison and Competition Metrics'{' '} - and 'Item History Statistics and Graphs' + + 'Listings Comparison and Competition Metrics' and{' '} + 'Item History Statistics and Graphs' . These two searches provide a more detailed overview of the statics on an item, to help you learn what price you can sell this for, what time of day you can sell it, what stack size to use when selling commodities and much, more!
          • -
          • +
          • Universalis Link: This will show you the universalis page for an item if you prefer to view it on universalis. Saddlebag Exchange uses data from Universais and Universalis gets its data from players using{' '} - + Dalamud Plugins {' '} who crowdsource the data. If the prices of anything in the search @@ -227,62 +286,62 @@ const HowtoCrossServerTradeinFFXIV = () => { This will update universalis and then update Saddlebag Exchange to prevent the item from showing the incorrect value.
          • -
          • +
          • NPC Vendor Info: If an item is sold by a vendor in game this will show you the Eorzea Database page with all details on where to find the vendor and what price they sell the item at. If an item is not sold by a vendor then this column will be empty.
          • -
          • +
          • Lowest Price Server: This shows you where to find the lowest priced item on which server and datacenter. It will say{' '} Sold by Vendor - None if the cheapest way to get an item is directly from an NPC vendor.
          • -
          • +
          • Home Server Price: The current lowest listing price on your server.
          • -
          • +
          • Lowest Price Per Unit: The lowest price from the{' '} - 'Lowest Price Server' + 'Lowest Price Server'
          • -
          • +
          • Profit Amount: The amount of profit you can gain from trading if you by at the lowest price found and sell at the current price on your home server. If an item is not listed on your home server the profit amount will appear at the top and have an{' '} symbol instead of a number.
          • -
          • +
          • Sale Rates: This is the sales per hour when looking at the last 40 sales. For example a sale rate of 0.25{' '} sales per hour means 6 sales per day on your home server and one sale on average happening once every 4 hours.
          • -
          • +
          • Average Price Per Unit: The average sale price on your home server when looking at the last 40 sales.
          • -
          • +
          • Return on Investment: This is the{' '} - 'Profit Amount' minus all costs (taxes and - purchase price) divided by the total amount of gil earned from a - sale at your servers current lowest listing price in a percentage. + 'Profit Amount' minus all costs (taxes and purchase + price) divided by the total amount of gil earned from a sale at your + servers current lowest listing price in a percentage.
          • -
          • +
          • Profit Percentage: A less useful number this is the{' '} - 'Profit Amount' divided by{' '} - 'Lowest Price Per Unit' in a percentage. + 'Profit Amount' divided by{' '} + 'Lowest Price Per Unit' in a percentage.
          • -
          • +
          • Lowest Price Stack Size: If the item sells in stacks of more than 1 then this will tell you the size of the stack at the - lowest price (sometimes you want quantity over price and don't - want to waste time traveling for a stack of 1 lumber). + lowest price (sometimes you want quantity over price and don't want + to waste time traveling for a stack of 1 lumber).
          • -
          • +
          • Lowest Price Last Update Time and{' '} Home Server Info Last Updated At: These are the last time universalis received data from its crowdsourcing for each data @@ -290,40 +349,53 @@ const HowtoCrossServerTradeinFFXIV = () => { the marketboard to help universalis update its data.
          -

          Advanced Search Options

          -

          +

          + Advanced Search Options +

          +

          If you click the Advanced Search Options you will see more options for your search.

          - {/*

          image

          */} -
            -
          • +

            + image +

            +
              +
            • Minimum Stack Size will filter out stacks in low amounts if you want to only buy in bulk.
            • -
            • +
            • Enable HQ only if you only want to see HQ items
            • -
            • +
            • Region Wide Search to search all servers in your region, unchecking this will only show trading options within your datacenter.
            • -
            • +
            • Include Out of Stock if unchecked will not show out of stock items
            • -
            • +
            • Include Vendor Prices will compare vendor prices to the{' '} Lowest Price Server value in results and show the vendor price if the vendor is the cheapest place to buy an item. This will not be shown if Enable HQ only is selected.
            • -
            • +
            • Filters will let you filter by item category. You will find all main and sub categories that you find on the marketboard and you can get{' '} - + the full explanation of these values on our wiki page about this subject @@ -333,7 +405,14 @@ const HowtoCrossServerTradeinFFXIV = () => { Crafter Quest Items.
            - {/*

            image

            */} +

            + image +

            how to mod ffxiv, how to install ffxiv mods, are mods allowed in ffxiv, can you mod ffxiv, how to add mods to ffxiv, how to download diff --git a/app/routes/blog.wow.tldr.tsx b/app/routes/blog.wow.tldr.tsx index 4995e196..bd3721cc 100644 --- a/app/routes/blog.wow.tldr.tsx +++ b/app/routes/blog.wow.tldr.tsx @@ -1,6 +1,5 @@ -// root.tsx - import { MetaFunction, LinksFunction } from '@remix-run/node' +import Banner from '~/components/Common/Banner' export const meta: MetaFunction = () => ({ charset: 'utf-8', @@ -21,6 +20,7 @@ export const links: LinksFunction = () => [ const HowToCrossServerTradeInWoW = () => { return (

            +

            How to Earn Gold with Cross-Realm Trading diff --git a/app/routes/ffxiv.extended-history.tsx b/app/routes/ffxiv.extended-history.tsx index 075925fa..80148611 100644 --- a/app/routes/ffxiv.extended-history.tsx +++ b/app/routes/ffxiv.extended-history.tsx @@ -194,7 +194,7 @@ const FFXIVSaleHistory = () => { <>
            { try { const shoppingList = JSON.parse(shoppingData) - const session = await getUserSessionData(request) + // Server-side validation for craft_amount + for (const item of shoppingList) { + if ( + item.craft_amount == null || + isNaN(item.craft_amount) || + item.craft_amount <= 0 + ) { + return json({ + exception: `Invalid craft amount for item ID ${item.itemID}.` + }) + } + } + + const session = await getUserSessionData(request) const homeServer = session.getWorld() return GetShoppingList({ @@ -167,17 +180,28 @@ const Row = ({ { const value = e.target.value - if (value === undefined || value === null) return + // Handle empty input + if (value === '') { + updateRow({ ...form, craft_amount: null }) + return + } - updateRow({ ...form, craft_amount: parseInt(value, 10) }) + const parsedValue = parseInt(value, 10) + if (!isNaN(parsedValue)) { + updateRow({ ...form, craft_amount: parsedValue }) + } }} /> + {error && ( +

            Craft amount is required.

            + )}
            { return ( <> +
            <> diff --git a/app/routes/wow.export-search.tsx b/app/routes/wow.export-search.tsx index 34a2b0b9..72ef8433 100644 --- a/app/routes/wow.export-search.tsx +++ b/app/routes/wow.export-search.tsx @@ -30,6 +30,7 @@ import { handleSearchParamChange } from '~/utils/urlSeachParamsHelpers' import { SubmitButton } from '~/components/form/SubmitButton' +import Banner from '~/components/Common/Banner' const PAGE_URL = '/wow/export-search' @@ -203,6 +204,7 @@ const ExportSearch = () => { return ( <PageWrapper> + <Banner /> <SmallFormContainer title="Export Search: Find the Best Place to Sell!" onClick={handleSubmit} diff --git a/app/routes/wow.full-scan.tsx b/app/routes/wow.full-scan.tsx index b771e02c..23b2be7c 100644 --- a/app/routes/wow.full-scan.tsx +++ b/app/routes/wow.full-scan.tsx @@ -15,6 +15,7 @@ import { setWoWScan } from '~/redux/reducers/wowSlice' import { getUserSessionData } from '~/sessions' import type { WoWLoaderData } from '~/requests/WoW/types' import ErrorBounds from '~/components/utilities/ErrorBoundary' +import Banner from '~/components/Common/Banner' // Overwrite default meta in the root.tsx export const meta: MetaFunction = () => { @@ -96,6 +97,7 @@ const Index = () => { return ( <PageWrapper> <> + <Banner /> <WoWScanForm onClick={onSubmit} onChange={() => { diff --git a/app/routes/wow.item-data.$itemId.tsx b/app/routes/wow.item-data.$itemId.tsx index fecac9ad..25f1bf11 100644 --- a/app/routes/wow.item-data.$itemId.tsx +++ b/app/routes/wow.item-data.$itemId.tsx @@ -14,6 +14,7 @@ import { useTypedSelector } from '~/redux/useTypedSelector' import { format, subHours } from 'date-fns' import SmallTable from '~/components/WoWResults/FullScan/SmallTable' import CustomButton from '~/components/utilities/CustomButton' +import Banner from '~/components/Common/Banner' export const ErrorBoundary = () => <ErrorBounds /> @@ -108,6 +109,7 @@ export default function Index() { ) return ( <PageWrapper> + <Banner /> <Title title={listing.itemName} /> <div className="flex flex-col justify-around mx-3 my-6 md:flex-row"> <div className="flex flex-col max-w-full"> diff --git a/app/routes/wow.price-alert.tsx b/app/routes/wow.price-alert.tsx index a5d4c538..2bf6671b 100644 --- a/app/routes/wow.price-alert.tsx +++ b/app/routes/wow.price-alert.tsx @@ -164,7 +164,7 @@ const Index = () => { Pick a list of your favorite World of Warcraft items for{' '} {priceOrQuantity} alerts. Then join the{' '} <a - href="https://discord.gg/836C8wDVNq" + href="https://discord.gg/saddlebag-exchange-973380473281724476" target="_blank" rel="noreferrer" className="text-blue-500 dark:text-blue-300 hover:underline"> diff --git a/app/routes/wow.region-undercut.tsx b/app/routes/wow.region-undercut.tsx index 2ef28102..279c9c09 100644 --- a/app/routes/wow.region-undercut.tsx +++ b/app/routes/wow.region-undercut.tsx @@ -172,6 +172,19 @@ const RegionUndercut = () => { columns: undercutColumns }} /> + <SmallTable + title="Not Uploaded Items" + description="Shows items that were not yet uploaded to the Blizzard API. Wait 1 hour for these to appear in the data." + columnList={columnList} + data={results.not_in_dataset_list} + columnSelectOptions={selectOptions} + sortingOrder={sortingOrder} + mobileColumnList={mobileColumnList} + csvOptions={{ + filename: 'saddlebag-undercut-not-in-dataset.csv', + columns: undercutColumns + }} + /> </PageWrapper> ) } diff --git a/app/routes/wow.shopping-list.tsx b/app/routes/wow.shopping-list.tsx index a4633655..f7527c2e 100644 --- a/app/routes/wow.shopping-list.tsx +++ b/app/routes/wow.shopping-list.tsx @@ -1,4 +1,4 @@ -import type { ActionFunction } from '@remix-run/cloudflare' +import type { ActionFunction, LoaderFunction } from '@remix-run/cloudflare' import { json } from '@remix-run/cloudflare' import { useEffect, useState } from 'react' import { PageWrapper } from '~/components/Common' @@ -7,7 +7,12 @@ import type { ListItem, WoWListResponse } from '~/requests/WoW/ShoppingList' import WoWShoppingList from '~/requests/WoW/ShoppingList' import { getUserSessionData } from '~/sessions' import z from 'zod' -import { useActionData, useNavigation } from '@remix-run/react' +import { + useActionData, + useNavigation, + useSearchParams, + useLoaderData +} from '@remix-run/react' import { InputWithLabel } from '~/components/form/InputWithLabel' import NoResults from '~/components/Common/NoResults' import SmallTable from '~/components/WoWResults/FullScan/SmallTable' @@ -15,13 +20,21 @@ import type { ColumnList } from '~/components/types' import ExternalLink from '~/components/utilities/ExternalLink' import DebouncedSelectInput from '~/components/Common/DebouncedSelectInput' import { wowItems, wowItemsList } from '~/utils/items/id_to_item' -import { getItemIDByName } from '~/utils/items' +import { getItemIDByName, getItemNameById } from '~/utils/items' import { parseStringToNumber, parseZodErrorsToDisplayString } from '~/utils/zodHelpers' +import { + getActionUrl, + handleCopyButton, + handleSearchParamChange +} from '~/utils/urlSeachParamsHelpers' + +const PAGE_URL = '/wow/shopping-list' const inputMap: Record<string, string> = { + itemID: 'Item ID', maxPurchasePrice: 'Maximum Purchase Price' } @@ -43,14 +56,15 @@ export const meta: MetaFunction = () => { // Overwrite default links in the root.tsx export const links: LinksFunction = () => [ - { rel: 'canonical', href: 'https://saddlebagexchange.com/wow/shopping-list' } + { + rel: 'canonical', + href: 'https://saddlebagexchange.com/wow/shopping-list' + } ] export const action: ActionFunction = async ({ request }) => { const session = await getUserSessionData(request) - const region = session.getWoWSessionData().region - const formData = Object.fromEntries(await request.formData()) const validatedFormData = validateInput.safeParse(formData) @@ -63,44 +77,114 @@ export const action: ActionFunction = async ({ request }) => { }) } + const region = session.getWoWSessionData().region + const result = await WoWShoppingList({ region, ...validatedFormData.data }) - // await the result and then return the json - return json({ ...(await result.json()), sortby: 'discount' }) } +export const loader: LoaderFunction = async ({ request }) => { + const url = new URL(request.url) + const params = url.searchParams + + const itemID = params.get('itemId') + const maxPurchasePrice = params.get('maxPurchasePrice') || '10000000' + + if (itemID) { + const validateInput = z.object({ + itemID: parseStringToNumber, + maxPurchasePrice: parseStringToNumber + }) + + const formData = { itemID, maxPurchasePrice } + const validatedFormData = validateInput.safeParse(formData) + if (!validatedFormData.success) { + return json({ + exception: parseZodErrorsToDisplayString( + validatedFormData.error, + inputMap + ) + }) + } + + const session = await getUserSessionData(request) + const region = session.getWoWSessionData().region + + const result = await WoWShoppingList({ + region, + ...validatedFormData.data + }) + + return json({ + ...(await result.json()), + sortby: 'discount', + formValues: validatedFormData.data + }) + } + + return json({}) +} + +type LoaderResponseType = + | {} + | { exception: string } + | (WoWListResponse & { + sortby: string + formValues: { itemID: number; maxPurchasePrice: number } + }) + type ActionResponseType = | {} | { exception: string } | (WoWListResponse & { sortby: string }) const ShoppingList = () => { - const result = useActionData<ActionResponseType>() - const transistion = useNavigation() + const actionData = useActionData<ActionResponseType>() + const loaderData = useLoaderData<LoaderResponseType>() + const [searchParams] = useSearchParams() + const result = actionData ?? loaderData + const transition = useNavigation() const [itemName, setItemName] = useState<string>('') + const [maxPurchasePrice, setMaxPurchasePrice] = useState<string>('10000000') + const [itemID, setItemID] = useState<string>('') - const isSubmitting = transistion.state === 'submitting' + const isSubmitting = transition.state === 'submitting' - const handleSelect = (value: string) => { - setItemName(value) - } - - const itemId = getItemIDByName(itemName.trim(), wowItems) const error = result && 'exception' in result ? result.exception : undefined - if (result && !Object.keys(result).length) { - return <NoResults href="/wow/shopping-list" /> - } + useEffect(() => { + const itemIdFromUrl = searchParams.get('itemId') + const maxPurchasePriceFromUrl = + searchParams.get('maxPurchasePrice') || '10000000' + + if (itemIdFromUrl) { + const itemNameFromId = getItemNameById(itemIdFromUrl, wowItems) + if (itemNameFromId) { + setItemName(itemNameFromId) + setItemID(itemIdFromUrl) + } + } else { + setItemName('') + setItemID('') + } + setMaxPurchasePrice(maxPurchasePriceFromUrl) + }, [searchParams]) - if (result && 'data' in result && !error) { - return <Results {...result} /> + const handleSelect = (value: string) => { + setItemName(value) + const itemId = getItemIDByName(value.trim(), wowItems) + if (itemId) { + setItemID(itemId.toString()) + } else { + setItemID('') + } } const handleSubmit = ( @@ -111,6 +195,59 @@ const ShoppingList = () => { } } + const hasSearched = + actionData !== undefined || (loaderData && 'data' in loaderData) + + if (hasSearched) { + if (error) { + // Display the form with error message + return ( + <PageWrapper> + <SmallFormContainer + title="Shopping List" + description="Search for the realms with the lowest price for an item." + onClick={handleSubmit} + error={error} + loading={isSubmitting} + hideSubmitButton={!itemID} + action={getActionUrl(PAGE_URL, { + itemId: itemID, + maxPurchasePrice + })}> + <div className="pt-3 flex flex-col"> + <DebouncedSelectInput + title={'Item to search for'} + label="Item" + id="export-item-select" + selectOptions={wowItemsList} + onSelect={handleSelect} + defaultValue={itemName} + /> + <input hidden name="itemID" value={itemID} /> + <InputWithLabel + labelTitle="Maximum Purchase Price" + name="maxPurchasePrice" + type="number" + value={maxPurchasePrice} + min={0} + onChange={(e) => setMaxPurchasePrice(e.currentTarget.value)} + /> + </div> + </SmallFormContainer> + </PageWrapper> + ) + } else if (result && 'data' in result) { + if (result.data && result.data.length > 0) { + return <Results {...result} /> + } else { + return <NoResults href={PAGE_URL} /> + } + } else { + return <NoResults href={PAGE_URL} /> + } + } + + // If no search has been performed, display the form return ( <PageWrapper> <SmallFormContainer @@ -119,7 +256,8 @@ const ShoppingList = () => { onClick={handleSubmit} error={error} loading={isSubmitting} - hideSubmitButton={!itemId}> + hideSubmitButton={!itemID} + action={getActionUrl(PAGE_URL, { itemId: itemID, maxPurchasePrice })}> <div className="pt-3 flex flex-col"> <DebouncedSelectInput title={'Item to search for'} @@ -127,14 +265,16 @@ const ShoppingList = () => { id="export-item-select" selectOptions={wowItemsList} onSelect={handleSelect} + defaultValue={itemName} /> - <input hidden name="itemID" value={itemId} /> + <input hidden name="itemID" value={itemID} /> <InputWithLabel labelTitle="Maximum Purchase Price" name="maxPurchasePrice" type="number" - defaultValue={10000000} + value={maxPurchasePrice} min={0} + onChange={(e) => setMaxPurchasePrice(e.currentTarget.value)} /> </div> </SmallFormContainer> diff --git a/app/routes/wow.shortage-predictor.tsx b/app/routes/wow.shortage-predictor.tsx index b33f74cd..af8317ff 100644 --- a/app/routes/wow.shortage-predictor.tsx +++ b/app/routes/wow.shortage-predictor.tsx @@ -3,7 +3,8 @@ import { PageWrapper } from '~/components/Common' import SmallFormContainer from '~/components/form/SmallFormContainer' import { ItemClassSelect, - ItemQualitySelect + ItemQualitySelect, + ExpansionSelect } from '~/components/form/WoW/WoWScanForm' import type { WoWServerData } from '~/requests/WoW/types' import { InputWithLabel } from '~/components/form/InputWithLabel' @@ -40,7 +41,8 @@ const defaultFormValues = { region: 'NA', homeRealmName: '3678---Thrall', desiredPriceVsAvgPercent: '120', - desiredQuantityVsAvgPercent: '50' + desiredQuantityVsAvgPercent: '50', + expansionNumber: '-1' } const inputMap: Record<keyof ShortagePredictorProps, string> = { @@ -52,7 +54,8 @@ const inputMap: Record<keyof ShortagePredictorProps, string> = { region: 'Select your region', homeRealmName: 'Search for server by name', desiredPriceVsAvgPercent: 'Current Price Percent Above Average', - desiredQuantityVsAvgPercent: 'Current Market Quantity Percentage' + desiredQuantityVsAvgPercent: 'Current Market Quantity Percentage', + expansionNumber: 'Expansion Number' } const pageTitle = 'Commodity Shortage Futures' @@ -78,7 +81,8 @@ export const action: ActionFunction = async ({ request }) => { .min(1) .transform((value) => value.split('---')[1]), desiredPriceVsAvgPercent: parseStringToNumber, - desiredQuantityVsAvgPercent: parseStringToNumber + desiredQuantityVsAvgPercent: parseStringToNumber, + expansionNumber: parseStringToNumber }) const validInput = validateFormData.safeParse(formPayload) @@ -150,7 +154,8 @@ export const loader: LoaderFunction = async ({ request }) => { }) .transform((value) => `${value.id}---${value.name}`), desiredPriceVsAvgPercent: parseStringToNumber, - desiredQuantityVsAvgPercent: parseStringToNumber + desiredQuantityVsAvgPercent: parseStringToNumber, + expansionNumber: parseStringToNumber }) const input = { @@ -173,7 +178,10 @@ export const loader: LoaderFunction = async ({ request }) => { defaultFormValues.desiredPriceVsAvgPercent.toString(), desiredQuantityVsAvgPercent: params.get('desiredQuantityVsAvgPercent') || - defaultFormValues.desiredQuantityVsAvgPercent.toString() + defaultFormValues.desiredQuantityVsAvgPercent.toString(), + expansionNumber: + params.get('expansionNumber') || + defaultFormValues.expansionNumber.toString() } const validInput = validateFormData.safeParse(input) @@ -271,6 +279,10 @@ const Index = () => { defaultValue={loaderData.itemQuality} onChange={(value) => handleFormChange('itemQuality', value)} /> + <ExpansionSelect + defaultValue={loaderData.expansionNumber} + onChange={(value) => handleFormChange('expansionNumber', value)} + /> <ItemClassSelect itemClass={parseInt(loaderData.itemClass)} itemSubClass={parseInt(loaderData.itemSubClass)} diff --git a/app/routes/wow.upload-timers.tsx b/app/routes/wow.upload-timers.tsx index ab2b05b2..81b84eea 100644 --- a/app/routes/wow.upload-timers.tsx +++ b/app/routes/wow.upload-timers.tsx @@ -9,6 +9,7 @@ import { useLoaderData } from '@remix-run/react' import NoResults from '~/components/Common/NoResults' import SmallTable from '~/components/WoWResults/FullScan/SmallTable' import type { ColumnList } from '~/components/types' +import Banner from '~/components/Common/Banner' // Overwrite default meta in the root.tsx export const meta: MetaFunction = () => { @@ -44,6 +45,7 @@ const Index = () => { return ( <PageWrapper> <h1>World of Warcraft API Upload Times</h1> + <Banner /> <SmallTable // title="Upload Timers" description="Shows when the WoW auction house data was last uploaded." diff --git a/app/tailwind.css b/app/tailwind.css index 907ea3ef..03e64ec7 100644 --- a/app/tailwind.css +++ b/app/tailwind.css @@ -1143,6 +1143,10 @@ select { height: 26px; } +.h-auto { + height: auto; +} + .h-full { height: 100%; } @@ -1976,6 +1980,10 @@ select { padding: 0.25rem; } +.p-12 { + padding: 3rem; +} + .p-2 { padding: 0.5rem; } diff --git a/app/utils/items/ffxivItems.ts b/app/utils/items/ffxivItems.ts index fb638f31..607ef301 100644 --- a/app/utils/items/ffxivItems.ts +++ b/app/utils/items/ffxivItems.ts @@ -14579,7 +14579,7 @@ export const ffxivItemsMap: Record<string, string> = { '41793': 'Hrothgar Caligae', '41805': 'Twilight Gemstone', '41806': 'High-density Fiberboard', - '41807': 'Rroneek Horn Token', + '41807': 'Mount Token', '41810': 'Country Cottage Wall', '41811': 'Country House Wall', '41812': 'Country Mansion Wall', @@ -15563,7 +15563,7 @@ export const ffxivItemsMap: Record<string, string> = { '44069': 'Lesser Apollyon Shell', '44070': "Ty'aitya Wingblade", '44071': 'Tumbleclaw Weeds', - '44072': 'Axe Beak Wing', + '44072': 'Alexandrian Axe Beak Wing', '44073': 'Boiled Alpaca Steak', '44074': 'Mezcal-marinated Swampmonk', '44076': 'Acqua Pazza', @@ -15577,7 +15577,7 @@ export const ffxivItemsMap: Record<string, string> = { '44085': 'Sweetmuffin', '44086': 'Creamy Hot Chocolate', '44087': 'Creamy Alpaca Pasta', - '44088': 'Gateau Au Chocolat', + '44088': 'Gateau au Chocolat', '44089': 'Tender Shortcake', '44090': 'Broccoli and Spinach Saute', '44091': 'Rroneek Steak', @@ -15624,7 +15624,7 @@ export const ffxivItemsMap: Record<string, string> = { '44143': "Potsworn's Abrasive", '44144': 'Pelupelu Yarn', '44145': 'Purussaurus Skin', - '44146': 'Glossy Dried Aether', + '44146': 'Glossy Dried Ether', '44147': 'Maraging Steel Ingot', '44148': 'Sterling Silver Ingot', '44149': 'Ipe Lumber', @@ -15698,5 +15698,210 @@ export const ffxivItemsMap: Record<string, string> = { '44322': 'Roads Forsaken Orchestrion Roll', '44323': 'Pathmaker Orchestrion Roll', '44326': 'Subterranean Drop Orchestrion Roll', - '44327': "Tinkerer's Treasure Trove Orchestrion Roll" + '44327': "Tinkerer's Treasure Trove Orchestrion Roll", + '44339': "Icuvlo's Barter", + '44340': 'Moongripper', + '44341': 'Cazuela Crab', + '44342': 'Stardust Sleeper', + '44343': 'Ilyon Asoh Cichlid', + '44344': 'Pixel Loach', + '44345': 'Hwittayoanaan Cichlid', + '44346': 'Thunderswift Trout', + '44347': 'Cloudsail', + '44365': 'Bayside Bevy Painting', + '44366': 'Resplendent Quarter Painting', + '44367': 'High Tide Harbor Painting', + '44368': "For'ard Cabins Painting", + '44369': "Hunu'iliy Painting", + '44370': 'Wachunpelo Painting', + '44371': "Miplu's Mate Garden Painting", + '44372': 'Shades of Grief Painting', + '44373': 'Naryor Gorna Painting', + '44374': 'Chirwagur Saltern Painting', + '44375': 'Worqor Lar Dor Painting', + '44376': 'House of Winds High Painting', + '44377': 'Cave Kikitola Painting', + '44378': 'Breath Between Painting', + '44379': 'Kozanuakiy Painting', + '44380': 'Earthenshire Painting', + '44381': 'Marsh Ligaka Painting', + '44382': "Iq Br'aax Painting", + '44383': 'Iq Rrax Tsoly Painting', + '44384': "Xobr'it Cinderfield Painting", + '44385': 'Choliselvaas Painting', + '44386': 'Ja Tiika Heartland Painting', + '44387': 'Tree of Living Light Painting', + '44388': 'Hhusatahwi Painting', + '44389': 'Mehwahhetsoan Painting', + '44390': 'Lake Toari Painting', + '44391': "Pyaayehe'pya Painting", + '44392': 'Mount Loazensasaya Painting', + '44393': 'Resolution Painting', + '44394': 'Residential Sector Painting', + '44395': 'Mosaic Painting', + '44396': 'True Vue Painting', + '44397': 'Nexus Arcade Painting', + '44398': 'Thunderyards Painting', + '44399': 'Outskirts Painting', + '44400': 'Everkeep Painting', + '44401': 'Crackling Chasm Painting', + '44402': 'Nameslates Painting', + '44403': 'Archeo Alexandria Painting', + '44404': 'Meso Terminal Painting', + '44405': 'Canal Town Painting', + '44406': 'Yesterland Painting', + '44407': 'Windspath Gardens Painting', + '44408': 'Asyle Volcane Painting', + '44409': 'Steps of the Speaker Painting', + '44411': "Everseeker's Saw", + '44412': "Everseeker's Cross-pein Hammer", + '44413': "Everseeker's Raising Hammer", + '44414': "Everseeker's Lapidary Hammer", + '44415': "Everseeker's Creasing Knife", + '44416': "Everseeker's Needle", + '44417': "Everseeker's Alembic", + '44418': "Everseeker's Bomb Frypan", + '44419': "Everseeker's Pickaxe", + '44420': "Everseeker's Hatchet", + '44421': "Everseeker's Fishing Rod", + '44422': "Everseeker's Claw Hammer", + '44423': "Everseeker's File", + '44424': "Everseeker's Pliers", + '44425': "Everseeker's Grinding Wheel", + '44426': "Everseeker's Awl", + '44427': "Everseeker's Spinning Wheel", + '44428': "Everseeker's Mortar", + '44429': "Everseeker's Culinary Knife", + '44430': "Everseeker's Sledgehammer", + '44431': "Everseeker's Garden Scythe", + '44432': "Everseeker's Headgear of Crafting", + '44433': "Everseeker's Top of Crafting", + '44434': "Everseeker's Armguards of Crafting", + '44435': "Everseeker's Slops of Crafting", + '44436': "Everseeker's Workboots of Crafting", + '44437': "Everseeker's Goggles of Gathering", + '44438': "Everseeker's Coat of Gathering", + '44439': "Everseeker's Gloves of Gathering", + '44440': "Everseeker's Kecks of Gathering", + '44441': "Everseeker's Shoes of Gathering", + '44486': 'Toco Toquito', + '44487': 'Shshuye', + '44488': 'Spinettesaurus', + '44490': 'Tin Soldier S3', + '44494': 'The Lawnblazer', + '44495': 'Eternal Barding', + '44502': 'Barreltender Whistle', + '44506': 'Modern Aesthetics - Doing the Wave', + '44650': "Wintertide Trapper's Hat", + '44651': 'Wintertide Blouson', + '44652': 'Wintertide Culottes', + '44653': 'Wintertide Shoes', + '44654': 'Wintertide Sheath Skirt', + '44823': 'Red Cattleya Corsage', + '44824': 'Blue Cattleya Corsage', + '44825': 'Yellow Cattleya Corsage', + '44826': 'Green Cattleya Corsage', + '44827': 'Orange Cattleya Corsage', + '44828': 'Purple Cattleya Corsage', + '44829': 'White Cattleya Corsage', + '44830': 'Black Cattleya Corsage', + '44831': 'Rainbow Cattleya Corsage', + '44832': 'Cattleya Seeds', + '44833': 'Red Cattleyas', + '44834': 'Blue Cattleyas', + '44835': 'Yellow Cattleyas', + '44836': 'Green Cattleyas', + '44837': 'Orange Cattleyas', + '44838': 'Purple Cattleyas', + '44839': 'White Cattleyas', + '44840': 'Black Cattleyas', + '44841': 'Rainbow Cattleyas', + '44842': 'Ceviche', + '44843': 'Cloudsail Meuniere', + '44844': 'Optical Fibergrass', + '44845': 'Alexandrian Ore', + '44846': 'Optical Nanofiber', + '44847': 'Alexandrian Plate', + '44848': 'Condensed Solution', + '44849': 'Eternal Queensguard', + '44870': 'Pure White Interior Wall', + '44871': 'Light Wood Flooring', + '44872': 'Flat Ceiling Lamp', + '44873': 'Wood-fired Range', + '44874': 'Holodisplay', + '44875': 'Storybook Bench', + '44876': "Sweet Dreamer's Stall", + '44877': 'Frilly Curtains', + '44878': "Botanist's Wall Frames", + '44879': 'Botanical Loft', + '44880': 'Patchwork Sofa', + '44881': "Gleaner's Knapsack", + '44882': 'Popoto Chips', + '44883': 'Investigation Map', + '44884': 'Apothecary Mortar', + '44885': 'Grassy Rug', + '44886': 'Royal Bed', + '44887': 'Eternal Cenotaph', + '44895': 'Climbing Wall Partition', + '44897': 'Imitation Closet', + '44898': 'Fighting Ring', + '44900': 'Laboratory Counter', + '44901': 'Kugane-yaki', + '44902': 'Classroom Desk', + '44903': 'Classroom Chair', + '44904': 'Classroom Blackboard', + '44905': 'Contemporary Iron Shelf', + '44911': 'Weathered Porthole', + '44912': 'Rail-and-stile Partition', + '44913': 'Tea Caddy Cabinet', + '44914': 'Indoor Awning', + '44928': 'Far Eastern Gazebo', + '44929': 'Square Stepping Stones', + '44934': 'Ja Ja Gural Tree', + '44958': 'Venturous Kamuy Flute', + '44959': 'Magicked Prism (Barreltender)', + '44970': 'Cushioned Bench', + '44971': 'Tropical Canopy Bed', + '44976': 'Adamant Bastard Sword', + '44977': 'Adamant Knuckles', + '44978': 'Adamant Battleaxe', + '44979': 'Adamant Partisan', + '44980': 'Adamant Longbow', + '44981': 'Adamant Cleavers', + '44982': 'Adamant Guillotine', + '44983': 'Adamant Pistol', + '44984': 'Adamant Cane', + '44985': 'Adamant Rod', + '44986': 'Adamant Index', + '44987': 'Adamant Codex', + '44988': 'Adamant Planisphere', + '44989': 'Adamant Blade', + '44990': 'Adamant Hanger', + '44991': 'Adamant Gunblade', + '44992': 'Adamant Chakrams', + '44993': 'Adamant Scutum', + '44994': "Paladin's Adamant Arms", + '45005': 'The Faces We Wear - Cat Eye Glasses', + '45006': 'The Faces We Wear - Slim Frame Glasses', + '45007': 'Unbroken Vessels Orchestrion Roll', + '45016': 'Paved in Solitude Orchestrion Roll', + '45017': 'Paved with Resolve Orchestrion Roll', + '45024': 'The Forgotten City - Tavnazian Safehold Orchestrion Roll', + '45025': 'Shadow Lord Orchestrion Roll', + '45026': 'Depths of the Soul Orchestrion Roll', + '45027': 'Battle in the Dungeon Orchestrion Roll', + '45028': 'Battle in the Dungeon #3 Orchestrion Roll', + '45029': "Tu'Lia Orchestrion Roll", + '45030': 'Battle Theme from FINAL FANTASY XI Orchestrion Roll', + '45031': 'Fighters of the Crystal Orchestrion Roll', + '45032': 'Castle Zvahl Orchestrion Roll', + '45033': 'Awakening Orchestrion Roll', + '45034': "Vana'diel March Orchestrion Roll", + '45037': "Nature's Bounty Orchestrion Roll", + '45038': 'Cast Stones in Shadow Orchestrion Roll', + '45039': 'Faded Copy of Paved in Solitude', + '45070': 'Figmental Weapon Coffer', + '45072': 'Corduroy Felt', + '45073': 'Aromatic Wood Strips', + '45579': 'Royal Damask' } diff --git a/app/utils/items/wowItems.ts b/app/utils/items/wowItems.ts index 8f05b8d1..d0d5c847 100644 --- a/app/utils/items/wowItems.ts +++ b/app/utils/items/wowItems.ts @@ -495,6 +495,7 @@ export const wowItemsMap: Record<string, string> = { '771': 'Chipped Boar Tusk', '774': 'Malachite', '776': 'Vendetta', + '777': 'Prowler Teeth', '778': 'Kobold Excavation Pick', '779': 'Shiny Seashell', '781': 'Stone Gnoll Hammer', @@ -525,8 +526,8 @@ export const wowItemsMap: Record<string, string> = { '817': 'Wild Jade Hatchling', '818': 'Wild Golden Hatchling', '819': 'Wild Crimson Hatchling', - '820': 'Singing Cricket', - '821': 'Feral Vermling', + '820': 'Slicer Blade', + '821': 'Riverpaw Leather Vest', '823': 'Highlands Skunk', '826': 'Brutish Riverpaw Axe', '827': 'Wicked Blackjack', @@ -651,7 +652,7 @@ export const wowItemsMap: Record<string, string> = { '1165': 'Nexus Whelpling', '1166': 'Kun-Lai Runt', '1167': 'Emerald Proto-Whelp', - '1168': 'Murki', + '1168': 'Skullflame Shield', '1169': 'Blackskull Shield', '1174': 'Gusting Grimoire', '1175': 'Thundertail Flapper', @@ -779,7 +780,7 @@ export const wowItemsMap: Record<string, string> = { '1388': 'Crooked Staff', '1389': 'Kobold Mining Mallet', '1391': 'Riverpaw Mystic Staff', - '1394': 'Weebomination', + '1394': 'Driftwood Club', '1395': "Lil' Leftovers", '1396': 'Crazy Carrot', '1399': 'Magic Candle', @@ -1158,7 +1159,7 @@ export const wowItemsMap: Record<string, string> = { '1932': 'Nightmare Lasher', '1933': 'Nightmare Treant', '1934': 'Benax', - '1935': 'Squirky', + '1935': "Assassin's Blade", '1936': 'Mischief', '1937': 'Wondrous Wisdomball', '1938': 'Rescued Fawn', @@ -1194,7 +1195,7 @@ export const wowItemsMap: Record<string, string> = { '1976': 'Sharptalon Hatchling', '1977': 'Bloodgazer Hatchling', '1978': 'Dutiful Squire', - '1979': 'Dutiful Gruntling', + '1979': 'Wall of the Dead', '1980': 'Underworld Band', '1981': 'Icemail Jerkin', '1982': 'Nightblade', @@ -1274,7 +1275,7 @@ export const wowItemsMap: Record<string, string> = { '2085': 'Drafty', '2086': 'Blazehound', '2087': 'Cinderweb Recluse', - '2088': 'Surger', + '2088': 'Long Crawler Limb', '2089': 'Infernal Pyreclaw', '2090': 'Faceless Mindlasher', '2091': 'Corrupted Blood', @@ -2174,6 +2175,7 @@ export const wowItemsMap: Record<string, string> = { '3262': 'Violet Violence', '3263': 'Secretive Frogduck', '3264': 'Crystalline Mini-Monster', + '3265': 'Mister Muskoxeles', '3266': 'Black Slyvern Pup', '3269': 'Azure Frillfish', '3270': "Jean's Lucky Fish", @@ -2391,7 +2393,7 @@ export const wowItemsMap: Record<string, string> = { '3544': 'Shalewing Devourer', '3545': 'Salverun', '3546': 'Skaarn', - '3547': 'Mikah', + '3547': 'Jade Cragviper', '3548': 'Aquapo', '3549': 'Heartseeker Moth', '3550': 'Undermoth', @@ -2925,7 +2927,6 @@ export const wowItemsMap: Record<string, string> = { '4455': 'Rak-Ush Threadling', '4456': 'Arachnoid Hatchling', '4457': 'Chitin Burrower', - '4458': 'Illskitter', '4459': 'Brittle Dragon Bone', '4460': 'Arathi Chicken', '4461': 'Greenlands Chicken', @@ -2943,7 +2944,7 @@ export const wowItemsMap: Record<string, string> = { '4474': "Anub'Rekyute", '4476': 'Itchbite', '4477': 'Verdant Scootlefish', - '4478': 'Caustic Oozeling', + '4478': 'Iridescent Scale Leggings', '4479': 'Burning Charm', '4480': 'Shadowy Oozeling', '4481': 'Voidling Ooze', @@ -2961,7 +2962,6 @@ export const wowItemsMap: Record<string, string> = { '4498': 'Ebon Ploughworm', '4499': 'Common Ploughworm', '4500': "Lil' Bonechewer", - '4502': 'Kaheti Bull Worm', '4506': 'Violet Sporbit', '4510': 'Winged Arachnoid', '4511': 'Venomwing', @@ -3034,8 +3034,8 @@ export const wowItemsMap: Record<string, string> = { '4589': 'Gale', '4590': 'Flash', '4591': 'Thundo', - '4592': 'Longjaw Mud Snapper', - '4593': 'Bristle Whisker Catfish', + '4592': 'Misty', + '4593': 'Craggles', '4594': 'Dalaran Sewer Turtle', '4595': "Lil' Flameo", '4596': 'Faithful Dog', @@ -3053,11 +3053,16 @@ export const wowItemsMap: Record<string, string> = { '4608': 'Raw Black Truffle', '4609': 'Recipe: Barbecued Buzzard Wing', '4611': 'Blue Pearl', + '4614': 'Gizmo the Pure', '4615': 'Parrlok', '4616': 'Gummi', + '4617': 'Thrillbot 9000', + '4618': 'Chillbot 9000', '4623': 'Lesser Stoneshield Potion', '4624': 'Recipe: Lesser Stoneshield Potion', '4625': 'Firebloom', + '4629': 'Brrrgl', + '4630': "Gill'el", '4639': 'Enchanted Sea Kelp', '4655': 'Giant Clam Meat', '4656': 'Small Pumpkin', @@ -3070,20 +3075,22 @@ export const wowItemsMap: Record<string, string> = { '4665': 'Burnt Cloak', '4666': 'Burnt Leather Belt', '4668': 'Battle Chain Cloak', - '4669': 'Battle Chain Girdle', + '4669': 'Bluedoo', '4671': 'Ancestral Cloak', '4672': 'Ancestral Belt', '4674': 'Tribal Cloak', '4675': 'Tribal Belt', '4677': 'Veteran Cloak', - '4678': 'Veteran Girdle', + '4678': "Lil'Doomy", + '4679': "Lil'Kaz", '4680': 'Brackwater Cloak', '4681': 'Brackwater Girdle', + '4682': 'Reven', '4683': 'Spellbinder Cloak', '4684': 'Spellbinder Belt', - '4686': 'Barbaric Cloth Cloak', + '4686': 'Specter', '4687': 'Barbaric Cloth Belt', - '4689': 'Hunting Cloak', + '4689': 'Karazhan Syphoner', '4690': 'Hunting Belt', '4692': 'Ceremonial Cloak', '4693': 'Ceremonial Leather Belt', @@ -5906,6 +5913,7 @@ export const wowItemsMap: Record<string, string> = { '13903': '22 Pound Salmon', '13904': '25 Pound Salmon', '13905': '29 Pound Salmon', + '13906': '32 Pound Salmon', '13907': '7 Pound Lobster', '13908': '9 Pound Lobster', '13909': '12 Pound Lobster', @@ -6290,6 +6298,7 @@ export const wowItemsMap: Record<string, string> = { '14482': 'Pattern: Cindercloth Cloak', '14483': 'Pattern: Felcloth Pants', '14484': 'Pattern: Brightcloth Cloak', + '14485': 'Pattern: Wizardweave Leggings', '14488': 'Pattern: Runecloth Boots', '14489': 'Pattern: Frostweave Pants', '14490': 'Pattern: Cindercloth Pants', @@ -6300,6 +6309,7 @@ export const wowItemsMap: Record<string, string> = { '14497': 'Pattern: Mooncloth Leggings', '14498': 'Pattern: Runecloth Headband', '14499': 'Pattern: Mooncloth Bag', + '14500': 'Pattern: Wizardweave Robe', '14501': 'Pattern: Mooncloth Vest', '14504': 'Pattern: Runecloth Shoulders', '14505': 'Pattern: Wizardweave Turban', @@ -8557,6 +8567,7 @@ export const wowItemsMap: Record<string, string> = { '23147': 'Design: Sovereign Shadow Draenite', '23148': 'Design: Brilliant Blood Garnet', '23151': 'Design: Rigid Azure Moonstone', + '23152': 'Design: Solid Azure Moonstone', '23153': 'Design: Sparkling Azure Moonstone', '23154': 'Design: Stormy Azure Moonstone', '23197': 'Idol of the Moon', @@ -8870,6 +8881,7 @@ export const wowItemsMap: Record<string, string> = { '24186': 'Copper Powder', '24188': 'Tin Powder', '24189': 'Crystalline Fragments', + '24190': 'Iron Powder', '24192': 'Design: Delicate Living Ruby', '24193': 'Design: Bold Living Ruby', '24194': 'Design: Delicate Living Ruby', @@ -10071,6 +10083,7 @@ export const wowItemsMap: Record<string, string> = { '28537': 'Wildhammer Throwing Axe', '28539': 'Razor-Edged Boomerang', '28540': 'Arakkoa Talon-Axe', + '28541': 'Sawshrike', '28542': 'Heartseeker Knives', '28543': 'Dreghood Throwing Axe', '28544': "Assassin's Shuriken", @@ -13550,6 +13563,7 @@ export const wowItemsMap: Record<string, string> = { '44696': "Giant's Toewrap", '44697': "Val'kyr Vestments", '44698': 'Intravenous Healing Potion', + '44699': 'Broken Voice Modulator', '44703': 'Dark Herring', '44708': "Dirkee's Superstructure", '44709': 'Tome of Polymorph: Black Cat', @@ -15817,6 +15831,7 @@ export const wowItemsMap: Record<string, string> = { '67105': 'Elementbinder Grips', '67106': 'Robes of Broken Dreams', '67109': 'Gauntlets of Chattering Valves', + '67110': "Goblin Surgeon's Kit", '67111': 'Soulsurge Necklace', '67112': "Brittany's Ceremonial Spaulders", '67113': "Medic's Bloodstained Sandals", @@ -16064,6 +16079,7 @@ export const wowItemsMap: Record<string, string> = { '70005': 'Bloodthirsty Pyrium Boots', '70006': 'Bloodthirsty Pyrium Gauntlets', '70007': 'Bloodthirsty Pyrium Helm', + '70008': 'Bloodthirsty Pyrium Legguards', '70009': 'Bloodthirsty Pyrium Shoulders', '70010': 'Bloodthirsty Pyrium Belt', '70011': 'Bloodthirsty Pyrium Bracers', @@ -16096,6 +16112,7 @@ export const wowItemsMap: Record<string, string> = { '70041': 'Bloodthirsty Dragonscale Helm', '70043': 'Bloodthirsty Dragonscale Shoulders', '70044': 'Bloodthirsty Charscale Belt', + '70046': 'Bloodthirsty Charscale Bracers', '70047': 'Bloodthirsty Charscale Chest', '70048': 'Bloodthirsty Charscale Gloves', '70049': 'Bloodthirsty Charscale Helm', @@ -16104,6 +16121,7 @@ export const wowItemsMap: Record<string, string> = { '70054': 'Bloodthirsty Fireweave Bracers', '70055': 'Bloodthirsty Fireweave Cowl', '70056': 'Bloodthirsty Fireweave Gloves', + '70057': 'Bloodthirsty Fireweave Pants', '70058': 'Bloodthirsty Fireweave Robe', '70059': 'Bloodthirsty Fireweave Shoulders', '70060': 'Bloodthirsty Embersilk Robe', @@ -17862,6 +17880,7 @@ export const wowItemsMap: Record<string, string> = { '89683': 'Hozen Cuervo', '89737': 'Enchant Shield - Greater Parry', '89739': 'Stripped Gear', + '89747': 'Wyvern Poison Gland', '89868': 'Mark of the Cheetah', '89888': 'Jade Blossom Firework', '89893': 'Autumn Flower Firework', @@ -19408,6 +19427,7 @@ export const wowItemsMap: Record<string, string> = { '113139': 'Visions Joker', '113140': 'War Joker', '113142': 'Moon Joker', + '113144': 'Unstable Weapon Crystal', '113183': 'Unstable Greater Weapon Crystal', '113261': 'Sorcerous Fire', '113262': 'Sorcerous Water', @@ -19865,6 +19885,7 @@ export const wowItemsMap: Record<string, string> = { '118008': "Hemet's Heartseeker", '118015': 'Enchant Weapon - Mark of Bleeding Hollow', '118061': 'Glyph of the Sun', + '118067': 'Old Chain Link', '118101': 'Zangar Spore', '118105': 'Seaborne Spore', '118197': 'Auction Memory Socket', @@ -23347,6 +23368,7 @@ export const wowItemsMap: Record<string, string> = { '168499': 'Superior Battle Potion of Stamina', '168500': 'Superior Battle Potion of Strength', '168501': 'Superior Steelskin Potion', + '168502': 'Potion of Reconstitution', '168506': 'Potion of Focused Resolve', '168529': 'Potion of Empowered Proximity', '168583': 'Widowbloom', @@ -24643,6 +24665,7 @@ export const wowItemsMap: Record<string, string> = { '180404': 'Embertone Lotion', '180405': 'Rusty Gargon Chain', '180406': 'Gargon Treat', + '180407': 'Empty Brandy Bottle', '180408': 'Empty Brandy Phial', '180409': 'Crimson Altar Wine', '180410': 'Simmiring Draft of Shadows', @@ -26957,6 +26980,7 @@ export const wowItemsMap: Record<string, string> = { '201164': 'Titan Runestone', '201165': 'Burnished Bauble', '201166': 'Guilded Hilt', + '201167': 'Stone Calendar', '201168': 'Untainted Scales', '201170': 'Knucklebones', '201171': 'Perfectly Round Stone', @@ -28005,14 +28029,17 @@ export const wowItemsMap: Record<string, string> = { '210938': 'Ironclaw Ore --- Quality 3', '210939': 'Null Stone', '210975': 'Date Simulation Modulator', + '210981': "Kriegval's Helm", '211102': 'Perfect Deadly Sapphire', '211103': 'Perfect Hungering Ruby', '211108': 'Perfect Masterful Amethyst', '211110': 'Perfect Quick Topaz', + '211209': 'Suspicious Candle', '211270': 'Pristine Core Leather', '211383': 'Luvkip', '211400': 'Glyph of the Lunar Chameleon', '211474': 'Shadowblind Grouper', + '211481': 'Stomping Shoes', '211536': "Draconic Combatant's Jeweled Signet", '211537': "Draconic Combatant's Jeweled Amulet", '211538': "Draconic Combatant's Resilient Boots", @@ -28251,6 +28278,8 @@ export const wowItemsMap: Record<string, string> = { '212771': 'Charred Snail Shells', '212772': 'False Silver Coin', '212774': 'Sharpened Shalewing Bones', + '212868': 'Precious Ore', + '213000': 'Holy Flamethrower Torch', '213026': 'Massive Drakonid Brush', '213027': 'Chipped Drakonid Cup', '213028': 'Cracked Drafting Compass', @@ -28465,6 +28494,10 @@ export const wowItemsMap: Record<string, string> = { '215145': 'Remembrance Stone', '215147': 'Beautification Iris', '215236': 'Vicious Bloodstone', + '215466': 'Sanctified Supplies', + '216420': 'Signal Flare', + '216433': 'Stolen Relic', + '216772': 'Whispering Explosives', '217040': 'Kobold Earwax', '217113': 'Cubic Blasphemia --- Quality 1', '217114': 'Cubic Blasphemia --- Quality 2', @@ -28518,13 +28551,21 @@ export const wowItemsMap: Record<string, string> = { '217169': 'Cloak of Beards', '217170': 'Backup Candles', '217171': 'CANDLE KING DIARY', + '217387': 'Princess Pumpkin', '217707': 'Imperfect Null Stone', + '217715': 'Key Scroll', + '217895': 'Pheromone Bottle', '217896': 'Cinderbrew Mead', '217897': 'Volatile Pheromone', '217958': 'Used Socks', '217959': 'Incomplete Painting', '217962': 'Dud Bomb', + '217965': 'Repair Kit', '217969': 'Bomb Debris', + '217999': 'Tasty Mussel', + '218000': 'Sack of Spices', + '218002': 'Priceless Pumpkin', + '218122': 'Stolen Relic', '218269': 'Draconic Tome of Awakening', '218336': 'Kaheti Swarm Chitin', '218337': 'Honed Bone Shards', @@ -28712,6 +28753,7 @@ export const wowItemsMap: Record<string, string> = { '220300': 'Translucent Wing', '220301': 'Crystallized Honey', '220302': 'Fractured Casing', + '220303': 'Decayed Flesh', '220307': 'Moth-Ridden Robe', '220308': 'Moth-Ridden Slippers', '220309': 'Moth-Ridden Mitts', @@ -28753,12 +28795,16 @@ export const wowItemsMap: Record<string, string> = { '220380': 'Immature Spiderling', '220438': 'Root-Staff Splinter', '220439': 'Half-Eaten Fish', + '220440': 'Kelp Necklace', '220441': 'Hardened Pearl', '220442': 'Weighty Shovel', '220443': 'Desecrated Arathi Tinderbox', '220444': 'Gnawed Spine', + '220445': 'Illusionary Charm', + '220447': 'Broken Trident Prong', '220448': 'Cerulean Orb', '220484': 'Bowl of Pulsing Goo', + '220486': 'Collection of Shiny Shells', '220491': 'Wicked Blade Shard', '220509': 'Gossamer Web', '221504': 'Elemental Pearl', @@ -29620,6 +29666,7 @@ export const wowItemsMap: Record<string, string> = { '225679': 'Design: Enduring Bloodstone', '225680': 'Design: Cognitive Bloodstone', '225681': 'Design: Determined Bloodstone', + '225719': "Light's Mantle", '225720': "Web Acolyte's Hood", '225721': 'Prime Slime Slippers', '225722': 'Adorned Lynxborne Pauldrons', @@ -29745,6 +29792,14 @@ export const wowItemsMap: Record<string, string> = { '226202': 'Echoing Flux', '226204': 'Fresh Parchment', '226205': 'Distilled Algari Freshwater', + '226222': 'Webbed Hookshot', + '226232': 'Green Hills of Stranglethorn - Page 6', + '226233': 'Green Hills of Stranglethorn - Page 11', + '226234': 'Green Hills of Stranglethorn - Page 16', + '226235': 'Green Hills of Stranglethorn - Page 18', + '226236': 'Green Hills of Stranglethorn - Page 21', + '226237': 'Green Hills of Stranglethorn - Page 25', + '226238': 'Green Hills of Stranglethorn - Page 27', '226524': 'Partially-Charged Hologem', '226643': "Plans: Beledar's Bulwark", '226681': 'Sizzling Cinderpollen', @@ -29879,6 +29934,7 @@ export const wowItemsMap: Record<string, string> = { '228921': "Griftah's Heavy-Duty Embellishing Powder", '228930': 'Adorning Ribbon', '228956': 'Junk Bucket', + '228990': 'Bottle of Steam', '229061': "Nisa's Spare Belt", '229062': "Nisa's Spare Coronet", '229063': "Nisa's Spare Wristguards", @@ -29929,5 +29985,38 @@ export const wowItemsMap: Record<string, string> = { '229161': "Lamplighter's Chopper", '229162': "Lamplighter's Sword", '229163': "Lamplighter's Blade", - '229371': 'Companion Experience' + '229199': 'Two of Air', + '229207': 'Two of Fire', + '229208': 'Three of Fire', + '229210': 'Five of Fire', + '229211': 'Six of Fire', + '229212': 'Seven of Fire', + '229213': 'Eight of Fire', + '229216': 'Three of Frost', + '229219': 'Six of Frost', + '229225': 'Four of Earth', + '229371': 'Companion Experience', + '229413': '"Dogg-Saron" Costume', + '231365': 'Karazhan Syphoner', + '231495': 'Ribsplitter', + '231496': "The Judge's Gavel", + '231497': 'Searing Needle', + '231498': 'Spire of the Stoneshaper', + '231499': 'Doomforged Straightedge', + '231500': 'Funeral Pyre Vestment', + '231501': 'Aristocratic Cuffs', + '231502': "Mar Alom's Grip", + '231503': 'Braincage', + '231504': 'Runed Golem Shackles', + '231505': 'Stoneshield Cloak', + '231506': 'Blisterbane Wrap', + '231507': "Battlechaser's Greaves", + '232374': 'Greasy Links', + '232375': 'Moon Bread', + '232376': 'Cherry Bombs', + '232377': "Pappy Thunderbrew's Cough Syrup", + '232378': "Jenkins' No Nonsense Fried Chicken", + '232380': "Brivelthwerp's Sassafras Float", + '232385': 'Elekk Ear', + '232521': 'Glyph of Arcane Familiar' }