From e970aa7a26fecfa38041bb1cfa59a1d5f40a0194 Mon Sep 17 00:00:00 2001
From: Pinapelz <yukais@pinapelz.com>
Date: Fri, 23 Aug 2024 15:06:37 -0700
Subject: initial groundwork for interactivity via mdx

---
 src/components/FFXIVWorldSelector.tsx | 59 +++++++++++++++++++++++++++++++++++
 1 file changed, 59 insertions(+)
 create mode 100644 src/components/FFXIVWorldSelector.tsx

(limited to 'src/components/FFXIVWorldSelector.tsx')

diff --git a/src/components/FFXIVWorldSelector.tsx b/src/components/FFXIVWorldSelector.tsx
new file mode 100644
index 0000000..f2dc4cc
--- /dev/null
+++ b/src/components/FFXIVWorldSelector.tsx
@@ -0,0 +1,59 @@
+import React, { useState, useEffect } from 'react';
+
+interface World {
+  id: number;
+  name: string;
+}
+
+const FFXIVWorldSelector: React.FC = () => {
+  const [worlds, setWorlds] = useState<World[]>([]);
+  const [selectedWorld, setSelectedWorld] = useState<string | null>(null);
+
+  useEffect(() => {
+    const fetchWorlds = async () => {
+      try {
+        const response = await fetch('https://universalis.app/api/v2/worlds');
+        const data = await response.json();
+        setWorlds(data);
+      } catch (error) {
+        console.error('Error fetching worlds:', error);
+      }
+    };
+
+    fetchWorlds();
+
+    // Load selected world from localStorage
+    const savedWorld = localStorage.getItem('selectedWorld');
+    if (savedWorld) {
+      setSelectedWorld(savedWorld);
+    }
+  }, []);
+
+  const handleWorldChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
+    const selectedWorld = event.target.value;
+    setSelectedWorld(selectedWorld);
+    localStorage.setItem('selectedWorld', selectedWorld); // Save to localStorage
+  };
+
+  const handleApplyClick = () => {
+    window.location.reload(); // Refresh the page
+  };
+
+  return (
+    <div>
+      <label htmlFor="world-select">Select a world: </label>
+      <select id="world-select" onChange={handleWorldChange} value={selectedWorld || ''}>
+        <option value="">--Please choose an option--</option>
+        {worlds.map((world) => (
+          <option key={world.id} value={world.name}>
+            {world.name}
+          </option>
+        ))}
+      </select>
+      {selectedWorld && <p>Selected World: {selectedWorld}</p>}
+      <button onClick={handleApplyClick}>Apply</button>
+    </div>
+  );
+};
+
+export default FFXIVWorldSelector;
\ No newline at end of file
-- 
cgit v1.2.3


From ba51582e825477bec23c2aa37ba43bd6482a2a29 Mon Sep 17 00:00:00 2001
From: Pinapelz <yukais@pinapelz.com>
Date: Fri, 23 Aug 2024 15:53:38 -0700
Subject: implement itemprice component

---
 src/components/AnotherComponent.tsx   | 28 ------------
 src/components/FFXIVItemPrice.tsx     | 83 +++++++++++++++++++++++++++++++++++
 src/components/FFXIVWorldSelector.tsx | 17 ++++---
 src/content/blog/ffxiv-gil-making.mdx | 13 +++---
 src/styles/ffxiv-selector.css         | 72 ++++++++++++++++++++++++++++++
 5 files changed, 172 insertions(+), 41 deletions(-)
 delete mode 100644 src/components/AnotherComponent.tsx
 create mode 100644 src/components/FFXIVItemPrice.tsx
 create mode 100644 src/styles/ffxiv-selector.css

(limited to 'src/components/FFXIVWorldSelector.tsx')

