From 98a65d29d07357e8991d5da1818a00680473ce4d Mon Sep 17 00:00:00 2001 From: Damon Greenhalgh Date: Sun, 17 Apr 2022 14:23:24 +1200 Subject: [PATCH 01/14] Added more media queries for mobile resolutions. --- changelog.md | 14 ++++++++ package.json | 2 +- src/App.css | 2 +- src/components/Achievements.jsx | 6 +--- src/components/Attributes.css | 6 ++-- src/components/Collection.css | 12 +++++-- src/components/Collection.jsx | 2 +- src/components/Equipment.css | 9 +++++ src/components/Featured.jsx | 2 +- src/components/Footer.css | 2 ++ src/components/Footer.jsx | 7 ++-- src/components/Jobs.css | 63 ++++++++++++++++++++++++++++++++- src/components/Jobs.jsx | 4 +-- src/components/Navbar.jsx | 16 +++------ src/components/Notice.jsx | 2 +- src/components/Quests.css | 15 ++++++-- src/components/Quests.jsx | 19 +++++----- src/components/Searchbar.css | 23 +++++++++--- src/components/Searchbar.jsx | 8 +++-- src/pages/Character.css | 14 +++++--- src/pages/Home.css | 2 +- src/pages/Settings.css | 2 +- src/pages/Settings.jsx | 5 +++ 23 files changed, 176 insertions(+), 61 deletions(-) diff --git a/changelog.md b/changelog.md index 88f1fd1..3fc07cf 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased +## [1.2.1] - 2022-04-17 + +Cleaning up patch. Fixes and mobile support. + +### Changes + +- Added media queries for multiple components. Support for mobile. +- Featured events link now open a new tab. + +### Fixed +- Quests not appearing when reference character data was not updating. +- Fixed failed search when using the searchbar within a character profile. + + ## Released ## [1.2.0] - 2022-04-16 diff --git a/package.json b/package.json index 62a115b..983e034 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xiv-tracker", - "version": "0.1.0", + "version": "1.2.1", "private": true, "homepage": "https://damongreenhalgh.github.io/xivtracker", "dependencies": { diff --git a/src/App.css b/src/App.css index df93f1c..f3b0029 100644 --- a/src/App.css +++ b/src/App.css @@ -186,7 +186,7 @@ input:focus { background-image: linear-gradient(-90deg, transparent, rgba(255, 255, 255, 0.25)); } -@media only screen and (max-width: 1050px) { +@media only screen and (max-width: 1150px) { * { --content-width: calc(100vw - 2rem) diff --git a/src/components/Achievements.jsx b/src/components/Achievements.jsx index b7db23c..e6bef4e 100644 --- a/src/components/Achievements.jsx +++ b/src/components/Achievements.jsx @@ -16,7 +16,6 @@ const Achievements = (props) => { // Mount useEffect(() => { - const fetchData = async () => { await fetch("https://xivapi.com/character/" + props.data.ID + "?extended=1&data=AC", {mode: 'cors'}) .then(response => response.json()) @@ -26,11 +25,8 @@ const Achievements = (props) => { setMaxPage(Math.ceil(data.Achievements.List.length / capacity) - 1) setLoading(false); }); - } - fetchData(); - }, [props.data.ID]); // Update @@ -57,7 +53,7 @@ const Achievements = (props) => {
-

Total Points

+

Points

{points}

