diff --git a/app/routes/allagan-data/index.tsx b/app/routes/allagan-data.tsx
similarity index 100%
rename from app/routes/allagan-data/index.tsx
rename to app/routes/allagan-data.tsx
diff --git a/app/routes/ffxiv/marketshare/queries.tsx b/app/routes/ffxiv.marketshare.queries.tsx
similarity index 100%
rename from app/routes/ffxiv/marketshare/queries.tsx
rename to app/routes/ffxiv.marketshare.queries.tsx
diff --git a/app/routes/ffxiv/marketshare/index.tsx b/app/routes/ffxiv.marketshare.tsx
similarity index 100%
rename from app/routes/ffxiv/marketshare/index.tsx
rename to app/routes/ffxiv.marketshare.tsx
diff --git a/app/routes/ffxiv/self-purchase/index.tsx b/app/routes/ffxiv.self-purchase.tsx
similarity index 100%
rename from app/routes/ffxiv/self-purchase/index.tsx
rename to app/routes/ffxiv.self-purchase.tsx
diff --git a/app/routes/ffxiv.tsx b/app/routes/ffxiv.tsx
deleted file mode 100644
index 02d8a893..00000000
--- a/app/routes/ffxiv.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Outlet } from '@remix-run/react'
-
-export default function FFXIV() {
- return (
-
-
-
- )
-}
diff --git a/app/routes/options.tsx b/app/routes/options.tsx
index 9d23276e..d9ea23c8 100644
--- a/app/routes/options.tsx
+++ b/app/routes/options.tsx
@@ -1,9 +1,292 @@
-import { Outlet } from '@remix-run/react'
+import { CheckIcon } from '@heroicons/react/solid'
+import {
+ Form,
+ useActionData,
+ useLoaderData,
+ useTransition
+} from '@remix-run/react'
+import type { ActionFunction, LoaderFunction } from '@remix-run/cloudflare'
+import { json, redirect } from '@remix-run/cloudflare'
+import { z } from 'zod'
+import {
+ commitSession,
+ DATA_CENTER,
+ FF14_WORLD,
+ getSession,
+ getUserSessionData,
+ WOW_REALM_ID,
+ WOW_REALM_NAME,
+ WOW_REGION
+} from '~/sessions'
+import { Switch } from '@headlessui/react'
+import { classNames } from '~/utils'
+import { useDispatch } from 'react-redux'
+import {
+ setFFxivWorld,
+ setWoWRealmData,
+ toggleDarkMode
+} from '~/redux/reducers/userSlice'
+import { useTypedSelector } from '~/redux/useTypedSelector'
+import React, { useState } from 'react'
+import { validateServerAndRegion } from '~/utils/WoWServers'
+import { validateWorldAndDataCenter } from '~/utils/locations'
+import RegionAndServerSelect from '~/components/form/WoW/RegionAndServerSelect'
+import SelectDCandWorld from '~/components/form/select/SelectWorld'
+import type { WoWServerData, WoWServerRegion } from '~/requests/WoW/types'
+import { PageWrapper } from '~/components/Common'
-export default function Options() {
+export const validator = z.object({
+ data_center: z.string().min(1),
+ world: z.string().min(1),
+ region: z.union([z.literal('NA'), z.literal('EU')]),
+ homeRealm: z.string().min(1)
+})
+
+export const action: ActionFunction = async ({ request }) => {
+ const formData = await request.formData()
+ const formPayload = Object.fromEntries(formData)
+
+ const result = validator.safeParse(formPayload)
+
+ if (!result.success) {
+ return json(result)
+ }
+
+ const [homeRealmId, homeRealmName] = result.data.homeRealm.split('---')
+
+ const session = await getSession(request.headers.get('Cookie'))
+
+ const { server, region } = validateServerAndRegion(
+ result.data.region,
+ homeRealmId,
+ homeRealmName
+ )
+
+ const { data_center, world } = validateWorldAndDataCenter(
+ result.data.world,
+ result.data.data_center
+ )
+
+ session.set(DATA_CENTER, data_center)
+ session.set(FF14_WORLD, world)
+ session.set(WOW_REALM_ID, server.id)
+ session.set(WOW_REALM_NAME, server.name)
+ session.set(WOW_REGION, region)
+
+ // Set the new option, yeet back to index (but save against session data within the cookie)
+ return redirect('/', {
+ headers: {
+ 'Set-Cookie': await commitSession(session)
+ }
+ })
+}
+
+const OptionSection = ({
+ title,
+ description,
+ children,
+ hideHRule
+}: {
+ title: string
+ description: string
+ children: React.ReactNode
+ hideHRule?: boolean
+}) => {
return (
-
-
+
+
+ <>
+
+
+
+
+
+ {title}
+
+
+ {description}
+
+
+
+
+
+
+
+ {!hideHRule && (
+
+ )}
+ >
+
)
}
+
+export const loader: LoaderFunction = async ({ request }) => {
+ const session = await getSession(request.headers.get('Cookie'))
+ const { getWorld, getDataCenter, getWoWSessionData } =
+ await getUserSessionData(request)
+
+ const data_center = getDataCenter()
+ const world = getWorld()
+ const { server, region } = getWoWSessionData()
+
+ return json({
+ ...session.data,
+ data_center,
+ world,
+ wowRealm: server,
+ wowRegion: region
+ })
+}
+
+export default function Options() {
+ const data = useLoaderData()
+ const transition = useTransition()
+ const actionData = useActionData()
+
+ const dispatch = useDispatch()
+ const { darkmode } = useTypedSelector((state) => state.user)
+
+ const [ffxivWorld, setFfxivWorld] = useState<{
+ data_center: string
+ world: string
+ }>({ data_center: data.data_center, world: data.world })
+
+ const [wowRealm, setWoWRealm] = useState<{
+ server: WoWServerData
+ region: WoWServerRegion
+ }>({
+ region: data.wowRegion,
+ server: data.wowRealm
+ })
+
+ const handleDarkModeToggle = () => {
+ dispatch(toggleDarkMode())
+ }
+
+ return (
+
+
+
+ )
+}
diff --git a/app/routes/options/index.tsx b/app/routes/options/index.tsx
deleted file mode 100644
index 54fa6dc7..00000000
--- a/app/routes/options/index.tsx
+++ /dev/null
@@ -1,292 +0,0 @@
-import { CheckIcon } from '@heroicons/react/solid'
-import {
- Form,
- useActionData,
- useLoaderData,
- useTransition
-} from '@remix-run/react'
-import type { ActionFunction, LoaderFunction } from '@remix-run/cloudflare'
-import { json, redirect } from '@remix-run/cloudflare'
-import { z } from 'zod'
-import {
- commitSession,
- DATA_CENTER,
- FF14_WORLD,
- getSession,
- getUserSessionData,
- WOW_REALM_ID,
- WOW_REALM_NAME,
- WOW_REGION
-} from '~/sessions'
-import { Switch } from '@headlessui/react'
-import { classNames } from '~/utils'
-import { useDispatch } from 'react-redux'
-import {
- setFFxivWorld,
- setWoWRealmData,
- toggleDarkMode
-} from '~/redux/reducers/userSlice'
-import { useTypedSelector } from '~/redux/useTypedSelector'
-import React, { useState } from 'react'
-import { validateServerAndRegion } from '~/utils/WoWServers'
-import { validateWorldAndDataCenter } from '~/utils/locations'
-import RegionAndServerSelect from '~/components/form/WoW/RegionAndServerSelect'
-import SelectDCandWorld from '~/components/form/select/SelectWorld'
-import type { WoWServerData, WoWServerRegion } from '~/requests/WoW/types'
-import { PageWrapper } from '~/components/Common'
-
-export const validator = z.object({
- data_center: z.string().min(1),
- world: z.string().min(1),
- region: z.union([z.literal('NA'), z.literal('EU')]),
- homeRealm: z.string().min(1)
-})
-
-export const action: ActionFunction = async ({ request }) => {
- const formData = await request.formData()
- const formPayload = Object.fromEntries(formData)
-
- const result = validator.safeParse(formPayload)
-
- if (!result.success) {
- return json(result)
- }
-
- const [homeRealmId, homeRealmName] = result.data.homeRealm.split('---')
-
- const session = await getSession(request.headers.get('Cookie'))
-
- const { server, region } = validateServerAndRegion(
- result.data.region,
- homeRealmId,
- homeRealmName
- )
-
- const { data_center, world } = validateWorldAndDataCenter(
- result.data.world,
- result.data.data_center
- )
-
- session.set(DATA_CENTER, data_center)
- session.set(FF14_WORLD, world)
- session.set(WOW_REALM_ID, server.id)
- session.set(WOW_REALM_NAME, server.name)
- session.set(WOW_REGION, region)
-
- // Set the new option, yeet back to index (but save against session data within the cookie)
- return redirect('/', {
- headers: {
- 'Set-Cookie': await commitSession(session)
- }
- })
-}
-
-const OptionSection = ({
- title,
- description,
- children,
- hideHRule
-}: {
- title: string
- description: string
- children: React.ReactNode
- hideHRule?: boolean
-}) => {
- return (
-
-
- <>
-
-
-
-
-
- {title}
-
-
- {description}
-
-
-
-
-
-
-
- {!hideHRule && (
-
- )}
- >
-
-
- )
-}
-
-export const loader: LoaderFunction = async ({ request }) => {
- const session = await getSession(request.headers.get('Cookie'))
- const { getWorld, getDataCenter, getWoWSessionData } =
- await getUserSessionData(request)
-
- const data_center = getDataCenter()
- const world = getWorld()
- const { server, region } = getWoWSessionData()
-
- return json({
- ...session.data,
- data_center,
- world,
- wowRealm: server,
- wowRegion: region
- })
-}
-
-export default function Options() {
- const data = useLoaderData()
- const transition = useTransition()
- const actionData = useActionData()
-
- const dispatch = useDispatch()
- const { darkmode } = useTypedSelector((state) => state.user)
-
- const [ffxivWorld, setFfxivWorld] = useState<{
- data_center: string
- world: string
- }>({ data_center: data.data_center, world: data.world })
-
- const [wowRealm, setWoWRealm] = useState<{
- server: WoWServerData
- region: WoWServerRegion
- }>({
- region: data.wowRegion,
- server: data.wowRealm
- })
-
- const handleDarkModeToggle = () => {
- dispatch(toggleDarkMode())
- }
-
- return (
-
-
-
- )
-}
diff --git a/app/routes/price-sniper.tsx b/app/routes/price-sniper.tsx
index a5c4f1d6..c1e4bdbe 100644
--- a/app/routes/price-sniper.tsx
+++ b/app/routes/price-sniper.tsx
@@ -1,9 +1,276 @@
-import { Outlet } from '@remix-run/react'
+import { useLoaderData } from '@remix-run/react'
+import { PageWrapper } from '~/components/Common'
+import SmallFormContainer from '~/components/form/SmallFormContainer'
+import type { LoaderFunction } from '@remix-run/cloudflare'
+import { json } from '@remix-run/cloudflare'
+import { getSession } from '~/sessions'
+import { validateWorldAndDataCenter } from '~/utils/locations'
+import { useState } from 'react'
+import { InputWithLabel } from '~/components/form/InputWithLabel'
+import Label from '~/components/form/Label'
+import HQCheckbox from '~/components/form/HQCheckbox'
+import CodeBlock from '~/components/Common/CodeBlock'
+import ItemSelect from '~/components/form/select/ItemSelect'
+
+export const loader: LoaderFunction = async ({ request }) => {
+ const session = await getSession(request.headers.get('Cookie'))
+ const { world, data_center } = validateWorldAndDataCenter(
+ session.get('world'),
+ session.get('data_center')
+ )
+ return json({ ...session.data, world, data_center })
+}
+
+interface Auction {
+ itemID: number
+ price: number
+ desiredState: 'above' | 'below'
+ hq: boolean
+}
+
+interface SubFormItem {
+ itemName: string
+ itemID: number
+ desiredState: 'above' | 'below'
+ hq: boolean
+ price: number
+}
+
+export const parseJSONInput = (input: Array
, isPrice = true) => {
+ if (!input.length) {
+ return ''
+ }
+
+ return `,\n "user_auctions": [${input
+ .map(({ itemID, price, desiredState, hq }) => {
+ return `\n { "itemID": ${itemID}, "${
+ isPrice ? 'price' : 'quantity'
+ }": ${price}, "desired_state": "${desiredState}", "hq": ${hq} }`
+ })
+ .join(',')}\n ]`
+}
+
+const Index = () => {
+ const { world } = useLoaderData()
+ const [jsonData, setJsonData] = useState<{
+ server: string
+ userAuctions: Array
+ }>({ server: world, userAuctions: [] })
+ const [subForm, setSubForm] = useState(null)
+ const [error, setError] = useState(undefined)
+ const [isPrice, setIsPrice] = useState(true)
+
+ const jsonToDisplay = `{\n "home_server": "${
+ jsonData.server
+ }"${parseJSONInput(jsonData.userAuctions, isPrice)}\n}`
+
+ const isPriceValue = isPrice ? 'price' : 'quantity'
+
+ const description = `To setup ${isPriceValue} alerts search, enter items and a desired
+ ${isPriceValue}. Copy and paste the below input for the Discord Bot in the
+ Saddlebage Exchange discord server.`
-export default function PriceSniper() {
return (
-
-
-
+
+ <>
+ {
+ e.preventDefault()
+ if (!subForm) {
+ setError('No item selected')
+ return
+ }
+
+ const userAuctions = [...jsonData.userAuctions]
+
+ const { itemID, price, desiredState, hq } = subForm
+
+ userAuctions.push({
+ itemID,
+ price,
+ desiredState,
+ hq
+ })
+
+ setJsonData({ ...jsonData, userAuctions })
+ setSubForm(null)
+ }}
+ buttonTitle="Add">
+
+
{
+ if (error) {
+ setError(undefined)
+ }
+ }}
+ onSelectChange={(item) => {
+ if (error) {
+ setError(undefined)
+ }
+
+ if (!item) {
+ setSubForm(null)
+ return
+ }
+
+ setSubForm({
+ itemName: item.name,
+ itemID: parseInt(item.id, 10),
+ desiredState: 'below',
+ hq: false,
+ price: 1000
+ })
+ }}
+ />
+
+ {subForm && (
+
+
+
{
+ const value = event.target.value
+ if (!value || isNaN(parseInt(value, 10))) {
+ setSubForm({
+ ...subForm,
+ price: 1000
+ })
+ return
+ }
+
+ const price = parseInt(value, 10)
+
+ if (price < 0) {
+ setSubForm({
+ ...subForm,
+ price: 0
+ })
+ return
+ }
+
+ setSubForm({
+ ...subForm,
+ price: parseInt(value, 10)
+ })
+ }}
+ onChange={(e) => {
+ setSubForm({
+ ...subForm,
+ price: parseInt(e.target.value, 10)
+ })
+ }}
+ />
+ ) => {
+ const value = (event.target as HTMLInputElement).value
+ if (value === 'above' || value === 'below') {
+ setSubForm({
+ ...subForm,
+ desiredState: value
+ })
+ }
+ }}>
+
+
+
+
+
+ setSubForm({
+ ...subForm,
+ hq: event.target.checked
+ })
+ }
+ />
+
+ )}
+
+
+
+
+
alert('Copied to clipboard!')}>
+
+ For the discord bot, use the{' '}
+ {isPrice ? (
+
+ commands '/ff price-register' or{' '}
+ '/ff price-update'
+
+ ) : (
+
+ command '/ff quantity-register'
+
+ )}
+ !
+
+
+
+ >
+
)
}
+
+export default Index
diff --git a/app/routes/price-sniper/index.tsx b/app/routes/price-sniper/index.tsx
deleted file mode 100644
index c1e4bdbe..00000000
--- a/app/routes/price-sniper/index.tsx
+++ /dev/null
@@ -1,276 +0,0 @@
-import { useLoaderData } from '@remix-run/react'
-import { PageWrapper } from '~/components/Common'
-import SmallFormContainer from '~/components/form/SmallFormContainer'
-import type { LoaderFunction } from '@remix-run/cloudflare'
-import { json } from '@remix-run/cloudflare'
-import { getSession } from '~/sessions'
-import { validateWorldAndDataCenter } from '~/utils/locations'
-import { useState } from 'react'
-import { InputWithLabel } from '~/components/form/InputWithLabel'
-import Label from '~/components/form/Label'
-import HQCheckbox from '~/components/form/HQCheckbox'
-import CodeBlock from '~/components/Common/CodeBlock'
-import ItemSelect from '~/components/form/select/ItemSelect'
-
-export const loader: LoaderFunction = async ({ request }) => {
- const session = await getSession(request.headers.get('Cookie'))
- const { world, data_center } = validateWorldAndDataCenter(
- session.get('world'),
- session.get('data_center')
- )
- return json({ ...session.data, world, data_center })
-}
-
-interface Auction {
- itemID: number
- price: number
- desiredState: 'above' | 'below'
- hq: boolean
-}
-
-interface SubFormItem {
- itemName: string
- itemID: number
- desiredState: 'above' | 'below'
- hq: boolean
- price: number
-}
-
-export const parseJSONInput = (input: Array, isPrice = true) => {
- if (!input.length) {
- return ''
- }
-
- return `,\n "user_auctions": [${input
- .map(({ itemID, price, desiredState, hq }) => {
- return `\n { "itemID": ${itemID}, "${
- isPrice ? 'price' : 'quantity'
- }": ${price}, "desired_state": "${desiredState}", "hq": ${hq} }`
- })
- .join(',')}\n ]`
-}
-
-const Index = () => {
- const { world } = useLoaderData()
- const [jsonData, setJsonData] = useState<{
- server: string
- userAuctions: Array
- }>({ server: world, userAuctions: [] })
- const [subForm, setSubForm] = useState(null)
- const [error, setError] = useState(undefined)
- const [isPrice, setIsPrice] = useState(true)
-
- const jsonToDisplay = `{\n "home_server": "${
- jsonData.server
- }"${parseJSONInput(jsonData.userAuctions, isPrice)}\n}`
-
- const isPriceValue = isPrice ? 'price' : 'quantity'
-
- const description = `To setup ${isPriceValue} alerts search, enter items and a desired
- ${isPriceValue}. Copy and paste the below input for the Discord Bot in the
- Saddlebage Exchange discord server.`
-
- return (
-
- <>
- {
- e.preventDefault()
- if (!subForm) {
- setError('No item selected')
- return
- }
-
- const userAuctions = [...jsonData.userAuctions]
-
- const { itemID, price, desiredState, hq } = subForm
-
- userAuctions.push({
- itemID,
- price,
- desiredState,
- hq
- })
-
- setJsonData({ ...jsonData, userAuctions })
- setSubForm(null)
- }}
- buttonTitle="Add">
-
-
{
- if (error) {
- setError(undefined)
- }
- }}
- onSelectChange={(item) => {
- if (error) {
- setError(undefined)
- }
-
- if (!item) {
- setSubForm(null)
- return
- }
-
- setSubForm({
- itemName: item.name,
- itemID: parseInt(item.id, 10),
- desiredState: 'below',
- hq: false,
- price: 1000
- })
- }}
- />
-
- {subForm && (
-
-
-
{
- const value = event.target.value
- if (!value || isNaN(parseInt(value, 10))) {
- setSubForm({
- ...subForm,
- price: 1000
- })
- return
- }
-
- const price = parseInt(value, 10)
-
- if (price < 0) {
- setSubForm({
- ...subForm,
- price: 0
- })
- return
- }
-
- setSubForm({
- ...subForm,
- price: parseInt(value, 10)
- })
- }}
- onChange={(e) => {
- setSubForm({
- ...subForm,
- price: parseInt(e.target.value, 10)
- })
- }}
- />
- ) => {
- const value = (event.target as HTMLInputElement).value
- if (value === 'above' || value === 'below') {
- setSubForm({
- ...subForm,
- desiredState: value
- })
- }
- }}>
-
-
-
-
-
- setSubForm({
- ...subForm,
- hq: event.target.checked
- })
- }
- />
-
- )}
-
-
-
-
-
alert('Copied to clipboard!')}>
-
- For the discord bot, use the{' '}
- {isPrice ? (
-
- commands '/ff price-register' or{' '}
- '/ff price-update'
-
- ) : (
-
- command '/ff quantity-register'
-
- )}
- !
-
-
-
- >
-
- )
-}
-
-export default Index
diff --git a/app/routes/undercut.tsx b/app/routes/undercut.tsx
index 9d23276e..4067e6c1 100644
--- a/app/routes/undercut.tsx
+++ b/app/routes/undercut.tsx
@@ -1,9 +1,128 @@
-import { Outlet } from '@remix-run/react'
+import { useActionData, useTransition } from '@remix-run/react'
+import { PageWrapper } from '~/components/Common'
+import { InputWithLabel } from '~/components/form/InputWithLabel'
+import SmallFormContainer from '~/components/form/SmallFormContainer'
+import type { ActionFunction } from '@remix-run/cloudflare'
+import { json } from '@remix-run/cloudflare'
+import GetSellerId from '~/requests/GetSellerId'
+import { getUserSessionData } from '~/sessions'
+import ItemSelect from '~/components/form/select/ItemSelect'
+import NoResults from '~/components/Common/NoResults'
+import Results from '../components/FFXIVResults/UndercutAlert/Results'
-export default function Options() {
+export const action: ActionFunction = async ({ request }) => {
+ const formData = await request.formData()
+ const session = await getUserSessionData(request)
+
+ const homeServer = session.getWorld()
+
+ const retainerName = formData.get('retainerName')
+
+ if (
+ !retainerName ||
+ typeof retainerName !== 'string' ||
+ !retainerName.length
+ ) {
+ return json({ exception: 'Missing retainer name' })
+ }
+
+ const itemIdData = formData.get('itemId')
+ if (
+ !itemIdData ||
+ typeof itemIdData !== 'string' ||
+ isNaN(parseInt(itemIdData))
+ ) {
+ return json({ exception: 'Missing item' })
+ }
+
+ const itemId = parseInt(itemIdData)
+
+ const response = await GetSellerId({
+ itemId,
+ homeServer,
+ retainerName
+ })
+ if (!response.ok) {
+ return json({ exception: response.statusText })
+ }
+
+ return json({ data: await response.json(), homeServer, itemId })
+}
+
+const Description = () => {
return (
-
-
-
+
+ To setup undercut alerts search{' '}
+
+ universalis
+ {' '}
+ for one of your retainers by name (with exact capitilization) and an item
+ they are selling on the Market Board.
+
)
}
+
+const Index = () => {
+ const transition = useTransition()
+ const results = useActionData()
+
+ const onSubmit = (e: React.MouseEvent) => {
+ if (transition.state === 'submitting') {
+ e.preventDefault()
+ }
+ }
+
+ const error =
+ results && results?.data?.exception ? results.data.exception : undefined
+
+ if (results && results.data && !error) {
+ if (!results.data?.seller_id) {
+ const itemId = results?.itemId
+
+ const link = itemId
+ ? `https://universalis.app/market/${itemId}`
+ : 'https://universalis.app'
+
+ return
+ }
+
+ return (
+
+ )
+ }
+
+ return (
+
+
+
+
+
+ )
+}
+
+export default Index
diff --git a/app/routes/undercut/index.tsx b/app/routes/undercut/index.tsx
deleted file mode 100644
index 3488429d..00000000
--- a/app/routes/undercut/index.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-import { useActionData, useTransition } from '@remix-run/react'
-import { PageWrapper } from '~/components/Common'
-import { InputWithLabel } from '~/components/form/InputWithLabel'
-import SmallFormContainer from '~/components/form/SmallFormContainer'
-import type { ActionFunction } from '@remix-run/cloudflare'
-import { json } from '@remix-run/cloudflare'
-import GetSellerId from '~/requests/GetSellerId'
-import { getUserSessionData } from '~/sessions'
-import ItemSelect from '~/components/form/select/ItemSelect'
-import NoResults from '~/components/Common/NoResults'
-import Results from '../../components/FFXIVResults/UndercutAlert/Results'
-
-export const action: ActionFunction = async ({ request }) => {
- const formData = await request.formData()
- const session = await getUserSessionData(request)
-
- const homeServer = session.getWorld()
-
- const retainerName = formData.get('retainerName')
-
- if (
- !retainerName ||
- typeof retainerName !== 'string' ||
- !retainerName.length
- ) {
- return json({ exception: 'Missing retainer name' })
- }
-
- const itemIdData = formData.get('itemId')
- if (
- !itemIdData ||
- typeof itemIdData !== 'string' ||
- isNaN(parseInt(itemIdData))
- ) {
- return json({ exception: 'Missing item' })
- }
-
- const itemId = parseInt(itemIdData)
-
- const response = await GetSellerId({
- itemId,
- homeServer,
- retainerName
- })
- if (!response.ok) {
- return json({ exception: response.statusText })
- }
-
- return json({ data: await response.json(), homeServer, itemId })
-}
-
-const Description = () => {
- return (
-
- To setup undercut alerts search{' '}
-
- universalis
- {' '}
- for one of your retainers by name (with exact capitilization) and an item
- they are selling on the Market Board.
-
- )
-}
-
-const Index = () => {
- const transition = useTransition()
- const results = useActionData()
-
- const onSubmit = (e: React.MouseEvent) => {
- if (transition.state === 'submitting') {
- e.preventDefault()
- }
- }
-
- const error =
- results && results?.data?.exception ? results.data.exception : undefined
-
- if (results && results.data && !error) {
- if (!results.data?.seller_id) {
- const itemId = results?.itemId
-
- const link = itemId
- ? `https://universalis.app/market/${itemId}`
- : 'https://universalis.app'
-
- return
- }
-
- return (
-
- )
- }
-
- return (
-
-
-
-
-
- )
-}
-
-export default Index
diff --git a/app/routes/why/advertisements.tsx b/app/routes/why.advertisements.tsx
similarity index 100%
rename from app/routes/why/advertisements.tsx
rename to app/routes/why.advertisements.tsx
diff --git a/app/routes/why.tsx b/app/routes/why.tsx
deleted file mode 100644
index ea73335c..00000000
--- a/app/routes/why.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Outlet } from '@remix-run/react'
-
-export default function Why() {
- return (
-
-
-
- )
-}
diff --git a/app/routes/wow.tsx b/app/routes/wow.tsx
index e5140d3a..2032201f 100644
--- a/app/routes/wow.tsx
+++ b/app/routes/wow.tsx
@@ -1,19 +1,115 @@
-import type { ErrorBoundaryComponent } from '@remix-run/cloudflare'
-import { Outlet } from '@remix-run/react'
+import { DocumentSearchIcon } from '@heroicons/react/outline'
+import Banner from '~/components/Common/Banner'
+import TileLink from '~/components/Common/TileLink'
-export default function Wow() {
- return (
-
-
-
- )
-}
+const recommendedQueries = [
+ {
+ name: 'Alpha Build Cross Server Trade Tools',
+ description:
+ 'Check out the latest and greatest Alpha versions of our server to server trade tools just in time for 10.1.5 cross realm trading!',
+ Icon: DocumentSearchIcon,
+ href: 'https://temp.saddlebagexchange.com/',
+ external: true
+ },
+ {
+ name: 'Ultra Fast Mega-Alerts Sniper',
+ description:
+ 'Try our standalone Mega-Alerts Sniper, designed to snipe across all realms for the best deals seconds after the Blizzard AH API updates so you get there first!',
+ Icon: DocumentSearchIcon,
+ href: 'https://github.com/ff14-advanced-market-search/mega-alerts',
+ external: true
+ },
+ {
+ name: 'Region Wide Undercut Checker',
+ description:
+ 'Use our Addon with this search to check all of your undercuts on all your alts on one page!',
+ Icon: DocumentSearchIcon,
+ href: '/wow/region-undercut'
+ },
+ {
+ name: 'Undercut Alerts Curseforge Addon',
+ description: 'The addon for our Undercut Checks and Alerts!',
+ Icon: DocumentSearchIcon,
+ href: 'https://www.curseforge.com/wow/addons/saddlebag-exchange',
+ external: true
+ },
+ {
+ name: 'Global Export Item Search',
+ description:
+ 'Search other servers to find the best place to sell & buy items',
+ Icon: DocumentSearchIcon,
+ href: '/wow/export-search'
+ },
+ {
+ name: 'Dragonflight Marketshare Overview',
+ description:
+ 'Find out what Dragonflight items are actually selling and what are the best items to sell. Shows the top 200 items matching your search.',
+ Icon: DocumentSearchIcon,
+ href: '/wow/marketshare'
+ },
+ {
+ name: 'Dragonflight Commodity Shortage Futures',
+ description:
+ 'Find Commodity Shortages and Price Spikes BEFORE they happen and be there first!',
+ Icon: DocumentSearchIcon,
+ href: '/wow/shortage-predictor'
+ },
+ {
+ name: 'Legacy Marketshare Overview',
+ description:
+ 'Find out what Legacy items are actually selling and what are the best items to sell. Shows the top 200 items matching your search.',
+ Icon: DocumentSearchIcon,
+ href: '/wow/legacy-marketshare'
+ },
+ {
+ name: 'Price Sniper and Price Spike Alerts',
+ description:
+ 'Alerts you when prices for items go above or below a price you pick!',
+ Icon: DocumentSearchIcon,
+ href: '/wow/price-alert'
+ },
+ {
+ name: 'Local Realm Shortage Finder',
+ description:
+ 'Searches for items on your local server / realm that you can flip and take over the market!',
+ Icon: DocumentSearchIcon,
+ href: '/wow/shortages/single'
+ },
+ {
+ name: 'Commodity Shortage Finder',
+ description:
+ 'Searches for region wide commodities that you can flip and take over the market!',
+ Icon: DocumentSearchIcon,
+ href: '/wow/shortages/commodities'
+ },
+ {
+ name: 'Server Transfer Trading Search',
+ description:
+ 'Search for items that can be bought cheaply on a your home server and sold for a profit when transfering realms.',
+ Icon: DocumentSearchIcon,
+ href: '/wow/full-scan'
+ }
+]
-export const ErrorBoundary: ErrorBoundaryComponent = ({ error }) => {
+export default function Index() {
return (
<>
- Erg! Something's broken! Maybe try again.
- {error.message}
+
+
+
+
+
+ World of Warcraft Queries
+
+
+ {recommendedQueries.map((query) => {
+ return
+ })}
+
+
+
+
>
)
}
diff --git a/app/routes/wow/index.tsx b/app/routes/wow/index.tsx
deleted file mode 100644
index 2032201f..00000000
--- a/app/routes/wow/index.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import { DocumentSearchIcon } from '@heroicons/react/outline'
-import Banner from '~/components/Common/Banner'
-import TileLink from '~/components/Common/TileLink'
-
-const recommendedQueries = [
- {
- name: 'Alpha Build Cross Server Trade Tools',
- description:
- 'Check out the latest and greatest Alpha versions of our server to server trade tools just in time for 10.1.5 cross realm trading!',
- Icon: DocumentSearchIcon,
- href: 'https://temp.saddlebagexchange.com/',
- external: true
- },
- {
- name: 'Ultra Fast Mega-Alerts Sniper',
- description:
- 'Try our standalone Mega-Alerts Sniper, designed to snipe across all realms for the best deals seconds after the Blizzard AH API updates so you get there first!',
- Icon: DocumentSearchIcon,
- href: 'https://github.com/ff14-advanced-market-search/mega-alerts',
- external: true
- },
- {
- name: 'Region Wide Undercut Checker',
- description:
- 'Use our Addon with this search to check all of your undercuts on all your alts on one page!',
- Icon: DocumentSearchIcon,
- href: '/wow/region-undercut'
- },
- {
- name: 'Undercut Alerts Curseforge Addon',
- description: 'The addon for our Undercut Checks and Alerts!',
- Icon: DocumentSearchIcon,
- href: 'https://www.curseforge.com/wow/addons/saddlebag-exchange',
- external: true
- },
- {
- name: 'Global Export Item Search',
- description:
- 'Search other servers to find the best place to sell & buy items',
- Icon: DocumentSearchIcon,
- href: '/wow/export-search'
- },
- {
- name: 'Dragonflight Marketshare Overview',
- description:
- 'Find out what Dragonflight items are actually selling and what are the best items to sell. Shows the top 200 items matching your search.',
- Icon: DocumentSearchIcon,
- href: '/wow/marketshare'
- },
- {
- name: 'Dragonflight Commodity Shortage Futures',
- description:
- 'Find Commodity Shortages and Price Spikes BEFORE they happen and be there first!',
- Icon: DocumentSearchIcon,
- href: '/wow/shortage-predictor'
- },
- {
- name: 'Legacy Marketshare Overview',
- description:
- 'Find out what Legacy items are actually selling and what are the best items to sell. Shows the top 200 items matching your search.',
- Icon: DocumentSearchIcon,
- href: '/wow/legacy-marketshare'
- },
- {
- name: 'Price Sniper and Price Spike Alerts',
- description:
- 'Alerts you when prices for items go above or below a price you pick!',
- Icon: DocumentSearchIcon,
- href: '/wow/price-alert'
- },
- {
- name: 'Local Realm Shortage Finder',
- description:
- 'Searches for items on your local server / realm that you can flip and take over the market!',
- Icon: DocumentSearchIcon,
- href: '/wow/shortages/single'
- },
- {
- name: 'Commodity Shortage Finder',
- description:
- 'Searches for region wide commodities that you can flip and take over the market!',
- Icon: DocumentSearchIcon,
- href: '/wow/shortages/commodities'
- },
- {
- name: 'Server Transfer Trading Search',
- description:
- 'Search for items that can be bought cheaply on a your home server and sold for a profit when transfering realms.',
- Icon: DocumentSearchIcon,
- href: '/wow/full-scan'
- }
-]
-
-export default function Index() {
- return (
- <>
-
-
-
-
-
- World of Warcraft Queries
-
-
- {recommendedQueries.map((query) => {
- return
- })}
-
-
-
-
- >
- )
-}
diff --git a/remix.config.js b/remix.config.js
index 40bc410b..69c2c12b 100644
--- a/remix.config.js
+++ b/remix.config.js
@@ -1,10 +1,15 @@
/** @type {import('@remix-run/dev').AppConfig} */
-module.exports = {serverBuildTarget: "cloudflare-pages",
- server: "./server.js",
+module.exports = {
+ serverBuildTarget: 'cloudflare-pages',
+ server: './server.js',
devServerBroadcastDelay: 1000,
- ignoredRouteFiles: ["**/.*"],
+ ignoredRouteFiles: ['**/.*'],
// appDirectory: "app",
// assetsBuildDirectory: "public/build",
// serverBuildPath: "build/index.js",
// publicPath: "/build/",
-};
+ future: {
+ v2_normalizeFormMethod: true,
+ v2_routeConvention: true
+ }
+}