diff --git a/src/components/AnotherComponent.tsx b/src/components/AnotherComponent.tsx
deleted file mode 100644
index f74e924..0000000
--- a/src/components/AnotherComponent.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import React, { useState, useEffect } from 'react';
-
-const AnotherComponent: React.FC = () => {
-  const [selectedWorld, setSelectedWorld] = useState<string | null>(null);
-
-  useEffect(() => {
-    // Load selected world from localStorage
-    const savedWorld = localStorage.getItem('selectedWorld');
-    if (savedWorld) {
-      setSelectedWorld(savedWorld);
-    }
-  }, []);
-
-  return (
-    <div>
-      <p>Selected World: {selectedWorld}</p>
-      <button onClick={() => {
-        const newWorld = 'New World';
-        setSelectedWorld(newWorld);
-        localStorage.setItem('selectedWorld', newWorld); // Save to localStorage
-      }}>
-        Change World
-      </button>
-    </div>
-  );
-};
-
-export default AnotherComponent;
\ No newline at end of file
diff --git a/src/components/FFXIVItemPrice.tsx b/src/components/FFXIVItemPrice.tsx
new file mode 100644
index 0000000..8f265fc
--- /dev/null
+++ b/src/components/FFXIVItemPrice.tsx
@@ -0,0 +1,83 @@
+import React, { useState, useEffect } from 'react';
+
+interface FFXIVItemPriceProps {
+  itemId: number;
+  itemName: string;
+  itemImageUrl: string;
+}
+
+const FFXIVItemPrice: React.FC<FFXIVItemPriceProps> = ({ itemId = 5530, itemName="Coke", itemImageUrl="https://xivapi.com/i/021000/021462_hr1.png"}) => {
+  const [selectedWorld, setSelectedWorld] = useState<string | null>(null);
+  const [dailySaleVelocity, setDailySaleVelocity] = useState<number | null>(null);
+  const [averageSalePrice, setAverageSalePrice] = useState<number | null>(null);
+  const [inputQuantity, setInputQuantity] = useState<number>(0);
+  const [potentialGil, setPotentialGil] = useState<number>(0);
+
+  useEffect(() => {
+    const savedWorld = localStorage.getItem('selectedWorld');
+    if (savedWorld) {
+      setSelectedWorld(savedWorld);
+    } else {
+      setSelectedWorld('Midgardsormr');
+    }
+
+    fetch(`https://universalis.app/api/v2/aggregated/${selectedWorld}/${itemId}`)
+      .then(response => response.json())
+      .then(data => {
+        const result = data.results[0];
+        setDailySaleVelocity(Math.round(result.nq.dailySaleVelocity.world.quantity));
+        setAverageSalePrice(Math.round(result.nq.averageSalePrice.world.price));
+      })
+      .catch(error => console.error('Error fetching data:', error));
+  }, [itemId, selectedWorld]);
+
+  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+    const quantity = parseInt(e.target.value, 10);
+    setInputQuantity(quantity);
+    setPotentialGil(quantity * (averageSalePrice || 0));
+  };
+
+  const formatNumber = (number: number | null) => {
+    return number !== null ? new Intl.NumberFormat().format(number) : 'N/A';
+  };
+
+  return (
+    <div className="ffxiv-container">
+      <div className="ffxiv-header">
+        <h1 className="ffxiv-h1">{itemName}</h1>
+        <img src={itemImageUrl} alt={itemName} className="ffxiv-item-icon" />
+      </div>
+      <table className="ffxiv-table">
+        <tbody>
+          <tr>
+            <td className="ffxiv-label">Daily Sale Velocity:</td>
+            <td className="ffxiv-value">{formatNumber(dailySaleVelocity)} items</td>
+          </tr>
+          <tr>
+            <td className="ffxiv-label">Average Price/Item:</td>
+            <td className="ffxiv-value">{formatNumber(averageSalePrice)} gil</td>
+          </tr>
+          <tr>
+            <td className="ffxiv-label">Enter Quantity:</td>
+            <td>
+              <input
+                type="number"
+                value={inputQuantity}
+                onChange={handleInputChange}
+                placeholder="Enter quantity"
+                className="ffxiv-input"
+              />
+            </td>
+          </tr>
+          <tr>
+            <td className="ffxiv-label">Potential Gil Earnings:</td>
+            <td className="ffxiv-value">{formatNumber(potentialGil)} gil</td>
+          </tr>
+        </tbody>
+      </table>
+      <footer>{selectedWorld} Marketboard Data provided by Universalis API</footer>
+    </div>
+  );
+};
+
+export default FFXIVItemPrice;
\ No newline at end of file
diff --git a/src/components/FFXIVWorldSelector.tsx b/src/components/FFXIVWorldSelector.tsx
index f2dc4cc..63eff07 100644
--- a/src/components/FFXIVWorldSelector.tsx
+++ b/src/components/FFXIVWorldSelector.tsx
@@ -1,11 +1,15 @@
 import React, { useState, useEffect } from 'react';
-
+import '../styles/ffxiv-selector.css';
 interface World {
   id: number;
   name: string;
 }
 