{ diff --git a/src/components/Attributes.css b/src/components/Attributes.css index cc3c016..e808c81 100644 --- a/src/components/Attributes.css +++ b/src/components/Attributes.css @@ -33,10 +33,10 @@ max-height: 2rem; } -@media only screen and (max-width: 1050px) { +@media only screen and (max-width: 500px) { - .attributes { - display: none; + .attributes__list { + grid-template-columns: 1fr; } } \ No newline at end of file diff --git a/src/components/Collection.css b/src/components/Collection.css index 2a78bf8..b00ff93 100644 --- a/src/components/Collection.css +++ b/src/components/Collection.css @@ -1,6 +1,6 @@ .collection { display: grid; - grid-template-columns: repeat(5, 3rem); + grid-template-columns: repeat(8, 3rem); grid-template-rows: repeat(5, 3rem); gap: .5rem; padding: 1rem; @@ -12,13 +12,19 @@ .collection__content { display: flex; gap: 2rem; - justify-content: center; + justify-content: space-around; } -@media only screen and (max-width: 750px) { +@media only screen and (max-width: 1150px) { .collection__content { flex-direction: column; } + .collection { + display: flex; + flex-wrap: wrap; + justify-content: center; + } + } \ No newline at end of file diff --git a/src/components/Collection.jsx b/src/components/Collection.jsx index 97ff040..0a9136a 100644 --- a/src/components/Collection.jsx +++ b/src/components/Collection.jsx @@ -15,7 +15,7 @@ const Collection = (props) => { const [maxMinionPage, setMaxMinionPage] = useState(0); const [loading, setLoading] = useState(true); const [numCollected, setNumCollected] = useState(0); - const capacity = 25; + const capacity = 40; // Will have to manually update these numbers after every major patch. const totalCollection = 747 diff --git a/src/components/Equipment.css b/src/components/Equipment.css index 7806975..bc7400c 100644 --- a/src/components/Equipment.css +++ b/src/components/Equipment.css @@ -26,4 +26,13 @@ background-size: cover; border-radius: .5rem; box-shadow: 0 .5rem 1rem var(--c-shadow); +} + +@media only screen and (max-width: 30rem) { + + .equipment{ + margin: 0; + grid-template-columns: 3rem 1fr 3rem; + } + } \ No newline at end of file diff --git a/src/components/Featured.jsx b/src/components/Featured.jsx index 3cab496..9fd7cab 100644 --- a/src/components/Featured.jsx +++ b/src/components/Featured.jsx @@ -58,7 +58,7 @@ const Featured = () => { > - +

{featureJSON[live[index]].type}

{ return (
-

- XIV Tracker is Designed and Developed by Damon Greenhalgh.
- FINAL FANTASY XIV CONTENT IS PROPERTY OF SQUARE ENIX CO,. LTD. +

+ XIV Tracker is Designed and Developed by Damon Greenhalgh.
+ FINAL FANTASY XIV CONTENT IS PROPERTY OF SQUARE ENIX CO,. LTD.

+

XIV Tracker v1.2.1

{profile} - {/* { - props.referenceCharacter !== null ? - - - : - null - } */} ); } diff --git a/src/components/Notice.jsx b/src/components/Notice.jsx index 31b35c2..d9a39c5 100644 --- a/src/components/Notice.jsx +++ b/src/components/Notice.jsx @@ -7,7 +7,7 @@ import { useState } from 'react'; const notice = { 0: { icon: , - text:

XIV Tracker uses cookies (and cookie like systems) to enhance user experience. Without cookies, many features may not function properly.

+ text:

XIV Tracker uses cookies (and cookie like systems) to enhance user experience.

}, 1: { icon: , diff --git a/src/components/Quests.css b/src/components/Quests.css index 768612f..4db665a 100644 --- a/src/components/Quests.css +++ b/src/components/Quests.css @@ -1,6 +1,6 @@ -.quests__collection { +.quests__header { display: flex; - justify-content: space-between; + align-items: center; } .quests__sub-header { @@ -42,11 +42,20 @@ outline: 1px solid var(--color-completed); } -@media only screen and (max-width: 1050px) { +@media only screen and (max-width: 1150px) { .quests__list { flex-direction: column; gap: 0; } +} + +@media only screen and (max-width: 500px) { + + .quests__header{ + flex-direction: column; + gap: 2rem; + } + } \ No newline at end of file diff --git a/src/components/Quests.jsx b/src/components/Quests.jsx index dac6b30..8aef0dc 100644 --- a/src/components/Quests.jsx +++ b/src/components/Quests.jsx @@ -87,19 +87,12 @@ const Quests = (props) => {

{Math.round(totals[panel][0] / totals[panel][1] * 100)} %

-
+
{achievementsJSON[panel].name

{achievementsJSON[panel].name}

-
-
+
{/* Main Scenario */}
    @@ -415,6 +408,14 @@ const Quests = (props) => {
+ +
); } diff --git a/src/components/Searchbar.css b/src/components/Searchbar.css index 055405c..aec21c5 100644 --- a/src/components/Searchbar.css +++ b/src/components/Searchbar.css @@ -16,7 +16,7 @@ .recent { z-index: 4; overflow: hidden; - top: calc(100% + 2rem); + top: calc(100% + 1rem); position: absolute; height: 0; width: 100%; @@ -81,16 +81,29 @@ color: white; } -@media only screen and (max-width: 1050px) { +@media only screen and (max-width: 500px) { .searchbar { - margin: 0 auto; + width: var(--content-width); } + } -@media only screen and (max-width: 500px) { +@media only screen and (max-width: 750px) { - .searchbar { + .searchbar--character { width: var(--content-width); + position: fixed; + bottom: 1rem; + left: 1rem; + background-color: var(--c-content-background-opaque); + border: 1px solid var(--c-mid-background); + box-shadow: 0 .5rem 1rem var(--c-shadow); + border-radius: .5rem; + } + + .recent--character { + bottom: 4rem; + top: auto; } } \ No newline at end of file diff --git a/src/components/Searchbar.jsx b/src/components/Searchbar.jsx index f28b7c4..636d5ca 100644 --- a/src/components/Searchbar.jsx +++ b/src/components/Searchbar.jsx @@ -14,6 +14,8 @@ const Searchbar = (props) => { const [displayDropdown, setDisplayDropdown] = useState(false); const [recent, setRecent] = useState(null); + const [expanded, setExpanded] = useState(false); + const callbackMethod = (event) => { event.preventDefault(); setDisplayRecent(false); @@ -41,7 +43,7 @@ const Searchbar = (props) => { return (
@@ -62,7 +64,7 @@ const Searchbar = (props) => { -
+

Servers

setDisplayDropdown(false)}/> @@ -145,7 +147,7 @@ const Searchbar = (props) => {
Zurvan
-
setDisplayRecent(false)}> +
setDisplayRecent(false)}>

Recently Viewed

setDisplayRecent(false)}/> diff --git a/src/pages/Character.css b/src/pages/Character.css index 9d29103..1afe255 100644 --- a/src/pages/Character.css +++ b/src/pages/Character.css @@ -11,7 +11,6 @@ border: 1px solid var(--c-mid-background); border-radius: .5rem; background-color: var(--c-content-background); - height: 4rem; } .character__panel { @@ -54,19 +53,24 @@ } -@media only screen and (max-width: 1050px) { +@media only screen and (max-width: 1150px) { .character { width: 100vw; padding: 0 1rem; } - .character__row { + .character__panel { flex-direction: column; } - .sidebar { - width: auto; +} + +@media only screen and (max-width: 700px) { + + .character__nav { + flex-direction: column; } + } \ No newline at end of file diff --git a/src/pages/Home.css b/src/pages/Home.css index 5ac6608..6f7300e 100644 --- a/src/pages/Home.css +++ b/src/pages/Home.css @@ -5,7 +5,7 @@ align-items: center; margin: auto; width: var(--content-width); - gap: 2rem; + gap: 1rem; } .home__brand { diff --git a/src/pages/Settings.css b/src/pages/Settings.css index 3870ce6..ef6c5fc 100644 --- a/src/pages/Settings.css +++ b/src/pages/Settings.css @@ -25,7 +25,7 @@ gap: 2rem; } -@media only screen and (max-width: 1050px) { +@media only screen and (max-width: 1150px) { .settings { margin: auto; diff --git a/src/pages/Settings.jsx b/src/pages/Settings.jsx index d868f7a..b3cf165 100644 --- a/src/pages/Settings.jsx +++ b/src/pages/Settings.jsx @@ -39,6 +39,11 @@ const Settings = (props) => { props.setShowSearchbar(false); document.title = "XIV Tracker | Settings"; + // Update reference character data. + if (props.referenceCharacter !== null) { + requestData(props.referenceCharacter.Character.ID); + } + }, []) const requestData = async(id) => { From aa6b6895ec0a87ea3115e465cedc80c6f963f3d2 Mon Sep 17 00:00:00 2001 From: Damon Greenhalgh Date: Mon, 18 Apr 2022 20:12:50 +1200 Subject: [PATCH 02/14] Events will be displayed based on the current time. --- changelog.md | 3 +- src/components/Featured.jsx | 77 ++++++++++++++++++------------------- src/data/feature.json | 10 +++++ 3 files changed, 50 insertions(+), 40 deletions(-) diff --git a/changelog.md b/changelog.md index 3fc07cf..68e5dd5 100644 --- a/changelog.md +++ b/changelog.md @@ -6,12 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [1.2.1] - 2022-04-17 -Cleaning up patch. Fixes and mobile support. +Cleaning up patch. Fixes to bugs. Support for mobile and Safari. ### Changes - Added media queries for multiple components. Support for mobile. - Featured events link now open a new tab. +- Events will be displayed based on the current time. ### Fixed - Quests not appearing when reference character data was not updating. diff --git a/src/components/Featured.jsx b/src/components/Featured.jsx index 9fd7cab..5bad93f 100644 --- a/src/components/Featured.jsx +++ b/src/components/Featured.jsx @@ -1,50 +1,52 @@ import './Featured.css'; +import { useState, useEffect, useRef } from 'react'; +import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; +import featureJSON from '../data/feature.json'; + import endwalkerBanner from '../images/featured/endwalker.png'; import newfoundAdventureBanner from '../images/featured/newfound-adventure.png'; import littleLadiesDayBanner from '../images/featured/little-ladies-day.png'; import moogleTreasureTroveBanner from '../images/featured/moogle-treasure-trove.png'; import hatchingTideBanner from '../images/featured/hatching-tide.png'; -import { useState, useEffect, useRef } from 'react'; -import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; -import featureJSON from '../data/feature.json'; - -// Reference to currently live events/relevant content. -const live = [ - 'EVENT_2', - 'PATCH_6.1', - 'PATCH_6.0' +const banner = [ + endwalkerBanner, + newfoundAdventureBanner, + littleLadiesDayBanner, + moogleTreasureTroveBanner, + hatchingTideBanner ] const Featured = () => { - const length = live.length; const backgroundContainer = useRef(null); const [index, setIndex] = useState(0); - const [auto, setAuto] = useState(true); - - // Update index function to keep the index within [0, length] - const updateIndex = (direction) => { - setIndex(index => { - // If index would fall below 0, wrap around. - if (index + direction < 0) { - return length - 1; - } - return (index + direction) % length; - }); - } + const [events, setEvents] = useState(Object.values(featureJSON)); + const [banners, setBanners] = useState(null); + // Mount useEffect(() => { - const interval = setInterval(() => { - updateIndex(1); - }, 3500); + let startDate, endDate; + const currentDate = new Date().getTime(); + let liveEvents = []; + let liveBanners = []; + + const event = Object.values(featureJSON); + for (let i = event.length - 1; i > -1; i--) { + startDate = new Date(event[i].start[0], event[i].start[1], event[i].start[2]).getTime(); + endDate = new Date(event[i].end[0], event[i].end[1], event[i].end[2]).getTime(); + if (startDate <= currentDate && currentDate <= endDate) { + liveEvents.push(event[i]); + liveBanners.push() + } + } - // Disable auto scroll if the user has interacted with slideshow. - if (!auto) { clearInterval(interval) } + setEvents(liveEvents); + setBanners(liveBanners); - return () => clearInterval(interval); - }, [auto]) + }, []) + // Update useEffect(() => { backgroundContainer.current.style.transform = "translate(-" + index * 30 + "rem, 0)"; }, [index]) @@ -54,32 +56,29 @@ const Featured = () => {
- - - + {banners}
); diff --git a/src/data/feature.json b/src/data/feature.json index 12c2825..fd15a1d 100644 --- a/src/data/feature.json +++ b/src/data/feature.json @@ -3,30 +3,40 @@ "title": "ENDWALKER", "type": "New Expansion", "date": "December 19th 2021", + "start": [2021, 11, 19], + "end": [2023, 11, 19], "link": "https://na.finalfantasyxiv.com/endwalker" }, "PATCH_6.1": { "title": "NEWFOUND ADVENTURE", "type": "Patch 6.1", "date": "April 12th 2022", + "start": [2022, 3, 12], + "end": [2022, 7, 12], "link": "https://na.finalfantasyxiv.com/endwalker/patch_6_1/" }, "EVENT_0": { "title": "LITTLE LADIES' DAY", "type": "Live Event", "date": "March 14 - March 31", + "start": [2022, 2, 14], + "end": [2022, 2, 31], "link": "https://na.finalfantasyxiv.com/lodestone/special/2022/Little_Ladies_Day/jsuk7gn8z4" }, "EVENT_1": { "title": "MOOGLE TREASURE TROVE", "type": "Live Event", "date": "March 14 - April 12", + "start": [2022, 2, 14], + "end": [2022, 3, 12], "link": "https://na.finalfantasyxiv.com/lodestone/special/mogmog-collection/202203/dceh8sj926" }, "EVENT_2": { "title": "HATCHING TIDE", "type": "Live Event", "date": "April 13 - April 27", + "start": [2022, 3, 13], + "end": [2022, 3, 27], "link": "https://na.finalfantasyxiv.com/lodestone/special/2022/Hatching_tide/qeng0r6k46" } } \ No newline at end of file From 39cf2b66cbbf96015917204079d94c73c1889024 Mon Sep 17 00:00:00 2001 From: Damon Greenhalgh Date: Mon, 18 Apr 2022 20:18:23 +1200 Subject: [PATCH 03/14] Added guide links to profession jobs. --- src/components/JobItem.jsx | 4 ++-- src/components/Jobs.jsx | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/JobItem.jsx b/src/components/JobItem.jsx index aa7b0ba..3b28a21 100644 --- a/src/components/JobItem.jsx +++ b/src/components/JobItem.jsx @@ -1,13 +1,13 @@ import Bar from './utility/Bar'; const JobItem = (props) => { - const link = "https://na.finalfantasyxiv.com/jobguide/"; + const link = "https://na.finalfantasyxiv.com/" + (props.isCombat ? "jobguide/" : "crafting_gathering_guide/"); let nameNoGap = (props.name).replace(/\s/g, ''); const name = nameNoGap.charAt(0).toUpperCase() + nameNoGap.slice(1); return (

{ name={job.Job.Name} level={job.Level} exp={[job.ExpLevel, job.ExpLevelMax]} - hasLink={true} + isCombat={false} /> )} @@ -104,6 +104,7 @@ const Jobs = (props) => { name={job.Job.Name} level={job.Level} exp={[job.ExpLevel, job.ExpLevelMax]} + isCombat={false} /> )} From 850e453f2b6b5b563dcf8aefc8b06fc09343b785 Mon Sep 17 00:00:00 2001 From: Damon Greenhalgh Date: Tue, 19 Apr 2022 20:33:01 +1200 Subject: [PATCH 04/14] Made splash fixed to the background. --- src/App.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/App.css b/src/App.css index f3b0029..e0c223f 100644 --- a/src/App.css +++ b/src/App.css @@ -31,6 +31,7 @@ body { background-color: var(--c-background); background-position: top; background-repeat: no-repeat; + background-attachment: fixed; } h1, h2, h3, h4, h5, p { From 339a24abe6932bfa9b74e5ee8f511cde41982b95 Mon Sep 17 00:00:00 2001 From: Damon Greenhalgh Date: Tue, 19 Apr 2022 20:33:20 +1200 Subject: [PATCH 05/14] Quests refactor. --- changelog.md | 6 + src/components/Quests.css | 21 +- src/components/Quests.jsx | 422 ++++++------------------------------- src/data/achievements.json | 214 ------------------- 4 files changed, 90 insertions(+), 573 deletions(-) delete mode 100644 src/data/achievements.json diff --git a/changelog.md b/changelog.md index 68e5dd5..51328db 100644 --- a/changelog.md +++ b/changelog.md @@ -8,11 +8,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Cleaning up patch. Fixes to bugs. Support for mobile and Safari. + +### Added + +- Added guide links to profession jobs. + ### Changes - Added media queries for multiple components. Support for mobile. - Featured events link now open a new tab. - Events will be displayed based on the current time. +- Quests system refactor. Improved maintainability and reduced overhead. ### Fixed - Quests not appearing when reference character data was not updating. diff --git a/src/components/Quests.css b/src/components/Quests.css index 4db665a..9c6604c 100644 --- a/src/components/Quests.css +++ b/src/components/Quests.css @@ -7,11 +7,19 @@ padding: 1rem 3rem; } -.quests__list { +/* .quests__list { display: flex; gap: 2rem; font-size: .8rem; width: 100%; +} */ + +.quests__list { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 1rem 2rem; + font-size: .8rem; + width: 100%; } .quests__column { @@ -45,8 +53,15 @@ @media only screen and (max-width: 1150px) { .quests__list { - flex-direction: column; - gap: 0; + grid-template-columns: 1fr 1fr; + } + +} + +@media only screen and (max-width: 800px) { + + .quests__list { + grid-template-columns: 1fr; } } diff --git a/src/components/Quests.jsx b/src/components/Quests.jsx index 8aef0dc..538d79f 100644 --- a/src/components/Quests.jsx +++ b/src/components/Quests.jsx @@ -5,12 +5,11 @@ import trialIcon from '../images/trials.png'; import raidIcon from '../images/raids.png'; import highEndIcon from '../images/high-end-duty.png'; import msqIcon from '../images/meteor.png'; -import { useEffect, useState, useRef } from 'react'; -import achievementsJSON from '../data/achievements.json'; +import { useEffect, useState} from 'react'; import Header from './Header'; import { FaCheck } from 'react-icons/fa'; import Navigator from './utility/Navigator'; -import Divider from './utility/Divider'; +import questsJSON from '../data/quests.json'; const questIcon = [ meteorIcon, @@ -22,13 +21,13 @@ const questIcon = [ const Quests = (props) => { - const eorzeadbBaseUrl = "https://na.finalfantasyxiv.com/lodestone/playguide/db/"; const [panel, setPanel] = useState(0); - const [totals, setTotals] = useState([[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]); - const msqRef = useRef(null); + const [content, setContent] = useState([]); + const [completion, setCompletion] = useState([0, 0]); const maxPanel = 4; useEffect(async () => { + let achievementData; await fetch("https://xivapi.com/character/" + props.id + "?data=AC", {mode: 'cors'}) .then(response => response.json()) @@ -47,367 +46,78 @@ const Quests = (props) => { refAchievementIds.push(props.referenceCharacter.Achievements.List[i].ID) } } - - // Determine what quests have been completed, and what to show relative - // to reference character. - let completedArray = [] - for (let i = 0; i < achievementsJSON.length; i++) { - let completed = 0; - const ids = Object.values(achievementsJSON[i].id); - const elements = msqRef.current.children[i].querySelectorAll('a'); - - for (let j = 0; j < achievementIds.length; j++) { - if (achievementIds.includes(ids[j])) { - if (refAchievementIds.includes(ids[j])) { - - // Both reference character and character has completed quest/encounter. - elements[j].setAttribute('class', 'eorzeadb_link completed'); - } else { - - // Reference character has not completed quest/encounter, but character has. - elements[j].setAttribute('class', elements[j].className + ' completed'); + + let keys, values; + let total = 0; + let completed = 0; + const eorzeadbBaseUrl = "https://na.finalfantasyxiv.com/lodestone/playguide/db/"; + const content = []; + const headers = Object.keys(questsJSON); + for (let i = 0; i < headers.length; i++) { + keys = Object.keys(questsJSON[headers[i]]); + values = Object.values(questsJSON[headers[i]]); + content.push( + <> +
+ +

{headers[i]}

+
+
+ { + values.map((expansion, index) => + + ) } - // Show checkmark - elements[j].children[0].setAttribute('class', 'icon'); - completed++; - } - } - completedArray.push([completed, ids.length]); +
+ + ) } - setTotals(completedArray) + setContent(content); + setCompletion([completed, total]); }, []); return (
- -
-

{totals[panel][0] + " / " + totals[panel][1]}

-

{Math.round(totals[panel][0] / totals[panel][1] * 100)} %

-
-
- {achievementsJSON[panel].name -

{achievementsJSON[panel].name}

+
+

{completion[0] + " / " + completion[1]}

+

{Math.round(completion[0] / completion[1] * 100) + " %"}

-
- - {/* Main Scenario */} -
    - - - - - -
    -

    Endwalker

    - Endwalker - New Found Adventure -
    - -
- - {/* Dungeons */} -
    - - - - - -
- - {/* Trials */} -
    - - - - -
- - {/* Raids */} -
    - - - -
    -
- - {/* High End */} -
    - - -
    -
- -
+ {content[panel]} Date: Tue, 19 Apr 2022 20:34:08 +1200 Subject: [PATCH 06/14] Added quests/duty static json --- src/data/quests.json | 1352 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1352 insertions(+) create mode 100644 src/data/quests.json diff --git a/src/data/quests.json b/src/data/quests.json new file mode 100644 index 0000000..8ea4d6d --- /dev/null +++ b/src/data/quests.json @@ -0,0 +1,1352 @@ +{ + "Main Scenario": { + "A Realm Reborn": [ + { + "name": "A Realm Reborn", + "id": 788, + "link": "quest/3ab32cfebf8/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "A Realm Awoken", + "id": 898, + "link": "quest/defec516ba9/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Through the Maelstrom", + "id": 899, + "link": "quest/7f72ebda286/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Defenders of Eorzea", + "id": 1001, + "link": "quest/e31b1481e7a/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Dreams of Ice", + "id": 1029, + "link": "quest/48e85c94175/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Before the Fall", + "id": 1129, + "link": "quest/7be5b6453e1/", + "isSpoiler": true, + "isMainStory": true + } + ], + "Heavensward": [ + { + "name": "Heavensward", + "id": 1139, + "link": "quest/29fa56153f5/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "As Goes Light, So Goes Darkness", + "id": 1387, + "link": "quest/02f039d7119/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Gears of Change", + "id": 1493, + "link": "quest/64c819d8eb3/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Revenge of the Horde", + "id": 1594, + "link": "quest/36d55886b37/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Soul Surrender", + "id": 1630, + "link": "quest/723efddb90c/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Far Edge of Fate", + "id": 1691, + "link": "quest/f024c2b6931/", + "isSpoiler": true, + "isMainStory": true + } + ], + "Stormblood": [ + { + "name": "Stormblood", + "id": 1794, + "link": "quest/08908744553/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Legend Returns", + "id": 1990, + "link": "quest/4b032f92080/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Rise of a New Sun", + "id": 2013, + "link": "quest/6ef50b6c9fe/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Under the Moonlight", + "id": 2098, + "link": "quest/82318efbb39/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Prelude in Violet", + "id": 2124, + "link": "quest/692bc7a1186/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "A Requiem for Heroes", + "id": 2233, + "link": "quest/4e3ff3ab391/", + "isSpoiler": true, + "isMainStory": true + } + ], + "Shadowbringers": [ + { + "name": "Shadowbringers", + "id": 2298, + "link": "quest/4ed1668d377/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Vows of Virtue, Deeds of Cruelty", + "id": 2424, + "link": "quest/fc70298b76f/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Echos of a Fallen Star", + "id": 2587, + "link": "quest/b7a00665b7b/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Reflections in Crystal", + "id": 2642, + "link": "quest/90de78eeddc/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Futures Rewritten", + "id": 2714, + "link": "quest/a424070bc4c/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Death Unto Dawn", + "id": 2851, + "link": "quest/964cf0528f1/", + "isSpoiler": true, + "isMainStory": true + } + ], + "Endwalker": [ + { + "name": "Endwalker", + "id": 2958, + "link": "quest/52a65d1961d/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "New Found Adventure", + "id": 3075, + "link": "quest/52a65d1961d/", + "isSpoiler": true, + "isMainStory": true + } + ] + }, + "Dungeons": { + "A Realm Reborn": [ + { + "name": "Sastasha", + "id": 663, + "link": "duty/b229b89b3a8/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Tam-Tara Deepcroft", + "id": 669, + "link": "duty/b229b89b3a8/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Copperbell Mines", + "id": 666, + "link": "duty/619923ac984/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Halatali", + "id": 667, + "link": "duty/98319325b98/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Thousand Maws of Toto-Rak", + "id": 673, + "link": "duty/cf7612fa294/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Haukke Manor", + "id": 670, + "link": "duty/e9740dde26c/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Brayflox's Longstop", + "id": 664, + "link": "duty/e371c7ab919/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Sunken Temple of Qarn", + "id": 668, + "link": "duty/b7a48152df9/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Cutter's Cry", + "id": 674, + "link": "duty/d601f85dc1e/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Stone Vigil", + "id": 672, + "link": "duty/b6491e1b508/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Dzemael Darkhold", + "id": 675, + "link": "duty/4a36cbca533/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Aurum Vale", + "id": 676, + "link": "duty/f507633618c/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Castrum Meridianum", + "id": 678, + "link": "duty/59c2b3b84fa/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Praetorium", + "id": 679, + "link": "duty/2407dbd0cae/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Wanderer's Palace", + "id": 665, + "link": "duty/3fd66be47b2/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Amdapor Keep", + "id": 671, + "link": "duty/a8dd3748467/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Pharos Sirius", + "id": 873, + "link": "duty/ae8a92122ec/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Copperbell Mines (Hard)", + "id": 871, + "link": "duty/a780ca9b970/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Haukke Manor (Hard)", + "id": 872, + "link": "duty/61c74c68e00/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Lost City of Amdapor", + "id": 897, + "link": "duty/87d3b951d3d/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Halatali (Hard)", + "id": 895, + "link": "duty/87d3b951d3d/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Brayflox's Longstop (Hard)", + "id": 896, + "link": "duty/40681f6c94a/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Hullbreaker Isle", + "id": 993, + "link": "duty/18aeffad7c5/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Stone Vigil (Hard)", + "id": 992, + "link": "duty/418628edfbf/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Tam-Tara Deepcroft (Hard)", + "id": 991, + "link": "duty/84d01fe5b6c/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Snowcloak", + "id": 1039, + "link": "duty/6f1778eb631/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Sastasha (Hard)", + "id": 1038, + "link": "duty/df38ed5c808/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Sunken Temple of Qarn (Hard)", + "id": 1037, + "link": "duty/6b35d4a1179/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Keeper of the Lake", + "id": 1072, + "link": "duty/5e75d2059af/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Wanderer's Palace (Hard)", + "id": 1071, + "link": "duty/7c11b0ba080/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Amdapor Keep (Hard)", + "id": 1070, + "link": "duty/a4288ecf826/", + "isSpoiler": false, + "isMainStory": false + } + ], + "Heavensward": [ + { + "name": "The Dusk Vigil", + "id": 1208, + "link": "duty/eed0add7a62/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Sohm Al", + "id": 1209, + "link": "duty/df5ab8bfd61/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Aery", + "id": 1210, + "link": "duty/339c4923556/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Vault", + "id": 1211, + "link": "duty/a62f7ee3718/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Great Gubal Library", + "id": 1212, + "link": "duty/f368b40c648/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Aetherochemical Research Facility", + "id": 1213, + "link": "duty/923e0a1d1d0/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Neverreap", + "id": 1214, + "link": "duty/618168354ea/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Fractal Continuum", + "id": 1215, + "link": "duty/c39cf50a6a5/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Saint Mocianne's Arboretum", + "id": 1402, + "link": "duty/cdad1cb65e8/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Pharos Sirius (Hard)", + "id": 1403, + "link": "duty/cd500c08682/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Antitower", + "id": 1486, + "link": "duty/36e172ff46c/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Lost City of Amdapor (Hard)", + "id": 1487, + "link": "duty/193a96b0fa4/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Sohr Khai", + "id": 1599, + "link": "duty/a8c7c5c13bd/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Hullbreaker Isle (Hard)", + "id": 1600, + "link": "duty/313b1415d0f/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Xelphatol", + "id": 1637, + "link": "duty/1d95a773990/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Great Gubal Library (Hard)", + "id": 1638, + "link": "duty/d6e98e35e6f/", + "isSpoiler": true, + "isMainStory": false + }, + { + "name": "Baelsar's Wall", + "id": 1686, + "link": "duty/bc72ef27ade/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Sohm Al (Hard)", + "id": 1687, + "link": "duty/9bd9004a140/", + "isSpoiler": true, + "isMainStory": false + } + ], + "Stormblood": [ + { + "name": "The Sirensong Sea", + "id": 1883, + "link": "duty/471227e1ee7/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Shisui of the Violet Tides", + "id": 1884, + "link": "duty/39ba54ada1c/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Bardam's Mettle", + "id": 1885, + "link": "duty/53d7100d839/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Doma Castle", + "id": 1886, + "link": "duty/cd924bd8eac/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Castrum Abania", + "id": 1887, + "link": "duty/e635797754f/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Ala Mhigo", + "id": 1888, + "link": "duty/c71bb06e67b/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Kugane Castle", + "id": 1889, + "link": "duty/37d0e83919d/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Temple of the Fist", + "id": 1890, + "link": "duty/23edcb0d626/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Drowned City of Skalla", + "id": 1988, + "link": "duty/47ef709fb04/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Hells' Lid", + "id": 2021, + "link": "duty/f9ab5899e9d/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Fractal Continuum (Hard)", + "id": 2022, + "link": "duty/b8bea03880d/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Swallow's Compass", + "id": 2057, + "link": "duty/35ed52cf463/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Burn", + "id": 2115, + "link": "duty/c8608c977a6/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Saint Mocianne's Arboretum (Hard)", + "id": 2116, + "link": "duty/25cf070eeb4/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Ghimlyt Dark", + "id": 2162, + "link": "duty/33a05f144e4/", + "isSpoiler": true, + "isMainStory": true + } + ], + "Shadowbringers": [ + { + "name": "Holminster Switch", + "id": 2377, + "link": "duty/a6165958a5c/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Dohn Mheg", + "id": 2378, + "link": "duty/5f9f024b774/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Qitana Ravel", + "id": 2379, + "link": "duty/3aff2d6760c/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Malikah's Well", + "id": 2380, + "link": "duty/f8fff809d77/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Mt. Gulg", + "id": 2381, + "link": "duty/72f2e86daba/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Amaurot", + "id": 2382, + "link": "duty/c5de427bfef/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Twinning", + "id": 2383, + "link": "duty/be6a14f45a6/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Akadaemia Anyder", + "id": 2384, + "link": "duty/d2b053e4e31/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Grand Cosmos", + "id": 2440, + "link": "duty/1f246825352/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Anamnesis Anyder", + "id": 2589, + "link": "duty/969e6501eb7/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Heroes' Gauntlet", + "id": 2618, + "link": "duty/44073b7449c/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Matoya's Relict", + "id": 2717, + "link": "duty/5a30eb6b20d/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Paglth'an", + "id": 2849, + "link": "duty/81f08141768/", + "isSpoiler": true, + "isMainStory": true + } + ], + "Endwalker": [ + { + "name": "The Tower of Zot", + "id": 2980, + "link": "duty/9c317b74e3a/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Tower of Babil", + "id": 2981, + "link": "duty/3297718033f/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Vanaspati", + "id": 2982, + "link": "duty/dadcd891cc1/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Ktisis Hyperboreia", + "id": 2983, + "link": "duty/8514a64c969/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Aitiascope", + "id": 2984, + "link": "duty/fd65b266f55/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Dead Ends", + "id": 2985, + "link": "duty/ba59c193b71/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Smileton", + "id": 2987, + "link": "duty/175e6a7245d/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Stigma Dreamscape", + "id": 2986, + "link": "duty/25f8ec27427/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Alzadaal’s Legacy", + "id": 3070, + "link": "duty/d56ff366a07/", + "isSpoiler": true, + "isMainStory": true + } + ] + }, + "Trials": { + "A Realm Reborn": [ + { + "name": "The Bowl of Embers", + "id": 856, + "link": "duty/6af1a94ccca/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Navel", + "id": 857, + "link": "duty/589c727302e/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Howling Eye", + "id": 855, + "link": "duty/7c17ae70cc6/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Thornmarch", + "id": 894, + "link": "duty/b7c47c44490/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Whorleater", + "id": 893, + "link": "duty/0850a8627aa/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Striking Tree", + "id": 994, + "link": "duty/4d8cae741db/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Akh Afah Amphitheatre", + "id": 1045, + "link": "duty/5f786d57228/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Urth's Fount", + "id": 1064, + "link": "duty/21d8c5bd54b/", + "isSpoiler": false, + "isMainStory": false + } + ], + "Heavensward": [ + { + "name": "Thok ask Thok", + "id": 1221, + "link": "duty/83f028575c8/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Limitless Blue", + "id": 1220, + "link": "duty/e9bb63551a4/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Thordan's Reign", + "id": 1400, + "link": "duty/a8a4860068c/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Containment Bay S1T7", + "id": 1485, + "link": "duty/e05c982993d/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Nidhogg's Rage", + "id": 1601, + "link": "duty/0e880006330/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Containment Bay P1T6", + "id": 1636, + "link": "duty/212ceb19a34/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Containment Bay Z1T9", + "id": 1685, + "link": "duty/26a86785be9/", + "isSpoiler": false, + "isMainStory": false + } + ], + "Stormblood": [ + { + "name": "The Pool of Tribute", + "id": 1902, + "link": "duty/b28f61e6212/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Emanation", + "id": 1901, + "link": "duty/7de6a27d145/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Shinryu's Domain", + "id": 1989, + "link": "duty/8c829b8dd8c/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Jade Stoa", + "id": 2023, + "link": "duty/44fc4abc0eb/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Tsukuyomi's Pain", + "id": 2060, + "link": "duty/0919d2674e0/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "The Great Hunt", + "id": 2109, + "link": "duty/52328939737/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Hell's Kier", + "id": 2117, + "link": "duty/628e9a05d34/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Wreath of Snakes", + "id": 2165, + "link": "duty/c79d50c803d/", + "isSpoiler": false, + "isMainStory": false + } + ], + "Shadowbringers": [ + { + "name": "The Dancing Plague", + "id": 2385, + "link": "duty/bad53f19540/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Crown of the Immaculate", + "id": 2386, + "link": "duty/55504d0495f/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Hade's Elegy", + "id": 2441, + "link": "duty/b2e08d16bce/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Cinder Drift", + "id": 2590, + "link": "duty/b388be5eb05/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Seat of Sacrifice", + "id": 2621, + "link": "duty/1543e03ff37/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Memoria Misera", + "id": 2586, + "link": "duty/841d3f69efd/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Castrum Marinum", + "id": 2718, + "link": "duty/16e9780d10e/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Cloud Deck", + "id": 2846, + "link": "duty/c4a2acf0912/", + "isSpoiler": false, + "isMainStory": false + } + ], + "Endwalker": [ + { + "name": "Zodiark's Fall", + "id": 2988, + "link": "duty/f40a8f14d6d/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Hydaelyn's Call", + "id": 2989, + "link": "duty/0dbba05fd0f/", + "isSpoiler": true, + "isMainStory": true + }, + { + "name": "Endsinger's Aria", + "id": 3072, + "link": "duty/0c90d0fd67b/", + "isSpoiler": true, + "isMainStory": true + } + ] + }, + "Raids": { + "Normal": [ + { + "name": "The Binding Coil of Bahamut", + "id": 747, + "link": "duty/7134211f501/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Second Coil of Bahamut", + "id": 887, + "link": "duty/1fed5f286f0/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Final Coil of Bahamut", + "id": 1040, + "link": "duty/f086f0517d0/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Alexander: Gordias", + "id": 1228, + "link": "duty/0e9dbcd3e88/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Alexander: Midas", + "id": 1476, + "link": "duty/d3146ecb9f8/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Alexander: The Creator", + "id": 1639, + "link": "duty/5516e8fd14f/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Omega: Deltascape", + "id": 1895, + "link": "duty/76f228004f2/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Omega: Sigmascape", + "id": 2024, + "link": "duty/f6bdf734a4a/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Omega: Alpahscape", + "id": 2118, + "link": "duty/6164ec419fe/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Eden's Gate", + "id": 2409, + "link": "duty/b2a4cb98763/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Eden's Verse", + "id": 2591, + "link": "duty/2024c23c039/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Eden's Promise", + "id": 2719, + "link": "duty/d53631d5202/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Asphodelos", + "id": 3035, + "link": "duty/8766feb6d35/", + "isSpoiler": false, + "isMainStory": false + } + ], + "Alliance": [ + { + "name": "Labyrinth of the Ancients", + "id": 883, + "link": "duty/d9f4e986d0e/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Syrcus Tower", + "id": 995, + "link": "duty/47eb1d018b6/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The World of Darkness", + "id": 1068, + "link": "duty/7f0a3551ab6/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Void Ark", + "id": 1399, + "link": "duty/07fc9cb5bc8/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Weeping City of Mhach", + "id": 1574, + "link": "duty/b0a1515fd3d/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Dun Scaith", + "id": 1689, + "link": "duty/35a8825ed8e/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Royal City of Rabanastre", + "id": 1992, + "link": "duty/56209386296/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Ridorana Lighthouse", + "id": 2106, + "link": "duty/390fb10fd68/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Orbonne Monastery", + "id": 2164, + "link": "duty/95dbcc947db/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Copied Factory", + "id": 2443, + "link": "duty/ed86e5291b2/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Puppet's Bunker", + "id": 2622, + "link": "duty/889b8d8cfa4/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Tower at Paradigm's Breach", + "id": 2847, + "link": "duty/f1a29897772/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Aglaia", + "id": 3073, + "link": "duty/f1a29897772/", + "isSpoiler": false, + "isMainStory": false + } + ] + }, + "High End": { + "Savage": [ + { + "name": "The Second Coil of Bahamut", + "id": 1000, + "link": "duty/0065196fbe4/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Alexander: Gordias", + "id": 1231, + "link": "duty/fd7aa157f29/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Alexander: Midas", + "id": 1479, + "link": "duty/f93d68d7d3c/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Alexander: The Creator", + "id": 1642, + "link": "duty/def61a894af/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Omega: Deltascape", + "id": 1898, + "link": "duty/0b519d7fd00/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Omega: Sigmascape", + "id": 2027, + "link": "duty/28d9a03c886/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Omega: Alpahscape", + "id": 2121, + "link": "duty/33360b27f26/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Eden's Gate", + "id": 2412, + "link": "duty/3f2c9ddbe05/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Eden's Verse", + "id": 2594, + "link": "duty/46c4d21a738/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Eden's Promise", + "id": 2722, + "link": "duty/6a038f0d5ef/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "Asphodelos", + "id": 3038, + "link": "duty/6a038f0d5ef/", + "isSpoiler": false, + "isMainStory": false + } + ], + "Ultimate": [ + { + "name": "The Unending Coils of Bahamut", + "id": 1993, + "link": "duty/1a863f1ea3b/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Weapon's Refrain", + "id": 2107, + "link": "duty/e6c2c586ba6/", + "isSpoiler": false, + "isMainStory": false + }, + { + "name": "The Epic of Alexander", + "id": 2444, + "link": "duty/b56710190e9/", + "isSpoiler": false, + "isMainStory": false + } + ] + } +} \ No newline at end of file From b6ea3da8c37dde20fec4ee8fc64bf06aad3d9120 Mon Sep 17 00:00:00 2001 From: Damon Greenhalgh Date: Wed, 20 Apr 2022 15:34:37 +1200 Subject: [PATCH 07/14] Added completion metric to Header component. --- src/components/Achievement.jsx | 4 ++-- src/components/Achievements.css | 21 ++++++++++++++------ src/components/Achievements.jsx | 12 +++++------ src/components/Collection.jsx | 13 ++++++------ src/components/Header.css | 35 +++++++++++++++++++++++++++++++++ src/components/Header.jsx | 9 ++++++++- src/components/Jobs.jsx | 11 +++++------ src/components/Quests.jsx | 11 +++++------ src/pages/Character.css | 20 ------------------- 9 files changed, 82 insertions(+), 54 deletions(-) create mode 100644 src/components/Header.css diff --git a/src/components/Achievement.jsx b/src/components/Achievement.jsx index 1074304..2308fa8 100644 --- a/src/components/Achievement.jsx +++ b/src/components/Achievement.jsx @@ -23,9 +23,9 @@ const Achievement = (props) => {
  • -
    +

    {props.name}

    -

    {type}

    +

    {type}

    {description}

    diff --git a/src/components/Achievements.css b/src/components/Achievements.css index 508d7f5..ceca211 100644 --- a/src/components/Achievements.css +++ b/src/components/Achievements.css @@ -1,9 +1,3 @@ -.achievements__content { - display: flex; - flex-direction: column; - gap: 1rem; -} - .achievement { display: flex; align-items: center; @@ -11,4 +5,19 @@ padding: 1rem; border-radius: .25rem; border: 1px solid var(--c-mid-background); +} + +.achievement__header { + display: flex; + align-items: center; + gap: .25rem 1rem; +} + +@media only screen and (max-width: 500px) { + + .achievement__header { + flex-direction: column; + align-items: flex-start; + } + } \ No newline at end of file diff --git a/src/components/Achievements.jsx b/src/components/Achievements.jsx index e6bef4e..73177a0 100644 --- a/src/components/Achievements.jsx +++ b/src/components/Achievements.jsx @@ -33,7 +33,7 @@ const Achievements = (props) => { useEffect(() => { if (!loading) { setAchievementsContent( -
      +
        {(data.slice(achievementsPage * capacity, (achievementsPage + 1) * capacity)).map(achievement => { return (
        -
        -
        -

        Points

        -

        {points}

        -
        +
        { loading ? : diff --git a/src/components/Collection.jsx b/src/components/Collection.jsx index 0a9136a..d2278d8 100644 --- a/src/components/Collection.jsx +++ b/src/components/Collection.jsx @@ -81,19 +81,18 @@ const Collection = (props) => { } return minionContent; }) - } + } setLoading(false); }, []); return (
        -
        - -
        -

        {numCollected + " / " + totalCollection}

        -

        {Math.round(numCollected / totalCollection * 100) + " %"}

        -
        +
        { diff --git a/src/components/Header.css b/src/components/Header.css new file mode 100644 index 0000000..59d0c1c --- /dev/null +++ b/src/components/Header.css @@ -0,0 +1,35 @@ +.header__content { + display: flex; + gap: 1rem; + justify-content: space-between; +} + +.completion-rate { + display: flex; + align-items: center; + gap: 1rem; +} + +.completion-rate h3 { + background-color: var(--color-completed); + color: white; + padding: .5rem; + border-radius: .25rem; +} + +.completion-rate h4 { + color: var(--color-completed); +} + +@media only screen and (max-width: 450px) { + + .header__content{ + flex-direction: column; + } + + .completion-rate { + flex-direction: row-reverse; + justify-content: flex-end; + } + +} diff --git a/src/components/Header.jsx b/src/components/Header.jsx index 68cd3d1..a30cba9 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -1,8 +1,15 @@ import Divider from './utility/Divider'; +import './Header.css'; const Header = (props) => { return (
        -

        {props.name}

        +
        +

        {props.name}

        +
        +

        {props.minor}

        +

        {props.major}

        +
        +
        ); diff --git a/src/components/Jobs.jsx b/src/components/Jobs.jsx index 475ea8b..8d92c33 100644 --- a/src/components/Jobs.jsx +++ b/src/components/Jobs.jsx @@ -52,12 +52,11 @@ const Jobs = (props) => { return (
        -
        - -
        -

        {completion[0]}

        -

        {completion[1]}

        -
        +
        diff --git a/src/pages/Character.jsx b/src/pages/Character.jsx index 2e01ae6..56417c8 100644 --- a/src/pages/Character.jsx +++ b/src/pages/Character.jsx @@ -69,10 +69,8 @@ const Character = (props) => { // On mount, fetch character data, update component props with retrieved data. useEffect(async () => { setIsLoading(true); - - // Enable searchbar on navbar. props.setShowSearchbar(true); - + document.documentElement.style.setProperty('--content-width', '70rem'); let characterData, freeCompanyData; // Get extended character data from xivapi.com @@ -86,8 +84,7 @@ const Character = (props) => { // Set window title to 'XIV Tracker | {character name}' document.title = "XIV Tracker | " + characterData.Name; - // Set new content width - document.documentElement.style.setProperty('--content-width', '70rem'); + // Add character to recently viewed list. storeRecent(characterData); From e66e3cd7964d1685462b4b8ca2865cfed32402bb Mon Sep 17 00:00:00 2001 From: Damon Greenhalgh Date: Fri, 6 May 2022 18:21:02 +1200 Subject: [PATCH 09/14] Add help page --- changelog.md | 1 + package.json | 2 +- src/App.jsx | 9 +++++++++ src/components/Navbar.jsx | 6 +++++- src/pages/Help.jsx | 41 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 src/pages/Help.jsx diff --git a/changelog.md b/changelog.md index 554947e..06803fc 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased ## [1.2.1] - 2022-04-17 +Moved from **Github Pages** to **AWS Amplify** as pages has problems with single page web applications. ### Added diff --git a/package.json b/package.json index a290546..3c70b16 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "xiv-tracker", "version": "1.2.1", "private": true, - "homepage": "https://v1-2-1.d3apguzgjacrer.amplifyapp.com/#/", + "homepage": "https://www.xivtracker.gg/", "dependencies": { "@testing-library/jest-dom": "^5.16.2", "@testing-library/react": "^12.1.2", diff --git a/src/App.jsx b/src/App.jsx index 9e3409a..215f8eb 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -4,6 +4,7 @@ import { BrowserRouter, Routes, Route} from 'react-router-dom'; import Home from './pages/Home'; import Character from './pages/Character'; import Settings from './pages/Settings'; +import Help from './pages/Help'; import Navbar from './components/Navbar'; import Footer from './components/Footer'; import Loading from './components/utility/Loading'; @@ -130,6 +131,14 @@ const App = () => { /> } /> + + } + /> { @@ -29,7 +30,10 @@ const Navbar = (props) => { /> : null } - + + + + {profile} diff --git a/src/pages/Help.jsx b/src/pages/Help.jsx new file mode 100644 index 0000000..56197d3 --- /dev/null +++ b/src/pages/Help.jsx @@ -0,0 +1,41 @@ +import { useEffect } from "react"; +import Divider from "../components/utility/Divider"; + +const Help = (props) => { + + useEffect(() => { + document.documentElement.style.setProperty('--content-width', '70rem'); + props.setShowSearchbar(false); + document.title = "XIV Tracker | Help"; + }, []) + + return( +
        +

        FAQ

        + +
        +
        +

        Why can't I see quests and achievements?

        +

        + XIV Tracker uses a characters achievements to determine quest + completions. Lodestone has achievements set to private by + default. You can change these settings here +

        +
        +
        +

        Wait a minute, that isn't correct!

        +

        + XIV Tracker is dependent on resources such as the official + Lodestone site. Loadestone itself updates approximately every 12 + hours. XIV Tracker can only show data from the most recent update. +

        +
        +
        +
        + ); +} + +export default Help; \ No newline at end of file From ea7c8cd3a3004098eb2cf85ed13d50ead563dbdd Mon Sep 17 00:00:00 2001 From: Damon Greenhalgh Date: Fri, 6 May 2022 20:06:42 +1200 Subject: [PATCH 10/14] Remove type information from feature banners --- changelog.md | 1 + src/components/Featured.css | 11 ----------- src/components/Featured.jsx | 2 -- src/data/feature.json | 9 ++------- 4 files changed, 3 insertions(+), 20 deletions(-) diff --git a/changelog.md b/changelog.md index 06803fc..c63a767 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,7 @@ Moved from **Github Pages** to **AWS Amplify** as pages has problems with single ### Added +- Added help page. - Added guide links to profession jobs. ### Changes diff --git a/src/components/Featured.css b/src/components/Featured.css index b43e33d..50b192b 100644 --- a/src/components/Featured.css +++ b/src/components/Featured.css @@ -24,17 +24,6 @@ height: 100%; } -.featured__overlay h4 { - background-color: var(--color-completed); - box-shadow: 0 .5rem 1rem var(--c-shadow); - color: white; - border-radius: .25rem; - padding: .5rem; - position: absolute; - right: 1rem; - top: 1rem; -} - .featured__overlay p { color: #d9dfe4; } diff --git a/src/components/Featured.jsx b/src/components/Featured.jsx index 5bad93f..f35d59b 100644 --- a/src/components/Featured.jsx +++ b/src/components/Featured.jsx @@ -61,14 +61,12 @@ const Featured = () => { -

        {events[index].type}

        {events[index].date}

        {events[index].title}

        -
        + ); +} + +export default Return; \ No newline at end of file diff --git a/src/pages/Help.jsx b/src/pages/Help.jsx index 83d7b6e..8ea4baa 100644 --- a/src/pages/Help.jsx +++ b/src/pages/Help.jsx @@ -1,5 +1,6 @@ import { useEffect } from "react"; import Divider from "../components/utility/Divider"; +import Return from "../components/utility/Return"; const Help = (props) => { @@ -13,6 +14,7 @@ const Help = (props) => {

        FAQ

        +

        "Why can't I see my quests and achievements?"

        diff --git a/src/pages/Settings.jsx b/src/pages/Settings.jsx index b3cf165..28496a0 100644 --- a/src/pages/Settings.jsx +++ b/src/pages/Settings.jsx @@ -1,5 +1,4 @@ import './Settings.css'; -import { useNavigate } from 'react-router-dom'; import { useState, useEffect } from 'react'; import { BsChevronDown, BsChevronUp } from 'react-icons/bs'; import Button from '../components/utility/Button'; @@ -7,6 +6,7 @@ import Banner from '../components/Banner'; import Divider from '../components/utility/Divider'; import Loading from '../components/utility/Loading'; import Checkbox from '../components/utility/Checkbox'; +import Return from '../components/utility/Return'; const splashName = [ 'None', @@ -29,7 +29,6 @@ const Settings = (props) => { const [displayDropdown, setDisplayDropdown] = useState(-1); const [referenceBanner, setReferenceBanner] = useState(null); const [isSearching, setIsSearching] = useState(false); - const navigate = useNavigate(); // Mount useEffect(() => { @@ -88,6 +87,7 @@ const Settings = (props) => {

        Settings

        +
        @@ -159,7 +159,6 @@ const Settings = (props) => {
        -
        ); } From 6d7008affba326dcdae7130bc7e8be5d29bc3606 Mon Sep 17 00:00:00 2001 From: Damon Greenhalgh Date: Sun, 8 May 2022 01:26:23 +1200 Subject: [PATCH 14/14] Add useFetchData custom hook --- changelog.md | 5 +- package-lock.json | 329 +----------------- package.json | 3 - src/App.css | 2 +- src/components/Achievement.jsx | 35 +- src/components/Achievements.jsx | 63 ++-- src/components/Attributes.css | 42 --- src/components/Attributes.jsx | 96 ----- src/components/Collection.jsx | 162 ++++----- src/components/Equipment.jsx | 36 -- src/components/Header.jsx | 2 +- src/components/Jobs.jsx | 6 +- src/components/{Equipment.css => Profile.css} | 43 +++ src/components/Profile.jsx | 162 +++++++++ src/components/Quests.jsx | 2 +- src/components/Searchbar.jsx | 2 - src/hooks/useFetchData.jsx | 29 ++ src/images/brand-extended.png | Bin 70451 -> 70451 bytes src/pages/Character.css | 15 +- src/pages/Character.jsx | 194 ++++------- 20 files changed, 425 insertions(+), 803 deletions(-) delete mode 100644 src/components/Attributes.css delete mode 100644 src/components/Attributes.jsx delete mode 100644 src/components/Equipment.jsx rename src/components/{Equipment.css => Profile.css} (61%) create mode 100644 src/components/Profile.jsx create mode 100644 src/hooks/useFetchData.jsx diff --git a/changelog.md b/changelog.md index c63a767..d51df01 100644 --- a/changelog.md +++ b/changelog.md @@ -11,12 +11,13 @@ Moved from **Github Pages** to **AWS Amplify** as pages has problems with single - Added help page. - Added guide links to profession jobs. +- Added useFetchData custom hook, replaces the useEffect nested async function pattern. ### Changes - Added media queries for multiple components. -- Featured events link now open a new tab. -- Events will be displayed based on the current time. +- Featured events link will now open a new tab. +- Events will be displayed based on the current date. - Quests system refactor. Improved maintainability and reduced overhead. ### Fixed diff --git a/package-lock.json b/package-lock.json index 1e0af2d..c2def05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "xiv-tracker", - "version": "0.1.0", + "version": "1.2.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "xiv-tracker", - "version": "0.1.0", + "version": "1.2.1", "dependencies": { "@testing-library/jest-dom": "^5.16.2", "@testing-library/react": "^12.1.2", @@ -17,9 +17,6 @@ "react-router-dom": "^6.2.1", "react-scripts": "^5.0.0", "web-vitals": "^2.1.4" - }, - "devDependencies": { - "gh-pages": "^3.2.3" } }, "node_modules/@babel/code-frame": { @@ -4207,15 +4204,6 @@ "node": ">=8" } }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array.prototype.flat": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", @@ -6148,12 +6136,6 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.50.tgz", "integrity": "sha512-g5X/6oVoqLyzKfsZ1HsJvxKoUAToFMCuq1USbmp/GPIwJDRYV1IEcv+plYTdh6h11hg140hycCBId0vf7rL0+Q==" }, - "node_modules/email-addresses": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", - "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==", - "dev": true - }, "node_modules/emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -7241,32 +7223,6 @@ "minimatch": "^3.0.4" } }, - "node_modules/filename-reserved-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", - "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/filenamify": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", - "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", - "dev": true, - "dependencies": { - "filename-reserved-regex": "^2.0.0", - "strip-outer": "^1.0.1", - "trim-repeated": "^1.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/filesize": { "version": "8.0.7", "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", @@ -7708,94 +7664,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gh-pages": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-3.2.3.tgz", - "integrity": "sha512-jA1PbapQ1jqzacECfjUaO9gV8uBgU6XNMV0oXLtfCX3haGLe5Atq8BxlrADhbD6/UdG9j6tZLWAkAybndOXTJg==", - "dev": true, - "dependencies": { - "async": "^2.6.1", - "commander": "^2.18.0", - "email-addresses": "^3.0.1", - "filenamify": "^4.3.0", - "find-cache-dir": "^3.3.1", - "fs-extra": "^8.1.0", - "globby": "^6.1.0" - }, - "bin": { - "gh-pages": "bin/gh-pages.js", - "gh-pages-clean": "bin/gh-pages-clean.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gh-pages/node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gh-pages/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/gh-pages/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/gh-pages/node_modules/globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "dependencies": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gh-pages/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/gh-pages/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -11582,36 +11450,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -14378,18 +14216,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-outer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/style-loader": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", @@ -14899,18 +14725,6 @@ "node": ">=8" } }, - "node_modules/trim-repeated": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", - "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/tryer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", @@ -19092,12 +18906,6 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, "array.prototype.flat": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", @@ -20532,12 +20340,6 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.50.tgz", "integrity": "sha512-g5X/6oVoqLyzKfsZ1HsJvxKoUAToFMCuq1USbmp/GPIwJDRYV1IEcv+plYTdh6h11hg140hycCBId0vf7rL0+Q==" }, - "email-addresses": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", - "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==", - "dev": true - }, "emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -21335,23 +21137,6 @@ "minimatch": "^3.0.4" } }, - "filename-reserved-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", - "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", - "dev": true - }, - "filenamify": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", - "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", - "dev": true, - "requires": { - "filename-reserved-regex": "^2.0.0", - "strip-outer": "^1.0.1", - "trim-repeated": "^1.0.0" - } - }, "filesize": { "version": "8.0.7", "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", @@ -21648,77 +21433,6 @@ "get-intrinsic": "^1.1.1" } }, - "gh-pages": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-3.2.3.tgz", - "integrity": "sha512-jA1PbapQ1jqzacECfjUaO9gV8uBgU6XNMV0oXLtfCX3haGLe5Atq8BxlrADhbD6/UdG9j6tZLWAkAybndOXTJg==", - "dev": true, - "requires": { - "async": "^2.6.1", - "commander": "^2.18.0", - "email-addresses": "^3.0.1", - "filenamify": "^4.3.0", - "find-cache-dir": "^3.3.1", - "fs-extra": "^8.1.0", - "globby": "^6.1.0" - }, - "dependencies": { - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - } - } - }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -24437,27 +24151,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, "pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -26369,15 +26062,6 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, - "strip-outer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.2" - } - }, "style-loader": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", @@ -26739,15 +26423,6 @@ "punycode": "^2.1.1" } }, - "trim-repeated": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", - "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.2" - } - }, "tryer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", diff --git a/package.json b/package.json index 3c70b16..d6e214b 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,5 @@ "last 1 firefox version", "last 1 safari version" ] - }, - "devDependencies": { - "gh-pages": "^3.2.3" } } diff --git a/src/App.css b/src/App.css index e0c223f..042f909 100644 --- a/src/App.css +++ b/src/App.css @@ -165,7 +165,7 @@ input:focus { } .icon--mid { - height: 4rem; + min-height: 4rem; min-width: 4rem; } diff --git a/src/components/Achievement.jsx b/src/components/Achievement.jsx index 2308fa8..0d65809 100644 --- a/src/components/Achievement.jsx +++ b/src/components/Achievement.jsx @@ -1,33 +1,26 @@ -import { useEffect, useState } from "react"; +import { useFetchData } from "../hooks/useFetchData"; import Item from "./Item"; -const Achievement = (props) => { - - const [description, setDescription] = useState(null); - const [type, setType] = useState(null); - - useEffect(() => { - - const fetchData = async () => { - await fetch("https://xivapi.com/achievement/" + props.id, {mode: 'cors'}) - .then(response => response.json()) - .then(data => { - setDescription(data.Description); - setType(data.AchievementCategory.Name); - }); - } - fetchData(); - - }, []) +/** + * @name Achievement + * @description The achievement component to be displayed. + * @param {*} props + * @returns + */ +const Achievement = (props) => { + const {data, loading} = useFetchData("https://xivapi.com/achievement/" + props.id) return ( + loading ? + null : +
      • {props.name}

        -

        {type}

        +

        {data.AchievementCategory.Name}

        -

        {description}

        +

        {data.Description}

        {props.points}

      • diff --git a/src/components/Achievements.jsx b/src/components/Achievements.jsx index 73177a0..e5daa47 100644 --- a/src/components/Achievements.jsx +++ b/src/components/Achievements.jsx @@ -1,40 +1,35 @@ +// Hooks import { useEffect, useState } from 'react'; -import Header from './Header'; +import { useFetchData } from '../hooks/useFetchData'; + +// Components import Achievement from './Achievement'; import Loading from './utility/Loading'; -import './Achievements.css'; +import Header from './Header'; import Navigator from './utility/Navigator'; +// Style +import './Achievements.css'; + +/** + * @name Achievements + * @description Container for character achievements. + * @param {*} props + * @returns + */ const Achievements = (props) => { - const [points, setPoints] = useState(0); - const [achivementsContent, setAchievementsContent] = useState(null); - const [achievementsPage, setAchievementsPage] = useState(0); - const [maxPage, setMaxPage] = useState(0); - const [loading, setLoading] = useState(true); - const [data, setData] = useState(null); - const capacity = 8; - // Mount - useEffect(() => { - const fetchData = async () => { - await fetch("https://xivapi.com/character/" + props.data.ID + "?extended=1&data=AC", {mode: 'cors'}) - .then(response => response.json()) - .then(data => { - setPoints(data.Achievements.Points); - setData(data.Achievements.List); - setMaxPage(Math.ceil(data.Achievements.List.length / capacity) - 1) - setLoading(false); - }); - } - fetchData(); - }, [props.data.ID]); + const capacity = 8; + const {data, loading} = useFetchData("https://xivapi.com/character/" + props.data.ID + "?extended=1&data=AC"); + const [content, setContent] = useState(null); + const [index, setIndex] = useState(0); // Update useEffect(() => { if (!loading) { - setAchievementsContent( + setContent(
          - {(data.slice(achievementsPage * capacity, (achievementsPage + 1) * capacity)).map(achievement => + {(data.Achievements.List.slice(index * capacity, (index + 1) * capacity)).map(achievement => {
        ) } - - }, [achievementsPage, data, loading]) + }, [index, loading]) return ( -
        + loading ? + null : + +
        { loading ? : - achivementsContent + content }
        diff --git a/src/components/Attributes.css b/src/components/Attributes.css deleted file mode 100644 index e808c81..0000000 --- a/src/components/Attributes.css +++ /dev/null @@ -1,42 +0,0 @@ -.attributes__main { - display: grid; - grid-template-areas: - "type value" - "bar bar " - ; -} - -.attributes__main h4 { - text-align: end; -} - -.attributes__list { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 1rem 2rem; -} - -.profile { - display: flex; - flex-direction: column; - gap: .25rem; -} - -.profile li { - display: flex; - align-items: center; - margin: .5rem 0 1rem 2rem; - gap: .5rem; -} - -.profile img { - max-height: 2rem; -} - -@media only screen and (max-width: 500px) { - - .attributes__list { - grid-template-columns: 1fr; - } - -} \ No newline at end of file diff --git a/src/components/Attributes.jsx b/src/components/Attributes.jsx deleted file mode 100644 index 6378792..0000000 --- a/src/components/Attributes.jsx +++ /dev/null @@ -1,96 +0,0 @@ -import './Attributes.css'; -import Bar from './utility/Bar'; -import Header from './Header'; -import Button from './utility/Button'; -import Divider from './utility/Divider'; -import { useState } from 'react'; -import maleIcon from '../images/male.png'; -import femaleIcon from '../images/female.png'; - -const Attributes = (props) => { - const [contentToggle, setContentToggle] = useState(true); - const baseUrl = "https://xivapi.com"; - return ( -
        -
        -
        -
        - -
          -
          -

          {props.data.GearSet.Attributes.at(-2).Attribute.Name}

          -

          {props.data.GearSet.Attributes.at(-2).Value}

          - -
          -
          -

          {props.data.GearSet.Attributes.at(-1).Attribute.Name}

          -

          {props.data.GearSet.Attributes.at(-1).Value}

          - -
          - {Object.values(props.data.GearSet.Attributes).slice(0, -2).map(attribute => -
        • -

          {attribute.Attribute.Name}

          -

          {attribute.Value}

          -
        • - )} -
        - -
          -

          Name / Title

          - -
        • -

          {props.data.Title.TitleTop ? props.data.Title.Name : null}

          -
          {props.data.Name}
          -

          {props.data.Title.TitleTop ? null: props.data.Title.Name}

          -
        • - { - props.data.GrandCompany.Company !== null && props.data.GrandCompany.Rank !== null ? - <> -

          Grand Company

          - -
        • - -
          {props.data.GrandCompany.Company.Name + ", " + props.data.GrandCompany.Rank.Name}
          -
        • - : - null - } -

          Race / Clan / Gender

          - -
        • - -
          {props.data.Race.Name + " / " + props.data.Tribe.Name + " / " + (props.data.Gender == 1 ? "Male" : "Female")}
          -
        • -

          City-state

          - -
        • - -
          {props.data.Town.Name}
          -
        • -

          Nameday

          - -
        • -
          {props.data.Nameday}
          -
        • -

          Guardian Diety

          - -
        • - -
          {props.data.GuardianDeity.Name}
          -
        • -
        -
        - ); -} - -export default Attributes; \ No newline at end of file diff --git a/src/components/Collection.jsx b/src/components/Collection.jsx index d2278d8..47c1607 100644 --- a/src/components/Collection.jsx +++ b/src/components/Collection.jsx @@ -1,127 +1,87 @@ +// Hooks import { useState, useEffect } from 'react'; +import { useFetchData } from '../hooks/useFetchData'; + +// Components import Item from './Item'; import Header from './Header'; -import Loading from '../components/utility/Loading'; import Navigator from './utility/Navigator'; + +// Style import './Collection.css'; +/** + * @name Collection + * @description Collection container for mount and minion items. + * @param {*} props + * @returns + */ const Collection = (props) => { - const [mountPage, setMountPage] = useState(0); - const [minionPage, setMinionPage] = useState(0); - const [mountContent, setMountContent] = useState([]); - const [minionContent, setMinionContent] = useState([]); - const [maxMountPage, setMaxMountPage] = useState(0); - const [maxMinionPage, setMaxMinionPage] = useState(0); - const [loading, setLoading] = useState(true); - const [numCollected, setNumCollected] = useState(0); - const capacity = 40; - - // Will have to manually update these numbers after every major patch. + // Will have to manually update these numbers after every patch. const totalCollection = 747 - useEffect(async () => { - - let mountData, minionData; - let storedData = localStorage.getItem("storedData"); - - if (storedData == null) { - localStorage.setItem("storedData", "{}"); - } - - storedData = JSON.parse(localStorage.getItem("storedData")); - - // Fetch character mounts and minions - await fetch("https://xivapi.com/character/" + props.id + "?data=MIMO", {mode: 'cors'}) - .then(response => response.json()) - .then(data => { - mountData = data.Mounts; - minionData = data.Minions; - }); - - // Determine if we need to retrieve data from localStorage. - if (mountData == null || minionData == null) { - if (storedData[props.id] != null) { - minionData = storedData[props.id].Minions; - mountData = storedData[props.id].Mounts; - } - } else { - storedData[props.id] = {"Minions": minionData, "Mounts": mountData}; - localStorage.setItem("storedData", JSON.stringify(storedData)); - } - - if (mountData !== null && minionData !== null) { - - setMaxMountPage(Math.ceil(mountData.length / capacity) - 1); - setMaxMinionPage(Math.ceil(minionData.length / capacity) - 1); - setNumCollected(mountData.length + minionData.length); - - setMountContent(mountContent => { - for (let i = 0; i < Math.ceil(mountData.length / capacity); i++) { - mountContent.push( -
        - {mountData.slice(i * capacity, (i + 1) * capacity).map((mount, index) => { + const capacity = 40; + const {data, loading} = useFetchData("https://xivapi.com/character/" + props.id + "?data=MIMO"); + const [mountPage, setMountPage] = useState(0); + const [minionPage, setMinionPage] = useState(0); + const [mountContent, setMountContent] = useState(null); + const [minionContent, setMinionContent] = useState(null); + + useEffect(() => { + if (!loading) { + if (data.Mounts !== null) { + setMountContent( +
        + {data.Mounts.slice(mountPage * capacity, (mountPage + 1) * capacity).map((mount, index) => { return })}
        - ); - } - return mountContent; - }) - - setMinionContent(minionContent => { - for (let i = 0; i < Math.ceil(minionData.length / capacity); i++) { - minionContent.push( -
        - {minionData.slice(i * capacity, (i + 1) * capacity).map((minion, index) => { + ) + } + if (data.Minions !== null) { + setMinionContent( +
        + {data.Minions.slice(minionPage * capacity, (minionPage + 1) * capacity).map((minion, index) => { return })} -
        - ); - } - return minionContent; - }) +
        + ) + } } - setLoading(false); - }, []); + }, [data, loading, mountPage, minionPage]); return ( -
        + loading ? + null : +
        -
        - { - loading ? - : - <> -
        - {mountContent[mountPage]} - -
        - -
        - {minionContent[minionPage]} - -
        - - } +
        + {mountContent} + +
        +
        + {minionContent} + +
        ); diff --git a/src/components/Equipment.jsx b/src/components/Equipment.jsx deleted file mode 100644 index 6ddc636..0000000 --- a/src/components/Equipment.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import './Equipment.css'; -import Item from './Item'; -import JobItem from './JobItem'; - -const Equipment = (props) => { - - const equipmentNames = Object.keys(props.gear); - - return ( -
        -
        - -
        - {Object.values(props.gear).map((item, index) => - - )} -
        - ); -} - -export default Equipment; \ No newline at end of file diff --git a/src/components/Header.jsx b/src/components/Header.jsx index 7a54e6e..8f71f94 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -5,7 +5,7 @@ const Header = (props) => {

        {props.name}

        -
        +

        {props.minor}

        {props.major}

        diff --git a/src/components/Jobs.jsx b/src/components/Jobs.jsx index 8d92c33..0a47fa4 100644 --- a/src/components/Jobs.jsx +++ b/src/components/Jobs.jsx @@ -1,5 +1,5 @@ import './Jobs.css'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import Header from './Header'; import Button from '../components/utility/Button'; import JobItem from './JobItem'; @@ -13,8 +13,6 @@ import rangedIcon from '../images/ranged.png'; import magicIcon from '../images/magic.png'; import handIcon from '../images/hand.png'; import landIcon from '../images/land.png'; -import { useEffect } from 'react'; - const Jobs = (props) => { @@ -50,7 +48,7 @@ const Jobs = (props) => { }, []) return ( -
        +
        { + const [contentToggle, setContentToggle] = useState(true); + const baseUrl = "https://xivapi.com"; + const equipmentNames = Object.keys(props.data.Character.GearSet.Gear); + + return( +
        +
        +
        +
        +
        +
        +
          +
          +

          {props.data.Character.GearSet.Attributes.at(-2).Attribute.Name}

          +

          {props.data.Character.GearSet.Attributes.at(-2).Value}

          + +
          +
          +

          {props.data.Character.GearSet.Attributes.at(-1).Attribute.Name}

          +

          {props.data.Character.GearSet.Attributes.at(-1).Value}

          + +
          + {Object.values(props.data.Character.GearSet.Attributes).slice(0, -2).map(attribute => +
        • +

          {attribute.Attribute.Name}

          +

          {attribute.Value}

          +
        • + )} +
        +
          +

          Name / Title

          + +
        • +

          {props.data.Character.Title.TitleTop ? props.data.Title.Name : null}

          +
          {props.data.Character.Name}
          +

          {props.data.Character.Title.TitleTop ? null: props.data.Character.Title.Name}

          +
        • + { + props.data.Character.GrandCompany.Company !== null && props.data.Character.GrandCompany.Rank !== null ? + <> +

          Grand Company

          + +
        • + +
          {props.data.Character.GrandCompany.Company.Name + ", " + props.data.Character.GrandCompany.Rank.Name}
          +
        • + : + null + } +

          Race / Clan / Gender

          + +
        • + +
          {props.data.Character.Race.Name + " / " + props.data.Character.Tribe.Name + " / " + (props.data.Character.Gender == 1 ? "Male" : "Female")}
          +
        • +

          City-state

          + +
        • + +
          {props.data.Character.Town.Name}
          +
        • +

          Nameday

          + +
        • +
          {props.data.Character.Nameday}
          +
        • +

          Guardian Diety

          + +
        • + +
          {props.data.Character.GuardianDeity.Name}
          +
        • + { + props.data.FreeCompany == null ? + null : + <> +

          Free Company

          + + + + + +
        + } + fc={props.data.FreeCompany.Crest} + name={props.data.FreeCompany.Name} + content={props.data.FreeCompany.Slogan} + misc={props.data.FreeCompany.Server} + /> + + } +
      +
    +
    +
    + +
    + {Object.values(props.data.Character.GearSet.Gear).map((item, index) => + + )} +
    +
  • +
    + ); +} + +export default Profile; \ No newline at end of file diff --git a/src/components/Quests.jsx b/src/components/Quests.jsx index a43435e..c02b9e7 100644 --- a/src/components/Quests.jsx +++ b/src/components/Quests.jsx @@ -108,7 +108,7 @@ const Quests = (props) => { }, []); return ( -
    +
    { const [displayDropdown, setDisplayDropdown] = useState(false); const [recent, setRecent] = useState(null); - const [expanded, setExpanded] = useState(false); - const callbackMethod = (event) => { event.preventDefault(); setDisplayRecent(false); diff --git a/src/hooks/useFetchData.jsx b/src/hooks/useFetchData.jsx new file mode 100644 index 0000000..21d47f2 --- /dev/null +++ b/src/hooks/useFetchData.jsx @@ -0,0 +1,29 @@ +import { useEffect, useState } from 'react'; + +/** + * @name useFetchData + * @description This hook requests data from the url parameter endpoint. + * @param {*} url The http endpoint to fetch from. + * @returns JSON object containing the response from the fetch request. + */ +export const useFetchData = (url) => { + + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchData = async (url) => { + + setLoading(true); + + await fetch(url, {mode: 'cors'}) + .then(response => response.json()) + .then(responseJson => setData(responseJson)) + + setLoading(false); + } + fetchData(url) + }, [url]) + + return { data, loading }; +} \ No newline at end of file diff --git a/src/images/brand-extended.png b/src/images/brand-extended.png index 19f6678e71c959412350b5cf41f5e0da8cd5045b..48da60c89764f77d69b6285a7453032729a26f15 100644 GIT binary patch delta 195 zcmdnIjAipOmJNrQtW9+d%tH)Ktc(n;jEuAmjjRj|loTp*3#@$eQ!>*kT@p)DZIz4+ zjEr;*(3MX9$0V*kT@p)DZIz4+ zjEr;*(3MX9$0V<8WNBn*VV-QEn`C5?sB2_qVxpUrY?`KPlx${cXkwabVPLk|f%yy* nF$yQ^vx)Me*fRMfi_PQ%tYVWFvqiGl#~fPO#J-K4(U=DSp20I~ diff --git a/src/pages/Character.css b/src/pages/Character.css index bab8b60..0366e13 100644 --- a/src/pages/Character.css +++ b/src/pages/Character.css @@ -13,11 +13,6 @@ background-color: var(--c-content-background); } -.character__panel { - display: flex; - gap: 2rem; -} - .section { width: 100%; position: relative; @@ -32,6 +27,11 @@ box-shadow: 0 0 .8rem var(--c-shadow); } +.profile__content { + display: flex; + gap: 2rem; +} + @media only screen and (max-width: 1150px) { @@ -40,7 +40,7 @@ padding: 0 1rem; } - .character__panel { + .profile__content { flex-direction: column; } @@ -50,7 +50,6 @@ .character__nav { flex-direction: column; - } - + } } \ No newline at end of file diff --git a/src/pages/Character.jsx b/src/pages/Character.jsx index 56417c8..9dc9053 100644 --- a/src/pages/Character.jsx +++ b/src/pages/Character.jsx @@ -1,32 +1,39 @@ +// Hooks import { useState, useEffect } from 'react'; import { useParams } from 'react-router-dom'; -import Banner from '../components/Banner'; -import Equipment from '../components/Equipment'; +import { useFetchData } from '../hooks/useFetchData'; + +// Components +import Profile from '../components/Profile'; import Jobs from '../components/Jobs'; import Collection from '../components/Collection'; -import Attributes from '../components/Attributes'; import Quests from '../components/Quests'; import Achievements from '../components/Achievements'; import Loading from '../components/utility/Loading'; -import './Character.css'; - +import Banner from '../components/Banner'; import Button from '../components/utility/Button'; +// Style +import './Character.css'; + +/** + * @name Character + * @description This component represents the character profile page. + * @param {*} props + * @returns + */ const Character = (props) => { - const [equipmentProps, setEquipmentProps] = useState(null); - const [freeCompanyProps, setFreeCompanyProps] = useState({isDisabled: true}); - const [jobsProps, setJobsProps] = useState(null); - const [attributeProps, setAttributeProps] = useState(null); - const [questsProps, setQuestsProps] = useState(null); - const [achievementProps, setAchievementProps] = useState(null); - const [characterProps, setCharacterProps] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [activePanel, setActivePanel] = useState(0); const { id } = useParams(); - - // This function stores the currently viewed character in the recent list - // for searchbar use. + const {data, loading} = useFetchData("https://xivapi.com/character/" + id + "?extended=1&data=FC"); + const [index, setIndex] = useState(0); + + /** + * @name storeRecent + * @description This function stores recently viewed characters into local + * storage to be viewed later. + * @param {*} data A JSON object from XIVAPI + */ const storeRecent = (data) => { // Define new character object. @@ -65,143 +72,80 @@ const Character = (props) => { localStorage.setItem("recent", JSON.stringify(recent)); } - - // On mount, fetch character data, update component props with retrieved data. - useEffect(async () => { - setIsLoading(true); + useEffect(() => { props.setShowSearchbar(true); document.documentElement.style.setProperty('--content-width', '70rem'); - let characterData, freeCompanyData; - - // Get extended character data from xivapi.com - await fetch("https://xivapi.com/character/" + id + "?extended=1&data=FC", {mode: 'cors'}) - .then(response => response.json()) - .then(data => { - characterData = data.Character; - freeCompanyData = data.FreeCompany; - }); - - // Set window title to 'XIV Tracker | {character name}' - document.title = "XIV Tracker | " + characterData.Name; - - - - // Add character to recently viewed list. - storeRecent(characterData); - - setCharacterProps({ - type: '', - avatar: , - name: characterData.Name, - title: characterData.Title.Name, - misc: characterData.Server - }) - - setEquipmentProps({ - portrait: characterData.Portrait, - level: characterData.ActiveClassJob.Level, - gear: characterData.GearSet.Gear, - name: characterData.ActiveClassJob.Job.Name, - exp: [characterData.ActiveClassJob.ExpLevel, characterData.ActiveClassJob.ExpLevelMax], - icon: characterData.ActiveClassJob.Job.Icon - }); - - // Only update free company props if the character belongs to - // a free company. - if (freeCompanyData !== null) { - setFreeCompanyProps({ - type: 'free-company', - avatar: -
    - - - -
    - , - fc: freeCompanyData.Crest, - name: freeCompanyData.Name, - content: freeCompanyData.Slogan, - misc: "Rank: " + freeCompanyData.Rank - }) + if (!loading) { + document.title = "XIV Tracker | " + data.Character.Name; + storeRecent(data.Character); } - - setJobsProps({jobs: characterData.ClassJobs}); - setAttributeProps({data: characterData}); - setQuestsProps ({ - id: characterData.ID, - referenceCharacter: props.referenceCharacter - }); - setAchievementProps({data: characterData}); - - // Completed loading, update. - setIsLoading(() => false); - }, [id]); + }, [data]); return ( - isLoading ? + loading ? :
    - - - + } + name={data.Character.Name} + title={data.Character.Title.Name} + misc={data.Character.Server} + /> - -
    - -
    - - -
    -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - + + + + +
    ); }