-const FFXIVWorldSelector: React.FC = () => {
+interface FFXIVWorldSelectorProps {
+  message: string;
+}
+
+const FFXIVWorldSelector: React.FC<FFXIVWorldSelectorProps> = ({ message = "Select a World" }) => {
   const [worlds, setWorlds] = useState<World[]>([]);
   const [selectedWorld, setSelectedWorld] = useState<string | null>(null);
 
@@ -40,9 +44,9 @@ const FFXIVWorldSelector: React.FC = () => {
   };
 
   return (
-    <div>
-      <label htmlFor="world-select">Select a world: </label>
-      <select id="world-select" onChange={handleWorldChange} value={selectedWorld || ''}>
+    <div className="ffxiv-container">
+      <label htmlFor="world-select" className="ffxiv-label">{message}</label>
+      <select id="world-select" onChange={handleWorldChange} value={selectedWorld || ''} className="ffxiv-select">
         <option value="">--Please choose an option--</option>
         {worlds.map((world) => (
           <option key={world.id} value={world.name}>
@@ -50,8 +54,7 @@ const FFXIVWorldSelector: React.FC = () => {
           </option>
         ))}
       </select>
-      {selectedWorld && <p>Selected World: {selectedWorld}</p>}
-      <button onClick={handleApplyClick}>Apply</button>
+      <button onClick={handleApplyClick} className="ffxiv-button">Apply</button>
     </div>
   );
 };
diff --git a/src/content/blog/ffxiv-gil-making.mdx b/src/content/blog/ffxiv-gil-making.mdx
index 002ab8c..eb35a28 100644
--- a/src/content/blog/ffxiv-gil-making.mdx
+++ b/src/content/blog/ffxiv-gil-making.mdx
@@ -5,15 +5,16 @@ pubDate: "2024-08-23"
 ---
 import '../../styles/ffxiv-gil-making.css'
 import FFXIVWorldSelector from '../../components/FFXIVWorldSelector';
-import AnotherComponent from '../../components/AnotherComponent';
-
-*Enter the world you play on below to get crowdsourced marketboard prices*
-<FFXIVWorldSelector client:load />
+import FFXIVItemPrice from '../../components/FFXIVItemPrice';
 
+<FFXIVWorldSelector client:load message="Set your home world to get crowdsourced marketboard information!" />
 
 
 I've seen a lot of these "making gil without crafting" or "making gil with combat job" guides during my time with the game.
 A lot of these videos and guides are fine, but in my opinion they neglect a lot of the late game and niche options that are available to players.
 
-This post will attempt to be a somewhat comprehensive guide to making gil without having to level any crafting jobs (cause I get it, crafting is not for everyone).
-<AnotherComponent client:load/>
\ No newline at end of file
+This post will attempt to be a somewhat comprehensive guide/info sheet to making gil without having to level any crafting jobs (cause I get it, crafting is not for everyone).
+
+Keep in mind though that crafters are probably one of the most consistent methods of getting gil. There will be a lot more "instability" with these methods.
+
+# Grand Company Seals
\ No newline at end of file
diff --git a/src/styles/ffxiv-selector.css b/src/styles/ffxiv-selector.css
new file mode 100644
index 0000000..efad540
--- /dev/null
+++ b/src/styles/ffxiv-selector.css
@@ -0,0 +1,72 @@
+.ffxiv-container {
+  background-color: #1a1a1a;
+  color: #e2c08d;
+  padding: 10px;
+  border: 2px solid #e2c08d;
+  border-radius: 10px;
+  font-family: 'Arial', serif;
+  max-width: 500px;
+  margin: auto;
+}
+
+.ffxiv-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 10px;
+}
+
+.ffxiv-item-icon {
+  width: 50px;
+  height: 50px;
+}
+
+.ffxiv-h1 {
+  font-size: 1.5em;
+  color: #e2c08d;
+  margin: 0;
+}
+
+.ffxiv-table {
+  width: 100%;
+  border-collapse: collapse;
+}
+
+.ffxiv-button {
+    background-color: #333;
+    color: #e2c08d;
+    border: 1px solid #e2c08d;
+    padding: 5px;
+    padding-left: 10px;
+    padding-right: 10px;
+    margin-left: 10px;
+    border-radius: 5px;
+    cursor: pointer;
+}
+
+.ffxiv-table td {
+  padding: 5px;
+  border: 1px solid #e2c08d;
+}
+
+.ffxiv-label {
+  font-size: 1em;
+}
+
+.ffxiv-value {
+  font-size: 1em;
+}
+
+.ffxiv-input {
+  background-color: #333;
+  color: #e2c08d;
+  border: 1px solid #e2c08d;
+  padding: 5px;
+  border-radius: 5px;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.ffxiv-input::placeholder {
+  color: #e2c08d;
+}
\ No newline at end of file
-- 
cgit v1.2.3

