Remove trailing semicolons

This commit is contained in:
Justin Edmund 2022-12-05 17:54:46 -08:00
parent 6d6eaf59ee
commit e8843699c7
106 changed files with 3690 additions and 3731 deletions

View file

@ -1,6 +1,5 @@
{ {
"editor.formatOnSave": true, "semi": false,
"prettier.semi": false,
"tabWidth": 2, "tabWidth": 2,
"singleQuote": true "singleQuote": true
} }

View file

@ -1,18 +1,18 @@
import React from "react"; import React from 'react'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import * as Dialog from "@radix-ui/react-dialog"; import * as Dialog from '@radix-ui/react-dialog'
import CrossIcon from "~public/icons/Cross.svg"; import CrossIcon from '~public/icons/Cross.svg'
import "./index.scss"; import './index.scss'
const AboutModal = () => { const AboutModal = () => {
const { t } = useTranslation("common"); const { t } = useTranslation('common')
return ( return (
<Dialog.Root> <Dialog.Root>
<Dialog.Trigger asChild> <Dialog.Trigger asChild>
<li className="MenuItem"> <li className="MenuItem">
<span>{t("modals.about.title")}</span> <span>{t('modals.about.title')}</span>
</li> </li>
</Dialog.Trigger> </Dialog.Trigger>
<Dialog.Portal> <Dialog.Portal>
@ -22,7 +22,7 @@ const AboutModal = () => {
> >
<div className="DialogHeader"> <div className="DialogHeader">
<Dialog.Title className="DialogTitle"> <Dialog.Title className="DialogTitle">
{t("menu.about")} {t('menu.about')}
</Dialog.Title> </Dialog.Title>
<Dialog.Close className="DialogClose" asChild> <Dialog.Close className="DialogClose" asChild>
<span> <span>
@ -33,7 +33,7 @@ const AboutModal = () => {
<section> <section>
<Dialog.Description className="DialogDescription"> <Dialog.Description className="DialogDescription">
Granblue.team is a tool to save and share team compositions for{" "} Granblue.team is a tool to save and share team compositions for{' '}
<a href="https://game.granbluefantasy.jp">Granblue Fantasy.</a> <a href="https://game.granbluefantasy.jp">Granblue Fantasy.</a>
</Dialog.Description> </Dialog.Description>
<Dialog.Description className="DialogDescription"> <Dialog.Description className="DialogDescription">
@ -49,10 +49,10 @@ const AboutModal = () => {
<section> <section>
<Dialog.Title className="DialogTitle">Credits</Dialog.Title> <Dialog.Title className="DialogTitle">Credits</Dialog.Title>
<Dialog.Description className="DialogDescription"> <Dialog.Description className="DialogDescription">
Granblue.team was built by{" "} Granblue.team was built by{' '}
<a href="https://twitter.com/jedmund">@jedmund</a> with a lot of <a href="https://twitter.com/jedmund">@jedmund</a> with a lot of
help from{" "} help from{' '}
<a href="https://twitter.com/lalalalinna">@lalalalinna</a> and{" "} <a href="https://twitter.com/lalalalinna">@lalalalinna</a> and{' '}
<a href="https://twitter.com/tarngerine">@tarngerine</a>. <a href="https://twitter.com/tarngerine">@tarngerine</a>.
</Dialog.Description> </Dialog.Description>
</section> </section>
@ -67,7 +67,7 @@ const AboutModal = () => {
<Dialog.Overlay className="Overlay" /> <Dialog.Overlay className="Overlay" />
</Dialog.Portal> </Dialog.Portal>
</Dialog.Root> </Dialog.Root>
); )
}; }
export default AboutModal; export default AboutModal

View file

@ -22,7 +22,7 @@
box-shadow: 0 0 0 2px $grey-10; box-shadow: 0 0 0 2px $grey-10;
} }
&[data-state="checked"] { &[data-state='checked'] {
background: $grey-10; background: $grey-10;
} }
} }
@ -40,7 +40,7 @@
cursor: pointer; cursor: pointer;
} }
&[data-state="checked"] { &[data-state='checked'] {
background: $grey-100; background: $grey-100;
transform: translateX(21px); transform: translateX(21px);
} }
@ -75,7 +75,7 @@
gap: $unit * 2; gap: $unit * 2;
select { select {
background: no-repeat url("/icons/ArrowDark.svg"), $grey-90; background: no-repeat url('/icons/ArrowDark.svg'), $grey-90;
background-position-y: center; background-position-y: center;
background-position-x: 95%; background-position-x: 95%;
margin: 0; margin: 0;

View file

@ -1,35 +1,33 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import { getCookie } from "cookies-next"; import { getCookie } from 'cookies-next'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useSnapshot } from "valtio"; import { useSnapshot } from 'valtio'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import * as Dialog from "@radix-ui/react-dialog"; import * as Dialog from '@radix-ui/react-dialog'
import * as Switch from "@radix-ui/react-switch"; import * as Switch from '@radix-ui/react-switch'
import api from "~utils/api"; import api from '~utils/api'
import { accountState } from "~utils/accountState"; import { accountState } from '~utils/accountState'
import { pictureData } from "~utils/pictureData"; import { pictureData } from '~utils/pictureData'
import Button from "~components/Button"; import Button from '~components/Button'
import CrossIcon from "~public/icons/Cross.svg"; import CrossIcon from '~public/icons/Cross.svg'
import "./index.scss"; import './index.scss'
const AccountModal = () => { const AccountModal = () => {
const { account } = useSnapshot(accountState); const { account } = useSnapshot(accountState)
const router = useRouter(); const router = useRouter()
const { t } = useTranslation("common"); const { t } = useTranslation('common')
const locale = const locale =
router.locale && ["en", "ja"].includes(router.locale) router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
? router.locale
: "en";
// Cookies // Cookies
const cookie = getCookie("account"); const cookie = getCookie('account')
const headers = {}; const headers = {}
// cookies.account != null // cookies.account != null
// ? { // ? {
// headers: { // headers: {
@ -39,17 +37,17 @@ const AccountModal = () => {
// : {} // : {}
// State // State
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false)
const [picture, setPicture] = useState(""); const [picture, setPicture] = useState('')
const [language, setLanguage] = useState(""); const [language, setLanguage] = useState('')
const [gender, setGender] = useState(0); const [gender, setGender] = useState(0)
const [privateProfile, setPrivateProfile] = useState(false); const [privateProfile, setPrivateProfile] = useState(false)
// Refs // Refs
const pictureSelect = React.createRef<HTMLSelectElement>(); const pictureSelect = React.createRef<HTMLSelectElement>()
const languageSelect = React.createRef<HTMLSelectElement>(); const languageSelect = React.createRef<HTMLSelectElement>()
const genderSelect = React.createRef<HTMLSelectElement>(); const genderSelect = React.createRef<HTMLSelectElement>()
const privateSelect = React.createRef<HTMLInputElement>(); const privateSelect = React.createRef<HTMLInputElement>()
// useEffect(() => { // useEffect(() => {
// if (cookies.user) setPicture(cookies.user.picture) // if (cookies.user) setPicture(cookies.user.picture)
@ -64,27 +62,27 @@ const AccountModal = () => {
<option key={`picture-${i}`} value={item.filename}> <option key={`picture-${i}`} value={item.filename}>
{item.name[locale]} {item.name[locale]}
</option> </option>
); )
}); })
function handlePictureChange(event: React.ChangeEvent<HTMLSelectElement>) { function handlePictureChange(event: React.ChangeEvent<HTMLSelectElement>) {
if (pictureSelect.current) setPicture(pictureSelect.current.value); if (pictureSelect.current) setPicture(pictureSelect.current.value)
} }
function handleLanguageChange(event: React.ChangeEvent<HTMLSelectElement>) { function handleLanguageChange(event: React.ChangeEvent<HTMLSelectElement>) {
if (languageSelect.current) setLanguage(languageSelect.current.value); if (languageSelect.current) setLanguage(languageSelect.current.value)
} }
function handleGenderChange(event: React.ChangeEvent<HTMLSelectElement>) { function handleGenderChange(event: React.ChangeEvent<HTMLSelectElement>) {
if (genderSelect.current) setGender(parseInt(genderSelect.current.value)); if (genderSelect.current) setGender(parseInt(genderSelect.current.value))
} }
function handlePrivateChange(checked: boolean) { function handlePrivateChange(checked: boolean) {
setPrivateProfile(checked); setPrivateProfile(checked)
} }
function update(event: React.FormEvent<HTMLFormElement>) { function update(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault(); event.preventDefault()
const object = { const object = {
user: { user: {
@ -94,7 +92,7 @@ const AccountModal = () => {
gender: gender, gender: gender,
private: privateProfile, private: privateProfile,
}, },
}; }
// api.endpoints.users // api.endpoints.users
// .update(cookies.account.user_id, object, headers) // .update(cookies.account.user_id, object, headers)
@ -131,14 +129,14 @@ const AccountModal = () => {
} }
function openChange(open: boolean) { function openChange(open: boolean) {
setOpen(open); setOpen(open)
} }
return ( return (
<Dialog.Root open={open} onOpenChange={openChange}> <Dialog.Root open={open} onOpenChange={openChange}>
<Dialog.Trigger asChild> <Dialog.Trigger asChild>
<li className="MenuItem"> <li className="MenuItem">
<span>{t("menu.settings")}</span> <span>{t('menu.settings')}</span>
</li> </li>
</Dialog.Trigger> </Dialog.Trigger>
<Dialog.Portal> <Dialog.Portal>
@ -149,7 +147,7 @@ const AccountModal = () => {
<div className="DialogHeader"> <div className="DialogHeader">
<div className="DialogTop"> <div className="DialogTop">
<Dialog.Title className="SubTitle"> <Dialog.Title className="SubTitle">
{t("modals.settings.title")} {t('modals.settings.title')}
</Dialog.Title> </Dialog.Title>
<Dialog.Title className="DialogTitle"> <Dialog.Title className="DialogTitle">
@{account.user?.username} @{account.user?.username}
@ -165,7 +163,7 @@ const AccountModal = () => {
<form onSubmit={update}> <form onSubmit={update}>
<div className="field"> <div className="field">
<div className="left"> <div className="left">
<label>{t("modals.settings.labels.picture")}</label> <label>{t('modals.settings.labels.picture')}</label>
</div> </div>
<div <div
@ -192,7 +190,7 @@ const AccountModal = () => {
</div> </div>
<div className="field"> <div className="field">
<div className="left"> <div className="left">
<label>{t("modals.settings.labels.gender")}</label> <label>{t('modals.settings.labels.gender')}</label>
</div> </div>
<select <select
@ -202,16 +200,16 @@ const AccountModal = () => {
ref={genderSelect} ref={genderSelect}
> >
<option key="gran" value="0"> <option key="gran" value="0">
{t("modals.settings.gender.gran")} {t('modals.settings.gender.gran')}
</option> </option>
<option key="djeeta" value="1"> <option key="djeeta" value="1">
{t("modals.settings.gender.djeeta")} {t('modals.settings.gender.djeeta')}
</option> </option>
</select> </select>
</div> </div>
<div className="field"> <div className="field">
<div className="left"> <div className="left">
<label>{t("modals.settings.labels.language")}</label> <label>{t('modals.settings.labels.language')}</label>
</div> </div>
<select <select
@ -221,18 +219,18 @@ const AccountModal = () => {
ref={languageSelect} ref={languageSelect}
> >
<option key="en" value="en"> <option key="en" value="en">
{t("modals.settings.language.english")} {t('modals.settings.language.english')}
</option> </option>
<option key="jp" value="ja"> <option key="jp" value="ja">
{t("modals.settings.language.japanese")} {t('modals.settings.language.japanese')}
</option> </option>
</select> </select>
</div> </div>
<div className="field"> <div className="field">
<div className="left"> <div className="left">
<label>{t("modals.settings.labels.private")}</label> <label>{t('modals.settings.labels.private')}</label>
<p className={locale}> <p className={locale}>
{t("modals.settings.descriptions.private")} {t('modals.settings.descriptions.private')}
</p> </p>
</div> </div>
@ -245,13 +243,13 @@ const AccountModal = () => {
</Switch.Root> </Switch.Root>
</div> </div>
<Button>{t("modals.settings.buttons.confirm")}</Button> <Button>{t('modals.settings.buttons.confirm')}</Button>
</form> </form>
</Dialog.Content> </Dialog.Content>
<Dialog.Overlay className="Overlay" /> <Dialog.Overlay className="Overlay" />
</Dialog.Portal> </Dialog.Portal>
</Dialog.Root> </Dialog.Root>
); )
}; }
export default AccountModal; export default AccountModal

View file

@ -1,19 +1,19 @@
import React from "react"; import React from 'react'
import * as AlertDialog from "@radix-ui/react-alert-dialog"; import * as AlertDialog from '@radix-ui/react-alert-dialog'
import "./index.scss"; import './index.scss'
import Button from "~components/Button"; import Button from '~components/Button'
import { ButtonType } from "~utils/enums"; import { ButtonType } from '~utils/enums'
// Props // Props
interface Props { interface Props {
open: boolean; open: boolean
title?: string; title?: string
message: string; message: string
primaryAction?: () => void; primaryAction?: () => void
primaryActionText?: string; primaryActionText?: string
cancelAction: () => void; cancelAction: () => void
cancelActionText: string; cancelActionText: string
} }
const Alert = (props: Props) => { const Alert = (props: Props) => {
@ -23,7 +23,7 @@ const Alert = (props: Props) => {
<AlertDialog.Overlay className="Overlay" onClick={props.cancelAction} /> <AlertDialog.Overlay className="Overlay" onClick={props.cancelAction} />
<div className="AlertWrapper"> <div className="AlertWrapper">
<AlertDialog.Content className="Alert"> <AlertDialog.Content className="Alert">
{props.title ? <AlertDialog.Title>Error</AlertDialog.Title> : ""} {props.title ? <AlertDialog.Title>Error</AlertDialog.Title> : ''}
<AlertDialog.Description className="description"> <AlertDialog.Description className="description">
{props.message} {props.message}
</AlertDialog.Description> </AlertDialog.Description>
@ -38,14 +38,14 @@ const Alert = (props: Props) => {
{props.primaryActionText} {props.primaryActionText}
</AlertDialog.Action> </AlertDialog.Action>
) : ( ) : (
"" ''
)} )}
</div> </div>
</AlertDialog.Content> </AlertDialog.Content>
</div> </div>
</AlertDialog.Portal> </AlertDialog.Portal>
</AlertDialog.Root> </AlertDialog.Root>
); )
}; }
export default Alert; export default Alert

View file

@ -1,82 +1,80 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import classNames from "classnames"; import classNames from 'classnames'
import { axData } from "~utils/axData"; import { axData } from '~utils/axData'
import "./index.scss"; import './index.scss'
interface ErrorMap { interface ErrorMap {
[index: string]: string; [index: string]: string
axValue1: string; axValue1: string
axValue2: string; axValue2: string
} }
interface Props { interface Props {
axType: number; axType: number
currentSkills?: SimpleAxSkill[]; currentSkills?: SimpleAxSkill[]
sendValidity: (isValid: boolean) => void; sendValidity: (isValid: boolean) => void
sendValues: ( sendValues: (
primaryAxModifier: number, primaryAxModifier: number,
primaryAxValue: number, primaryAxValue: number,
secondaryAxModifier: number, secondaryAxModifier: number,
secondaryAxValue: number secondaryAxValue: number
) => void; ) => void
} }
const AXSelect = (props: Props) => { const AXSelect = (props: Props) => {
const router = useRouter(); const router = useRouter()
const locale = const locale =
router.locale && ["en", "ja"].includes(router.locale) router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
? router.locale const { t } = useTranslation('common')
: "en";
const { t } = useTranslation("common");
// Set up form states and error handling // Set up form states and error handling
const [errors, setErrors] = useState<ErrorMap>({ const [errors, setErrors] = useState<ErrorMap>({
axValue1: "", axValue1: '',
axValue2: "", axValue2: '',
}); })
const primaryErrorClasses = classNames({ const primaryErrorClasses = classNames({
errors: true, errors: true,
visible: errors.axValue1.length > 0, visible: errors.axValue1.length > 0,
}); })
const secondaryErrorClasses = classNames({ const secondaryErrorClasses = classNames({
errors: true, errors: true,
visible: errors.axValue2.length > 0, visible: errors.axValue2.length > 0,
}); })
// Refs // Refs
const primaryAxModifierSelect = React.createRef<HTMLSelectElement>(); const primaryAxModifierSelect = React.createRef<HTMLSelectElement>()
const primaryAxValueInput = React.createRef<HTMLInputElement>(); const primaryAxValueInput = React.createRef<HTMLInputElement>()
const secondaryAxModifierSelect = React.createRef<HTMLSelectElement>(); const secondaryAxModifierSelect = React.createRef<HTMLSelectElement>()
const secondaryAxValueInput = React.createRef<HTMLInputElement>(); const secondaryAxValueInput = React.createRef<HTMLInputElement>()
// States // States
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1); const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1); const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1)
const [primaryAxValue, setPrimaryAxValue] = useState(0.0); const [primaryAxValue, setPrimaryAxValue] = useState(0.0)
const [secondaryAxValue, setSecondaryAxValue] = useState(0.0); const [secondaryAxValue, setSecondaryAxValue] = useState(0.0)
useEffect(() => { useEffect(() => {
if (props.currentSkills && props.currentSkills[0]) { if (props.currentSkills && props.currentSkills[0]) {
if (props.currentSkills[0].modifier != null) if (props.currentSkills[0].modifier != null)
setPrimaryAxModifier(props.currentSkills[0].modifier); setPrimaryAxModifier(props.currentSkills[0].modifier)
setPrimaryAxValue(props.currentSkills[0].strength); setPrimaryAxValue(props.currentSkills[0].strength)
} }
if (props.currentSkills && props.currentSkills[1]) { if (props.currentSkills && props.currentSkills[1]) {
if (props.currentSkills[1].modifier != null) if (props.currentSkills[1].modifier != null)
setSecondaryAxModifier(props.currentSkills[1].modifier); setSecondaryAxModifier(props.currentSkills[1].modifier)
setSecondaryAxValue(props.currentSkills[1].strength); setSecondaryAxValue(props.currentSkills[1].strength)
} }
}, [props.currentSkills]); }, [props.currentSkills])
useEffect(() => { useEffect(() => {
props.sendValues( props.sendValues(
@ -84,201 +82,198 @@ const AXSelect = (props: Props) => {
primaryAxValue, primaryAxValue,
secondaryAxModifier, secondaryAxModifier,
secondaryAxValue secondaryAxValue
); )
}, [ }, [
props, props,
primaryAxModifier, primaryAxModifier,
primaryAxValue, primaryAxValue,
secondaryAxModifier, secondaryAxModifier,
secondaryAxValue, secondaryAxValue,
]); ])
useEffect(() => { useEffect(() => {
props.sendValidity( props.sendValidity(
primaryAxValue > 0 && errors.axValue1 === "" && errors.axValue2 === "" primaryAxValue > 0 && errors.axValue1 === '' && errors.axValue2 === ''
); )
}, [props, primaryAxValue, errors]); }, [props, primaryAxValue, errors])
// Classes // Classes
const secondarySetClasses = classNames({ const secondarySetClasses = classNames({
AXSet: true, AXSet: true,
hidden: primaryAxModifier < 0, hidden: primaryAxModifier < 0,
}); })
function generateOptions(modifierSet: number) { function generateOptions(modifierSet: number) {
const axOptions = axData[props.axType - 1]; const axOptions = axData[props.axType - 1]
let axOptionElements: React.ReactNode[] = []; let axOptionElements: React.ReactNode[] = []
if (modifierSet == 0) { if (modifierSet == 0) {
axOptionElements = axOptions.map((ax, i) => { axOptionElements = axOptions.map((ax, i) => {
return ( return (
<option key={i} value={ax.id}> <option key={i} value={ax.id}>
{ax.name[locale]} {ax.name[locale]}
</option> </option>
); )
}); })
} else { } else {
// If we are loading data from the server, state doesn't set before render, // If we are loading data from the server, state doesn't set before render,
// so our defaultValue is undefined. // so our defaultValue is undefined.
let modifier = -1; let modifier = -1
if (primaryAxModifier >= 0) modifier = primaryAxModifier; if (primaryAxModifier >= 0) modifier = primaryAxModifier
else if (props.currentSkills) modifier = props.currentSkills[0].modifier; else if (props.currentSkills) modifier = props.currentSkills[0].modifier
if (modifier >= 0 && axOptions[modifier]) { if (modifier >= 0 && axOptions[modifier]) {
const primarySkill = axOptions[modifier]; const primarySkill = axOptions[modifier]
if (primarySkill.secondary) { if (primarySkill.secondary) {
const secondaryAxOptions = primarySkill.secondary; const secondaryAxOptions = primarySkill.secondary
axOptionElements = secondaryAxOptions.map((ax, i) => { axOptionElements = secondaryAxOptions.map((ax, i) => {
return ( return (
<option key={i} value={ax.id}> <option key={i} value={ax.id}>
{ax.name[locale]} {ax.name[locale]}
</option> </option>
); )
}); })
} }
} }
} }
axOptionElements?.unshift( axOptionElements?.unshift(
<option key={-1} value={-1}> <option key={-1} value={-1}>
{t("ax.no_skill")} {t('ax.no_skill')}
</option> </option>
); )
return axOptionElements; return axOptionElements
} }
function handleSelectChange(event: React.ChangeEvent<HTMLSelectElement>) { function handleSelectChange(event: React.ChangeEvent<HTMLSelectElement>) {
const value = parseInt(event.target.value); const value = parseInt(event.target.value)
if (primaryAxModifierSelect.current == event.target) { if (primaryAxModifierSelect.current == event.target) {
setPrimaryAxModifier(value); setPrimaryAxModifier(value)
if ( if (
primaryAxValueInput.current && primaryAxValueInput.current &&
secondaryAxModifierSelect.current && secondaryAxModifierSelect.current &&
secondaryAxValueInput.current secondaryAxValueInput.current
) { ) {
setupInput( setupInput(axData[props.axType - 1][value], primaryAxValueInput.current)
axData[props.axType - 1][value],
primaryAxValueInput.current
);
secondaryAxModifierSelect.current.value = "-1"; secondaryAxModifierSelect.current.value = '-1'
secondaryAxValueInput.current.value = ""; secondaryAxValueInput.current.value = ''
} }
} else { } else {
setSecondaryAxModifier(value); setSecondaryAxModifier(value)
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]; const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
const currentAxSkill = primaryAxSkill.secondary const currentAxSkill = primaryAxSkill.secondary
? primaryAxSkill.secondary.find((skill) => skill.id == value) ? primaryAxSkill.secondary.find((skill) => skill.id == value)
: undefined; : undefined
if (secondaryAxValueInput.current) if (secondaryAxValueInput.current)
setupInput(currentAxSkill, secondaryAxValueInput.current); setupInput(currentAxSkill, secondaryAxValueInput.current)
} }
} }
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) { function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
const value = parseFloat(event.target.value); const value = parseFloat(event.target.value)
let newErrors = { ...errors }; let newErrors = { ...errors }
if (primaryAxValueInput.current == event.target) { if (primaryAxValueInput.current == event.target) {
if (handlePrimaryErrors(value)) setPrimaryAxValue(value); if (handlePrimaryErrors(value)) setPrimaryAxValue(value)
} else { } else {
if (handleSecondaryErrors(value)) setSecondaryAxValue(value); if (handleSecondaryErrors(value)) setSecondaryAxValue(value)
} }
} }
function handlePrimaryErrors(value: number) { function handlePrimaryErrors(value: number) {
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]; const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
let newErrors = { ...errors }; let newErrors = { ...errors }
if (value < primaryAxSkill.minValue) { if (value < primaryAxSkill.minValue) {
newErrors.axValue1 = t("ax.errors.value_too_low", { newErrors.axValue1 = t('ax.errors.value_too_low', {
name: primaryAxSkill.name[locale], name: primaryAxSkill.name[locale],
minValue: primaryAxSkill.minValue, minValue: primaryAxSkill.minValue,
suffix: primaryAxSkill.suffix ? primaryAxSkill.suffix : "", suffix: primaryAxSkill.suffix ? primaryAxSkill.suffix : '',
}); })
} else if (value > primaryAxSkill.maxValue) { } else if (value > primaryAxSkill.maxValue) {
newErrors.axValue1 = t("ax.errors.value_too_high", { newErrors.axValue1 = t('ax.errors.value_too_high', {
name: primaryAxSkill.name[locale], name: primaryAxSkill.name[locale],
maxValue: primaryAxSkill.minValue, maxValue: primaryAxSkill.minValue,
suffix: primaryAxSkill.suffix ? primaryAxSkill.suffix : "", suffix: primaryAxSkill.suffix ? primaryAxSkill.suffix : '',
}); })
} else if (!value || value <= 0) { } else if (!value || value <= 0) {
newErrors.axValue1 = t("ax.errors.value_empty", { newErrors.axValue1 = t('ax.errors.value_empty', {
name: primaryAxSkill.name[locale], name: primaryAxSkill.name[locale],
}); })
} else { } else {
newErrors.axValue1 = ""; newErrors.axValue1 = ''
} }
setErrors(newErrors); setErrors(newErrors)
return newErrors.axValue1.length === 0; return newErrors.axValue1.length === 0
} }
function handleSecondaryErrors(value: number) { function handleSecondaryErrors(value: number) {
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]; const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
let newErrors = { ...errors }; let newErrors = { ...errors }
if (primaryAxSkill.secondary) { if (primaryAxSkill.secondary) {
const secondaryAxSkill = primaryAxSkill.secondary.find( const secondaryAxSkill = primaryAxSkill.secondary.find(
(skill) => skill.id == secondaryAxModifier (skill) => skill.id == secondaryAxModifier
); )
if (secondaryAxSkill) { if (secondaryAxSkill) {
if (value < secondaryAxSkill.minValue) { if (value < secondaryAxSkill.minValue) {
newErrors.axValue2 = t("ax.errors.value_too_low", { newErrors.axValue2 = t('ax.errors.value_too_low', {
name: secondaryAxSkill.name[locale], name: secondaryAxSkill.name[locale],
minValue: secondaryAxSkill.minValue, minValue: secondaryAxSkill.minValue,
suffix: secondaryAxSkill.suffix ? secondaryAxSkill.suffix : "", suffix: secondaryAxSkill.suffix ? secondaryAxSkill.suffix : '',
}); })
} else if (value > secondaryAxSkill.maxValue) { } else if (value > secondaryAxSkill.maxValue) {
newErrors.axValue2 = t("ax.errors.value_too_high", { newErrors.axValue2 = t('ax.errors.value_too_high', {
name: secondaryAxSkill.name[locale], name: secondaryAxSkill.name[locale],
maxValue: secondaryAxSkill.minValue, maxValue: secondaryAxSkill.minValue,
suffix: secondaryAxSkill.suffix ? secondaryAxSkill.suffix : "", suffix: secondaryAxSkill.suffix ? secondaryAxSkill.suffix : '',
}); })
} else if (!secondaryAxSkill.suffix && value % 1 !== 0) { } else if (!secondaryAxSkill.suffix && value % 1 !== 0) {
newErrors.axValue2 = t("ax.errors.value_not_whole", { newErrors.axValue2 = t('ax.errors.value_not_whole', {
name: secondaryAxSkill.name[locale], name: secondaryAxSkill.name[locale],
}); })
} else if (primaryAxValue <= 0) { } else if (primaryAxValue <= 0) {
newErrors.axValue1 = t("ax.errors.value_empty", { newErrors.axValue1 = t('ax.errors.value_empty', {
name: primaryAxSkill.name[locale], name: primaryAxSkill.name[locale],
}); })
} else { } else {
newErrors.axValue2 = ""; newErrors.axValue2 = ''
} }
} }
} }
setErrors(newErrors); setErrors(newErrors)
return newErrors.axValue2.length === 0; return newErrors.axValue2.length === 0
} }
function setupInput(ax: AxSkill | undefined, element: HTMLInputElement) { function setupInput(ax: AxSkill | undefined, element: HTMLInputElement) {
if (ax) { if (ax) {
const rangeString = `${ax.minValue}~${ax.maxValue}${ax.suffix || ""}`; const rangeString = `${ax.minValue}~${ax.maxValue}${ax.suffix || ''}`
element.disabled = false; element.disabled = false
element.placeholder = rangeString; element.placeholder = rangeString
element.min = `${ax.minValue}`; element.min = `${ax.minValue}`
element.max = `${ax.maxValue}`; element.max = `${ax.maxValue}`
element.step = ax.suffix ? "0.5" : "1"; element.step = ax.suffix ? '0.5' : '1'
} else { } else {
if (primaryAxValueInput.current && secondaryAxValueInput.current) { if (primaryAxValueInput.current && secondaryAxValueInput.current) {
if (primaryAxValueInput.current == element) { if (primaryAxValueInput.current == element) {
primaryAxValueInput.current.disabled = true; primaryAxValueInput.current.disabled = true
primaryAxValueInput.current.placeholder = ""; primaryAxValueInput.current.placeholder = ''
} }
secondaryAxValueInput.current.disabled = true; secondaryAxValueInput.current.disabled = true
secondaryAxValueInput.current.placeholder = ""; secondaryAxValueInput.current.placeholder = ''
} }
} }
} }
@ -345,7 +340,7 @@ const AXSelect = (props: Props) => {
<p className={secondaryErrorClasses}>{errors.axValue2}</p> <p className={secondaryErrorClasses}>{errors.axValue2}</p>
</div> </div>
</div> </div>
); )
}; }
export default AXSelect; export default AXSelect

View file

@ -1,143 +1,143 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import classNames from "classnames"; import classNames from 'classnames'
import Link from "next/link"; import Link from 'next/link'
import AddIcon from "~public/icons/Add.svg"; import AddIcon from '~public/icons/Add.svg'
import CheckIcon from "~public/icons/LargeCheck.svg"; import CheckIcon from '~public/icons/LargeCheck.svg'
import CrossIcon from "~public/icons/Cross.svg"; import CrossIcon from '~public/icons/Cross.svg'
import EditIcon from "~public/icons/Edit.svg"; import EditIcon from '~public/icons/Edit.svg'
import LinkIcon from "~public/icons/Link.svg"; import LinkIcon from '~public/icons/Link.svg'
import MenuIcon from "~public/icons/Menu.svg"; import MenuIcon from '~public/icons/Menu.svg'
import SaveIcon from "~public/icons/Save.svg"; import SaveIcon from '~public/icons/Save.svg'
import SettingsIcon from "~public/icons/Settings.svg"; import SettingsIcon from '~public/icons/Settings.svg'
import "./index.scss"; import './index.scss'
import { ButtonType } from "~utils/enums"; import { ButtonType } from '~utils/enums'
interface Props { interface Props {
active?: boolean; active?: boolean
disabled?: boolean; disabled?: boolean
classes?: string[]; classes?: string[]
icon?: string; icon?: string
type?: ButtonType; type?: ButtonType
children?: React.ReactNode; children?: React.ReactNode
onClick?: (event: React.MouseEvent<HTMLElement>) => void; onClick?: (event: React.MouseEvent<HTMLElement>) => void
} }
const Button = (props: Props) => { const Button = (props: Props) => {
// States // States
const [active, setActive] = useState(false); const [active, setActive] = useState(false)
const [disabled, setDisabled] = useState(false); const [disabled, setDisabled] = useState(false)
const [pressed, setPressed] = useState(false); const [pressed, setPressed] = useState(false)
const [buttonType, setButtonType] = useState(ButtonType.Base); const [buttonType, setButtonType] = useState(ButtonType.Base)
const classes = classNames( const classes = classNames(
{ {
Button: true, Button: true,
Active: active, Active: active,
"btn-pressed": pressed, 'btn-pressed': pressed,
"btn-disabled": disabled, 'btn-disabled': disabled,
save: props.icon === "save", save: props.icon === 'save',
destructive: props.type == ButtonType.Destructive, destructive: props.type == ButtonType.Destructive,
}, },
props.classes props.classes
); )
useEffect(() => { useEffect(() => {
if (props.active) setActive(props.active); if (props.active) setActive(props.active)
if (props.disabled) setDisabled(props.disabled); if (props.disabled) setDisabled(props.disabled)
if (props.type) setButtonType(props.type); if (props.type) setButtonType(props.type)
}, [props.active, props.disabled, props.type]); }, [props.active, props.disabled, props.type])
const addIcon = ( const addIcon = (
<span className="icon"> <span className="icon">
<AddIcon /> <AddIcon />
</span> </span>
); )
const menuIcon = ( const menuIcon = (
<span className="icon"> <span className="icon">
<MenuIcon /> <MenuIcon />
</span> </span>
); )
const linkIcon = ( const linkIcon = (
<span className="icon stroke"> <span className="icon stroke">
<LinkIcon /> <LinkIcon />
</span> </span>
); )
const checkIcon = ( const checkIcon = (
<span className="icon check"> <span className="icon check">
<CheckIcon /> <CheckIcon />
</span> </span>
); )
const crossIcon = ( const crossIcon = (
<span className="icon"> <span className="icon">
<CrossIcon /> <CrossIcon />
</span> </span>
); )
const editIcon = ( const editIcon = (
<span className="icon"> <span className="icon">
<EditIcon /> <EditIcon />
</span> </span>
); )
const saveIcon = ( const saveIcon = (
<span className="icon stroke"> <span className="icon stroke">
<SaveIcon /> <SaveIcon />
</span> </span>
); )
const settingsIcon = ( const settingsIcon = (
<span className="icon settings"> <span className="icon settings">
<SettingsIcon /> <SettingsIcon />
</span> </span>
); )
function getIcon() { function getIcon() {
let icon: React.ReactNode; let icon: React.ReactNode
switch (props.icon) { switch (props.icon) {
case "new": case 'new':
icon = addIcon; icon = addIcon
break; break
case "menu": case 'menu':
icon = menuIcon; icon = menuIcon
break; break
case "link": case 'link':
icon = linkIcon; icon = linkIcon
break; break
case "check": case 'check':
icon = checkIcon; icon = checkIcon
break; break
case "cross": case 'cross':
icon = crossIcon; icon = crossIcon
break; break
case "edit": case 'edit':
icon = editIcon; icon = editIcon
break; break
case "save": case 'save':
icon = saveIcon; icon = saveIcon
break; break
case "settings": case 'settings':
icon = settingsIcon; icon = settingsIcon
break; break
} }
return icon; return icon
} }
function handleMouseDown() { function handleMouseDown() {
setPressed(true); setPressed(true)
} }
function handleMouseUp() { function handleMouseUp() {
setPressed(false); setPressed(false)
} }
return ( return (
<button <button
@ -152,10 +152,10 @@ const Button = (props: Props) => {
{props.type != ButtonType.IconOnly ? ( {props.type != ButtonType.IconOnly ? (
<span className="text">{props.children}</span> <span className="text">{props.children}</span>
) : ( ) : (
"" ''
)} )}
</button> </button>
); )
}; }
export default Button; export default Button

View file

@ -1,33 +1,33 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
fieldName: string; fieldName: string
placeholder: string; placeholder: string
value?: string; value?: string
limit: number; limit: number
error: string; error: string
onBlur?: (event: React.ChangeEvent<HTMLInputElement>) => void; onBlur?: (event: React.ChangeEvent<HTMLInputElement>) => void
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void; onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
} }
const CharLimitedFieldset = React.forwardRef<HTMLInputElement, Props>( const CharLimitedFieldset = React.forwardRef<HTMLInputElement, Props>(
function useFieldSet(props, ref) { function useFieldSet(props, ref) {
const fieldType = ["password", "confirm_password"].includes(props.fieldName) const fieldType = ['password', 'confirm_password'].includes(props.fieldName)
? "password" ? 'password'
: "text"; : 'text'
const [currentCount, setCurrentCount] = useState(0); const [currentCount, setCurrentCount] = useState(0)
useEffect(() => { useEffect(() => {
setCurrentCount( setCurrentCount(
props.value ? props.limit - props.value.length : props.limit props.value ? props.limit - props.value.length : props.limit
); )
}, [props.limit, props.value]); }, [props.limit, props.value])
function onChange(event: React.ChangeEvent<HTMLInputElement>) { function onChange(event: React.ChangeEvent<HTMLInputElement>) {
setCurrentCount(props.limit - event.currentTarget.value.length); setCurrentCount(props.limit - event.currentTarget.value.length)
if (props.onChange) props.onChange(event); if (props.onChange) props.onChange(event)
} }
return ( return (
@ -39,7 +39,7 @@ const CharLimitedFieldset = React.forwardRef<HTMLInputElement, Props>(
type={fieldType} type={fieldType}
name={props.fieldName} name={props.fieldName}
placeholder={props.placeholder} placeholder={props.placeholder}
defaultValue={props.value || ""} defaultValue={props.value || ''}
onBlur={props.onBlur} onBlur={props.onBlur}
onChange={onChange} onChange={onChange}
maxLength={props.limit} maxLength={props.limit}
@ -50,8 +50,8 @@ const CharLimitedFieldset = React.forwardRef<HTMLInputElement, Props>(
</div> </div>
{props.error.length > 0 && <p className="InputError">{props.error}</p>} {props.error.length > 0 && <p className="InputError">{props.error}</p>}
</fieldset> </fieldset>
); )
} }
); )
export default CharLimitedFieldset; export default CharLimitedFieldset

View file

@ -1,70 +1,70 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import { setCookie } from "cookies-next"; import { setCookie } from 'cookies-next'
import Router, { useRouter } from "next/router"; import Router, { useRouter } from 'next/router'
import { useTranslation } from "react-i18next"; import { useTranslation } from 'react-i18next'
import { AxiosResponse } from "axios"; import { AxiosResponse } from 'axios'
import * as Dialog from "@radix-ui/react-dialog"; import * as Dialog from '@radix-ui/react-dialog'
import api from "~utils/api"; import api from '~utils/api'
import { appState } from "~utils/appState"; import { appState } from '~utils/appState'
import { accountState } from "~utils/accountState"; import { accountState } from '~utils/accountState'
import Button from "~components/Button"; import Button from '~components/Button'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
open: boolean; open: boolean
incomingCharacter?: Character; incomingCharacter?: Character
conflictingCharacters?: GridCharacter[]; conflictingCharacters?: GridCharacter[]
desiredPosition: number; desiredPosition: number
resolveConflict: () => void; resolveConflict: () => void
resetConflict: () => void; resetConflict: () => void
} }
const CharacterConflictModal = (props: Props) => { const CharacterConflictModal = (props: Props) => {
const { t } = useTranslation("common"); const { t } = useTranslation('common')
// States // States
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false)
useEffect(() => { useEffect(() => {
setOpen(props.open); setOpen(props.open)
}, [setOpen, props.open]); }, [setOpen, props.open])
function imageUrl(character?: Character, uncap: number = 0) { function imageUrl(character?: Character, uncap: number = 0) {
// Change the image based on the uncap level // Change the image based on the uncap level
let suffix = "01"; let suffix = '01'
if (uncap == 6) suffix = "04"; if (uncap == 6) suffix = '04'
else if (uncap == 5) suffix = "03"; else if (uncap == 5) suffix = '03'
else if (uncap > 2) suffix = "02"; else if (uncap > 2) suffix = '02'
// Special casing for Lyria (and Young Cat eventually) // Special casing for Lyria (and Young Cat eventually)
if (character?.granblue_id === "3030182000") { if (character?.granblue_id === '3030182000') {
let element = 1; let element = 1
if ( if (
appState.grid.weapons.mainWeapon && appState.grid.weapons.mainWeapon &&
appState.grid.weapons.mainWeapon.element appState.grid.weapons.mainWeapon.element
) { ) {
element = appState.grid.weapons.mainWeapon.element; element = appState.grid.weapons.mainWeapon.element
} else if (appState.party.element != 0) { } else if (appState.party.element != 0) {
element = appState.party.element; element = appState.party.element
} }
suffix = `${suffix}_0${element}`; suffix = `${suffix}_0${element}`
} }
return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-square/${character?.granblue_id}_${suffix}.jpg`; return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-square/${character?.granblue_id}_${suffix}.jpg`
} }
function openChange(open: boolean) { function openChange(open: boolean) {
setOpen(open); setOpen(open)
} }
function close() { function close() {
setOpen(false); setOpen(false)
props.resetConflict(); props.resetConflict()
} }
return ( return (
@ -107,7 +107,7 @@ const CharacterConflictModal = (props: Props) => {
<Dialog.Overlay className="Overlay" /> <Dialog.Overlay className="Overlay" />
</Dialog.Portal> </Dialog.Portal>
</Dialog.Root> </Dialog.Root>
); )
}; }
export default CharacterConflictModal; export default CharacterConflictModal

View file

@ -1,68 +1,68 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { getCookie } from "cookies-next"; import { getCookie } from 'cookies-next'
import { useSnapshot } from "valtio"; import { useSnapshot } from 'valtio'
import { AxiosResponse } from "axios"; import { AxiosResponse } from 'axios'
import debounce from "lodash.debounce"; import debounce from 'lodash.debounce'
import Alert from "~components/Alert"; import Alert from '~components/Alert'
import JobSection from "~components/JobSection"; import JobSection from '~components/JobSection'
import CharacterUnit from "~components/CharacterUnit"; import CharacterUnit from '~components/CharacterUnit'
import CharacterConflictModal from "~components/CharacterConflictModal"; import CharacterConflictModal from '~components/CharacterConflictModal'
import type { JobSkillObject, SearchableObject } from "~types"; import type { JobSkillObject, SearchableObject } from '~types'
import api from "~utils/api"; import api from '~utils/api'
import { appState } from "~utils/appState"; import { appState } from '~utils/appState'
import "./index.scss"; import './index.scss'
// Props // Props
interface Props { interface Props {
new: boolean; new: boolean
characters?: GridCharacter[]; characters?: GridCharacter[]
createParty: () => Promise<AxiosResponse<any, any>>; createParty: () => Promise<AxiosResponse<any, any>>
pushHistory?: (path: string) => void; pushHistory?: (path: string) => void
} }
const CharacterGrid = (props: Props) => { const CharacterGrid = (props: Props) => {
// Constants // Constants
const numCharacters: number = 5; const numCharacters: number = 5
// Cookies // Cookies
const cookie = getCookie("account"); const cookie = getCookie('account')
const accountData: AccountCookie = cookie const accountData: AccountCookie = cookie
? JSON.parse(cookie as string) ? JSON.parse(cookie as string)
: null; : null
const headers = accountData const headers = accountData
? { headers: { Authorization: `Bearer ${accountData.token}` } } ? { headers: { Authorization: `Bearer ${accountData.token}` } }
: {}; : {}
// Set up state for view management // Set up state for view management
const { party, grid } = useSnapshot(appState); const { party, grid } = useSnapshot(appState)
const [slug, setSlug] = useState(); const [slug, setSlug] = useState()
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false)
// Set up state for conflict management // Set up state for conflict management
const [incoming, setIncoming] = useState<Character>(); const [incoming, setIncoming] = useState<Character>()
const [conflicts, setConflicts] = useState<GridCharacter[]>([]); const [conflicts, setConflicts] = useState<GridCharacter[]>([])
const [position, setPosition] = useState(0); const [position, setPosition] = useState(0)
// Set up state for data // Set up state for data
const [job, setJob] = useState<Job | undefined>(); const [job, setJob] = useState<Job | undefined>()
const [jobSkills, setJobSkills] = useState<JobSkillObject>({ const [jobSkills, setJobSkills] = useState<JobSkillObject>({
0: undefined, 0: undefined,
1: undefined, 1: undefined,
2: undefined, 2: undefined,
3: undefined, 3: undefined,
}); })
const [errorMessage, setErrorMessage] = useState(""); const [errorMessage, setErrorMessage] = useState('')
// Create a temporary state to store previous character uncap values // Create a temporary state to store previous character uncap values
const [previousUncapValues, setPreviousUncapValues] = useState<{ const [previousUncapValues, setPreviousUncapValues] = useState<{
[key: number]: number | undefined; [key: number]: number | undefined
}>({}); }>({})
// Set the editable flag only on first load // Set the editable flag only on first load
useEffect(() => { useEffect(() => {
@ -71,58 +71,58 @@ const CharacterGrid = (props: Props) => {
(accountData && party.user && accountData.userId === party.user.id) || (accountData && party.user && accountData.userId === party.user.id) ||
props.new props.new
) )
appState.party.editable = true; appState.party.editable = true
else appState.party.editable = false; else appState.party.editable = false
}, [props.new, accountData, party]); }, [props.new, accountData, party])
useEffect(() => { useEffect(() => {
setJob(appState.party.job); setJob(appState.party.job)
setJobSkills(appState.party.jobSkills); setJobSkills(appState.party.jobSkills)
}, [appState]); }, [appState])
// Initialize an array of current uncap values for each characters // Initialize an array of current uncap values for each characters
useEffect(() => { useEffect(() => {
let initialPreviousUncapValues: { [key: number]: number } = {}; let initialPreviousUncapValues: { [key: number]: number } = {}
Object.values(appState.grid.characters).map((o) => { Object.values(appState.grid.characters).map((o) => {
o ? (initialPreviousUncapValues[o.position] = o.uncap_level) : 0; o ? (initialPreviousUncapValues[o.position] = o.uncap_level) : 0
}); })
setPreviousUncapValues(initialPreviousUncapValues); setPreviousUncapValues(initialPreviousUncapValues)
}, [appState.grid.characters]); }, [appState.grid.characters])
// Methods: Adding an object from search // Methods: Adding an object from search
function receiveCharacterFromSearch( function receiveCharacterFromSearch(
object: SearchableObject, object: SearchableObject,
position: number position: number
) { ) {
const character = object as Character; const character = object as Character
if (!party.id) { if (!party.id) {
props.createParty().then((response) => { props.createParty().then((response) => {
const party = response.data.party; const party = response.data.party
appState.party.id = party.id; appState.party.id = party.id
setSlug(party.shortcode); setSlug(party.shortcode)
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`); if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`)
saveCharacter(party.id, character, position) saveCharacter(party.id, character, position)
.then((response) => storeGridCharacter(response.data.grid_character)) .then((response) => storeGridCharacter(response.data.grid_character))
.catch((error) => console.error(error)); .catch((error) => console.error(error))
}); })
} else { } else {
if (party.editable) if (party.editable)
saveCharacter(party.id, character, position) saveCharacter(party.id, character, position)
.then((response) => handleCharacterResponse(response.data)) .then((response) => handleCharacterResponse(response.data))
.catch((error) => console.error(error)); .catch((error) => console.error(error))
} }
} }
async function handleCharacterResponse(data: any) { async function handleCharacterResponse(data: any) {
if (data.hasOwnProperty("conflicts")) { if (data.hasOwnProperty('conflicts')) {
setIncoming(data.incoming); setIncoming(data.incoming)
setConflicts(data.conflicts); setConflicts(data.conflicts)
setPosition(data.position); setPosition(data.position)
setModalOpen(true); setModalOpen(true)
} else { } else {
storeGridCharacter(data.grid_character); storeGridCharacter(data.grid_character)
} }
} }
@ -141,11 +141,11 @@ const CharacterGrid = (props: Props) => {
}, },
}, },
headers headers
); )
} }
function storeGridCharacter(gridCharacter: GridCharacter) { function storeGridCharacter(gridCharacter: GridCharacter) {
appState.grid.characters[gridCharacter.position] = gridCharacter; appState.grid.characters[gridCharacter.position] = gridCharacter
} }
async function resolveConflict() { async function resolveConflict() {
@ -159,128 +159,128 @@ const CharacterGrid = (props: Props) => {
}) })
.then((response) => { .then((response) => {
// Store new character in state // Store new character in state
storeGridCharacter(response.data.grid_character); storeGridCharacter(response.data.grid_character)
// Remove conflicting characters from state // Remove conflicting characters from state
conflicts.forEach( conflicts.forEach(
(c) => (appState.grid.characters[c.position] = undefined) (c) => (appState.grid.characters[c.position] = undefined)
); )
// Reset conflict // Reset conflict
resetConflict(); resetConflict()
// Close modal // Close modal
setModalOpen(false); setModalOpen(false)
}); })
} }
} }
function resetConflict() { function resetConflict() {
setPosition(-1); setPosition(-1)
setConflicts([]); setConflicts([])
setIncoming(undefined); setIncoming(undefined)
} }
// Methods: Saving job and job skills // Methods: Saving job and job skills
const saveJob = function (job: Job) { const saveJob = function (job: Job) {
const payload = { const payload = {
party: { party: {
job_id: job ? job.id : "", job_id: job ? job.id : '',
}, },
...headers, ...headers,
}; }
if (party.id && appState.party.editable) { if (party.id && appState.party.editable) {
api.updateJob({ partyId: party.id, params: payload }).then((response) => { api.updateJob({ partyId: party.id, params: payload }).then((response) => {
const newParty = response.data.party; const newParty = response.data.party
setJob(newParty.job); setJob(newParty.job)
appState.party.job = newParty.job; appState.party.job = newParty.job
setJobSkills(newParty.job_skills); setJobSkills(newParty.job_skills)
appState.party.jobSkills = newParty.job_skills; appState.party.jobSkills = newParty.job_skills
}); })
}
} }
};
const saveJobSkill = function (skill: JobSkill, position: number) { const saveJobSkill = function (skill: JobSkill, position: number) {
if (party.id && appState.party.editable) { if (party.id && appState.party.editable) {
const positionedKey = `skill${position}_id`; const positionedKey = `skill${position}_id`
let skillObject: { let skillObject: {
[key: string]: string | undefined; [key: string]: string | undefined
skill0_id?: string; skill0_id?: string
skill1_id?: string; skill1_id?: string
skill2_id?: string; skill2_id?: string
skill3_id?: string; skill3_id?: string
} = {}; } = {}
const payload = { const payload = {
party: skillObject, party: skillObject,
...headers, ...headers,
}; }
skillObject[positionedKey] = skill.id; skillObject[positionedKey] = skill.id
api api
.updateJobSkills({ partyId: party.id, params: payload }) .updateJobSkills({ partyId: party.id, params: payload })
.then((response) => { .then((response) => {
// Update the current skills // Update the current skills
const newSkills = response.data.party.job_skills; const newSkills = response.data.party.job_skills
setJobSkills(newSkills); setJobSkills(newSkills)
appState.party.jobSkills = newSkills; appState.party.jobSkills = newSkills
}) })
.catch((error) => { .catch((error) => {
const data = error.response.data; const data = error.response.data
if (data.code == "too_many_skills_of_type") { if (data.code == 'too_many_skills_of_type') {
const message = `You can only add up to 2 ${ const message = `You can only add up to 2 ${
data.skill_type === "emp" data.skill_type === 'emp'
? data.skill_type.toUpperCase() ? data.skill_type.toUpperCase()
: data.skill_type : data.skill_type
} skills to your party at once.`; } skills to your party at once.`
setErrorMessage(message); setErrorMessage(message)
}
console.log(error.response.data)
})
} }
console.log(error.response.data);
});
} }
};
// Methods: Helpers // Methods: Helpers
function characterUncapLevel(character: Character) { function characterUncapLevel(character: Character) {
let uncapLevel; let uncapLevel
if (character.special) { if (character.special) {
uncapLevel = 3; uncapLevel = 3
if (character.uncap.ulb) uncapLevel = 5; if (character.uncap.ulb) uncapLevel = 5
else if (character.uncap.flb) uncapLevel = 4; else if (character.uncap.flb) uncapLevel = 4
} else { } else {
uncapLevel = 4; uncapLevel = 4
if (character.uncap.ulb) uncapLevel = 6; if (character.uncap.ulb) uncapLevel = 6
else if (character.uncap.flb) uncapLevel = 5; else if (character.uncap.flb) uncapLevel = 5
} }
return uncapLevel; return uncapLevel
} }
// Methods: Updating uncap level // Methods: Updating uncap level
// Note: Saves, but debouncing is not working properly // Note: Saves, but debouncing is not working properly
async function saveUncap(id: string, position: number, uncapLevel: number) { async function saveUncap(id: string, position: number, uncapLevel: number) {
storePreviousUncapValue(position); storePreviousUncapValue(position)
try { try {
if (uncapLevel != previousUncapValues[position]) if (uncapLevel != previousUncapValues[position])
await api.updateUncap("character", id, uncapLevel).then((response) => { await api.updateUncap('character', id, uncapLevel).then((response) => {
storeGridCharacter(response.data.grid_character); storeGridCharacter(response.data.grid_character)
}); })
} catch (error) { } catch (error) {
console.error(error); console.error(error)
// Revert optimistic UI // Revert optimistic UI
updateUncapLevel(position, previousUncapValues[position]); updateUncapLevel(position, previousUncapValues[position])
// Remove optimistic key // Remove optimistic key
let newPreviousValues = { ...previousUncapValues }; let newPreviousValues = { ...previousUncapValues }
delete newPreviousValues[position]; delete newPreviousValues[position]
setPreviousUncapValues(newPreviousValues); setPreviousUncapValues(newPreviousValues)
} }
} }
@ -289,50 +289,50 @@ const CharacterGrid = (props: Props) => {
position: number, position: number,
uncapLevel: number uncapLevel: number
) { ) {
memoizeAction(id, position, uncapLevel); memoizeAction(id, position, uncapLevel)
// Optimistically update UI // Optimistically update UI
updateUncapLevel(position, uncapLevel); updateUncapLevel(position, uncapLevel)
} }
const memoizeAction = useCallback( const memoizeAction = useCallback(
(id: string, position: number, uncapLevel: number) => { (id: string, position: number, uncapLevel: number) => {
debouncedAction(id, position, uncapLevel); debouncedAction(id, position, uncapLevel)
}, },
[props, previousUncapValues] [props, previousUncapValues]
); )
const debouncedAction = useMemo( const debouncedAction = useMemo(
() => () =>
debounce((id, position, number) => { debounce((id, position, number) => {
saveUncap(id, position, number); saveUncap(id, position, number)
}, 500), }, 500),
[props, saveUncap] [props, saveUncap]
); )
const updateUncapLevel = ( const updateUncapLevel = (
position: number, position: number,
uncapLevel: number | undefined uncapLevel: number | undefined
) => { ) => {
const character = appState.grid.characters[position]; const character = appState.grid.characters[position]
if (character && uncapLevel) { if (character && uncapLevel) {
character.uncap_level = uncapLevel; character.uncap_level = uncapLevel
appState.grid.characters[position] = character; appState.grid.characters[position] = character
}
} }
};
function storePreviousUncapValue(position: number) { function storePreviousUncapValue(position: number) {
// Save the current value in case of an unexpected result // Save the current value in case of an unexpected result
let newPreviousValues = { ...previousUncapValues }; let newPreviousValues = { ...previousUncapValues }
if (grid.characters[position]) { if (grid.characters[position]) {
newPreviousValues[position] = grid.characters[position]?.uncap_level; newPreviousValues[position] = grid.characters[position]?.uncap_level
setPreviousUncapValues(newPreviousValues); setPreviousUncapValues(newPreviousValues)
} }
} }
function cancelAlert() { function cancelAlert() {
setErrorMessage(""); setErrorMessage('')
} }
// Render: JSX components // Render: JSX components
@ -342,7 +342,7 @@ const CharacterGrid = (props: Props) => {
open={errorMessage.length > 0} open={errorMessage.length > 0}
message={errorMessage} message={errorMessage}
cancelAction={cancelAlert} cancelAction={cancelAlert}
cancelActionText={"Got it"} cancelActionText={'Got it'}
/> />
<div id="CharacterGrid"> <div id="CharacterGrid">
<JobSection <JobSection
@ -372,12 +372,12 @@ const CharacterGrid = (props: Props) => {
updateUncap={initiateUncapUpdate} updateUncap={initiateUncapUpdate}
/> />
</li> </li>
); )
})} })}
</ul> </ul>
</div> </div>
</div> </div>
); )
}; }
export default CharacterGrid; export default CharacterGrid

View file

@ -1,71 +1,69 @@
import React from "react"; import React from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import * as HoverCard from "@radix-ui/react-hover-card"; import * as HoverCard from '@radix-ui/react-hover-card'
import WeaponLabelIcon from "~components/WeaponLabelIcon"; import WeaponLabelIcon from '~components/WeaponLabelIcon'
import UncapIndicator from "~components/UncapIndicator"; import UncapIndicator from '~components/UncapIndicator'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
gridCharacter: GridCharacter; gridCharacter: GridCharacter
children: React.ReactNode; children: React.ReactNode
} }
interface KeyNames { interface KeyNames {
[key: string]: { [key: string]: {
en: string; en: string
jp: string; jp: string
}; }
} }
const CharacterHovercard = (props: Props) => { const CharacterHovercard = (props: Props) => {
const router = useRouter(); const router = useRouter()
const { t } = useTranslation("common"); const { t } = useTranslation('common')
const locale = const locale =
router.locale && ["en", "ja"].includes(router.locale) router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
? router.locale
: "en";
const Element = ["null", "wind", "fire", "water", "earth", "dark", "light"]; const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
const Proficiency = [ const Proficiency = [
"none", 'none',
"sword", 'sword',
"dagger", 'dagger',
"axe", 'axe',
"spear", 'spear',
"bow", 'bow',
"staff", 'staff',
"fist", 'fist',
"harp", 'harp',
"gun", 'gun',
"katana", 'katana',
]; ]
const tintElement = Element[props.gridCharacter.object.element]; const tintElement = Element[props.gridCharacter.object.element]
const wikiUrl = `https://gbf.wiki/${props.gridCharacter.object.name.en.replaceAll( const wikiUrl = `https://gbf.wiki/${props.gridCharacter.object.name.en.replaceAll(
" ", ' ',
"_" '_'
)}`; )}`
function characterImage() { function characterImage() {
let imgSrc = ""; let imgSrc = ''
if (props.gridCharacter) { if (props.gridCharacter) {
const character = props.gridCharacter.object; const character = props.gridCharacter.object
// Change the image based on the uncap level // Change the image based on the uncap level
let suffix = "01"; let suffix = '01'
if (props.gridCharacter.uncap_level == 6) suffix = "04"; if (props.gridCharacter.uncap_level == 6) suffix = '04'
else if (props.gridCharacter.uncap_level == 5) suffix = "03"; else if (props.gridCharacter.uncap_level == 5) suffix = '03'
else if (props.gridCharacter.uncap_level > 2) suffix = "02"; else if (props.gridCharacter.uncap_level > 2) suffix = '02'
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_${suffix}.jpg`; imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_${suffix}.jpg`
} }
return imgSrc; return imgSrc
} }
return ( return (
@ -101,7 +99,7 @@ const CharacterHovercard = (props: Props) => {
} }
/> />
) : ( ) : (
"" ''
)} )}
</div> </div>
<UncapIndicator <UncapIndicator
@ -114,12 +112,12 @@ const CharacterHovercard = (props: Props) => {
</div> </div>
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new"> <a className={`Button ${tintElement}`} href={wikiUrl} target="_new">
{t("buttons.wiki")} {t('buttons.wiki')}
</a> </a>
<HoverCard.Arrow /> <HoverCard.Arrow />
</HoverCard.Content> </HoverCard.Content>
</HoverCard.Root> </HoverCard.Root>
); )
}; }
export default CharacterHovercard; export default CharacterHovercard

View file

@ -1,36 +1,34 @@
import React from "react"; import React from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import UncapIndicator from "~components/UncapIndicator"; import UncapIndicator from '~components/UncapIndicator'
import WeaponLabelIcon from "~components/WeaponLabelIcon"; import WeaponLabelIcon from '~components/WeaponLabelIcon'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
data: Character; data: Character
onClick: () => void; onClick: () => void
} }
const Element = ["null", "wind", "fire", "water", "earth", "dark", "light"]; const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
const CharacterResult = (props: Props) => { const CharacterResult = (props: Props) => {
const router = useRouter(); const router = useRouter()
const locale = const locale =
router.locale && ["en", "ja"].includes(router.locale) router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
? router.locale
: "en";
const character = props.data; const character = props.data
const characterUrl = () => { const characterUrl = () => {
let url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_01.jpg`; let url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_01.jpg`
if (character.granblue_id === "3030182000") { if (character.granblue_id === '3030182000') {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_01_01.jpg`; url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_01_01.jpg`
} }
return url; return url
}; }
return ( return (
<li className="CharacterResult" onClick={props.onClick}> <li className="CharacterResult" onClick={props.onClick}>
@ -48,7 +46,7 @@ const CharacterResult = (props: Props) => {
</div> </div>
</div> </div>
</li> </li>
); )
}; }
export default CharacterResult; export default CharacterResult

View file

@ -1,134 +1,134 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import cloneDeep from "lodash.clonedeep"; import cloneDeep from 'lodash.clonedeep'
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import SearchFilter from "~components/SearchFilter"; import SearchFilter from '~components/SearchFilter'
import SearchFilterCheckboxItem from "~components/SearchFilterCheckboxItem"; import SearchFilterCheckboxItem from '~components/SearchFilterCheckboxItem'
import "./index.scss"; import './index.scss'
import { import {
emptyElementState, emptyElementState,
emptyProficiencyState, emptyProficiencyState,
emptyRarityState, emptyRarityState,
} from "~utils/emptyStates"; } from '~utils/emptyStates'
import { elements, proficiencies, rarities } from "~utils/stateValues"; import { elements, proficiencies, rarities } from '~utils/stateValues'
interface Props { interface Props {
sendFilters: (filters: { [key: string]: number[] }) => void; sendFilters: (filters: { [key: string]: number[] }) => void
} }
const CharacterSearchFilterBar = (props: Props) => { const CharacterSearchFilterBar = (props: Props) => {
const { t } = useTranslation("common"); const { t } = useTranslation('common')
const [rarityMenu, setRarityMenu] = useState(false); const [rarityMenu, setRarityMenu] = useState(false)
const [elementMenu, setElementMenu] = useState(false); const [elementMenu, setElementMenu] = useState(false)
const [proficiency1Menu, setProficiency1Menu] = useState(false); const [proficiency1Menu, setProficiency1Menu] = useState(false)
const [proficiency2Menu, setProficiency2Menu] = useState(false); const [proficiency2Menu, setProficiency2Menu] = useState(false)
const [rarityState, setRarityState] = useState<RarityState>(emptyRarityState); const [rarityState, setRarityState] = useState<RarityState>(emptyRarityState)
const [elementState, setElementState] = const [elementState, setElementState] =
useState<ElementState>(emptyElementState); useState<ElementState>(emptyElementState)
const [proficiency1State, setProficiency1State] = useState<ProficiencyState>( const [proficiency1State, setProficiency1State] = useState<ProficiencyState>(
emptyProficiencyState emptyProficiencyState
); )
const [proficiency2State, setProficiency2State] = useState<ProficiencyState>( const [proficiency2State, setProficiency2State] = useState<ProficiencyState>(
emptyProficiencyState emptyProficiencyState
); )
function rarityMenuOpened(open: boolean) { function rarityMenuOpened(open: boolean) {
if (open) { if (open) {
setRarityMenu(true); setRarityMenu(true)
setElementMenu(false); setElementMenu(false)
setProficiency1Menu(false); setProficiency1Menu(false)
setProficiency2Menu(false); setProficiency2Menu(false)
} else setRarityMenu(false); } else setRarityMenu(false)
} }
function elementMenuOpened(open: boolean) { function elementMenuOpened(open: boolean) {
if (open) { if (open) {
setRarityMenu(false); setRarityMenu(false)
setElementMenu(true); setElementMenu(true)
setProficiency1Menu(false); setProficiency1Menu(false)
setProficiency2Menu(false); setProficiency2Menu(false)
} else setElementMenu(false); } else setElementMenu(false)
} }
function proficiency1MenuOpened(open: boolean) { function proficiency1MenuOpened(open: boolean) {
if (open) { if (open) {
setRarityMenu(false); setRarityMenu(false)
setElementMenu(false); setElementMenu(false)
setProficiency1Menu(true); setProficiency1Menu(true)
setProficiency2Menu(false); setProficiency2Menu(false)
} else setProficiency1Menu(false); } else setProficiency1Menu(false)
} }
function proficiency2MenuOpened(open: boolean) { function proficiency2MenuOpened(open: boolean) {
if (open) { if (open) {
setRarityMenu(false); setRarityMenu(false)
setElementMenu(false); setElementMenu(false)
setProficiency1Menu(false); setProficiency1Menu(false)
setProficiency2Menu(true); setProficiency2Menu(true)
} else setProficiency2Menu(false); } else setProficiency2Menu(false)
} }
function handleRarityChange(checked: boolean, key: string) { function handleRarityChange(checked: boolean, key: string) {
let newRarityState = cloneDeep(rarityState); let newRarityState = cloneDeep(rarityState)
newRarityState[key].checked = checked; newRarityState[key].checked = checked
setRarityState(newRarityState); setRarityState(newRarityState)
} }
function handleElementChange(checked: boolean, key: string) { function handleElementChange(checked: boolean, key: string) {
let newElementState = cloneDeep(elementState); let newElementState = cloneDeep(elementState)
newElementState[key].checked = checked; newElementState[key].checked = checked
setElementState(newElementState); setElementState(newElementState)
} }
function handleProficiency1Change(checked: boolean, key: string) { function handleProficiency1Change(checked: boolean, key: string) {
let newProficiencyState = cloneDeep(proficiency1State); let newProficiencyState = cloneDeep(proficiency1State)
newProficiencyState[key].checked = checked; newProficiencyState[key].checked = checked
setProficiency1State(newProficiencyState); setProficiency1State(newProficiencyState)
} }
function handleProficiency2Change(checked: boolean, key: string) { function handleProficiency2Change(checked: boolean, key: string) {
let newProficiencyState = cloneDeep(proficiency2State); let newProficiencyState = cloneDeep(proficiency2State)
newProficiencyState[key].checked = checked; newProficiencyState[key].checked = checked
setProficiency2State(newProficiencyState); setProficiency2State(newProficiencyState)
} }
function sendFilters() { function sendFilters() {
const checkedRarityFilters = Object.values(rarityState) const checkedRarityFilters = Object.values(rarityState)
.filter((x) => x.checked) .filter((x) => x.checked)
.map((x, i) => x.id); .map((x, i) => x.id)
const checkedElementFilters = Object.values(elementState) const checkedElementFilters = Object.values(elementState)
.filter((x) => x.checked) .filter((x) => x.checked)
.map((x, i) => x.id); .map((x, i) => x.id)
const checkedProficiency1Filters = Object.values(proficiency1State) const checkedProficiency1Filters = Object.values(proficiency1State)
.filter((x) => x.checked) .filter((x) => x.checked)
.map((x, i) => x.id); .map((x, i) => x.id)
const checkedProficiency2Filters = Object.values(proficiency2State) const checkedProficiency2Filters = Object.values(proficiency2State)
.filter((x) => x.checked) .filter((x) => x.checked)
.map((x, i) => x.id); .map((x, i) => x.id)
const filters = { const filters = {
rarity: checkedRarityFilters, rarity: checkedRarityFilters,
element: checkedElementFilters, element: checkedElementFilters,
proficiency1: checkedProficiency1Filters, proficiency1: checkedProficiency1Filters,
proficiency2: checkedProficiency2Filters, proficiency2: checkedProficiency2Filters,
}; }
props.sendFilters(filters); props.sendFilters(filters)
} }
useEffect(() => { useEffect(() => {
sendFilters(); sendFilters()
}, [rarityState, elementState, proficiency1State, proficiency2State]); }, [rarityState, elementState, proficiency1State, proficiency2State])
function renderProficiencyFilter(proficiency: 1 | 2) { function renderProficiencyFilter(proficiency: 1 | 2) {
const onCheckedChange = const onCheckedChange =
proficiency == 1 ? handleProficiency1Change : handleProficiency2Change; proficiency == 1 ? handleProficiency1Change : handleProficiency2Change
const numSelected = const numSelected =
proficiency == 1 proficiency == 1
? Object.values(proficiency1State) ? Object.values(proficiency1State)
@ -136,20 +136,20 @@ const CharacterSearchFilterBar = (props: Props) => {
.filter(Boolean).length .filter(Boolean).length
: Object.values(proficiency2State) : Object.values(proficiency2State)
.map((x) => x.checked) .map((x) => x.checked)
.filter(Boolean).length; .filter(Boolean).length
const open = proficiency == 1 ? proficiency1Menu : proficiency2Menu; const open = proficiency == 1 ? proficiency1Menu : proficiency2Menu
const onOpenChange = const onOpenChange =
proficiency == 1 ? proficiency1MenuOpened : proficiency2MenuOpened; proficiency == 1 ? proficiency1MenuOpened : proficiency2MenuOpened
return ( return (
<SearchFilter <SearchFilter
label={`${t("filters.labels.proficiency")} ${proficiency}`} label={`${t('filters.labels.proficiency')} ${proficiency}`}
numSelected={numSelected} numSelected={numSelected}
open={open} open={open}
onOpenChange={onOpenChange} onOpenChange={onOpenChange}
> >
<DropdownMenu.Label className="Label">{`${t( <DropdownMenu.Label className="Label">{`${t(
"filters.labels.proficiency" 'filters.labels.proficiency'
)} ${proficiency}`}</DropdownMenu.Label> )} ${proficiency}`}</DropdownMenu.Label>
<section> <section>
<DropdownMenu.Group className="Group"> <DropdownMenu.Group className="Group">
@ -157,7 +157,7 @@ const CharacterSearchFilterBar = (props: Props) => {
const checked = const checked =
proficiency == 1 proficiency == 1
? proficiency1State[proficiencies[i]].checked ? proficiency1State[proficiencies[i]].checked
: proficiency2State[proficiencies[i]].checked; : proficiency2State[proficiencies[i]].checked
return ( return (
<SearchFilterCheckboxItem <SearchFilterCheckboxItem
@ -168,7 +168,7 @@ const CharacterSearchFilterBar = (props: Props) => {
> >
{t(`proficiencies.${proficiencies[i]}`)} {t(`proficiencies.${proficiencies[i]}`)}
</SearchFilterCheckboxItem> </SearchFilterCheckboxItem>
); )
})} })}
</DropdownMenu.Group> </DropdownMenu.Group>
<DropdownMenu.Group className="Group"> <DropdownMenu.Group className="Group">
@ -180,7 +180,7 @@ const CharacterSearchFilterBar = (props: Props) => {
].checked ].checked
: proficiency2State[ : proficiency2State[
proficiencies[i + proficiencies.length / 2] proficiencies[i + proficiencies.length / 2]
].checked; ].checked
return ( return (
<SearchFilterCheckboxItem <SearchFilterCheckboxItem
@ -195,18 +195,18 @@ const CharacterSearchFilterBar = (props: Props) => {
}` }`
)} )}
</SearchFilterCheckboxItem> </SearchFilterCheckboxItem>
); )
})} })}
</DropdownMenu.Group> </DropdownMenu.Group>
</section> </section>
</SearchFilter> </SearchFilter>
); )
} }
return ( return (
<div className="SearchFilterBar"> <div className="SearchFilterBar">
<SearchFilter <SearchFilter
label={t("filters.labels.rarity")} label={t('filters.labels.rarity')}
numSelected={ numSelected={
Object.values(rarityState) Object.values(rarityState)
.map((x) => x.checked) .map((x) => x.checked)
@ -216,7 +216,7 @@ const CharacterSearchFilterBar = (props: Props) => {
onOpenChange={rarityMenuOpened} onOpenChange={rarityMenuOpened}
> >
<DropdownMenu.Label className="Label"> <DropdownMenu.Label className="Label">
{t("filters.labels.rarity")} {t('filters.labels.rarity')}
</DropdownMenu.Label> </DropdownMenu.Label>
{Array.from(Array(rarities.length)).map((x, i) => { {Array.from(Array(rarities.length)).map((x, i) => {
return ( return (
@ -228,12 +228,12 @@ const CharacterSearchFilterBar = (props: Props) => {
> >
{t(`rarities.${rarities[i]}`)} {t(`rarities.${rarities[i]}`)}
</SearchFilterCheckboxItem> </SearchFilterCheckboxItem>
); )
})} })}
</SearchFilter> </SearchFilter>
<SearchFilter <SearchFilter
label={t("filters.labels.element")} label={t('filters.labels.element')}
numSelected={ numSelected={
Object.values(elementState) Object.values(elementState)
.map((x) => x.checked) .map((x) => x.checked)
@ -243,7 +243,7 @@ const CharacterSearchFilterBar = (props: Props) => {
onOpenChange={elementMenuOpened} onOpenChange={elementMenuOpened}
> >
<DropdownMenu.Label className="Label"> <DropdownMenu.Label className="Label">
{t("filters.labels.element")} {t('filters.labels.element')}
</DropdownMenu.Label> </DropdownMenu.Label>
{Array.from(Array(elements.length)).map((x, i) => { {Array.from(Array(elements.length)).map((x, i) => {
return ( return (
@ -255,14 +255,14 @@ const CharacterSearchFilterBar = (props: Props) => {
> >
{t(`elements.${elements[i]}`)} {t(`elements.${elements[i]}`)}
</SearchFilterCheckboxItem> </SearchFilterCheckboxItem>
); )
})} })}
</SearchFilter> </SearchFilter>
{renderProficiencyFilter(1)} {renderProficiencyFilter(1)}
{renderProficiencyFilter(2)} {renderProficiencyFilter(2)}
</div> </div>
); )
}; }
export default CharacterSearchFilterBar; export default CharacterSearchFilterBar

View file

@ -1,87 +1,85 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useSnapshot } from "valtio"; import { useSnapshot } from 'valtio'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import classnames from "classnames"; import classnames from 'classnames'
import { appState } from "~utils/appState"; import { appState } from '~utils/appState'
import CharacterHovercard from "~components/CharacterHovercard"; import CharacterHovercard from '~components/CharacterHovercard'
import SearchModal from "~components/SearchModal"; import SearchModal from '~components/SearchModal'
import UncapIndicator from "~components/UncapIndicator"; import UncapIndicator from '~components/UncapIndicator'
import PlusIcon from "~public/icons/Add.svg"; import PlusIcon from '~public/icons/Add.svg'
import type { SearchableObject } from "~types"; import type { SearchableObject } from '~types'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
gridCharacter?: GridCharacter; gridCharacter?: GridCharacter
position: number; position: number
editable: boolean; editable: boolean
updateObject: (object: SearchableObject, position: number) => void; updateObject: (object: SearchableObject, position: number) => void
updateUncap: (id: string, position: number, uncap: number) => void; updateUncap: (id: string, position: number, uncap: number) => void
} }
const CharacterUnit = (props: Props) => { const CharacterUnit = (props: Props) => {
const { t } = useTranslation("common"); const { t } = useTranslation('common')
const { party, grid } = useSnapshot(appState); const { party, grid } = useSnapshot(appState)
const router = useRouter(); const router = useRouter()
const locale = const locale =
router.locale && ["en", "ja"].includes(router.locale) router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
? router.locale
: "en";
const [imageUrl, setImageUrl] = useState(""); const [imageUrl, setImageUrl] = useState('')
const classes = classnames({ const classes = classnames({
CharacterUnit: true, CharacterUnit: true,
editable: props.editable, editable: props.editable,
filled: props.gridCharacter !== undefined, filled: props.gridCharacter !== undefined,
}); })
const gridCharacter = props.gridCharacter; const gridCharacter = props.gridCharacter
const character = gridCharacter?.object; const character = gridCharacter?.object
useEffect(() => { useEffect(() => {
generateImageUrl(); generateImageUrl()
}); })
function generateImageUrl() { function generateImageUrl() {
let imgSrc = ""; let imgSrc = ''
if (props.gridCharacter) { if (props.gridCharacter) {
const character = props.gridCharacter.object!; const character = props.gridCharacter.object!
// Change the image based on the uncap level // Change the image based on the uncap level
let suffix = "01"; let suffix = '01'
if (props.gridCharacter.uncap_level == 6) suffix = "04"; if (props.gridCharacter.uncap_level == 6) suffix = '04'
else if (props.gridCharacter.uncap_level == 5) suffix = "03"; else if (props.gridCharacter.uncap_level == 5) suffix = '03'
else if (props.gridCharacter.uncap_level > 2) suffix = "02"; else if (props.gridCharacter.uncap_level > 2) suffix = '02'
// Special casing for Lyria (and Young Cat eventually) // Special casing for Lyria (and Young Cat eventually)
if (props.gridCharacter.object.granblue_id === "3030182000") { if (props.gridCharacter.object.granblue_id === '3030182000') {
let element = 1; let element = 1
if (grid.weapons.mainWeapon && grid.weapons.mainWeapon.element) { if (grid.weapons.mainWeapon && grid.weapons.mainWeapon.element) {
element = grid.weapons.mainWeapon.element; element = grid.weapons.mainWeapon.element
} else if (party.element != 0) { } else if (party.element != 0) {
element = party.element; element = party.element
} }
suffix = `${suffix}_0${element}`; suffix = `${suffix}_0${element}`
} }
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-main/${character.granblue_id}_${suffix}.jpg`; imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-main/${character.granblue_id}_${suffix}.jpg`
} }
setImageUrl(imgSrc); setImageUrl(imgSrc)
} }
function passUncapData(uncap: number) { function passUncapData(uncap: number) {
if (props.gridCharacter) if (props.gridCharacter)
props.updateUncap(props.gridCharacter.id, props.position, uncap); props.updateUncap(props.gridCharacter.id, props.position, uncap)
} }
const image = ( const image = (
@ -92,21 +90,21 @@ const CharacterUnit = (props: Props) => {
<PlusIcon /> <PlusIcon />
</span> </span>
) : ( ) : (
"" ''
)} )}
</div> </div>
); )
const editableImage = ( const editableImage = (
<SearchModal <SearchModal
placeholderText={t("search.placeholders.character")} placeholderText={t('search.placeholders.character')}
fromPosition={props.position} fromPosition={props.position}
object="characters" object="characters"
send={props.updateObject} send={props.updateObject}
> >
{image} {image}
</SearchModal> </SearchModal>
); )
const unitContent = ( const unitContent = (
<div className={classes}> <div className={classes}>
@ -121,19 +119,19 @@ const CharacterUnit = (props: Props) => {
special={character.special} special={character.special}
/> />
) : ( ) : (
"" ''
)} )}
<h3 className="CharacterName">{character?.name[locale]}</h3> <h3 className="CharacterName">{character?.name[locale]}</h3>
</div> </div>
); )
const withHovercard = ( const withHovercard = (
<CharacterHovercard gridCharacter={gridCharacter!}> <CharacterHovercard gridCharacter={gridCharacter!}>
{unitContent} {unitContent}
</CharacterHovercard> </CharacterHovercard>
); )
return gridCharacter && !props.editable ? withHovercard : unitContent; return gridCharacter && !props.editable ? withHovercard : unitContent
}; }
export default CharacterUnit; export default CharacterUnit

View file

@ -27,7 +27,7 @@
} }
&:hover, &:hover,
&[data-state="on"] { &[data-state='on'] {
background: $grey-80; background: $grey-80;
color: $grey-10; color: $grey-10;

View file

@ -1,23 +1,21 @@
import React from "react"; import React from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import * as ToggleGroup from "@radix-ui/react-toggle-group"; import * as ToggleGroup from '@radix-ui/react-toggle-group'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
currentElement: number; currentElement: number
sendValue: (value: string) => void; sendValue: (value: string) => void
} }
const ElementToggle = (props: Props) => { const ElementToggle = (props: Props) => {
const router = useRouter(); const router = useRouter()
const { t } = useTranslation("common"); const { t } = useTranslation('common')
const locale = const locale =
router.locale && ["en", "ja"].includes(router.locale) router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
? router.locale
: "en";
return ( return (
<ToggleGroup.Root <ToggleGroup.Root
@ -32,52 +30,52 @@ const ElementToggle = (props: Props) => {
value="0" value="0"
aria-label="null" aria-label="null"
> >
{t("elements.null")} {t('elements.null')}
</ToggleGroup.Item> </ToggleGroup.Item>
<ToggleGroup.Item <ToggleGroup.Item
className={`ToggleItem wind ${locale}`} className={`ToggleItem wind ${locale}`}
value="1" value="1"
aria-label="wind" aria-label="wind"
> >
{t("elements.wind")} {t('elements.wind')}
</ToggleGroup.Item> </ToggleGroup.Item>
<ToggleGroup.Item <ToggleGroup.Item
className={`ToggleItem fire ${locale}`} className={`ToggleItem fire ${locale}`}
value="2" value="2"
aria-label="fire" aria-label="fire"
> >
{t("elements.fire")} {t('elements.fire')}
</ToggleGroup.Item> </ToggleGroup.Item>
<ToggleGroup.Item <ToggleGroup.Item
className={`ToggleItem water ${locale}`} className={`ToggleItem water ${locale}`}
value="3" value="3"
aria-label="water" aria-label="water"
> >
{t("elements.water")} {t('elements.water')}
</ToggleGroup.Item> </ToggleGroup.Item>
<ToggleGroup.Item <ToggleGroup.Item
className={`ToggleItem earth ${locale}`} className={`ToggleItem earth ${locale}`}
value="4" value="4"
aria-label="earth" aria-label="earth"
> >
{t("elements.earth")} {t('elements.earth')}
</ToggleGroup.Item> </ToggleGroup.Item>
<ToggleGroup.Item <ToggleGroup.Item
className={`ToggleItem dark ${locale}`} className={`ToggleItem dark ${locale}`}
value="5" value="5"
aria-label="dark" aria-label="dark"
> >
{t("elements.dark")} {t('elements.dark')}
</ToggleGroup.Item> </ToggleGroup.Item>
<ToggleGroup.Item <ToggleGroup.Item
className={`ToggleItem light ${locale}`} className={`ToggleItem light ${locale}`}
value="6" value="6"
aria-label="light" aria-label="light"
> >
{t("elements.light")} {t('elements.light')}
</ToggleGroup.Item> </ToggleGroup.Item>
</ToggleGroup.Root> </ToggleGroup.Root>
); )
}; }
export default ElementToggle; export default ElementToggle

View file

@ -1,28 +1,28 @@
import React from "react"; import React from 'react'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import SummonUnit from "~components/SummonUnit"; import SummonUnit from '~components/SummonUnit'
import { SearchableObject } from "~types"; import { SearchableObject } from '~types'
import "./index.scss"; import './index.scss'
// Props // Props
interface Props { interface Props {
grid: GridArray<GridSummon>; grid: GridArray<GridSummon>
editable: boolean; editable: boolean
exists: boolean; exists: boolean
found?: boolean; found?: boolean
offset: number; offset: number
updateObject: (object: SearchableObject, position: number) => void; updateObject: (object: SearchableObject, position: number) => void
updateUncap: (id: string, position: number, uncap: number) => void; updateUncap: (id: string, position: number, uncap: number) => void
} }
const ExtraSummons = (props: Props) => { const ExtraSummons = (props: Props) => {
const numSummons: number = 2; const numSummons: number = 2
const { t } = useTranslation("common"); const { t } = useTranslation('common')
return ( return (
<div id="ExtraSummons"> <div id="ExtraSummons">
<span>{t("summons.subaura")}</span> <span>{t('summons.subaura')}</span>
<ul id="grid_summons"> <ul id="grid_summons">
{Array.from(Array(numSummons)).map((x, i) => { {Array.from(Array(numSummons)).map((x, i) => {
return ( return (
@ -36,11 +36,11 @@ const ExtraSummons = (props: Props) => {
updateUncap={props.updateUncap} updateUncap={props.updateUncap}
/> />
</li> </li>
); )
})} })}
</ul> </ul>
</div> </div>
); )
}; }
export default ExtraSummons; export default ExtraSummons

View file

@ -1,28 +1,28 @@
import React from "react"; import React from 'react'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import WeaponUnit from "~components/WeaponUnit"; import WeaponUnit from '~components/WeaponUnit'
import type { SearchableObject } from "~types"; import type { SearchableObject } from '~types'
import "./index.scss"; import './index.scss'
// Props // Props
interface Props { interface Props {
grid: GridArray<GridWeapon>; grid: GridArray<GridWeapon>
editable: boolean; editable: boolean
found?: boolean; found?: boolean
offset: number; offset: number
updateObject: (object: SearchableObject, position: number) => void; updateObject: (object: SearchableObject, position: number) => void
updateUncap: (id: string, position: number, uncap: number) => void; updateUncap: (id: string, position: number, uncap: number) => void
} }
const ExtraWeapons = (props: Props) => { const ExtraWeapons = (props: Props) => {
const numWeapons: number = 3; const numWeapons: number = 3
const { t } = useTranslation("common"); const { t } = useTranslation('common')
return ( return (
<div id="ExtraGrid"> <div id="ExtraGrid">
<span>{t("extra_weapons")}</span> <span>{t('extra_weapons')}</span>
<ul className="grid_weapons"> <ul className="grid_weapons">
{Array.from(Array(numWeapons)).map((x, i) => { {Array.from(Array(numWeapons)).map((x, i) => {
return ( return (
@ -36,11 +36,11 @@ const ExtraWeapons = (props: Props) => {
updateUncap={props.updateUncap} updateUncap={props.updateUncap}
/> />
</li> </li>
); )
})} })}
</ul> </ul>
</div> </div>
); )
}; }
export default ExtraWeapons; export default ExtraWeapons

View file

@ -1,22 +1,22 @@
import React from "react"; import React from 'react'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
fieldName: string; fieldName: string
placeholder: string; placeholder: string
value?: string; value?: string
error: string; error: string
onBlur?: (event: React.ChangeEvent<HTMLInputElement>) => void; onBlur?: (event: React.ChangeEvent<HTMLInputElement>) => void
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void; onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
} }
const Fieldset = React.forwardRef<HTMLInputElement, Props>(function fieldSet( const Fieldset = React.forwardRef<HTMLInputElement, Props>(function fieldSet(
props, props,
ref ref
) { ) {
const fieldType = ["password", "confirm_password"].includes(props.fieldName) const fieldType = ['password', 'confirm_password'].includes(props.fieldName)
? "password" ? 'password'
: "text"; : 'text'
return ( return (
<fieldset className="Fieldset"> <fieldset className="Fieldset">
@ -26,7 +26,7 @@ const Fieldset = React.forwardRef<HTMLInputElement, Props>(function fieldSet(
type={fieldType} type={fieldType}
name={props.fieldName} name={props.fieldName}
placeholder={props.placeholder} placeholder={props.placeholder}
defaultValue={props.value || ""} defaultValue={props.value || ''}
onBlur={props.onBlur} onBlur={props.onBlur}
onChange={props.onChange} onChange={props.onChange}
ref={ref} ref={ref}
@ -34,7 +34,7 @@ const Fieldset = React.forwardRef<HTMLInputElement, Props>(function fieldSet(
/> />
{props.error.length > 0 && <p className="InputError">{props.error}</p>} {props.error.length > 0 && <p className="InputError">{props.error}</p>}
</fieldset> </fieldset>
); )
}); })
export default Fieldset; export default Fieldset

View file

@ -1,59 +1,59 @@
import React from "react"; import React from 'react'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import classNames from "classnames"; import classNames from 'classnames'
import RaidDropdown from "~components/RaidDropdown"; import RaidDropdown from '~components/RaidDropdown'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
children: React.ReactNode; children: React.ReactNode
scrolled: boolean; scrolled: boolean
element?: number; element?: number
raidSlug?: string; raidSlug?: string
recency?: number; recency?: number
onFilter: ({ onFilter: ({
element, element,
raidSlug, raidSlug,
recency, recency,
}: { }: {
element?: number; element?: number
raidSlug?: string; raidSlug?: string
recency?: number; recency?: number
}) => void; }) => void
} }
const FilterBar = (props: Props) => { const FilterBar = (props: Props) => {
// Set up translation // Set up translation
const { t } = useTranslation("common"); const { t } = useTranslation('common')
// Set up refs for filter dropdowns // Set up refs for filter dropdowns
const elementSelect = React.createRef<HTMLSelectElement>(); const elementSelect = React.createRef<HTMLSelectElement>()
const raidSelect = React.createRef<HTMLSelectElement>(); const raidSelect = React.createRef<HTMLSelectElement>()
const recencySelect = React.createRef<HTMLSelectElement>(); const recencySelect = React.createRef<HTMLSelectElement>()
// Set up classes object for showing shadow on scroll // Set up classes object for showing shadow on scroll
const classes = classNames({ const classes = classNames({
FilterBar: true, FilterBar: true,
shadow: props.scrolled, shadow: props.scrolled,
}); })
function elementSelectChanged() { function elementSelectChanged() {
const elementValue = elementSelect.current const elementValue = elementSelect.current
? parseInt(elementSelect.current.value) ? parseInt(elementSelect.current.value)
: -1; : -1
props.onFilter({ element: elementValue }); props.onFilter({ element: elementValue })
} }
function recencySelectChanged() { function recencySelectChanged() {
const recencyValue = recencySelect.current const recencyValue = recencySelect.current
? parseInt(recencySelect.current.value) ? parseInt(recencySelect.current.value)
: -1; : -1
props.onFilter({ recency: recencyValue }); props.onFilter({ recency: recencyValue })
} }
function raidSelectChanged(slug?: string) { function raidSelectChanged(slug?: string) {
props.onFilter({ raidSlug: slug }); props.onFilter({ raidSlug: slug })
} }
return ( return (
@ -65,28 +65,28 @@ const FilterBar = (props: Props) => {
value={props.element} value={props.element}
> >
<option data-element="all" key={-1} value={-1}> <option data-element="all" key={-1} value={-1}>
{t("elements.full.all")} {t('elements.full.all')}
</option> </option>
<option data-element="null" key={0} value={0}> <option data-element="null" key={0} value={0}>
{t("elements.full.null")} {t('elements.full.null')}
</option> </option>
<option data-element="wind" key={1} value={1}> <option data-element="wind" key={1} value={1}>
{t("elements.full.wind")} {t('elements.full.wind')}
</option> </option>
<option data-element="fire" key={2} value={2}> <option data-element="fire" key={2} value={2}>
{t("elements.full.fire")} {t('elements.full.fire')}
</option> </option>
<option data-element="water" key={3} value={3}> <option data-element="water" key={3} value={3}>
{t("elements.full.water")} {t('elements.full.water')}
</option> </option>
<option data-element="earth" key={4} value={4}> <option data-element="earth" key={4} value={4}>
{t("elements.full.earth")} {t('elements.full.earth')}
</option> </option>
<option data-element="dark" key={5} value={5}> <option data-element="dark" key={5} value={5}>
{t("elements.full.dark")} {t('elements.full.dark')}
</option> </option>
<option data-element="light" key={6} value={6}> <option data-element="light" key={6} value={6}>
{t("elements.full.light")} {t('elements.full.light')}
</option> </option>
</select> </select>
<RaidDropdown <RaidDropdown
@ -97,29 +97,29 @@ const FilterBar = (props: Props) => {
/> />
<select onChange={recencySelectChanged} ref={recencySelect}> <select onChange={recencySelectChanged} ref={recencySelect}>
<option key={-1} value={-1}> <option key={-1} value={-1}>
{t("recency.all_time")} {t('recency.all_time')}
</option> </option>
<option key={86400} value={86400}> <option key={86400} value={86400}>
{t("recency.last_day")} {t('recency.last_day')}
</option> </option>
<option key={604800} value={604800}> <option key={604800} value={604800}>
{t("recency.last_week")} {t('recency.last_week')}
</option> </option>
<option key={2629746} value={2629746}> <option key={2629746} value={2629746}>
{t("recency.last_month")} {t('recency.last_month')}
</option> </option>
<option key={7889238} value={7889238}> <option key={7889238} value={7889238}>
{t("recency.last_3_months")} {t('recency.last_3_months')}
</option> </option>
<option key={15778476} value={15778476}> <option key={15778476} value={15778476}>
{t("recency.last_6_months")} {t('recency.last_6_months')}
</option> </option>
<option key={31556952} value={31556952}> <option key={31556952} value={31556952}>
{t("recency.last_year")} {t('recency.last_year')}
</option> </option>
</select> </select>
</div> </div>
); )
}; }
export default FilterBar; export default FilterBar

View file

@ -50,8 +50,8 @@
width: 70px; width: 70px;
} }
.grid_mainhand img[src*="jpg"], .grid_mainhand img[src*='jpg'],
.grid_weapon img[src*="jpg"] { .grid_weapon img[src*='jpg'] {
border-radius: 4px; border-radius: 4px;
width: 100%; width: 100%;
height: 100%; height: 100%;

View file

@ -1,122 +1,120 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useSnapshot } from "valtio"; import { useSnapshot } from 'valtio'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import classNames from "classnames"; import classNames from 'classnames'
import { accountState } from "~utils/accountState"; import { accountState } from '~utils/accountState'
import { formatTimeAgo } from "~utils/timeAgo"; import { formatTimeAgo } from '~utils/timeAgo'
import Button from "~components/Button"; import Button from '~components/Button'
import { ButtonType } from "~utils/enums"; import { ButtonType } from '~utils/enums'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
shortcode: string; shortcode: string
id: string; id: string
name: string; name: string
raid: Raid; raid: Raid
grid: GridWeapon[]; grid: GridWeapon[]
user?: User; user?: User
favorited: boolean; favorited: boolean
createdAt: Date; createdAt: Date
displayUser?: boolean | false; displayUser?: boolean | false
onClick: (shortcode: string) => void; onClick: (shortcode: string) => void
onSave?: (partyId: string, favorited: boolean) => void; onSave?: (partyId: string, favorited: boolean) => void
} }
const GridRep = (props: Props) => { const GridRep = (props: Props) => {
const numWeapons: number = 9; const numWeapons: number = 9
const { account } = useSnapshot(accountState); const { account } = useSnapshot(accountState)
const router = useRouter(); const router = useRouter()
const { t } = useTranslation("common"); const { t } = useTranslation('common')
const locale = const locale =
router.locale && ["en", "ja"].includes(router.locale) router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
? router.locale
: "en";
const [mainhand, setMainhand] = useState<Weapon>(); const [mainhand, setMainhand] = useState<Weapon>()
const [weapons, setWeapons] = useState<GridArray<Weapon>>({}); const [weapons, setWeapons] = useState<GridArray<Weapon>>({})
const [grid, setGrid] = useState<GridArray<GridWeapon>>({}); const [grid, setGrid] = useState<GridArray<GridWeapon>>({})
const titleClass = classNames({ const titleClass = classNames({
empty: !props.name, empty: !props.name,
}); })
const raidClass = classNames({ const raidClass = classNames({
raid: true, raid: true,
empty: !props.raid, empty: !props.raid,
}); })
const userClass = classNames({ const userClass = classNames({
user: true, user: true,
empty: !props.user, empty: !props.user,
}); })
useEffect(() => { useEffect(() => {
const newWeapons = Array(numWeapons); const newWeapons = Array(numWeapons)
const gridWeapons = Array(numWeapons); const gridWeapons = Array(numWeapons)
for (const [key, value] of Object.entries(props.grid)) { for (const [key, value] of Object.entries(props.grid)) {
if (value.position == -1) setMainhand(value.object); if (value.position == -1) setMainhand(value.object)
else if (!value.mainhand && value.position != null) { else if (!value.mainhand && value.position != null) {
newWeapons[value.position] = value.object; newWeapons[value.position] = value.object
gridWeapons[value.position] = value; gridWeapons[value.position] = value
} }
} }
setWeapons(newWeapons); setWeapons(newWeapons)
setGrid(gridWeapons); setGrid(gridWeapons)
}, [props.grid]); }, [props.grid])
function navigate() { function navigate() {
props.onClick(props.shortcode); props.onClick(props.shortcode)
} }
function generateMainhandImage() { function generateMainhandImage() {
let url = ""; let url = ''
if (mainhand) { if (mainhand) {
if (mainhand.element == 0 && props.grid[0].element) { if (mainhand.element == 0 && props.grid[0].element) {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.granblue_id}_${props.grid[0].element}.jpg`; url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.granblue_id}_${props.grid[0].element}.jpg`
} else { } else {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.granblue_id}.jpg`; url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.granblue_id}.jpg`
} }
} }
return mainhand && props.grid[0] ? ( return mainhand && props.grid[0] ? (
<img alt={mainhand.name[locale]} src={url} /> <img alt={mainhand.name[locale]} src={url} />
) : ( ) : (
"" ''
); )
} }
function generateGridImage(position: number) { function generateGridImage(position: number) {
let url = ""; let url = ''
const weapon = weapons[position]; const weapon = weapons[position]
const gridWeapon = grid[position]; const gridWeapon = grid[position]
if (weapon && gridWeapon) { if (weapon && gridWeapon) {
if (weapon.element == 0 && gridWeapon.element) { if (weapon.element == 0 && gridWeapon.element) {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}_${gridWeapon.element}.jpg`; url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}_${gridWeapon.element}.jpg`
} else { } else {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`; url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`
} }
} }
return weapons[position] ? ( return weapons[position] ? (
<img alt={weapons[position]?.name[locale]} src={url} /> <img alt={weapons[position]?.name[locale]} src={url} />
) : ( ) : (
"" ''
); )
} }
function sendSaveData() { function sendSaveData() {
if (props.onSave) props.onSave(props.id, props.favorited); if (props.onSave) props.onSave(props.id, props.favorited)
} }
const userImage = () => { const userImage = () => {
@ -129,35 +127,35 @@ const GridRep = (props: Props) => {
/profile/${props.user.picture.picture}@2x.png 2x`} /profile/${props.user.picture.picture}@2x.png 2x`}
src={`/profile/${props.user.picture.picture}.png`} src={`/profile/${props.user.picture.picture}.png`}
/> />
); )
} else return <div className="no-user" />; } else return <div className="no-user" />
}; }
const details = ( const details = (
<div className="Details"> <div className="Details">
<h2 className={titleClass} onClick={navigate}> <h2 className={titleClass} onClick={navigate}>
{props.name ? props.name : t("no_title")} {props.name ? props.name : t('no_title')}
</h2> </h2>
<div className="bottom"> <div className="bottom">
<div className={raidClass}> <div className={raidClass}>
{props.raid ? props.raid.name[locale] : t("no_raid")} {props.raid ? props.raid.name[locale] : t('no_raid')}
</div> </div>
<time className="last-updated" dateTime={props.createdAt.toISOString()}> <time className="last-updated" dateTime={props.createdAt.toISOString()}>
{formatTimeAgo(props.createdAt, locale)} {formatTimeAgo(props.createdAt, locale)}
</time> </time>
</div> </div>
</div> </div>
); )
const detailsWithUsername = ( const detailsWithUsername = (
<div className="Details"> <div className="Details">
<div className="top"> <div className="top">
<div className="info"> <div className="info">
<h2 className={titleClass} onClick={navigate}> <h2 className={titleClass} onClick={navigate}>
{props.name ? props.name : t("no_title")} {props.name ? props.name : t('no_title')}
</h2> </h2>
<div className={raidClass}> <div className={raidClass}>
{props.raid ? props.raid.name[locale] : t("no_raid")} {props.raid ? props.raid.name[locale] : t('no_raid')}
</div> </div>
</div> </div>
{account.authorized && {account.authorized &&
@ -170,20 +168,20 @@ const GridRep = (props: Props) => {
onClick={sendSaveData} onClick={sendSaveData}
/> />
) : ( ) : (
"" ''
)} )}
</div> </div>
<div className="bottom"> <div className="bottom">
<div className={userClass}> <div className={userClass}>
{userImage()} {userImage()}
{props.user ? props.user.username : t("no_user")} {props.user ? props.user.username : t('no_user')}
</div> </div>
<time className="last-updated" dateTime={props.createdAt.toISOString()}> <time className="last-updated" dateTime={props.createdAt.toISOString()}>
{formatTimeAgo(props.createdAt, locale)} {formatTimeAgo(props.createdAt, locale)}
</time> </time>
</div> </div>
</div> </div>
); )
return ( return (
<div className="GridRep"> <div className="GridRep">
@ -200,12 +198,12 @@ const GridRep = (props: Props) => {
> >
{generateGridImage(i)} {generateGridImage(i)}
</li> </li>
); )
})} })}
</ul> </ul>
</div> </div>
</div> </div>
); )
}; }
export default GridRep; export default GridRep

View file

@ -1,18 +1,18 @@
import classNames from "classnames"; import classNames from 'classnames'
import React from "react"; import React from 'react'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
children: React.ReactNode; children: React.ReactNode
} }
const GridRepCollection = (props: Props) => { const GridRepCollection = (props: Props) => {
const classes = classNames({ const classes = classNames({
GridRepCollection: true, GridRepCollection: true,
}); })
return <div className={classes}>{props.children}</div>; return <div className={classes}>{props.children}</div>
}; }
export default GridRepCollection; export default GridRepCollection

View file

@ -1,11 +1,11 @@
import React from "react"; import React from 'react'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
position: "top" | "bottom"; position: 'top' | 'bottom'
left: JSX.Element; left: JSX.Element
right: JSX.Element; right: JSX.Element
} }
const Header = (props: Props) => { const Header = (props: Props) => {
@ -15,7 +15,7 @@ const Header = (props: Props) => {
<div className="push" /> <div className="push" />
<div id="right">{props.right}</div> <div id="right">{props.right}</div>
</nav> </nav>
); )
}; }
export default Header; export default Header

View file

@ -67,7 +67,7 @@
cursor: pointer; cursor: pointer;
} }
&[data-state="checked"] { &[data-state='checked'] {
background: $grey-100; background: $grey-100;
transform: translateX(17px); transform: translateX(17px);
} }

View file

@ -1,51 +1,51 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import { getCookie, setCookie } from "cookies-next"; import { getCookie, setCookie } from 'cookies-next'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import Link from "next/link"; import Link from 'next/link'
import * as Switch from "@radix-ui/react-switch"; import * as Switch from '@radix-ui/react-switch'
import AboutModal from "~components/AboutModal"; import AboutModal from '~components/AboutModal'
import AccountModal from "~components/AccountModal"; import AccountModal from '~components/AccountModal'
import LoginModal from "~components/LoginModal"; import LoginModal from '~components/LoginModal'
import SignupModal from "~components/SignupModal"; import SignupModal from '~components/SignupModal'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
authenticated: boolean; authenticated: boolean
username?: string; username?: string
logout?: () => void; logout?: () => void
} }
const HeaderMenu = (props: Props) => { const HeaderMenu = (props: Props) => {
const router = useRouter(); const router = useRouter()
const { t } = useTranslation("common"); const { t } = useTranslation('common')
const accountCookie = getCookie("account"); const accountCookie = getCookie('account')
const accountData: AccountCookie = accountCookie const accountData: AccountCookie = accountCookie
? JSON.parse(accountCookie as string) ? JSON.parse(accountCookie as string)
: null; : null
const userCookie = getCookie("user"); const userCookie = getCookie('user')
const userData: UserCookie = userCookie const userData: UserCookie = userCookie
? JSON.parse(userCookie as string) ? JSON.parse(userCookie as string)
: null; : null
const localeCookie = getCookie("NEXT_LOCALE"); const localeCookie = getCookie('NEXT_LOCALE')
const [checked, setChecked] = useState(false); const [checked, setChecked] = useState(false)
useEffect(() => { useEffect(() => {
const locale = localeCookie; const locale = localeCookie
setChecked(locale === "ja" ? true : false); setChecked(locale === 'ja' ? true : false)
}, [localeCookie]); }, [localeCookie])
function handleCheckedChange(value: boolean) { function handleCheckedChange(value: boolean) {
const language = value ? "ja" : "en"; const language = value ? 'ja' : 'en'
setCookie("NEXT_LOCALE", language, { path: "/" }); setCookie('NEXT_LOCALE', language, { path: '/' })
router.push(router.asPath, undefined, { locale: language }); router.push(router.asPath, undefined, { locale: language })
} }
function authItems() { function authItems() {
@ -54,7 +54,7 @@ const HeaderMenu = (props: Props) => {
<ul className="Menu auth"> <ul className="Menu auth">
<div className="MenuGroup"> <div className="MenuGroup">
<li className="MenuItem profile"> <li className="MenuItem profile">
<Link href={`/${accountData.username}` || ""} passHref> <Link href={`/${accountData.username}` || ''} passHref>
<div> <div>
<span>{accountData.username}</span> <span>{accountData.username}</span>
<img <img
@ -68,18 +68,18 @@ const HeaderMenu = (props: Props) => {
</Link> </Link>
</li> </li>
<li className="MenuItem"> <li className="MenuItem">
<Link href={`/saved` || ""}>{t("menu.saved")}</Link> <Link href={`/saved` || ''}>{t('menu.saved')}</Link>
</li> </li>
</div> </div>
<div className="MenuGroup"> <div className="MenuGroup">
<li className="MenuItem"> <li className="MenuItem">
<Link href="/teams">{t("menu.teams")}</Link> <Link href="/teams">{t('menu.teams')}</Link>
</li> </li>
<li className="MenuItem disabled"> <li className="MenuItem disabled">
<div> <div>
<span>{t("menu.guides")}</span> <span>{t('menu.guides')}</span>
<i className="tag">{t("coming_soon")}</i> <i className="tag">{t('coming_soon')}</i>
</div> </div>
</li> </li>
</div> </div>
@ -87,12 +87,12 @@ const HeaderMenu = (props: Props) => {
<AboutModal /> <AboutModal />
<AccountModal /> <AccountModal />
<li className="MenuItem" onClick={props.logout}> <li className="MenuItem" onClick={props.logout}>
<span>{t("menu.logout")}</span> <span>{t('menu.logout')}</span>
</li> </li>
</div> </div>
</ul> </ul>
</nav> </nav>
); )
} }
function unauthItems() { function unauthItems() {
@ -100,7 +100,7 @@ const HeaderMenu = (props: Props) => {
<ul className="Menu unauth"> <ul className="Menu unauth">
<div className="MenuGroup"> <div className="MenuGroup">
<li className="MenuItem language"> <li className="MenuItem language">
<span>{t("menu.language")}</span> <span>{t('menu.language')}</span>
<Switch.Root <Switch.Root
className="Switch" className="Switch"
onCheckedChange={handleCheckedChange} onCheckedChange={handleCheckedChange}
@ -114,13 +114,13 @@ const HeaderMenu = (props: Props) => {
</div> </div>
<div className="MenuGroup"> <div className="MenuGroup">
<li className="MenuItem"> <li className="MenuItem">
<Link href="/teams">{t("menu.teams")}</Link> <Link href="/teams">{t('menu.teams')}</Link>
</li> </li>
<li className="MenuItem disabled"> <li className="MenuItem disabled">
<div> <div>
<span>{t("menu.guides")}</span> <span>{t('menu.guides')}</span>
<i className="tag">{t("coming_soon")}</i> <i className="tag">{t('coming_soon')}</i>
</div> </div>
</li> </li>
</div> </div>
@ -132,10 +132,10 @@ const HeaderMenu = (props: Props) => {
<SignupModal /> <SignupModal />
</div> </div>
</ul> </ul>
); )
} }
return props.authenticated ? authItems() : unauthItems(); return props.authenticated ? authItems() : unauthItems()
}; }
export default HeaderMenu; export default HeaderMenu

View file

@ -1,69 +1,69 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useSnapshot } from "valtio"; import { useSnapshot } from 'valtio'
import { appState } from "~utils/appState"; import { appState } from '~utils/appState'
import { jobGroups } from "~utils/jobGroups"; import { jobGroups } from '~utils/jobGroups'
import "./index.scss"; import './index.scss'
// Props // Props
interface Props { interface Props {
currentJob?: string; currentJob?: string
onChange?: (job?: Job) => void; onChange?: (job?: Job) => void
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void; onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void
} }
type GroupedJob = { [key: string]: Job[] }; type GroupedJob = { [key: string]: Job[] }
const JobDropdown = React.forwardRef<HTMLSelectElement, Props>( const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
function useFieldSet(props, ref) { function useFieldSet(props, ref) {
// Set up router for locale // Set up router for locale
const router = useRouter(); const router = useRouter()
const locale = router.locale || "en"; const locale = router.locale || 'en'
// Create snapshot of app state // Create snapshot of app state
const { party } = useSnapshot(appState); const { party } = useSnapshot(appState)
// Set up local states for storing jobs // Set up local states for storing jobs
const [currentJob, setCurrentJob] = useState<Job>(); const [currentJob, setCurrentJob] = useState<Job>()
const [jobs, setJobs] = useState<Job[]>(); const [jobs, setJobs] = useState<Job[]>()
const [sortedJobs, setSortedJobs] = useState<GroupedJob>(); const [sortedJobs, setSortedJobs] = useState<GroupedJob>()
// Set current job from state on mount // Set current job from state on mount
useEffect(() => { useEffect(() => {
setCurrentJob(party.job); setCurrentJob(party.job)
}, []); }, [])
// Organize jobs into groups on mount // Organize jobs into groups on mount
useEffect(() => { useEffect(() => {
const jobGroups = appState.jobs const jobGroups = appState.jobs
.map((job) => job.row) .map((job) => job.row)
.filter((value, index, self) => self.indexOf(value) === index); .filter((value, index, self) => self.indexOf(value) === index)
let groupedJobs: GroupedJob = {}; let groupedJobs: GroupedJob = {}
jobGroups.forEach((group) => { jobGroups.forEach((group) => {
groupedJobs[group] = appState.jobs.filter((job) => job.row === group); groupedJobs[group] = appState.jobs.filter((job) => job.row === group)
}); })
setJobs(appState.jobs); setJobs(appState.jobs)
setSortedJobs(groupedJobs); setSortedJobs(groupedJobs)
}, [appState]); }, [appState])
// Set current job on mount // Set current job on mount
useEffect(() => { useEffect(() => {
if (jobs && props.currentJob) { if (jobs && props.currentJob) {
const job = appState.jobs.find((job) => job.id === props.currentJob); const job = appState.jobs.find((job) => job.id === props.currentJob)
setCurrentJob(job); setCurrentJob(job)
} }
}, [appState, props.currentJob]); }, [appState, props.currentJob])
// Enable changing select value // Enable changing select value
function handleChange(event: React.ChangeEvent<HTMLSelectElement>) { function handleChange(event: React.ChangeEvent<HTMLSelectElement>) {
if (jobs) { if (jobs) {
const job = jobs.find((job) => job.id === event.target.value); const job = jobs.find((job) => job.id === event.target.value)
if (props.onChange) props.onChange(job); if (props.onChange) props.onChange(job)
setCurrentJob(job); setCurrentJob(job)
} }
} }
@ -79,16 +79,16 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
<option key={i} value={item.id}> <option key={i} value={item.id}>
{item.name[locale]} {item.name[locale]}
</option> </option>
); )
}); })
const groupName = jobGroups.find((g) => g.slug === group)?.name[locale]; const groupName = jobGroups.find((g) => g.slug === group)?.name[locale]
return ( return (
<optgroup key={group} label={groupName}> <optgroup key={group} label={groupName}>
{options} {options}
</optgroup> </optgroup>
); )
} }
return ( return (
@ -104,10 +104,10 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
</option> </option>
{sortedJobs {sortedJobs
? Object.keys(sortedJobs).map((x) => renderJobGroup(x)) ? Object.keys(sortedJobs).map((x) => renderJobGroup(x))
: ""} : ''}
</select> </select>
); )
} }
); )
export default JobDropdown; export default JobDropdown

View file

@ -31,7 +31,7 @@
$height: 249px; $height: 249px;
$width: 447px; $width: 447px;
background: url("/images/background_a.jpg"); background: url('/images/background_a.jpg');
background-size: 500px 281px; background-size: 500px 281px;
border-radius: $unit; border-radius: $unit;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);

View file

@ -1,101 +1,99 @@
import React, { ForwardedRef, useEffect, useState } from "react"; import React, { ForwardedRef, useEffect, useState } from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useSnapshot } from "valtio"; import { useSnapshot } from 'valtio'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import JobDropdown from "~components/JobDropdown"; import JobDropdown from '~components/JobDropdown'
import JobSkillItem from "~components/JobSkillItem"; import JobSkillItem from '~components/JobSkillItem'
import SearchModal from "~components/SearchModal"; import SearchModal from '~components/SearchModal'
import { appState } from "~utils/appState"; import { appState } from '~utils/appState'
import type { JobSkillObject, SearchableObject } from "~types"; import type { JobSkillObject, SearchableObject } from '~types'
import "./index.scss"; import './index.scss'
// Props // Props
interface Props { interface Props {
job?: Job; job?: Job
jobSkills: JobSkillObject; jobSkills: JobSkillObject
editable: boolean; editable: boolean
saveJob: (job: Job) => void; saveJob: (job: Job) => void
saveSkill: (skill: JobSkill, position: number) => void; saveSkill: (skill: JobSkill, position: number) => void
} }
const JobSection = (props: Props) => { const JobSection = (props: Props) => {
const { party } = useSnapshot(appState); const { party } = useSnapshot(appState)
const { t } = useTranslation("common"); const { t } = useTranslation('common')
const router = useRouter(); const router = useRouter()
const locale = const locale =
router.locale && ["en", "ja"].includes(router.locale) router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
? router.locale
: "en";
const [job, setJob] = useState<Job>(); const [job, setJob] = useState<Job>()
const [imageUrl, setImageUrl] = useState(""); const [imageUrl, setImageUrl] = useState('')
const [numSkills, setNumSkills] = useState(4); const [numSkills, setNumSkills] = useState(4)
const [skills, setSkills] = useState<{ [key: number]: JobSkill | undefined }>( const [skills, setSkills] = useState<{ [key: number]: JobSkill | undefined }>(
[] []
); )
const selectRef = React.createRef<HTMLSelectElement>(); const selectRef = React.createRef<HTMLSelectElement>()
useEffect(() => { useEffect(() => {
// Set current job based on ID // Set current job based on ID
if (props.job) { if (props.job) {
setJob(props.job); setJob(props.job)
setSkills({ setSkills({
0: props.jobSkills[0], 0: props.jobSkills[0],
1: props.jobSkills[1], 1: props.jobSkills[1],
2: props.jobSkills[2], 2: props.jobSkills[2],
3: props.jobSkills[3], 3: props.jobSkills[3],
}); })
if (selectRef.current) selectRef.current.value = props.job.id; if (selectRef.current) selectRef.current.value = props.job.id
} }
}, [props]); }, [props])
useEffect(() => { useEffect(() => {
generateImageUrl(); generateImageUrl()
}); })
useEffect(() => { useEffect(() => {
if (job) { if (job) {
if ((party.job && job.id != party.job.id) || !party.job) if ((party.job && job.id != party.job.id) || !party.job)
appState.party.job = job; appState.party.job = job
if (job.row === "1") setNumSkills(3); if (job.row === '1') setNumSkills(3)
else setNumSkills(4); else setNumSkills(4)
} }
}, [job]); }, [job])
function receiveJob(job?: Job) { function receiveJob(job?: Job) {
if (job) { if (job) {
setJob(job); setJob(job)
props.saveJob(job); props.saveJob(job)
} }
} }
function generateImageUrl() { function generateImageUrl() {
let imgSrc = ""; let imgSrc = ''
if (job) { if (job) {
const slug = job?.name.en.replaceAll(" ", "-").toLowerCase(); const slug = job?.name.en.replaceAll(' ', '-').toLowerCase()
const gender = party.user && party.user.gender == 1 ? "b" : "a"; const gender = party.user && party.user.gender == 1 ? 'b' : 'a'
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/jobs/${slug}_${gender}.png`; imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/jobs/${slug}_${gender}.png`
} }
setImageUrl(imgSrc); setImageUrl(imgSrc)
} }
const canEditSkill = (skill?: JobSkill) => { const canEditSkill = (skill?: JobSkill) => {
if (job && skill) { if (job && skill) {
if (skill.job.id === job.id && skill.main && !skill.sub) return false; if (skill.job.id === job.id && skill.main && !skill.sub) return false
} }
return props.editable; return props.editable
}; }
const skillItem = (index: number, editable: boolean) => { const skillItem = (index: number, editable: boolean) => {
return ( return (
@ -103,15 +101,15 @@ const JobSection = (props: Props) => {
skill={skills[index]} skill={skills[index]}
editable={canEditSkill(skills[index])} editable={canEditSkill(skills[index])}
key={`skill-${index}`} key={`skill-${index}`}
hasJob={job != undefined && job.id != "-1"} hasJob={job != undefined && job.id != '-1'}
/> />
); )
}; }
const editableSkillItem = (index: number) => { const editableSkillItem = (index: number) => {
return ( return (
<SearchModal <SearchModal
placeholderText={t("search.placeholders.job_skill")} placeholderText={t('search.placeholders.job_skill')}
fromPosition={index} fromPosition={index}
object="job_skills" object="job_skills"
job={job} job={job}
@ -119,17 +117,17 @@ const JobSection = (props: Props) => {
> >
{skillItem(index, true)} {skillItem(index, true)}
</SearchModal> </SearchModal>
); )
}; }
function saveJobSkill(object: SearchableObject, position: number) { function saveJobSkill(object: SearchableObject, position: number) {
const skill = object as JobSkill; const skill = object as JobSkill
const newSkills = skills; const newSkills = skills
newSkills[position] = skill; newSkills[position] = skill
setSkills(newSkills); setSkills(newSkills)
props.saveSkill(skill, position); props.saveSkill(skill, position)
} }
// Render: JSX components // Render: JSX components
@ -161,7 +159,7 @@ const JobSection = (props: Props) => {
</ul> </ul>
</div> </div>
</section> </section>
); )
}; }
export default JobSection; export default JobSection

View file

@ -1,40 +1,40 @@
import React from "react"; import React from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import classNames from "classnames"; import classNames from 'classnames'
import PlusIcon from "~public/icons/Add.svg"; import PlusIcon from '~public/icons/Add.svg'
import "./index.scss"; import './index.scss'
// Props // Props
interface Props extends React.ComponentPropsWithoutRef<"div"> { interface Props extends React.ComponentPropsWithoutRef<'div'> {
skill?: JobSkill; skill?: JobSkill
editable: boolean; editable: boolean
hasJob: boolean; hasJob: boolean
} }
const JobSkillItem = React.forwardRef<HTMLDivElement, Props>( const JobSkillItem = React.forwardRef<HTMLDivElement, Props>(
function useJobSkillItem({ ...props }, forwardedRef) { function useJobSkillItem({ ...props }, forwardedRef) {
const router = useRouter(); const router = useRouter()
const { t } = useTranslation("common"); const { t } = useTranslation('common')
const locale = const locale =
router.locale && ["en", "ja"].includes(router.locale) router.locale && ['en', 'ja'].includes(router.locale)
? router.locale ? router.locale
: "en"; : 'en'
const classes = classNames({ const classes = classNames({
JobSkill: true, JobSkill: true,
editable: props.editable, editable: props.editable,
}); })
const imageClasses = classNames({ const imageClasses = classNames({
placeholder: !props.skill, placeholder: !props.skill,
editable: props.editable && props.hasJob, editable: props.editable && props.hasJob,
}); })
const skillImage = () => { const skillImage = () => {
let jsx: React.ReactNode; let jsx: React.ReactNode
if (props.skill) { if (props.skill) {
jsx = ( jsx = (
@ -43,39 +43,39 @@ const JobSkillItem = React.forwardRef<HTMLDivElement, Props>(
className={imageClasses} className={imageClasses}
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-skills/${props.skill.slug}.png`} src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-skills/${props.skill.slug}.png`}
/> />
); )
} else { } else {
jsx = ( jsx = (
<div className={imageClasses}> <div className={imageClasses}>
{props.editable && props.hasJob ? <PlusIcon /> : ""} {props.editable && props.hasJob ? <PlusIcon /> : ''}
</div> </div>
); )
} }
return jsx; return jsx
}; }
const label = () => { const label = () => {
let jsx: React.ReactNode; let jsx: React.ReactNode
if (props.skill) { if (props.skill) {
jsx = <p>{props.skill.name[locale]}</p>; jsx = <p>{props.skill.name[locale]}</p>
} else if (props.editable && props.hasJob) { } else if (props.editable && props.hasJob) {
jsx = <p className="placeholder">{t("job_skills.state.selectable")}</p>; jsx = <p className="placeholder">{t('job_skills.state.selectable')}</p>
} else { } else {
jsx = <p className="placeholder">{t("job_skills.state.no_skill")}</p>; jsx = <p className="placeholder">{t('job_skills.state.no_skill')}</p>
} }
return jsx; return jsx
}; }
return ( return (
<div className={classes} onClick={props.onClick} ref={forwardedRef}> <div className={classes} onClick={props.onClick} ref={forwardedRef}>
{skillImage()} {skillImage()}
{label()} {label()}
</div> </div>
); )
} }
); )
export default JobSkillItem; export default JobSkillItem

View file

@ -1,43 +1,41 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { SkillGroup, skillClassification } from "~utils/skillGroups"; import { SkillGroup, skillClassification } from '~utils/skillGroups'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
data: JobSkill; data: JobSkill
onClick: () => void; onClick: () => void
} }
const JobSkillResult = (props: Props) => { const JobSkillResult = (props: Props) => {
const router = useRouter(); const router = useRouter()
const locale = const locale =
router.locale && ["en", "ja"].includes(router.locale) router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
? router.locale
: "en";
const skill = props.data; const skill = props.data
const [group, setGroup] = useState<SkillGroup | undefined>(); const [group, setGroup] = useState<SkillGroup | undefined>()
useEffect(() => { useEffect(() => {
setGroup(skillClassification.find((group) => group.id === skill.color)); setGroup(skillClassification.find((group) => group.id === skill.color))
}, [skill, setGroup, skillClassification]); }, [skill, setGroup, skillClassification])
const jobSkillUrl = () => const jobSkillUrl = () =>
`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-skills/${skill.slug}.png`; `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-skills/${skill.slug}.png`
return ( return (
<li className="JobSkillResult" onClick={props.onClick}> <li className="JobSkillResult" onClick={props.onClick}>
<img alt={skill.name[locale]} src={jobSkillUrl()} /> <img alt={skill.name[locale]} src={jobSkillUrl()} />
<div className="Info"> <div className="Info">
<h5>{skill.name[locale]}</h5> <h5>{skill.name[locale]}</h5>
<div className={`skill pill ${group?.name["en"].toLowerCase()}`}> <div className={`skill pill ${group?.name['en'].toLowerCase()}`}>
{group?.name[locale]} {group?.name[locale]}
</div> </div>
</div> </div>
</li> </li>
); )
}; }
export default JobSkillResult; export default JobSkillResult

View file

@ -1,23 +1,23 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useTranslation } from "react-i18next"; import { useTranslation } from 'react-i18next'
import { skillGroups } from "~utils/skillGroups"; import { skillGroups } from '~utils/skillGroups'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
sendFilters: (filters: { [key: string]: number }) => void; sendFilters: (filters: { [key: string]: number }) => void
} }
const JobSkillSearchFilterBar = (props: Props) => { const JobSkillSearchFilterBar = (props: Props) => {
// Set up translation // Set up translation
const { t } = useTranslation("common"); const { t } = useTranslation('common')
const [currentGroup, setCurrentGroup] = useState(-1); const [currentGroup, setCurrentGroup] = useState(-1)
function onChange(event: React.ChangeEvent<HTMLSelectElement>) { function onChange(event: React.ChangeEvent<HTMLSelectElement>) {
setCurrentGroup(parseInt(event.target.value)); setCurrentGroup(parseInt(event.target.value))
} }
function onBlur(event: React.ChangeEvent<HTMLSelectElement>) {} function onBlur(event: React.ChangeEvent<HTMLSelectElement>) {}
@ -25,14 +25,14 @@ const JobSkillSearchFilterBar = (props: Props) => {
function sendFilters() { function sendFilters() {
const filters = { const filters = {
group: currentGroup, group: currentGroup,
}; }
props.sendFilters(filters); props.sendFilters(filters)
} }
useEffect(() => { useEffect(() => {
sendFilters(); sendFilters()
}, [currentGroup]); }, [currentGroup])
return ( return (
<div className="SearchFilterBar"> <div className="SearchFilterBar">
@ -65,7 +65,7 @@ const JobSkillSearchFilterBar = (props: Props) => {
</option> </option>
</select> </select>
</div> </div>
); )
}; }
export default JobSkillSearchFilterBar; export default JobSkillSearchFilterBar

View file

@ -1,8 +1,8 @@
import type { ReactElement } from "react"; import type { ReactElement } from 'react'
import TopHeader from "~components/TopHeader"; import TopHeader from '~components/TopHeader'
interface Props { interface Props {
children: ReactElement; children: ReactElement
} }
const Layout = ({ children }: Props) => { const Layout = ({ children }: Props) => {
@ -11,7 +11,7 @@ const Layout = ({ children }: Props) => {
<TopHeader /> <TopHeader />
<main>{children}</main> <main>{children}</main>
</> </>
); )
}; }
export default Layout; export default Layout

View file

@ -1,138 +1,138 @@
import React, { useState } from "react"; import React, { useState } from 'react'
import { setCookie } from "cookies-next"; import { setCookie } from 'cookies-next'
import Router, { useRouter } from "next/router"; import Router, { useRouter } from 'next/router'
import { useTranslation } from "react-i18next"; import { useTranslation } from 'react-i18next'
import { AxiosResponse } from "axios"; import { AxiosResponse } from 'axios'
import * as Dialog from "@radix-ui/react-dialog"; import * as Dialog from '@radix-ui/react-dialog'
import api from "~utils/api"; import api from '~utils/api'
import { accountState } from "~utils/accountState"; import { accountState } from '~utils/accountState'
import Button from "~components/Button"; import Button from '~components/Button'
import Fieldset from "~components/Fieldset"; import Fieldset from '~components/Fieldset'
import CrossIcon from "~public/icons/Cross.svg"; import CrossIcon from '~public/icons/Cross.svg'
import "./index.scss"; import './index.scss'
interface Props {} interface Props {}
interface ErrorMap { interface ErrorMap {
[index: string]: string; [index: string]: string
email: string; email: string
password: string; password: string
} }
const emailRegex = const emailRegex =
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
const LoginModal = (props: Props) => { const LoginModal = (props: Props) => {
const router = useRouter(); const router = useRouter()
const { t } = useTranslation("common"); const { t } = useTranslation('common')
// Set up form states and error handling // Set up form states and error handling
const [formValid, setFormValid] = useState(false); const [formValid, setFormValid] = useState(false)
const [errors, setErrors] = useState<ErrorMap>({ const [errors, setErrors] = useState<ErrorMap>({
email: "", email: '',
password: "", password: '',
}); })
// States // States
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false)
// Set up form refs // Set up form refs
const emailInput: React.RefObject<HTMLInputElement> = React.createRef(); const emailInput: React.RefObject<HTMLInputElement> = React.createRef()
const passwordInput: React.RefObject<HTMLInputElement> = React.createRef(); const passwordInput: React.RefObject<HTMLInputElement> = React.createRef()
const form: React.RefObject<HTMLInputElement>[] = [emailInput, passwordInput]; const form: React.RefObject<HTMLInputElement>[] = [emailInput, passwordInput]
function handleChange(event: React.ChangeEvent<HTMLInputElement>) { function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
const { name, value } = event.target; const { name, value } = event.target
let newErrors = { ...errors }; let newErrors = { ...errors }
switch (name) { switch (name) {
case "email": case 'email':
if (value.length == 0) if (value.length == 0)
newErrors.email = t("modals.login.errors.empty_email"); newErrors.email = t('modals.login.errors.empty_email')
else if (!emailRegex.test(value)) else if (!emailRegex.test(value))
newErrors.email = t("modals.login.errors.invalid_email"); newErrors.email = t('modals.login.errors.invalid_email')
else newErrors.email = ""; else newErrors.email = ''
break; break
case "password": case 'password':
newErrors.password = newErrors.password =
value.length == 0 ? t("modals.login.errors.empty_password") : ""; value.length == 0 ? t('modals.login.errors.empty_password') : ''
break; break
default: default:
break; break
} }
setErrors(newErrors); setErrors(newErrors)
setFormValid(validateForm(newErrors)); setFormValid(validateForm(newErrors))
} }
function validateForm(errors: ErrorMap) { function validateForm(errors: ErrorMap) {
let valid = true; let valid = true
Object.values(form).forEach( Object.values(form).forEach(
(input) => input.current?.value.length == 0 && (valid = false) (input) => input.current?.value.length == 0 && (valid = false)
); )
Object.values(errors).forEach( Object.values(errors).forEach(
(error) => error.length > 0 && (valid = false) (error) => error.length > 0 && (valid = false)
); )
return valid; return valid
} }
function login(event: React.FormEvent) { function login(event: React.FormEvent) {
event.preventDefault(); event.preventDefault()
const body = { const body = {
email: emailInput.current?.value, email: emailInput.current?.value,
password: passwordInput.current?.value, password: passwordInput.current?.value,
grant_type: "password", grant_type: 'password',
}; }
if (formValid) { if (formValid) {
api api
.login(body) .login(body)
.then((response) => { .then((response) => {
storeCookieInfo(response); storeCookieInfo(response)
return response.data.user.id; return response.data.user.id
}) })
.then((id) => fetchUserInfo(id)) .then((id) => fetchUserInfo(id))
.then((infoResponse) => storeUserInfo(infoResponse)); .then((infoResponse) => storeUserInfo(infoResponse))
} }
} }
function fetchUserInfo(id: string) { function fetchUserInfo(id: string) {
return api.userInfo(id); return api.userInfo(id)
} }
function storeCookieInfo(response: AxiosResponse) { function storeCookieInfo(response: AxiosResponse) {
const user = response.data.user; const user = response.data.user
const cookieObj: AccountCookie = { const cookieObj: AccountCookie = {
userId: user.id, userId: user.id,
username: user.username, username: user.username,
token: response.data.access_token, token: response.data.access_token,
}; }
setCookie("account", cookieObj, { path: "/" }); setCookie('account', cookieObj, { path: '/' })
} }
function storeUserInfo(response: AxiosResponse) { function storeUserInfo(response: AxiosResponse) {
const user = response.data.user; const user = response.data.user
const cookieObj: UserCookie = { const cookieObj: UserCookie = {
picture: user.picture.picture, picture: user.picture.picture,
element: user.picture.element, element: user.picture.element,
language: user.language, language: user.language,
gender: user.gender, gender: user.gender,
}; }
setCookie("user", cookieObj, { path: "/" }); setCookie('user', cookieObj, { path: '/' })
accountState.account.user = { accountState.account.user = {
id: user.id, id: user.id,
@ -140,35 +140,35 @@ const LoginModal = (props: Props) => {
picture: user.picture.picture, picture: user.picture.picture,
element: user.picture.element, element: user.picture.element,
gender: user.gender, gender: user.gender,
}; }
console.log("Authorizing account..."); console.log('Authorizing account...')
accountState.account.authorized = true; accountState.account.authorized = true
setOpen(false); setOpen(false)
changeLanguage(user.language); changeLanguage(user.language)
} }
function changeLanguage(newLanguage: string) { function changeLanguage(newLanguage: string) {
if (newLanguage !== router.locale) { if (newLanguage !== router.locale) {
setCookie("NEXT_LOCALE", newLanguage, { path: "/" }); setCookie('NEXT_LOCALE', newLanguage, { path: '/' })
router.push(router.asPath, undefined, { locale: newLanguage }); router.push(router.asPath, undefined, { locale: newLanguage })
} }
} }
function openChange(open: boolean) { function openChange(open: boolean) {
setOpen(open); setOpen(open)
setErrors({ setErrors({
email: "", email: '',
password: "", password: '',
}); })
} }
return ( return (
<Dialog.Root open={open} onOpenChange={openChange}> <Dialog.Root open={open} onOpenChange={openChange}>
<Dialog.Trigger asChild> <Dialog.Trigger asChild>
<li className="MenuItem"> <li className="MenuItem">
<span>{t("menu.login")}</span> <span>{t('menu.login')}</span>
</li> </li>
</Dialog.Trigger> </Dialog.Trigger>
<Dialog.Portal> <Dialog.Portal>
@ -178,7 +178,7 @@ const LoginModal = (props: Props) => {
> >
<div className="DialogHeader"> <div className="DialogHeader">
<Dialog.Title className="DialogTitle"> <Dialog.Title className="DialogTitle">
{t("modals.login.title")} {t('modals.login.title')}
</Dialog.Title> </Dialog.Title>
<Dialog.Close className="DialogClose" asChild> <Dialog.Close className="DialogClose" asChild>
<span> <span>
@ -190,7 +190,7 @@ const LoginModal = (props: Props) => {
<form className="form" onSubmit={login}> <form className="form" onSubmit={login}>
<Fieldset <Fieldset
fieldName="email" fieldName="email"
placeholder={t("modals.login.placeholders.email")} placeholder={t('modals.login.placeholders.email')}
onChange={handleChange} onChange={handleChange}
error={errors.email} error={errors.email}
ref={emailInput} ref={emailInput}
@ -198,19 +198,19 @@ const LoginModal = (props: Props) => {
<Fieldset <Fieldset
fieldName="password" fieldName="password"
placeholder={t("modals.login.placeholders.password")} placeholder={t('modals.login.placeholders.password')}
onChange={handleChange} onChange={handleChange}
error={errors.password} error={errors.password}
ref={passwordInput} ref={passwordInput}
/> />
<Button>{t("modals.login.buttons.confirm")}</Button> <Button>{t('modals.login.buttons.confirm')}</Button>
</form> </form>
</Dialog.Content> </Dialog.Content>
<Dialog.Overlay className="Overlay" /> <Dialog.Overlay className="Overlay" />
</Dialog.Portal> </Dialog.Portal>
</Dialog.Root> </Dialog.Root>
); )
}; }
export default LoginModal; export default LoginModal

View file

@ -1,53 +1,53 @@
import React from "react"; import React from 'react'
import { useSnapshot } from "valtio"; import { useSnapshot } from 'valtio'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import { appState } from "~utils/appState"; import { appState } from '~utils/appState'
import SegmentedControl from "~components/SegmentedControl"; import SegmentedControl from '~components/SegmentedControl'
import Segment from "~components/Segment"; import Segment from '~components/Segment'
import ToggleSwitch from "~components/ToggleSwitch"; import ToggleSwitch from '~components/ToggleSwitch'
import { GridType } from "~utils/enums"; import { GridType } from '~utils/enums'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
selectedTab: GridType; selectedTab: GridType
onClick: (event: React.ChangeEvent<HTMLInputElement>) => void; onClick: (event: React.ChangeEvent<HTMLInputElement>) => void
onCheckboxChange: (event: React.ChangeEvent<HTMLInputElement>) => void; onCheckboxChange: (event: React.ChangeEvent<HTMLInputElement>) => void
} }
const PartySegmentedControl = (props: Props) => { const PartySegmentedControl = (props: Props) => {
const { t } = useTranslation("common"); const { t } = useTranslation('common')
const { party, grid } = useSnapshot(appState); const { party, grid } = useSnapshot(appState)
function getElement() { function getElement() {
let element: number = 0; let element: number = 0
if (party.element == 0 && grid.weapons.mainWeapon) if (party.element == 0 && grid.weapons.mainWeapon)
element = grid.weapons.mainWeapon.element; element = grid.weapons.mainWeapon.element
else element = party.element; else element = party.element
switch (element) { switch (element) {
case 1: case 1:
return "wind"; return 'wind'
break; break
case 2: case 2:
return "fire"; return 'fire'
break; break
case 3: case 3:
return "water"; return 'water'
break; break
case 4: case 4:
return "earth"; return 'earth'
break; break
case 5: case 5:
return "dark"; return 'dark'
break; break
case 6: case 6:
return "light"; return 'light'
break; break
} }
} }
@ -61,7 +61,7 @@ const PartySegmentedControl = (props: Props) => {
onChange={props.onCheckboxChange} onChange={props.onCheckboxChange}
/> />
</div> </div>
); )
return ( return (
<div className="PartyNavigation"> <div className="PartyNavigation">
@ -79,7 +79,7 @@ const PartySegmentedControl = (props: Props) => {
selected={props.selectedTab == GridType.Character} selected={props.selectedTab == GridType.Character}
onClick={props.onClick} onClick={props.onClick}
> >
{t("party.segmented_control.characters")} {t('party.segmented_control.characters')}
</Segment> </Segment>
<Segment <Segment
@ -88,7 +88,7 @@ const PartySegmentedControl = (props: Props) => {
selected={props.selectedTab == GridType.Weapon} selected={props.selectedTab == GridType.Weapon}
onClick={props.onClick} onClick={props.onClick}
> >
{t("party.segmented_control.weapons")} {t('party.segmented_control.weapons')}
</Segment> </Segment>
<Segment <Segment
@ -97,17 +97,17 @@ const PartySegmentedControl = (props: Props) => {
selected={props.selectedTab == GridType.Summon} selected={props.selectedTab == GridType.Summon}
onClick={props.onClick} onClick={props.onClick}
> >
{t("party.segmented_control.summons")} {t('party.segmented_control.summons')}
</Segment> </Segment>
</SegmentedControl> </SegmentedControl>
{(() => { {(() => {
if (party.editable && props.selectedTab == GridType.Weapon) { if (party.editable && props.selectedTab == GridType.Weapon) {
return extraToggle; return extraToggle
} }
})()} })()}
</div> </div>
); )
}; }
export default PartySegmentedControl; export default PartySegmentedControl

View file

@ -1,16 +1,16 @@
import React from "react"; import React from 'react'
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import ArrowIcon from "~public/icons/Arrow.svg"; import ArrowIcon from '~public/icons/Arrow.svg'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
label: string; label: string
open: boolean; open: boolean
numSelected: number; numSelected: number
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void
children: React.ReactNode; children: React.ReactNode
} }
const SearchFilter = (props: Props) => { const SearchFilter = (props: Props) => {
@ -28,7 +28,7 @@ const SearchFilter = (props: Props) => {
<DropdownMenu.Arrow /> <DropdownMenu.Arrow />
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Root> </DropdownMenu.Root>
); )
}; }
export default SearchFilter; export default SearchFilter

View file

@ -14,7 +14,7 @@
cursor: pointer; cursor: pointer;
} }
&[data-state="checked"] { &[data-state='checked'] {
background: $grey-90; background: $grey-90;
svg { svg {

View file

@ -1,20 +1,20 @@
import React from "react"; import React from 'react'
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import CheckIcon from "~public/icons/Check.svg"; import CheckIcon from '~public/icons/Check.svg'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
checked?: boolean; checked?: boolean
valueKey: string; valueKey: string
onCheckedChange: (open: boolean, key: string) => void; onCheckedChange: (open: boolean, key: string) => void
children: React.ReactNode; children: React.ReactNode
} }
const SearchFilterCheckboxItem = (props: Props) => { const SearchFilterCheckboxItem = (props: Props) => {
function handleCheckedChange(checked: boolean) { function handleCheckedChange(checked: boolean) {
props.onCheckedChange(checked, props.valueKey); props.onCheckedChange(checked, props.valueKey)
} }
return ( return (
@ -29,7 +29,7 @@ const SearchFilterCheckboxItem = (props: Props) => {
</DropdownMenu.ItemIndicator> </DropdownMenu.ItemIndicator>
{props.children} {props.children}
</DropdownMenu.CheckboxItem> </DropdownMenu.CheckboxItem>
); )
}; }
export default SearchFilterCheckboxItem; export default SearchFilterCheckboxItem

View file

@ -1,70 +1,70 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import { getCookie, setCookie } from "cookies-next"; import { getCookie, setCookie } from 'cookies-next'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useTranslation } from "react-i18next"; import { useTranslation } from 'react-i18next'
import InfiniteScroll from "react-infinite-scroll-component"; import InfiniteScroll from 'react-infinite-scroll-component'
import api from "~utils/api"; import api from '~utils/api'
import * as Dialog from "@radix-ui/react-dialog"; import * as Dialog from '@radix-ui/react-dialog'
import CharacterSearchFilterBar from "~components/CharacterSearchFilterBar"; import CharacterSearchFilterBar from '~components/CharacterSearchFilterBar'
import WeaponSearchFilterBar from "~components/WeaponSearchFilterBar"; import WeaponSearchFilterBar from '~components/WeaponSearchFilterBar'
import SummonSearchFilterBar from "~components/SummonSearchFilterBar"; import SummonSearchFilterBar from '~components/SummonSearchFilterBar'
import JobSkillSearchFilterBar from "~components/JobSkillSearchFilterBar"; import JobSkillSearchFilterBar from '~components/JobSkillSearchFilterBar'
import CharacterResult from "~components/CharacterResult"; import CharacterResult from '~components/CharacterResult'
import WeaponResult from "~components/WeaponResult"; import WeaponResult from '~components/WeaponResult'
import SummonResult from "~components/SummonResult"; import SummonResult from '~components/SummonResult'
import JobSkillResult from "~components/JobSkillResult"; import JobSkillResult from '~components/JobSkillResult'
import type { SearchableObject, SearchableObjectArray } from "~types"; import type { SearchableObject, SearchableObjectArray } from '~types'
import "./index.scss"; import './index.scss'
import CrossIcon from "~public/icons/Cross.svg"; import CrossIcon from '~public/icons/Cross.svg'
import cloneDeep from "lodash.clonedeep"; import cloneDeep from 'lodash.clonedeep'
interface Props { interface Props {
send: (object: SearchableObject, position: number) => any; send: (object: SearchableObject, position: number) => any
placeholderText: string; placeholderText: string
fromPosition: number; fromPosition: number
job?: Job; job?: Job
object: "weapons" | "characters" | "summons" | "job_skills"; object: 'weapons' | 'characters' | 'summons' | 'job_skills'
children: React.ReactNode; children: React.ReactNode
} }
const SearchModal = (props: Props) => { const SearchModal = (props: Props) => {
// Set up router // Set up router
const router = useRouter(); const router = useRouter()
const locale = router.locale; const locale = router.locale
// Set up translation // Set up translation
const { t } = useTranslation("common"); const { t } = useTranslation('common')
let searchInput = React.createRef<HTMLInputElement>(); let searchInput = React.createRef<HTMLInputElement>()
let scrollContainer = React.createRef<HTMLDivElement>(); let scrollContainer = React.createRef<HTMLDivElement>()
const [firstLoad, setFirstLoad] = useState(true); const [firstLoad, setFirstLoad] = useState(true)
const [filters, setFilters] = useState<{ [key: string]: any }>(); const [filters, setFilters] = useState<{ [key: string]: any }>()
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false)
const [query, setQuery] = useState(""); const [query, setQuery] = useState('')
const [results, setResults] = useState<SearchableObjectArray>([]); const [results, setResults] = useState<SearchableObjectArray>([])
// Pagination states // Pagination states
const [recordCount, setRecordCount] = useState(0); const [recordCount, setRecordCount] = useState(0)
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1)
const [totalPages, setTotalPages] = useState(1); const [totalPages, setTotalPages] = useState(1)
useEffect(() => { useEffect(() => {
if (searchInput.current) searchInput.current.focus(); if (searchInput.current) searchInput.current.focus()
}, [searchInput]); }, [searchInput])
function inputChanged(event: React.ChangeEvent<HTMLInputElement>) { function inputChanged(event: React.ChangeEvent<HTMLInputElement>) {
const text = event.target.value; const text = event.target.value
if (text.length) { if (text.length) {
setQuery(text); setQuery(text)
} else { } else {
setQuery(""); setQuery('')
} }
} }
@ -79,131 +79,131 @@ const SearchModal = (props: Props) => {
page: currentPage, page: currentPage,
}) })
.then((response) => { .then((response) => {
setTotalPages(response.data.total_pages); setTotalPages(response.data.total_pages)
setRecordCount(response.data.count); setRecordCount(response.data.count)
if (replace) { if (replace) {
replaceResults(response.data.count, response.data.results); replaceResults(response.data.count, response.data.results)
} else { } else {
appendResults(response.data.results); appendResults(response.data.results)
} }
}) })
.catch((error) => { .catch((error) => {
console.error(error); console.error(error)
}); })
} }
function replaceResults(count: number, list: SearchableObjectArray) { function replaceResults(count: number, list: SearchableObjectArray) {
if (count > 0) { if (count > 0) {
setResults(list); setResults(list)
} else { } else {
setResults([]); setResults([])
} }
} }
function appendResults(list: SearchableObjectArray) { function appendResults(list: SearchableObjectArray) {
setResults([...results, ...list]); setResults([...results, ...list])
} }
function storeRecentResult(result: SearchableObject) { function storeRecentResult(result: SearchableObject) {
const key = `recent_${props.object}`; const key = `recent_${props.object}`
const cookie = getCookie(key); const cookie = getCookie(key)
const cookieObj: SearchableObjectArray = cookie const cookieObj: SearchableObjectArray = cookie
? JSON.parse(cookie as string) ? JSON.parse(cookie as string)
: []; : []
let recents: SearchableObjectArray = []; let recents: SearchableObjectArray = []
if (props.object === "weapons") { if (props.object === 'weapons') {
recents = cloneDeep(cookieObj as Weapon[]) || []; recents = cloneDeep(cookieObj as Weapon[]) || []
if ( if (
!recents.find( !recents.find(
(item) => (item) =>
(item as Weapon).granblue_id === (result as Weapon).granblue_id (item as Weapon).granblue_id === (result as Weapon).granblue_id
) )
) { ) {
recents.unshift(result as Weapon); recents.unshift(result as Weapon)
} }
} else if (props.object === "summons") { } else if (props.object === 'summons') {
recents = cloneDeep(cookieObj as Summon[]) || []; recents = cloneDeep(cookieObj as Summon[]) || []
if ( if (
!recents.find( !recents.find(
(item) => (item) =>
(item as Summon).granblue_id === (result as Summon).granblue_id (item as Summon).granblue_id === (result as Summon).granblue_id
) )
) { ) {
recents.unshift(result as Summon); recents.unshift(result as Summon)
} }
} }
if (recents && recents.length > 5) recents.pop(); if (recents && recents.length > 5) recents.pop()
setCookie(`recent_${props.object}`, recents, { path: "/" }); setCookie(`recent_${props.object}`, recents, { path: '/' })
sendData(result); sendData(result)
} }
function sendData(result: SearchableObject) { function sendData(result: SearchableObject) {
props.send(result, props.fromPosition); props.send(result, props.fromPosition)
openChange(); openChange()
} }
function receiveFilters(filters: { [key: string]: any }) { function receiveFilters(filters: { [key: string]: any }) {
setCurrentPage(1); setCurrentPage(1)
setResults([]); setResults([])
setFilters(filters); setFilters(filters)
} }
useEffect(() => { useEffect(() => {
// Current page changed // Current page changed
if (open && currentPage > 1) { if (open && currentPage > 1) {
fetchResults({ replace: false }); fetchResults({ replace: false })
} else if (open && currentPage == 1) { } else if (open && currentPage == 1) {
fetchResults({ replace: true }); fetchResults({ replace: true })
} }
}, [currentPage]); }, [currentPage])
useEffect(() => { useEffect(() => {
// Filters changed // Filters changed
const key = `recent_${props.object}`; const key = `recent_${props.object}`
const cookie = getCookie(key); const cookie = getCookie(key)
const cookieObj: Weapon[] | Summon[] | Character[] = cookie const cookieObj: Weapon[] | Summon[] | Character[] = cookie
? JSON.parse(cookie as string) ? JSON.parse(cookie as string)
: []; : []
if (open) { if (open) {
if (firstLoad && cookieObj && cookieObj.length > 0) { if (firstLoad && cookieObj && cookieObj.length > 0) {
setResults(cookieObj); setResults(cookieObj)
setRecordCount(cookieObj.length); setRecordCount(cookieObj.length)
setFirstLoad(false); setFirstLoad(false)
} else { } else {
setCurrentPage(1); setCurrentPage(1)
fetchResults({ replace: true }); fetchResults({ replace: true })
} }
} }
}, [filters]); }, [filters])
useEffect(() => { useEffect(() => {
// Query changed // Query changed
if (open && query.length != 1) { if (open && query.length != 1) {
setCurrentPage(1); setCurrentPage(1)
fetchResults({ replace: true }); fetchResults({ replace: true })
} }
}, [query]); }, [query])
function renderResults() { function renderResults() {
let jsx; let jsx
switch (props.object) { switch (props.object) {
case "weapons": case 'weapons':
jsx = renderWeaponSearchResults(); jsx = renderWeaponSearchResults()
break; break
case "summons": case 'summons':
jsx = renderSummonSearchResults(results); jsx = renderSummonSearchResults(results)
break; break
case "characters": case 'characters':
jsx = renderCharacterSearchResults(results); jsx = renderCharacterSearchResults(results)
break; break
case "job_skills": case 'job_skills':
jsx = renderJobSkillSearchResults(results); jsx = renderJobSkillSearchResults(results)
break; break
} }
return ( return (
@ -216,13 +216,13 @@ const SearchModal = (props: Props) => {
> >
{jsx} {jsx}
</InfiniteScroll> </InfiniteScroll>
); )
} }
function renderWeaponSearchResults() { function renderWeaponSearchResults() {
let jsx: React.ReactNode; let jsx: React.ReactNode
const castResults: Weapon[] = results as Weapon[]; const castResults: Weapon[] = results as Weapon[]
if (castResults && Object.keys(castResults).length > 0) { if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: Weapon) => { jsx = castResults.map((result: Weapon) => {
return ( return (
@ -230,20 +230,20 @@ const SearchModal = (props: Props) => {
key={result.id} key={result.id}
data={result} data={result}
onClick={() => { onClick={() => {
storeRecentResult(result); storeRecentResult(result)
}} }}
/> />
); )
}); })
} }
return jsx; return jsx
} }
function renderSummonSearchResults(results: { [key: string]: any }) { function renderSummonSearchResults(results: { [key: string]: any }) {
let jsx: React.ReactNode; let jsx: React.ReactNode
const castResults: Summon[] = results as Summon[]; const castResults: Summon[] = results as Summon[]
if (castResults && Object.keys(castResults).length > 0) { if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: Summon) => { jsx = castResults.map((result: Summon) => {
return ( return (
@ -251,20 +251,20 @@ const SearchModal = (props: Props) => {
key={result.id} key={result.id}
data={result} data={result}
onClick={() => { onClick={() => {
storeRecentResult(result); storeRecentResult(result)
}} }}
/> />
); )
}); })
} }
return jsx; return jsx
} }
function renderCharacterSearchResults(results: { [key: string]: any }) { function renderCharacterSearchResults(results: { [key: string]: any }) {
let jsx: React.ReactNode; let jsx: React.ReactNode
const castResults: Character[] = results as Character[]; const castResults: Character[] = results as Character[]
if (castResults && Object.keys(castResults).length > 0) { if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: Character) => { jsx = castResults.map((result: Character) => {
return ( return (
@ -272,20 +272,20 @@ const SearchModal = (props: Props) => {
key={result.id} key={result.id}
data={result} data={result}
onClick={() => { onClick={() => {
storeRecentResult(result); storeRecentResult(result)
}} }}
/> />
); )
}); })
} }
return jsx; return jsx
} }
function renderJobSkillSearchResults(results: { [key: string]: any }) { function renderJobSkillSearchResults(results: { [key: string]: any }) {
let jsx: React.ReactNode; let jsx: React.ReactNode
const castResults: JobSkill[] = results as JobSkill[]; const castResults: JobSkill[] = results as JobSkill[]
if (castResults && Object.keys(castResults).length > 0) { if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: JobSkill) => { jsx = castResults.map((result: JobSkill) => {
return ( return (
@ -293,26 +293,26 @@ const SearchModal = (props: Props) => {
key={result.id} key={result.id}
data={result} data={result}
onClick={() => { onClick={() => {
storeRecentResult(result); storeRecentResult(result)
}} }}
/> />
); )
}); })
} }
return jsx; return jsx
} }
function openChange() { function openChange() {
if (open) { if (open) {
setQuery(""); setQuery('')
setFirstLoad(true); setFirstLoad(true)
setResults([]); setResults([])
setRecordCount(0); setRecordCount(0)
setCurrentPage(1); setCurrentPage(1)
setOpen(false); setOpen(false)
} else { } else {
setOpen(true); setOpen(true)
} }
} }
@ -340,39 +340,39 @@ const SearchModal = (props: Props) => {
<CrossIcon /> <CrossIcon />
</Dialog.Close> </Dialog.Close>
</div> </div>
{props.object === "characters" ? ( {props.object === 'characters' ? (
<CharacterSearchFilterBar sendFilters={receiveFilters} /> <CharacterSearchFilterBar sendFilters={receiveFilters} />
) : ( ) : (
"" ''
)} )}
{props.object === "weapons" ? ( {props.object === 'weapons' ? (
<WeaponSearchFilterBar sendFilters={receiveFilters} /> <WeaponSearchFilterBar sendFilters={receiveFilters} />
) : ( ) : (
"" ''
)} )}
{props.object === "summons" ? ( {props.object === 'summons' ? (
<SummonSearchFilterBar sendFilters={receiveFilters} /> <SummonSearchFilterBar sendFilters={receiveFilters} />
) : ( ) : (
"" ''
)} )}
{props.object === "job_skills" ? ( {props.object === 'job_skills' ? (
<JobSkillSearchFilterBar sendFilters={receiveFilters} /> <JobSkillSearchFilterBar sendFilters={receiveFilters} />
) : ( ) : (
"" ''
)} )}
</div> </div>
<div id="Results" ref={scrollContainer}> <div id="Results" ref={scrollContainer}>
<h5 className="total"> <h5 className="total">
{t("search.result_count", { record_count: recordCount })} {t('search.result_count', { record_count: recordCount })}
</h5> </h5>
{open ? renderResults() : ""} {open ? renderResults() : ''}
</div> </div>
</Dialog.Content> </Dialog.Content>
<Dialog.Overlay className="Overlay" /> <Dialog.Overlay className="Overlay" />
</Dialog.Portal> </Dialog.Portal>
</Dialog.Root> </Dialog.Root>
); )
}; }
export default SearchModal; export default SearchModal

View file

@ -1,13 +1,13 @@
import React from "react"; import React from 'react'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
groupName: string; groupName: string
name: string; name: string
selected: boolean; selected: boolean
children: string; children: string
onClick: (event: React.ChangeEvent<HTMLInputElement>) => void; onClick: (event: React.ChangeEvent<HTMLInputElement>) => void
} }
const Segment: React.FC<Props> = (props: Props) => { const Segment: React.FC<Props> = (props: Props) => {
@ -23,7 +23,7 @@ const Segment: React.FC<Props> = (props: Props) => {
/> />
<label htmlFor={props.name}>{props.children}</label> <label htmlFor={props.name}>{props.children}</label>
</div> </div>
); )
}; }
export default Segment; export default Segment

View file

@ -1,19 +1,19 @@
import React from "react"; import React from 'react'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
elementClass?: string; elementClass?: string
} }
const SegmentedControl: React.FC<Props> = ({ elementClass, children }) => { const SegmentedControl: React.FC<Props> = ({ elementClass, children }) => {
return ( return (
<div className="SegmentedControlWrapper"> <div className="SegmentedControlWrapper">
<div className={`SegmentedControl ${elementClass ? elementClass : ""}`}> <div className={`SegmentedControl ${elementClass ? elementClass : ''}`}>
{children} {children}
</div> </div>
</div> </div>
); )
}; }
export default SegmentedControl; export default SegmentedControl

View file

@ -1,64 +1,64 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import Link from "next/link"; import Link from 'next/link'
import { setCookie } from "cookies-next"; import { setCookie } from 'cookies-next'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { Trans, useTranslation } from "next-i18next"; import { Trans, useTranslation } from 'next-i18next'
import { AxiosResponse } from "axios"; import { AxiosResponse } from 'axios'
import * as Dialog from "@radix-ui/react-dialog"; import * as Dialog from '@radix-ui/react-dialog'
import api from "~utils/api"; import api from '~utils/api'
import { accountState } from "~utils/accountState"; import { accountState } from '~utils/accountState'
import Button from "~components/Button"; import Button from '~components/Button'
import Fieldset from "~components/Fieldset"; import Fieldset from '~components/Fieldset'
import CrossIcon from "~public/icons/Cross.svg"; import CrossIcon from '~public/icons/Cross.svg'
import "./index.scss"; import './index.scss'
interface Props {} interface Props {}
interface ErrorMap { interface ErrorMap {
[index: string]: string; [index: string]: string
username: string; username: string
email: string; email: string
password: string; password: string
passwordConfirmation: string; passwordConfirmation: string
} }
const emailRegex = const emailRegex =
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
const SignupModal = (props: Props) => { const SignupModal = (props: Props) => {
const router = useRouter(); const router = useRouter()
const { t } = useTranslation("common"); const { t } = useTranslation('common')
// Set up form states and error handling // Set up form states and error handling
const [formValid, setFormValid] = useState(false); const [formValid, setFormValid] = useState(false)
const [errors, setErrors] = useState<ErrorMap>({ const [errors, setErrors] = useState<ErrorMap>({
username: "", username: '',
email: "", email: '',
password: "", password: '',
passwordConfirmation: "", passwordConfirmation: '',
}); })
// States // States
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false)
// Set up form refs // Set up form refs
const usernameInput = React.createRef<HTMLInputElement>(); const usernameInput = React.createRef<HTMLInputElement>()
const emailInput = React.createRef<HTMLInputElement>(); const emailInput = React.createRef<HTMLInputElement>()
const passwordInput = React.createRef<HTMLInputElement>(); const passwordInput = React.createRef<HTMLInputElement>()
const passwordConfirmationInput = React.createRef<HTMLInputElement>(); const passwordConfirmationInput = React.createRef<HTMLInputElement>()
const form = [ const form = [
usernameInput, usernameInput,
emailInput, emailInput,
passwordInput, passwordInput,
passwordConfirmationInput, passwordConfirmationInput,
]; ]
function register(event: React.FormEvent) { function register(event: React.FormEvent) {
event.preventDefault(); event.preventDefault()
const body = { const body = {
user: { user: {
@ -68,47 +68,47 @@ const SignupModal = (props: Props) => {
password_confirmation: passwordConfirmationInput.current?.value, password_confirmation: passwordConfirmationInput.current?.value,
language: router.locale, language: router.locale,
}, },
}; }
if (formValid) if (formValid)
api.endpoints.users api.endpoints.users
.create(body) .create(body)
.then((response) => { .then((response) => {
storeCookieInfo(response); storeCookieInfo(response)
return response.data.user.user_id; return response.data.user.user_id
}) })
.then((id) => fetchUserInfo(id)) .then((id) => fetchUserInfo(id))
.then((infoResponse) => storeUserInfo(infoResponse)); .then((infoResponse) => storeUserInfo(infoResponse))
} }
function storeCookieInfo(response: AxiosResponse) { function storeCookieInfo(response: AxiosResponse) {
const user = response.data.user; const user = response.data.user
const cookieObj: AccountCookie = { const cookieObj: AccountCookie = {
userId: user.user_id, userId: user.user_id,
username: user.username, username: user.username,
token: user.token, token: user.token,
}; }
setCookie("account", cookieObj, { path: "/" }); setCookie('account', cookieObj, { path: '/' })
} }
function fetchUserInfo(id: string) { function fetchUserInfo(id: string) {
return api.userInfo(id); return api.userInfo(id)
} }
function storeUserInfo(response: AxiosResponse) { function storeUserInfo(response: AxiosResponse) {
const user = response.data.user; const user = response.data.user
const cookieObj: UserCookie = { const cookieObj: UserCookie = {
picture: user.picture.picture, picture: user.picture.picture,
element: user.picture.element, element: user.picture.element,
language: user.language, language: user.language,
gender: user.gender, gender: user.gender,
}; }
// TODO: Set language // TODO: Set language
setCookie("user", cookieObj, { path: "/" }); setCookie('user', cookieObj, { path: '/' })
accountState.account.user = { accountState.account.user = {
id: user.id, id: user.id,
@ -116,29 +116,29 @@ const SignupModal = (props: Props) => {
picture: user.picture.picture, picture: user.picture.picture,
element: user.picture.element, element: user.picture.element,
gender: user.gender, gender: user.gender,
}; }
accountState.account.authorized = true; accountState.account.authorized = true
setOpen(false); setOpen(false)
} }
function handleNameChange(event: React.ChangeEvent<HTMLInputElement>) { function handleNameChange(event: React.ChangeEvent<HTMLInputElement>) {
event.preventDefault(); event.preventDefault()
const fieldName = event.target.name; const fieldName = event.target.name
const value = event.target.value; const value = event.target.value
if (value.length >= 3) { if (value.length >= 3) {
api.check(fieldName, value).then( api.check(fieldName, value).then(
(response) => { (response) => {
processNameCheck(fieldName, value, response.data.available); processNameCheck(fieldName, value, response.data.available)
}, },
(error) => { (error) => {
console.error(error); console.error(error)
} }
); )
} else { } else {
validateName(fieldName, value); validateName(fieldName, value)
} }
} }
@ -147,114 +147,114 @@ const SignupModal = (props: Props) => {
value: string, value: string,
available: boolean available: boolean
) { ) {
const newErrors = { ...errors }; const newErrors = { ...errors }
if (available) { if (available) {
// Continue checking for errors // Continue checking for errors
newErrors[fieldName] = ""; newErrors[fieldName] = ''
setErrors(newErrors); setErrors(newErrors)
setFormValid(true); setFormValid(true)
validateName(fieldName, value); validateName(fieldName, value)
} else { } else {
newErrors[fieldName] = t("modals.signup.errors.field_in_use", { newErrors[fieldName] = t('modals.signup.errors.field_in_use', {
field: fieldName, field: fieldName,
}); })
setErrors(newErrors); setErrors(newErrors)
setFormValid(false); setFormValid(false)
} }
} }
function validateName(fieldName: string, value: string) { function validateName(fieldName: string, value: string) {
let newErrors = { ...errors }; let newErrors = { ...errors }
switch (fieldName) { switch (fieldName) {
case "username": case 'username':
if (value.length < 3) if (value.length < 3)
newErrors.username = t("modals.signup.errors.username_too_short"); newErrors.username = t('modals.signup.errors.username_too_short')
else if (value.length > 20) else if (value.length > 20)
newErrors.username = t("modals.signup.errors.username_too_long"); newErrors.username = t('modals.signup.errors.username_too_long')
else newErrors.username = ""; else newErrors.username = ''
break; break
case "email": case 'email':
newErrors.email = emailRegex.test(value) newErrors.email = emailRegex.test(value)
? "" ? ''
: t("modals.signup.errors.invalid_email"); : t('modals.signup.errors.invalid_email')
break; break
default: default:
break; break
} }
setFormValid(validateForm(newErrors)); setFormValid(validateForm(newErrors))
} }
function handlePasswordChange(event: React.ChangeEvent<HTMLInputElement>) { function handlePasswordChange(event: React.ChangeEvent<HTMLInputElement>) {
event.preventDefault(); event.preventDefault()
const { name, value } = event.target; const { name, value } = event.target
let newErrors = { ...errors }; let newErrors = { ...errors }
switch (name) { switch (name) {
case "password": case 'password':
newErrors.password = passwordInput.current?.value.includes( newErrors.password = passwordInput.current?.value.includes(
usernameInput.current?.value! usernameInput.current?.value!
) )
? t("modals.signup.errors.password_contains_username") ? t('modals.signup.errors.password_contains_username')
: ""; : ''
break; break
case "password": case 'password':
newErrors.password = newErrors.password =
value.length < 8 ? t("modals.signup.errors.password_too_short") : ""; value.length < 8 ? t('modals.signup.errors.password_too_short') : ''
break; break
case "confirm_password": case 'confirm_password':
newErrors.passwordConfirmation = newErrors.passwordConfirmation =
passwordInput.current?.value === passwordInput.current?.value ===
passwordConfirmationInput.current?.value passwordConfirmationInput.current?.value
? "" ? ''
: t("modals.signup.errors.passwords_dont_match"); : t('modals.signup.errors.passwords_dont_match')
break; break
default: default:
break; break
} }
setFormValid(validateForm(newErrors)); setFormValid(validateForm(newErrors))
} }
function validateForm(errors: ErrorMap) { function validateForm(errors: ErrorMap) {
let valid = true; let valid = true
Object.values(form).forEach( Object.values(form).forEach(
(input) => input.current?.value.length == 0 && (valid = false) (input) => input.current?.value.length == 0 && (valid = false)
); )
Object.values(errors).forEach( Object.values(errors).forEach(
(error) => error.length > 0 && (valid = false) (error) => error.length > 0 && (valid = false)
); )
return valid; return valid
} }
function openChange(open: boolean) { function openChange(open: boolean) {
setOpen(open); setOpen(open)
setErrors({ setErrors({
username: "", username: '',
email: "", email: '',
password: "", password: '',
passwordConfirmation: "", passwordConfirmation: '',
}); })
} }
return ( return (
<Dialog.Root open={open} onOpenChange={openChange}> <Dialog.Root open={open} onOpenChange={openChange}>
<Dialog.Trigger asChild> <Dialog.Trigger asChild>
<li className="MenuItem"> <li className="MenuItem">
<span>{t("menu.signup")}</span> <span>{t('menu.signup')}</span>
</li> </li>
</Dialog.Trigger> </Dialog.Trigger>
<Dialog.Portal> <Dialog.Portal>
@ -264,7 +264,7 @@ const SignupModal = (props: Props) => {
> >
<div className="DialogHeader"> <div className="DialogHeader">
<Dialog.Title className="DialogTitle"> <Dialog.Title className="DialogTitle">
{t("modals.signup.title")} {t('modals.signup.title')}
</Dialog.Title> </Dialog.Title>
<Dialog.Close className="DialogClose" asChild> <Dialog.Close className="DialogClose" asChild>
<span> <span>
@ -276,7 +276,7 @@ const SignupModal = (props: Props) => {
<form className="form" onSubmit={register}> <form className="form" onSubmit={register}>
<Fieldset <Fieldset
fieldName="username" fieldName="username"
placeholder={t("modals.signup.placeholders.username")} placeholder={t('modals.signup.placeholders.username')}
onChange={handleNameChange} onChange={handleNameChange}
error={errors.username} error={errors.username}
ref={usernameInput} ref={usernameInput}
@ -284,7 +284,7 @@ const SignupModal = (props: Props) => {
<Fieldset <Fieldset
fieldName="email" fieldName="email"
placeholder={t("modals.signup.placeholders.email")} placeholder={t('modals.signup.placeholders.email')}
onChange={handleNameChange} onChange={handleNameChange}
error={errors.email} error={errors.email}
ref={emailInput} ref={emailInput}
@ -292,7 +292,7 @@ const SignupModal = (props: Props) => {
<Fieldset <Fieldset
fieldName="password" fieldName="password"
placeholder={t("modals.signup.placeholders.password")} placeholder={t('modals.signup.placeholders.password')}
onChange={handlePasswordChange} onChange={handlePasswordChange}
error={errors.password} error={errors.password}
ref={passwordInput} ref={passwordInput}
@ -300,13 +300,13 @@ const SignupModal = (props: Props) => {
<Fieldset <Fieldset
fieldName="confirm_password" fieldName="confirm_password"
placeholder={t("modals.signup.placeholders.password_confirm")} placeholder={t('modals.signup.placeholders.password_confirm')}
onChange={handlePasswordChange} onChange={handlePasswordChange}
error={errors.passwordConfirmation} error={errors.passwordConfirmation}
ref={passwordConfirmationInput} ref={passwordConfirmationInput}
/> />
<Button>{t("modals.signup.buttons.confirm")}</Button> <Button>{t('modals.signup.buttons.confirm')}</Button>
<Dialog.Description className="terms"> <Dialog.Description className="terms">
{/* <Trans i18nKey="modals.signup.agreement"> {/* <Trans i18nKey="modals.signup.agreement">
@ -318,7 +318,7 @@ const SignupModal = (props: Props) => {
<Dialog.Overlay className="Overlay" /> <Dialog.Overlay className="Overlay" />
</Dialog.Portal> </Dialog.Portal>
</Dialog.Root> </Dialog.Root>
); )
}; }
export default SignupModal; export default SignupModal

View file

@ -1,53 +1,53 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { getCookie } from "cookies-next"; import { getCookie } from 'cookies-next'
import { useSnapshot } from "valtio"; import { useSnapshot } from 'valtio'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import { AxiosResponse } from "axios"; import { AxiosResponse } from 'axios'
import debounce from "lodash.debounce"; import debounce from 'lodash.debounce'
import SummonUnit from "~components/SummonUnit"; import SummonUnit from '~components/SummonUnit'
import ExtraSummons from "~components/ExtraSummons"; import ExtraSummons from '~components/ExtraSummons'
import api from "~utils/api"; import api from '~utils/api'
import { appState } from "~utils/appState"; import { appState } from '~utils/appState'
import type { SearchableObject } from "~types"; import type { SearchableObject } from '~types'
import "./index.scss"; import './index.scss'
// Props // Props
interface Props { interface Props {
new: boolean; new: boolean
summons?: GridSummon[]; summons?: GridSummon[]
createParty: () => Promise<AxiosResponse<any, any>>; createParty: () => Promise<AxiosResponse<any, any>>
pushHistory?: (path: string) => void; pushHistory?: (path: string) => void
} }
const SummonGrid = (props: Props) => { const SummonGrid = (props: Props) => {
// Constants // Constants
const numSummons: number = 4; const numSummons: number = 4
// Cookies // Cookies
const cookie = getCookie("account"); const cookie = getCookie('account')
const accountData: AccountCookie = cookie const accountData: AccountCookie = cookie
? JSON.parse(cookie as string) ? JSON.parse(cookie as string)
: null; : null
const headers = accountData const headers = accountData
? { headers: { Authorization: `Bearer ${accountData.token}` } } ? { headers: { Authorization: `Bearer ${accountData.token}` } }
: {}; : {}
// Localization // Localization
const { t } = useTranslation("common"); const { t } = useTranslation('common')
// Set up state for view management // Set up state for view management
const { party, grid } = useSnapshot(appState); const { party, grid } = useSnapshot(appState)
const [slug, setSlug] = useState(); const [slug, setSlug] = useState()
// Create a temporary state to store previous weapon uncap value // Create a temporary state to store previous weapon uncap value
const [previousUncapValues, setPreviousUncapValues] = useState<{ const [previousUncapValues, setPreviousUncapValues] = useState<{
[key: number]: number; [key: number]: number
}>({}); }>({})
// Set the editable flag only on first load // Set the editable flag only on first load
useEffect(() => { useEffect(() => {
@ -56,61 +56,61 @@ const SummonGrid = (props: Props) => {
(accountData && party.user && accountData.userId === party.user.id) || (accountData && party.user && accountData.userId === party.user.id) ||
props.new props.new
) )
appState.party.editable = true; appState.party.editable = true
else appState.party.editable = false; else appState.party.editable = false
}, [props.new, accountData, party]); }, [props.new, accountData, party])
// Initialize an array of current uncap values for each summon // Initialize an array of current uncap values for each summon
useEffect(() => { useEffect(() => {
let initialPreviousUncapValues: { [key: number]: number } = {}; let initialPreviousUncapValues: { [key: number]: number } = {}
if (appState.grid.summons.mainSummon) if (appState.grid.summons.mainSummon)
initialPreviousUncapValues[-1] = initialPreviousUncapValues[-1] =
appState.grid.summons.mainSummon.uncap_level; appState.grid.summons.mainSummon.uncap_level
if (appState.grid.summons.friendSummon) if (appState.grid.summons.friendSummon)
initialPreviousUncapValues[6] = initialPreviousUncapValues[6] =
appState.grid.summons.friendSummon.uncap_level; appState.grid.summons.friendSummon.uncap_level
Object.values(appState.grid.summons.allSummons).map((o) => Object.values(appState.grid.summons.allSummons).map((o) =>
o ? (initialPreviousUncapValues[o.position] = o.uncap_level) : 0 o ? (initialPreviousUncapValues[o.position] = o.uncap_level) : 0
); )
setPreviousUncapValues(initialPreviousUncapValues); setPreviousUncapValues(initialPreviousUncapValues)
}, [ }, [
appState.grid.summons.mainSummon, appState.grid.summons.mainSummon,
appState.grid.summons.friendSummon, appState.grid.summons.friendSummon,
appState.grid.summons.allSummons, appState.grid.summons.allSummons,
]); ])
// Methods: Adding an object from search // Methods: Adding an object from search
function receiveSummonFromSearch(object: SearchableObject, position: number) { function receiveSummonFromSearch(object: SearchableObject, position: number) {
const summon = object as Summon; const summon = object as Summon
if (!party.id) { if (!party.id) {
props.createParty().then((response) => { props.createParty().then((response) => {
const party = response.data.party; const party = response.data.party
appState.party.id = party.id; appState.party.id = party.id
setSlug(party.shortcode); setSlug(party.shortcode)
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`); if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`)
saveSummon(party.id, summon, position).then((response) => saveSummon(party.id, summon, position).then((response) =>
storeGridSummon(response.data.grid_summon) storeGridSummon(response.data.grid_summon)
); )
}); })
} else { } else {
if (party.editable) if (party.editable)
saveSummon(party.id, summon, position).then((response) => saveSummon(party.id, summon, position).then((response) =>
storeGridSummon(response.data.grid_summon) storeGridSummon(response.data.grid_summon)
); )
} }
} }
async function saveSummon(partyId: string, summon: Summon, position: number) { async function saveSummon(partyId: string, summon: Summon, position: number) {
let uncapLevel = 3; let uncapLevel = 3
if (summon.uncap.ulb) uncapLevel = 5; if (summon.uncap.ulb) uncapLevel = 5
else if (summon.uncap.flb) uncapLevel = 4; else if (summon.uncap.flb) uncapLevel = 4
return await api.endpoints.summons.create( return await api.endpoints.summons.create(
{ {
@ -124,37 +124,36 @@ const SummonGrid = (props: Props) => {
}, },
}, },
headers headers
); )
} }
function storeGridSummon(gridSummon: GridSummon) { function storeGridSummon(gridSummon: GridSummon) {
if (gridSummon.position == -1) if (gridSummon.position == -1) appState.grid.summons.mainSummon = gridSummon
appState.grid.summons.mainSummon = gridSummon;
else if (gridSummon.position == 6) else if (gridSummon.position == 6)
appState.grid.summons.friendSummon = gridSummon; appState.grid.summons.friendSummon = gridSummon
else appState.grid.summons.allSummons[gridSummon.position] = gridSummon; else appState.grid.summons.allSummons[gridSummon.position] = gridSummon
} }
// Methods: Updating uncap level // Methods: Updating uncap level
// Note: Saves, but debouncing is not working properly // Note: Saves, but debouncing is not working properly
async function saveUncap(id: string, position: number, uncapLevel: number) { async function saveUncap(id: string, position: number, uncapLevel: number) {
storePreviousUncapValue(position); storePreviousUncapValue(position)
try { try {
if (uncapLevel != previousUncapValues[position]) if (uncapLevel != previousUncapValues[position])
await api.updateUncap("summon", id, uncapLevel).then((response) => { await api.updateUncap('summon', id, uncapLevel).then((response) => {
storeGridSummon(response.data.grid_summon); storeGridSummon(response.data.grid_summon)
}); })
} catch (error) { } catch (error) {
console.error(error); console.error(error)
// Revert optimistic UI // Revert optimistic UI
updateUncapLevel(position, previousUncapValues[position]); updateUncapLevel(position, previousUncapValues[position])
// Remove optimistic key // Remove optimistic key
let newPreviousValues = { ...previousUncapValues }; let newPreviousValues = { ...previousUncapValues }
delete newPreviousValues[position]; delete newPreviousValues[position]
setPreviousUncapValues(newPreviousValues); setPreviousUncapValues(newPreviousValues)
} }
} }
@ -163,63 +162,62 @@ const SummonGrid = (props: Props) => {
position: number, position: number,
uncapLevel: number uncapLevel: number
) { ) {
memoizeAction(id, position, uncapLevel); memoizeAction(id, position, uncapLevel)
// Optimistically update UI // Optimistically update UI
updateUncapLevel(position, uncapLevel); updateUncapLevel(position, uncapLevel)
} }
const memoizeAction = useCallback( const memoizeAction = useCallback(
(id: string, position: number, uncapLevel: number) => { (id: string, position: number, uncapLevel: number) => {
debouncedAction(id, position, uncapLevel); debouncedAction(id, position, uncapLevel)
}, },
[props, previousUncapValues] [props, previousUncapValues]
); )
const debouncedAction = useMemo( const debouncedAction = useMemo(
() => () =>
debounce((id, position, number) => { debounce((id, position, number) => {
saveUncap(id, position, number); saveUncap(id, position, number)
}, 500), }, 500),
[props, saveUncap] [props, saveUncap]
); )
const updateUncapLevel = (position: number, uncapLevel: number) => { const updateUncapLevel = (position: number, uncapLevel: number) => {
if (appState.grid.summons.mainSummon && position == -1) if (appState.grid.summons.mainSummon && position == -1)
appState.grid.summons.mainSummon.uncap_level = uncapLevel; appState.grid.summons.mainSummon.uncap_level = uncapLevel
else if (appState.grid.summons.friendSummon && position == 6) else if (appState.grid.summons.friendSummon && position == 6)
appState.grid.summons.friendSummon.uncap_level = uncapLevel; appState.grid.summons.friendSummon.uncap_level = uncapLevel
else { else {
const summon = appState.grid.summons.allSummons[position]; const summon = appState.grid.summons.allSummons[position]
if (summon) { if (summon) {
summon.uncap_level = uncapLevel; summon.uncap_level = uncapLevel
appState.grid.summons.allSummons[position] = summon; appState.grid.summons.allSummons[position] = summon
}
} }
} }
};
function storePreviousUncapValue(position: number) { function storePreviousUncapValue(position: number) {
// Save the current value in case of an unexpected result // Save the current value in case of an unexpected result
let newPreviousValues = { ...previousUncapValues }; let newPreviousValues = { ...previousUncapValues }
if (appState.grid.summons.mainSummon && position == -1) if (appState.grid.summons.mainSummon && position == -1)
newPreviousValues[position] = newPreviousValues[position] = appState.grid.summons.mainSummon.uncap_level
appState.grid.summons.mainSummon.uncap_level;
else if (appState.grid.summons.friendSummon && position == 6) else if (appState.grid.summons.friendSummon && position == 6)
newPreviousValues[position] = newPreviousValues[position] =
appState.grid.summons.friendSummon.uncap_level; appState.grid.summons.friendSummon.uncap_level
else { else {
const summon = appState.grid.summons.allSummons[position]; const summon = appState.grid.summons.allSummons[position]
newPreviousValues[position] = summon ? summon.uncap_level : 0; newPreviousValues[position] = summon ? summon.uncap_level : 0
} }
setPreviousUncapValues(newPreviousValues); setPreviousUncapValues(newPreviousValues)
} }
// Render: JSX components // Render: JSX components
const mainSummonElement = ( const mainSummonElement = (
<div className="LabeledUnit"> <div className="LabeledUnit">
<div className="Label">{t("summons.main")}</div> <div className="Label">{t('summons.main')}</div>
<SummonUnit <SummonUnit
gridSummon={grid.summons.mainSummon} gridSummon={grid.summons.mainSummon}
editable={party.editable} editable={party.editable}
@ -230,11 +228,11 @@ const SummonGrid = (props: Props) => {
updateUncap={initiateUncapUpdate} updateUncap={initiateUncapUpdate}
/> />
</div> </div>
); )
const friendSummonElement = ( const friendSummonElement = (
<div className="LabeledUnit"> <div className="LabeledUnit">
<div className="Label">{t("summons.friend")}</div> <div className="Label">{t('summons.friend')}</div>
<SummonUnit <SummonUnit
gridSummon={grid.summons.friendSummon} gridSummon={grid.summons.friendSummon}
editable={party.editable} editable={party.editable}
@ -245,10 +243,10 @@ const SummonGrid = (props: Props) => {
updateUncap={initiateUncapUpdate} updateUncap={initiateUncapUpdate}
/> />
</div> </div>
); )
const summonGridElement = ( const summonGridElement = (
<div id="LabeledGrid"> <div id="LabeledGrid">
<div className="Label">{t("summons.summons")}</div> <div className="Label">{t('summons.summons')}</div>
<ul id="grid_summons"> <ul id="grid_summons">
{Array.from(Array(numSummons)).map((x, i) => { {Array.from(Array(numSummons)).map((x, i) => {
return ( return (
@ -262,11 +260,11 @@ const SummonGrid = (props: Props) => {
updateUncap={initiateUncapUpdate} updateUncap={initiateUncapUpdate}
/> />
</li> </li>
); )
})} })}
</ul> </ul>
</div> </div>
); )
const subAuraSummonElement = ( const subAuraSummonElement = (
<ExtraSummons <ExtraSummons
grid={grid.summons.allSummons} grid={grid.summons.allSummons}
@ -276,7 +274,7 @@ const SummonGrid = (props: Props) => {
updateObject={receiveSummonFromSearch} updateObject={receiveSummonFromSearch}
updateUncap={initiateUncapUpdate} updateUncap={initiateUncapUpdate}
/> />
); )
return ( return (
<div> <div>
<div id="SummonGrid"> <div id="SummonGrid">
@ -287,7 +285,7 @@ const SummonGrid = (props: Props) => {
{subAuraSummonElement} {subAuraSummonElement}
</div> </div>
); )
}; }
export default SummonGrid; export default SummonGrid

View file

@ -1,64 +1,62 @@
import React from "react"; import React from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import * as HoverCard from "@radix-ui/react-hover-card"; import * as HoverCard from '@radix-ui/react-hover-card'
import WeaponLabelIcon from "~components/WeaponLabelIcon"; import WeaponLabelIcon from '~components/WeaponLabelIcon'
import UncapIndicator from "~components/UncapIndicator"; import UncapIndicator from '~components/UncapIndicator'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
gridSummon: GridSummon; gridSummon: GridSummon
children: React.ReactNode; children: React.ReactNode
} }
const SummonHovercard = (props: Props) => { const SummonHovercard = (props: Props) => {
const router = useRouter(); const router = useRouter()
const { t } = useTranslation("common"); const { t } = useTranslation('common')
const locale = const locale =
router.locale && ["en", "ja"].includes(router.locale) router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
? router.locale
: "en";
const Element = ["null", "wind", "fire", "water", "earth", "dark", "light"]; const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
const tintElement = Element[props.gridSummon.object.element]; const tintElement = Element[props.gridSummon.object.element]
const wikiUrl = `https://gbf.wiki/${props.gridSummon.object.name.en.replaceAll( const wikiUrl = `https://gbf.wiki/${props.gridSummon.object.name.en.replaceAll(
" ", ' ',
"_" '_'
)}`; )}`
function summonImage() { function summonImage() {
let imgSrc = ""; let imgSrc = ''
if (props.gridSummon) { if (props.gridSummon) {
const summon = props.gridSummon.object; const summon = props.gridSummon.object
const upgradedSummons = [ const upgradedSummons = [
"2040094000", '2040094000',
"2040100000", '2040100000',
"2040080000", '2040080000',
"2040098000", '2040098000',
"2040090000", '2040090000',
"2040084000", '2040084000',
"2040003000", '2040003000',
"2040056000", '2040056000',
]; ]
let suffix = ""; let suffix = ''
if ( if (
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 && upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 &&
props.gridSummon.uncap_level == 5 props.gridSummon.uncap_level == 5
) )
suffix = "_02"; suffix = '_02'
// Generate the correct source for the summon // Generate the correct source for the summon
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}${suffix}.jpg`; imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}${suffix}.jpg`
} }
return imgSrc; return imgSrc
} }
return ( return (
@ -88,12 +86,12 @@ const SummonHovercard = (props: Props) => {
</div> </div>
</div> </div>
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new"> <a className={`Button ${tintElement}`} href={wikiUrl} target="_new">
{t("buttons.wiki")} {t('buttons.wiki')}
</a> </a>
<HoverCard.Arrow /> <HoverCard.Arrow />
</HoverCard.Content> </HoverCard.Content>
</HoverCard.Root> </HoverCard.Root>
); )
}; }
export default SummonHovercard; export default SummonHovercard

View file

@ -1,26 +1,24 @@
import React from "react"; import React from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import UncapIndicator from "~components/UncapIndicator"; import UncapIndicator from '~components/UncapIndicator'
import WeaponLabelIcon from "~components/WeaponLabelIcon"; import WeaponLabelIcon from '~components/WeaponLabelIcon'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
data: Summon; data: Summon
onClick: () => void; onClick: () => void
} }
const Element = ["null", "wind", "fire", "water", "earth", "dark", "light"]; const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
const SummonResult = (props: Props) => { const SummonResult = (props: Props) => {
const router = useRouter(); const router = useRouter()
const locale = const locale =
router.locale && ["en", "ja"].includes(router.locale) router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
? router.locale
: "en";
const summon = props.data; const summon = props.data
return ( return (
<li className="SummonResult" onClick={props.onClick}> <li className="SummonResult" onClick={props.onClick}>
@ -41,7 +39,7 @@ const SummonResult = (props: Props) => {
</div> </div>
</div> </div>
</li> </li>
); )
}; }
export default SummonResult; export default SummonResult

View file

@ -1,81 +1,81 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import cloneDeep from "lodash.clonedeep"; import cloneDeep from 'lodash.clonedeep'
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import SearchFilter from "~components/SearchFilter"; import SearchFilter from '~components/SearchFilter'
import SearchFilterCheckboxItem from "~components/SearchFilterCheckboxItem"; import SearchFilterCheckboxItem from '~components/SearchFilterCheckboxItem'
import "./index.scss"; import './index.scss'
import { emptyElementState, emptyRarityState } from "~utils/emptyStates"; import { emptyElementState, emptyRarityState } from '~utils/emptyStates'
import { elements, rarities } from "~utils/stateValues"; import { elements, rarities } from '~utils/stateValues'
interface Props { interface Props {
sendFilters: (filters: { [key: string]: number[] }) => void; sendFilters: (filters: { [key: string]: number[] }) => void
} }
const SummonSearchFilterBar = (props: Props) => { const SummonSearchFilterBar = (props: Props) => {
const { t } = useTranslation("common"); const { t } = useTranslation('common')
const [rarityMenu, setRarityMenu] = useState(false); const [rarityMenu, setRarityMenu] = useState(false)
const [elementMenu, setElementMenu] = useState(false); const [elementMenu, setElementMenu] = useState(false)
const [rarityState, setRarityState] = useState<RarityState>(emptyRarityState); const [rarityState, setRarityState] = useState<RarityState>(emptyRarityState)
const [elementState, setElementState] = const [elementState, setElementState] =
useState<ElementState>(emptyElementState); useState<ElementState>(emptyElementState)
function rarityMenuOpened(open: boolean) { function rarityMenuOpened(open: boolean) {
if (open) { if (open) {
setRarityMenu(true); setRarityMenu(true)
setElementMenu(false); setElementMenu(false)
} else setRarityMenu(false); } else setRarityMenu(false)
} }
function elementMenuOpened(open: boolean) { function elementMenuOpened(open: boolean) {
if (open) { if (open) {
setRarityMenu(false); setRarityMenu(false)
setElementMenu(true); setElementMenu(true)
} else setElementMenu(false); } else setElementMenu(false)
} }
function handleRarityChange(checked: boolean, key: string) { function handleRarityChange(checked: boolean, key: string) {
let newRarityState = cloneDeep(rarityState); let newRarityState = cloneDeep(rarityState)
newRarityState[key].checked = checked; newRarityState[key].checked = checked
setRarityState(newRarityState); setRarityState(newRarityState)
} }
function handleElementChange(checked: boolean, key: string) { function handleElementChange(checked: boolean, key: string) {
let newElementState = cloneDeep(elementState); let newElementState = cloneDeep(elementState)
newElementState[key].checked = checked; newElementState[key].checked = checked
setElementState(newElementState); setElementState(newElementState)
} }
function sendFilters() { function sendFilters() {
const checkedRarityFilters = Object.values(rarityState) const checkedRarityFilters = Object.values(rarityState)
.filter((x) => x.checked) .filter((x) => x.checked)
.map((x, i) => x.id); .map((x, i) => x.id)
const checkedElementFilters = Object.values(elementState) const checkedElementFilters = Object.values(elementState)
.filter((x) => x.checked) .filter((x) => x.checked)
.map((x, i) => x.id); .map((x, i) => x.id)
const filters = { const filters = {
rarity: checkedRarityFilters, rarity: checkedRarityFilters,
element: checkedElementFilters, element: checkedElementFilters,
}; }
props.sendFilters(filters); props.sendFilters(filters)
} }
useEffect(() => { useEffect(() => {
sendFilters(); sendFilters()
}, [rarityState, elementState]); }, [rarityState, elementState])
return ( return (
<div className="SearchFilterBar"> <div className="SearchFilterBar">
<SearchFilter <SearchFilter
label={t("filters.labels.rarity")} label={t('filters.labels.rarity')}
numSelected={ numSelected={
Object.values(rarityState) Object.values(rarityState)
.map((x) => x.checked) .map((x) => x.checked)
@ -85,7 +85,7 @@ const SummonSearchFilterBar = (props: Props) => {
onOpenChange={rarityMenuOpened} onOpenChange={rarityMenuOpened}
> >
<DropdownMenu.Label className="Label"> <DropdownMenu.Label className="Label">
{t("filters.labels.rarity")} {t('filters.labels.rarity')}
</DropdownMenu.Label> </DropdownMenu.Label>
{Array.from(Array(rarities.length)).map((x, i) => { {Array.from(Array(rarities.length)).map((x, i) => {
return ( return (
@ -97,12 +97,12 @@ const SummonSearchFilterBar = (props: Props) => {
> >
{t(`rarities.${rarities[i]}`)} {t(`rarities.${rarities[i]}`)}
</SearchFilterCheckboxItem> </SearchFilterCheckboxItem>
); )
})} })}
</SearchFilter> </SearchFilter>
<SearchFilter <SearchFilter
label={t("filters.labels.element")} label={t('filters.labels.element')}
numSelected={ numSelected={
Object.values(elementState) Object.values(elementState)
.map((x) => x.checked) .map((x) => x.checked)
@ -112,7 +112,7 @@ const SummonSearchFilterBar = (props: Props) => {
onOpenChange={elementMenuOpened} onOpenChange={elementMenuOpened}
> >
<DropdownMenu.Label className="Label"> <DropdownMenu.Label className="Label">
{t("filters.labels.element")} {t('filters.labels.element')}
</DropdownMenu.Label> </DropdownMenu.Label>
{Array.from(Array(elements.length)).map((x, i) => { {Array.from(Array(elements.length)).map((x, i) => {
return ( return (
@ -124,11 +124,11 @@ const SummonSearchFilterBar = (props: Props) => {
> >
{t(`elements.${elements[i]}`)} {t(`elements.${elements[i]}`)}
</SearchFilterCheckboxItem> </SearchFilterCheckboxItem>
); )
})} })}
</SearchFilter> </SearchFilter>
</div> </div>
); )
}; }
export default SummonSearchFilterBar; export default SummonSearchFilterBar

View file

@ -1,36 +1,34 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import classnames from "classnames"; import classnames from 'classnames'
import SearchModal from "~components/SearchModal"; import SearchModal from '~components/SearchModal'
import SummonHovercard from "~components/SummonHovercard"; import SummonHovercard from '~components/SummonHovercard'
import UncapIndicator from "~components/UncapIndicator"; import UncapIndicator from '~components/UncapIndicator'
import PlusIcon from "~public/icons/Add.svg"; import PlusIcon from '~public/icons/Add.svg'
import type { SearchableObject } from "~types"; import type { SearchableObject } from '~types'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
gridSummon: GridSummon | undefined; gridSummon: GridSummon | undefined
unitType: 0 | 1 | 2; unitType: 0 | 1 | 2
position: number; position: number
editable: boolean; editable: boolean
updateObject: (object: SearchableObject, position: number) => void; updateObject: (object: SearchableObject, position: number) => void
updateUncap: (id: string, position: number, uncap: number) => void; updateUncap: (id: string, position: number, uncap: number) => void
} }
const SummonUnit = (props: Props) => { const SummonUnit = (props: Props) => {
const { t } = useTranslation("common"); const { t } = useTranslation('common')
const [imageUrl, setImageUrl] = useState(""); const [imageUrl, setImageUrl] = useState('')
const router = useRouter(); const router = useRouter()
const locale = const locale =
router.locale && ["en", "ja"].includes(router.locale) router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
? router.locale
: "en";
const classes = classnames({ const classes = classnames({
SummonUnit: true, SummonUnit: true,
@ -39,57 +37,57 @@ const SummonUnit = (props: Props) => {
friend: props.unitType == 2, friend: props.unitType == 2,
editable: props.editable, editable: props.editable,
filled: props.gridSummon !== undefined, filled: props.gridSummon !== undefined,
}); })
const gridSummon = props.gridSummon; const gridSummon = props.gridSummon
const summon = gridSummon?.object; const summon = gridSummon?.object
useEffect(() => { useEffect(() => {
generateImageUrl(); generateImageUrl()
}); })
function generateImageUrl() { function generateImageUrl() {
let imgSrc = ""; let imgSrc = ''
if (props.gridSummon) { if (props.gridSummon) {
const summon = props.gridSummon.object!; const summon = props.gridSummon.object!
const upgradedSummons = [ const upgradedSummons = [
"2040094000", '2040094000',
"2040100000", '2040100000',
"2040080000", '2040080000',
"2040098000", '2040098000',
"2040090000", '2040090000',
"2040084000", '2040084000',
"2040003000", '2040003000',
"2040056000", '2040056000',
"2040020000", '2040020000',
"2040034000", '2040034000',
"2040028000", '2040028000',
"2040027000", '2040027000',
"2040046000", '2040046000',
"2040047000", '2040047000',
]; ]
let suffix = ""; let suffix = ''
if ( if (
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 && upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 &&
props.gridSummon.uncap_level == 5 props.gridSummon.uncap_level == 5
) )
suffix = "_02"; suffix = '_02'
// Generate the correct source for the summon // Generate the correct source for the summon
if (props.unitType == 0 || props.unitType == 2) if (props.unitType == 0 || props.unitType == 2)
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-main/${summon.granblue_id}${suffix}.jpg`; imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-main/${summon.granblue_id}${suffix}.jpg`
else else
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}${suffix}.jpg`; imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}${suffix}.jpg`
} }
setImageUrl(imgSrc); setImageUrl(imgSrc)
} }
function passUncapData(uncap: number) { function passUncapData(uncap: number) {
if (props.gridSummon) if (props.gridSummon)
props.updateUncap(props.gridSummon.id, props.position, uncap); props.updateUncap(props.gridSummon.id, props.position, uncap)
} }
const image = ( const image = (
@ -100,21 +98,21 @@ const SummonUnit = (props: Props) => {
<PlusIcon /> <PlusIcon />
</span> </span>
) : ( ) : (
"" ''
)} )}
</div> </div>
); )
const editableImage = ( const editableImage = (
<SearchModal <SearchModal
placeholderText={t("search.placeholders.summon")} placeholderText={t('search.placeholders.summon')}
fromPosition={props.position} fromPosition={props.position}
object="summons" object="summons"
send={props.updateObject} send={props.updateObject}
> >
{image} {image}
</SearchModal> </SearchModal>
); )
const unitContent = ( const unitContent = (
<div className={classes}> <div className={classes}>
@ -129,17 +127,17 @@ const SummonUnit = (props: Props) => {
special={false} special={false}
/> />
) : ( ) : (
"" ''
)} )}
<h3 className="SummonName">{summon?.name[locale]}</h3> <h3 className="SummonName">{summon?.name[locale]}</h3>
</div> </div>
); )
const withHovercard = ( const withHovercard = (
<SummonHovercard gridSummon={gridSummon!}>{unitContent}</SummonHovercard> <SummonHovercard gridSummon={gridSummon!}>{unitContent}</SummonHovercard>
); )
return gridSummon && !props.editable ? withHovercard : unitContent; return gridSummon && !props.editable ? withHovercard : unitContent
}; }
export default SummonUnit; export default SummonUnit

View file

@ -1,6 +1,6 @@
.Fieldset textarea { .Fieldset textarea {
color: $grey-10; color: $grey-10;
font-family: system-ui, -apple-system, "Helvetica Neue", Helvetica, Arial, font-family: system-ui, -apple-system, 'Helvetica Neue', Helvetica, Arial,
sans-serif; sans-serif;
line-height: 21px; line-height: 21px;
} }

View file

@ -1,13 +1,13 @@
import React from "react"; import React from 'react'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
fieldName: string; fieldName: string
placeholder: string; placeholder: string
value?: string; value?: string
error: string; error: string
onBlur?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void; onBlur?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void; onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
} }
const TextFieldset = React.forwardRef<HTMLTextAreaElement, Props>( const TextFieldset = React.forwardRef<HTMLTextAreaElement, Props>(
@ -18,15 +18,15 @@ const TextFieldset = React.forwardRef<HTMLTextAreaElement, Props>(
className="Input" className="Input"
name={props.fieldName} name={props.fieldName}
placeholder={props.placeholder} placeholder={props.placeholder}
defaultValue={props.value || ""} defaultValue={props.value || ''}
onBlur={props.onBlur} onBlur={props.onBlur}
onChange={props.onChange} onChange={props.onChange}
ref={ref} ref={ref}
/> />
{props.error.length > 0 && <p className="InputError">{props.error}</p>} {props.error.length > 0 && <p className="InputError">{props.error}</p>}
</fieldset> </fieldset>
); )
} }
); )
export default TextFieldset; export default TextFieldset

View file

@ -1,12 +1,12 @@
import React from "react"; import React from 'react'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
name: string; name: string
checked: boolean; checked: boolean
editable: boolean; editable: boolean
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void; onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
} }
const ToggleSwitch: React.FC<Props> = (props: Props) => { const ToggleSwitch: React.FC<Props> = (props: Props) => {
@ -25,7 +25,7 @@ const ToggleSwitch: React.FC<Props> = (props: Props) => {
<span className="toggle-switch-switch" /> <span className="toggle-switch-switch" />
</label> </label>
</div> </div>
); )
}; }
export default ToggleSwitch; export default ToggleSwitch

View file

@ -1,103 +1,103 @@
import React from "react"; import React from 'react'
import { useSnapshot } from "valtio"; import { useSnapshot } from 'valtio'
import { getCookie, deleteCookie } from "cookies-next"; import { getCookie, deleteCookie } from 'cookies-next'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import clonedeep from "lodash.clonedeep"; import clonedeep from 'lodash.clonedeep'
import api from "~utils/api"; import api from '~utils/api'
import { accountState, initialAccountState } from "~utils/accountState"; import { accountState, initialAccountState } from '~utils/accountState'
import { appState, initialAppState } from "~utils/appState"; import { appState, initialAppState } from '~utils/appState'
import Header from "~components/Header"; import Header from '~components/Header'
import Button from "~components/Button"; import Button from '~components/Button'
import HeaderMenu from "~components/HeaderMenu"; import HeaderMenu from '~components/HeaderMenu'
const TopHeader = () => { const TopHeader = () => {
const { t } = useTranslation("common"); const { t } = useTranslation('common')
// Cookies // Cookies
const accountCookie = getCookie("account"); const accountCookie = getCookie('account')
const userCookie = getCookie("user"); const userCookie = getCookie('user')
const headers = {}; const headers = {}
// accountCookies.account != null // accountCookies.account != null
// ? { // ? {
// Authorization: `Bearer ${accountCookies.account.access_token}`, // Authorization: `Bearer ${accountCookies.account.access_token}`,
// } // }
// : {} // : {}
const { account } = useSnapshot(accountState); const { account } = useSnapshot(accountState)
const { party } = useSnapshot(appState); const { party } = useSnapshot(appState)
const router = useRouter(); const router = useRouter()
function copyToClipboard() { function copyToClipboard() {
const el = document.createElement("input"); const el = document.createElement('input')
el.value = window.location.href; el.value = window.location.href
el.id = "url-input"; el.id = 'url-input'
document.body.appendChild(el); document.body.appendChild(el)
el.select(); el.select()
document.execCommand("copy"); document.execCommand('copy')
el.remove(); el.remove()
} }
function newParty() { function newParty() {
// Push the root URL // Push the root URL
router.push("/"); router.push('/')
// Clean state // Clean state
const resetState = clonedeep(initialAppState); const resetState = clonedeep(initialAppState)
Object.keys(resetState).forEach((key) => { Object.keys(resetState).forEach((key) => {
appState[key] = resetState[key]; appState[key] = resetState[key]
}); })
// Set party to be editable // Set party to be editable
appState.party.editable = true; appState.party.editable = true
} }
function logout() { function logout() {
deleteCookie("account"); deleteCookie('account')
deleteCookie("user"); deleteCookie('user')
// Clean state // Clean state
const resetState = clonedeep(initialAccountState); const resetState = clonedeep(initialAccountState)
Object.keys(resetState).forEach((key) => { Object.keys(resetState).forEach((key) => {
if (key !== "language") accountState[key] = resetState[key]; if (key !== 'language') accountState[key] = resetState[key]
}); })
if (router.route != "/new") appState.party.editable = false; if (router.route != '/new') appState.party.editable = false
router.push("/"); router.push('/')
return false; return false
} }
function toggleFavorite() { function toggleFavorite() {
if (party.favorited) unsaveFavorite(); if (party.favorited) unsaveFavorite()
else saveFavorite(); else saveFavorite()
} }
function saveFavorite() { function saveFavorite() {
if (party.id) if (party.id)
api.saveTeam({ id: party.id, params: headers }).then((response) => { api.saveTeam({ id: party.id, params: headers }).then((response) => {
if (response.status == 201) appState.party.favorited = true; if (response.status == 201) appState.party.favorited = true
}); })
else console.error("Failed to save team: No party ID"); else console.error('Failed to save team: No party ID')
} }
function unsaveFavorite() { function unsaveFavorite() {
if (party.id) if (party.id)
api.unsaveTeam({ id: party.id, params: headers }).then((response) => { api.unsaveTeam({ id: party.id, params: headers }).then((response) => {
if (response.status == 200) appState.party.favorited = false; if (response.status == 200) appState.party.favorited = false
}); })
else console.error("Failed to unsave team: No party ID"); else console.error('Failed to unsave team: No party ID')
} }
const leftNav = () => { const leftNav = () => {
return ( return (
<div className="dropdown"> <div className="dropdown">
<Button icon="menu">{t("buttons.menu")}</Button> <Button icon="menu">{t('buttons.menu')}</Button>
{account.user ? ( {account.user ? (
<HeaderMenu <HeaderMenu
authenticated={account.authorized} authenticated={account.authorized}
@ -108,8 +108,8 @@ const TopHeader = () => {
<HeaderMenu authenticated={account.authorized} /> <HeaderMenu authenticated={account.authorized} />
)} )}
</div> </div>
); )
}; }
const saveButton = () => { const saveButton = () => {
if (party.favorited) if (party.favorited)
@ -117,38 +117,38 @@ const TopHeader = () => {
<Button icon="save" active={true} onClick={toggleFavorite}> <Button icon="save" active={true} onClick={toggleFavorite}>
Saved Saved
</Button> </Button>
); )
else else
return ( return (
<Button icon="save" onClick={toggleFavorite}> <Button icon="save" onClick={toggleFavorite}>
Save Save
</Button> </Button>
); )
}; }
const rightNav = () => { const rightNav = () => {
return ( return (
<div> <div>
{router.route === "/p/[party]" && {router.route === '/p/[party]' &&
account.user && account.user &&
(!party.user || party.user.id !== account.user.id) (!party.user || party.user.id !== account.user.id)
? saveButton() ? saveButton()
: ""} : ''}
{router.route === "/p/[party]" ? ( {router.route === '/p/[party]' ? (
<Button icon="link" onClick={copyToClipboard}> <Button icon="link" onClick={copyToClipboard}>
{t("buttons.copy")} {t('buttons.copy')}
</Button> </Button>
) : ( ) : (
"" ''
)} )}
<Button icon="new" onClick={newParty}> <Button icon="new" onClick={newParty}>
{t("buttons.new")} {t('buttons.new')}
</Button> </Button>
</div> </div>
); )
}; }
return <Header position="top" left={leftNav()} right={rightNav()} />; return <Header position="top" left={leftNav()} right={rightNav()} />
}; }
export default TopHeader; export default TopHeader

View file

@ -1,60 +1,60 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from 'react'
import UncapStar from "~components/UncapStar"; import UncapStar from '~components/UncapStar'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
type: "character" | "weapon" | "summon"; type: 'character' | 'weapon' | 'summon'
rarity?: number; rarity?: number
uncapLevel?: number; uncapLevel?: number
flb: boolean; flb: boolean
ulb: boolean; ulb: boolean
special: boolean; special: boolean
updateUncap?: (uncap: number) => void; updateUncap?: (uncap: number) => void
} }
const UncapIndicator = (props: Props) => { const UncapIndicator = (props: Props) => {
const [uncap, setUncap] = useState(props.uncapLevel); const [uncap, setUncap] = useState(props.uncapLevel)
const numStars = setNumStars(); const numStars = setNumStars()
function setNumStars() { function setNumStars() {
let numStars; let numStars
if (props.type === "character") { if (props.type === 'character') {
if (props.special) { if (props.special) {
if (props.ulb) { if (props.ulb) {
numStars = 5; numStars = 5
} else if (props.flb) { } else if (props.flb) {
numStars = 4; numStars = 4
} else { } else {
numStars = 3; numStars = 3
} }
} else { } else {
if (props.ulb) { if (props.ulb) {
numStars = 6; numStars = 6
} else if (props.flb) { } else if (props.flb) {
numStars = 5; numStars = 5
} else { } else {
numStars = 4; numStars = 4
} }
} }
} else { } else {
if (props.ulb) { if (props.ulb) {
numStars = 5; numStars = 5
} else if (props.flb) { } else if (props.flb) {
numStars = 4; numStars = 4
} else { } else {
numStars = 3; numStars = 3
} }
} }
return numStars; return numStars
} }
function toggleStar(index: number, empty: boolean) { function toggleStar(index: number, empty: boolean) {
if (props.updateUncap) { if (props.updateUncap) {
if (empty) props.updateUncap(index + 1); if (empty) props.updateUncap(index + 1)
else props.updateUncap(index); else props.updateUncap(index)
} }
} }
@ -67,8 +67,8 @@ const UncapIndicator = (props: Props) => {
index={i} index={i}
onClick={toggleStar} onClick={toggleStar}
/> />
); )
}; }
const ulb = (i: number) => { const ulb = (i: number) => {
return ( return (
@ -79,8 +79,8 @@ const UncapIndicator = (props: Props) => {
index={i} index={i}
onClick={toggleStar} onClick={toggleStar}
/> />
); )
}; }
const flb = (i: number) => { const flb = (i: number) => {
return ( return (
@ -91,8 +91,8 @@ const UncapIndicator = (props: Props) => {
index={i} index={i}
onClick={toggleStar} onClick={toggleStar}
/> />
); )
}; }
const mlb = (i: number) => { const mlb = (i: number) => {
// console.log("MLB; Number of stars:", props.uncapLevel) // console.log("MLB; Number of stars:", props.uncapLevel)
@ -103,27 +103,27 @@ const UncapIndicator = (props: Props) => {
index={i} index={i}
onClick={toggleStar} onClick={toggleStar}
/> />
); )
}; }
return ( return (
<ul className="UncapIndicator"> <ul className="UncapIndicator">
{Array.from(Array(numStars)).map((x, i) => { {Array.from(Array(numStars)).map((x, i) => {
if (props.type === "character" && i > 4) { if (props.type === 'character' && i > 4) {
if (props.special) return ulb(i); if (props.special) return ulb(i)
else return transcendence(i); else return transcendence(i)
} else if ( } else if (
(props.special && props.type === "character" && i == 3) || (props.special && props.type === 'character' && i == 3) ||
(props.type === "character" && i == 4) || (props.type === 'character' && i == 4) ||
(props.type !== "character" && i > 2) (props.type !== 'character' && i > 2)
) { ) {
return flb(i); return flb(i)
} else { } else {
return mlb(i); return mlb(i)
} }
})} })}
</ul> </ul>
); )
}; }
export default UncapIndicator; export default UncapIndicator

View file

@ -14,42 +14,42 @@
&.empty.flb, &.empty.flb,
&.empty.ulb, &.empty.ulb,
&.empty.special { &.empty.special {
background: url("/icons/uncap/empty.svg"); background: url('/icons/uncap/empty.svg');
&:hover { &:hover {
background: url("/icons/uncap/empty-hover.svg"); background: url('/icons/uncap/empty-hover.svg');
} }
} }
&.mlb { &.mlb {
background: url("/icons/uncap/yellow.svg"); background: url('/icons/uncap/yellow.svg');
&:hover { &:hover {
background: url("/icons/uncap/yellow-hover.svg"); background: url('/icons/uncap/yellow-hover.svg');
} }
} }
&.special { &.special {
background: url("/icons/uncap/red.svg"); background: url('/icons/uncap/red.svg');
&:hover { &:hover {
background: url("/icons/uncap/red-hover.svg"); background: url('/icons/uncap/red-hover.svg');
} }
} }
&.flb { &.flb {
background: url("/icons/uncap/blue.svg"); background: url('/icons/uncap/blue.svg');
&:hover { &:hover {
background: url("/icons/uncap/blue-hover.svg"); background: url('/icons/uncap/blue-hover.svg');
} }
} }
&.ulb { &.ulb {
background: url("/icons/uncap/purple.svg"); background: url('/icons/uncap/purple.svg');
&:hover { &:hover {
background: url("/icons/uncap/purple-hover.svg"); background: url('/icons/uncap/purple-hover.svg');
} }
} }
} }

View file

@ -1,15 +1,15 @@
import React from "react"; import React from 'react'
import classnames from "classnames"; import classnames from 'classnames'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
empty: boolean; empty: boolean
special: boolean; special: boolean
flb: boolean; flb: boolean
ulb: boolean; ulb: boolean
index: number; index: number
onClick: (index: number, empty: boolean) => void; onClick: (index: number, empty: boolean) => void
} }
const UncapStar = (props: Props) => { const UncapStar = (props: Props) => {
@ -20,20 +20,20 @@ const UncapStar = (props: Props) => {
mlb: !props.special, mlb: !props.special,
flb: props.flb, flb: props.flb,
ulb: props.ulb, ulb: props.ulb,
}); })
function clicked() { function clicked() {
props.onClick(props.index, props.empty); props.onClick(props.index, props.empty)
} }
return <li className={classes} onClick={clicked}></li>; return <li className={classes} onClick={clicked}></li>
}; }
UncapStar.defaultProps = { UncapStar.defaultProps = {
empty: false, empty: false,
special: false, special: false,
flb: false, flb: false,
ulb: false, ulb: false,
}; }
export default UncapStar; export default UncapStar

View file

@ -1,50 +1,50 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { getCookie } from "cookies-next"; import { getCookie } from 'cookies-next'
import { useSnapshot } from "valtio"; import { useSnapshot } from 'valtio'
import { AxiosResponse } from "axios"; import { AxiosResponse } from 'axios'
import debounce from "lodash.debounce"; import debounce from 'lodash.debounce'
import WeaponUnit from "~components/WeaponUnit"; import WeaponUnit from '~components/WeaponUnit'
import ExtraWeapons from "~components/ExtraWeapons"; import ExtraWeapons from '~components/ExtraWeapons'
import api from "~utils/api"; import api from '~utils/api'
import { appState } from "~utils/appState"; import { appState } from '~utils/appState'
import type { SearchableObject } from "~types"; import type { SearchableObject } from '~types'
import "./index.scss"; import './index.scss'
// Props // Props
interface Props { interface Props {
new: boolean; new: boolean
weapons?: GridWeapon[]; weapons?: GridWeapon[]
createParty: (extra: boolean) => Promise<AxiosResponse<any, any>>; createParty: (extra: boolean) => Promise<AxiosResponse<any, any>>
pushHistory?: (path: string) => void; pushHistory?: (path: string) => void
} }
const WeaponGrid = (props: Props) => { const WeaponGrid = (props: Props) => {
// Constants // Constants
const numWeapons: number = 9; const numWeapons: number = 9
// Cookies // Cookies
const cookie = getCookie("account"); const cookie = getCookie('account')
const accountData: AccountCookie = cookie const accountData: AccountCookie = cookie
? JSON.parse(cookie as string) ? JSON.parse(cookie as string)
: null; : null
const headers = accountData const headers = accountData
? { headers: { Authorization: `Bearer ${accountData.token}` } } ? { headers: { Authorization: `Bearer ${accountData.token}` } }
: {}; : {}
// Set up state for view management // Set up state for view management
const { party, grid } = useSnapshot(appState); const { party, grid } = useSnapshot(appState)
const [slug, setSlug] = useState(); const [slug, setSlug] = useState()
// Create a temporary state to store previous weapon uncap values // Create a temporary state to store previous weapon uncap values
const [previousUncapValues, setPreviousUncapValues] = useState<{ const [previousUncapValues, setPreviousUncapValues] = useState<{
[key: number]: number; [key: number]: number
}>({}); }>({})
// Set the editable flag only on first load // Set the editable flag only on first load
useEffect(() => { useEffect(() => {
@ -53,53 +53,53 @@ const WeaponGrid = (props: Props) => {
(accountData && party.user && accountData.userId === party.user.id) || (accountData && party.user && accountData.userId === party.user.id) ||
props.new props.new
) )
appState.party.editable = true; appState.party.editable = true
else appState.party.editable = false; else appState.party.editable = false
}, [props.new, accountData, party]); }, [props.new, accountData, party])
// Initialize an array of current uncap values for each weapon // Initialize an array of current uncap values for each weapon
useEffect(() => { useEffect(() => {
let initialPreviousUncapValues: { [key: number]: number } = {}; let initialPreviousUncapValues: { [key: number]: number } = {}
if (appState.grid.weapons.mainWeapon) if (appState.grid.weapons.mainWeapon)
initialPreviousUncapValues[-1] = initialPreviousUncapValues[-1] =
appState.grid.weapons.mainWeapon.uncap_level; appState.grid.weapons.mainWeapon.uncap_level
Object.values(appState.grid.weapons.allWeapons).map((o) => Object.values(appState.grid.weapons.allWeapons).map((o) =>
o ? (initialPreviousUncapValues[o.position] = o.uncap_level) : 0 o ? (initialPreviousUncapValues[o.position] = o.uncap_level) : 0
); )
setPreviousUncapValues(initialPreviousUncapValues); setPreviousUncapValues(initialPreviousUncapValues)
}, [appState.grid.weapons.mainWeapon, appState.grid.weapons.allWeapons]); }, [appState.grid.weapons.mainWeapon, appState.grid.weapons.allWeapons])
// Methods: Adding an object from search // Methods: Adding an object from search
function receiveWeaponFromSearch(object: SearchableObject, position: number) { function receiveWeaponFromSearch(object: SearchableObject, position: number) {
const weapon = object as Weapon; const weapon = object as Weapon
if (position == 1) appState.party.element = weapon.element; if (position == 1) appState.party.element = weapon.element
if (!party.id) { if (!party.id) {
props.createParty(party.extra).then((response) => { props.createParty(party.extra).then((response) => {
const party = response.data.party; const party = response.data.party
appState.party.id = party.id; appState.party.id = party.id
setSlug(party.shortcode); setSlug(party.shortcode)
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`); if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`)
saveWeapon(party.id, weapon, position).then((response) => saveWeapon(party.id, weapon, position).then((response) =>
storeGridWeapon(response.data.grid_weapon) storeGridWeapon(response.data.grid_weapon)
); )
}); })
} else { } else {
saveWeapon(party.id, weapon, position).then((response) => saveWeapon(party.id, weapon, position).then((response) =>
storeGridWeapon(response.data.grid_weapon) storeGridWeapon(response.data.grid_weapon)
); )
} }
} }
async function saveWeapon(partyId: string, weapon: Weapon, position: number) { async function saveWeapon(partyId: string, weapon: Weapon, position: number) {
let uncapLevel = 3; let uncapLevel = 3
if (weapon.uncap.ulb) uncapLevel = 5; if (weapon.uncap.ulb) uncapLevel = 5
else if (weapon.uncap.flb) uncapLevel = 4; else if (weapon.uncap.flb) uncapLevel = 4
return await api.endpoints.weapons.create( return await api.endpoints.weapons.create(
{ {
@ -112,39 +112,39 @@ const WeaponGrid = (props: Props) => {
}, },
}, },
headers headers
); )
} }
function storeGridWeapon(gridWeapon: GridWeapon) { function storeGridWeapon(gridWeapon: GridWeapon) {
if (gridWeapon.position == -1) { if (gridWeapon.position == -1) {
appState.grid.weapons.mainWeapon = gridWeapon; appState.grid.weapons.mainWeapon = gridWeapon
appState.party.element = gridWeapon.object.element; appState.party.element = gridWeapon.object.element
} else { } else {
// Store the grid unit at the correct position // Store the grid unit at the correct position
appState.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon; appState.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon
} }
} }
// Methods: Updating uncap level // Methods: Updating uncap level
// Note: Saves, but debouncing is not working properly // Note: Saves, but debouncing is not working properly
async function saveUncap(id: string, position: number, uncapLevel: number) { async function saveUncap(id: string, position: number, uncapLevel: number) {
storePreviousUncapValue(position); storePreviousUncapValue(position)
try { try {
if (uncapLevel != previousUncapValues[position]) if (uncapLevel != previousUncapValues[position])
await api.updateUncap("weapon", id, uncapLevel).then((response) => { await api.updateUncap('weapon', id, uncapLevel).then((response) => {
storeGridWeapon(response.data.grid_weapon); storeGridWeapon(response.data.grid_weapon)
}); })
} catch (error) { } catch (error) {
console.error(error); console.error(error)
// Revert optimistic UI // Revert optimistic UI
updateUncapLevel(position, previousUncapValues[position]); updateUncapLevel(position, previousUncapValues[position])
// Remove optimistic key // Remove optimistic key
let newPreviousValues = { ...previousUncapValues }; let newPreviousValues = { ...previousUncapValues }
delete newPreviousValues[position]; delete newPreviousValues[position]
setPreviousUncapValues(newPreviousValues); setPreviousUncapValues(newPreviousValues)
} }
} }
@ -153,56 +153,55 @@ const WeaponGrid = (props: Props) => {
position: number, position: number,
uncapLevel: number uncapLevel: number
) { ) {
memoizeAction(id, position, uncapLevel); memoizeAction(id, position, uncapLevel)
// Optimistically update UI // Optimistically update UI
updateUncapLevel(position, uncapLevel); updateUncapLevel(position, uncapLevel)
} }
const memoizeAction = useCallback( const memoizeAction = useCallback(
(id: string, position: number, uncapLevel: number) => { (id: string, position: number, uncapLevel: number) => {
debouncedAction(id, position, uncapLevel); debouncedAction(id, position, uncapLevel)
}, },
[props, previousUncapValues] [props, previousUncapValues]
); )
const debouncedAction = useMemo( const debouncedAction = useMemo(
() => () =>
debounce((id, position, number) => { debounce((id, position, number) => {
saveUncap(id, position, number); saveUncap(id, position, number)
}, 500), }, 500),
[props, saveUncap] [props, saveUncap]
); )
const updateUncapLevel = (position: number, uncapLevel: number) => { const updateUncapLevel = (position: number, uncapLevel: number) => {
if (appState.grid.weapons.mainWeapon && position == -1) if (appState.grid.weapons.mainWeapon && position == -1)
appState.grid.weapons.mainWeapon.uncap_level = uncapLevel; appState.grid.weapons.mainWeapon.uncap_level = uncapLevel
else { else {
const weapon = appState.grid.weapons.allWeapons[position]; const weapon = appState.grid.weapons.allWeapons[position]
if (weapon) { if (weapon) {
weapon.uncap_level = uncapLevel; weapon.uncap_level = uncapLevel
appState.grid.weapons.allWeapons[position] = weapon; appState.grid.weapons.allWeapons[position] = weapon
}
} }
} }
};
function storePreviousUncapValue(position: number) { function storePreviousUncapValue(position: number) {
// Save the current value in case of an unexpected result // Save the current value in case of an unexpected result
let newPreviousValues = { ...previousUncapValues }; let newPreviousValues = { ...previousUncapValues }
if (appState.grid.weapons.mainWeapon && position == -1) { if (appState.grid.weapons.mainWeapon && position == -1) {
newPreviousValues[position] = newPreviousValues[position] = appState.grid.weapons.mainWeapon.uncap_level
appState.grid.weapons.mainWeapon.uncap_level;
} else { } else {
const weapon = appState.grid.weapons.allWeapons[position]; const weapon = appState.grid.weapons.allWeapons[position]
if (weapon) { if (weapon) {
newPreviousValues[position] = weapon.uncap_level; newPreviousValues[position] = weapon.uncap_level
} else { } else {
newPreviousValues[position] = 0; newPreviousValues[position] = 0
} }
} }
setPreviousUncapValues(newPreviousValues); setPreviousUncapValues(newPreviousValues)
} }
// Render: JSX components // Render: JSX components
@ -216,7 +215,7 @@ const WeaponGrid = (props: Props) => {
updateObject={receiveWeaponFromSearch} updateObject={receiveWeaponFromSearch}
updateUncap={initiateUncapUpdate} updateUncap={initiateUncapUpdate}
/> />
); )
const weaponGridElement = Array.from(Array(numWeapons)).map((x, i) => { const weaponGridElement = Array.from(Array(numWeapons)).map((x, i) => {
return ( return (
@ -230,8 +229,8 @@ const WeaponGrid = (props: Props) => {
updateUncap={initiateUncapUpdate} updateUncap={initiateUncapUpdate}
/> />
</li> </li>
); )
}); })
const extraGridElement = ( const extraGridElement = (
<ExtraWeapons <ExtraWeapons
@ -241,7 +240,7 @@ const WeaponGrid = (props: Props) => {
updateObject={receiveWeaponFromSearch} updateObject={receiveWeaponFromSearch}
updateUncap={initiateUncapUpdate} updateUncap={initiateUncapUpdate}
/> />
); )
return ( return (
<div id="WeaponGrid"> <div id="WeaponGrid">
@ -251,10 +250,10 @@ const WeaponGrid = (props: Props) => {
</div> </div>
{(() => { {(() => {
return party.extra ? extraGridElement : ""; return party.extra ? extraGridElement : ''
})()} })()}
</div> </div>
); )
}; }
export default WeaponGrid; export default WeaponGrid

View file

@ -1,134 +1,132 @@
import React from "react"; import React from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import * as HoverCard from "@radix-ui/react-hover-card"; import * as HoverCard from '@radix-ui/react-hover-card'
import WeaponLabelIcon from "~components/WeaponLabelIcon"; import WeaponLabelIcon from '~components/WeaponLabelIcon'
import UncapIndicator from "~components/UncapIndicator"; import UncapIndicator from '~components/UncapIndicator'
import { axData } from "~utils/axData"; import { axData } from '~utils/axData'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
gridWeapon: GridWeapon; gridWeapon: GridWeapon
children: React.ReactNode; children: React.ReactNode
} }
interface KeyNames { interface KeyNames {
[key: string]: { [key: string]: {
[key: string]: string; [key: string]: string
en: string; en: string
ja: string; ja: string
}; }
} }
const WeaponHovercard = (props: Props) => { const WeaponHovercard = (props: Props) => {
const router = useRouter(); const router = useRouter()
const { t } = useTranslation("common"); const { t } = useTranslation('common')
const locale = const locale =
router.locale && ["en", "ja"].includes(router.locale) router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
? router.locale
: "en";
const Element = ["null", "wind", "fire", "water", "earth", "dark", "light"]; const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
const Proficiency = [ const Proficiency = [
"none", 'none',
"sword", 'sword',
"dagger", 'dagger',
"axe", 'axe',
"spear", 'spear',
"bow", 'bow',
"staff", 'staff',
"fist", 'fist',
"harp", 'harp',
"gun", 'gun',
"katana", 'katana',
]; ]
const WeaponKeyNames: KeyNames = { const WeaponKeyNames: KeyNames = {
"2": { '2': {
en: "Pendulum", en: 'Pendulum',
ja: "ペンデュラム", ja: 'ペンデュラム',
}, },
"3": { '3': {
en: "Teluma", en: 'Teluma',
ja: "テルマ", ja: 'テルマ',
}, },
"17": { '17': {
en: "Gauph Key", en: 'Gauph Key',
ja: "ガフスキー", ja: 'ガフスキー',
}, },
"22": { '22': {
en: "Emblem", en: 'Emblem',
ja: "エンブレム", ja: 'エンブレム',
}, },
}; }
const tintElement = const tintElement =
props.gridWeapon.object.element == 0 && props.gridWeapon.element props.gridWeapon.object.element == 0 && props.gridWeapon.element
? Element[props.gridWeapon.element] ? Element[props.gridWeapon.element]
: Element[props.gridWeapon.object.element]; : Element[props.gridWeapon.object.element]
const wikiUrl = `https://gbf.wiki/${props.gridWeapon.object.name.en.replaceAll( const wikiUrl = `https://gbf.wiki/${props.gridWeapon.object.name.en.replaceAll(
" ", ' ',
"_" '_'
)}`; )}`
const hovercardSide = () => { const hovercardSide = () => {
if (props.gridWeapon.position == -1) return "right"; if (props.gridWeapon.position == -1) return 'right'
else if ([6, 7, 8, 9, 10, 11].includes(props.gridWeapon.position)) else if ([6, 7, 8, 9, 10, 11].includes(props.gridWeapon.position))
return "top"; return 'top'
else return "bottom"; else return 'bottom'
};
const createPrimaryAxSkillString = () => {
const primaryAxSkills = axData[props.gridWeapon.object.ax - 1];
if (props.gridWeapon.ax) {
const simpleAxSkill = props.gridWeapon.ax[0];
const axSkill = primaryAxSkills.find(
(skill) => skill.id == simpleAxSkill.modifier
);
return `${axSkill?.name[locale]} +${simpleAxSkill.strength}${
axSkill?.suffix ? axSkill.suffix : ""
}`;
} }
return ""; const createPrimaryAxSkillString = () => {
}; const primaryAxSkills = axData[props.gridWeapon.object.ax - 1]
const createSecondaryAxSkillString = () => {
const primaryAxSkills = axData[props.gridWeapon.object.ax - 1];
if (props.gridWeapon.ax) { if (props.gridWeapon.ax) {
const primarySimpleAxSkill = props.gridWeapon.ax[0]; const simpleAxSkill = props.gridWeapon.ax[0]
const secondarySimpleAxSkill = props.gridWeapon.ax[1]; const axSkill = primaryAxSkills.find(
(skill) => skill.id == simpleAxSkill.modifier
)
return `${axSkill?.name[locale]} +${simpleAxSkill.strength}${
axSkill?.suffix ? axSkill.suffix : ''
}`
}
return ''
}
const createSecondaryAxSkillString = () => {
const primaryAxSkills = axData[props.gridWeapon.object.ax - 1]
if (props.gridWeapon.ax) {
const primarySimpleAxSkill = props.gridWeapon.ax[0]
const secondarySimpleAxSkill = props.gridWeapon.ax[1]
const primaryAxSkill = primaryAxSkills.find( const primaryAxSkill = primaryAxSkills.find(
(skill) => skill.id == primarySimpleAxSkill.modifier (skill) => skill.id == primarySimpleAxSkill.modifier
); )
if (primaryAxSkill && primaryAxSkill.secondary) { if (primaryAxSkill && primaryAxSkill.secondary) {
const secondaryAxSkill = primaryAxSkill.secondary.find( const secondaryAxSkill = primaryAxSkill.secondary.find(
(skill) => skill.id == secondarySimpleAxSkill.modifier (skill) => skill.id == secondarySimpleAxSkill.modifier
); )
return `${secondaryAxSkill?.name[locale]} +${ return `${secondaryAxSkill?.name[locale]} +${
secondarySimpleAxSkill.strength secondarySimpleAxSkill.strength
}${secondaryAxSkill?.suffix ? secondaryAxSkill.suffix : ""}`; }${secondaryAxSkill?.suffix ? secondaryAxSkill.suffix : ''}`
} }
} }
return ""; return ''
}; }
function weaponImage() { function weaponImage() {
const weapon = props.gridWeapon.object; const weapon = props.gridWeapon.object
if (props.gridWeapon.object.element == 0 && props.gridWeapon.element) if (props.gridWeapon.object.element == 0 && props.gridWeapon.element)
return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}_${props.gridWeapon.element}.jpg`; return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}_${props.gridWeapon.element}.jpg`
else else
return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`; return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`
} }
const keysSection = ( const keysSection = (
@ -136,10 +134,10 @@ const WeaponHovercard = (props: Props) => {
{WeaponKeyNames[props.gridWeapon.object.series] ? ( {WeaponKeyNames[props.gridWeapon.object.series] ? (
<h5 className={tintElement}> <h5 className={tintElement}>
{WeaponKeyNames[props.gridWeapon.object.series][locale]} {WeaponKeyNames[props.gridWeapon.object.series][locale]}
{locale === "en" ? "s" : ""} {locale === 'en' ? 's' : ''}
</h5> </h5>
) : ( ) : (
"" ''
)} )}
{props.gridWeapon.weapon_keys {props.gridWeapon.weapon_keys
@ -151,21 +149,21 @@ const WeaponHovercard = (props: Props) => {
> >
<span>{props.gridWeapon.weapon_keys![i].name[locale]}</span> <span>{props.gridWeapon.weapon_keys![i].name[locale]}</span>
</div> </div>
); )
}) })
: ""} : ''}
</section> </section>
); )
const axSection = ( const axSection = (
<section className="axSkills"> <section className="axSkills">
<h5 className={tintElement}>{t("modals.weapon.subtitles.ax_skills")}</h5> <h5 className={tintElement}>{t('modals.weapon.subtitles.ax_skills')}</h5>
<div className="skills"> <div className="skills">
<div className="primary axSkill"> <div className="primary axSkill">
<img <img
alt="AX1" alt="AX1"
src={`/icons/ax/primary_${ src={`/icons/ax/primary_${
props.gridWeapon.ax ? props.gridWeapon.ax[0].modifier : "" props.gridWeapon.ax ? props.gridWeapon.ax[0].modifier : ''
}.png`} }.png`}
/> />
<span>{createPrimaryAxSkillString()}</span> <span>{createPrimaryAxSkillString()}</span>
@ -178,17 +176,17 @@ const WeaponHovercard = (props: Props) => {
<img <img
alt="AX2" alt="AX2"
src={`/icons/ax/secondary_${ src={`/icons/ax/secondary_${
props.gridWeapon.ax ? props.gridWeapon.ax[1].modifier : "" props.gridWeapon.ax ? props.gridWeapon.ax[1].modifier : ''
}.png`} }.png`}
/> />
<span>{createSecondaryAxSkillString()}</span> <span>{createSecondaryAxSkillString()}</span>
</div> </div>
) : ( ) : (
"" ''
)} )}
</div> </div>
</section> </section>
); )
return ( return (
<HoverCard.Root> <HoverCard.Root>
@ -216,7 +214,7 @@ const WeaponHovercard = (props: Props) => {
} }
/> />
) : ( ) : (
"" ''
)} )}
<WeaponLabelIcon <WeaponLabelIcon
labelType={Proficiency[props.gridWeapon.object.proficiency]} labelType={Proficiency[props.gridWeapon.object.proficiency]}
@ -236,17 +234,17 @@ const WeaponHovercard = (props: Props) => {
props.gridWeapon.ax[0].modifier && props.gridWeapon.ax[0].modifier &&
props.gridWeapon.ax[0].strength props.gridWeapon.ax[0].strength
? axSection ? axSection
: ""} : ''}
{props.gridWeapon.weapon_keys && props.gridWeapon.weapon_keys.length > 0 {props.gridWeapon.weapon_keys && props.gridWeapon.weapon_keys.length > 0
? keysSection ? keysSection
: ""} : ''}
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new"> <a className={`Button ${tintElement}`} href={wikiUrl} target="_new">
{t("buttons.wiki")} {t('buttons.wiki')}
</a> </a>
<HoverCard.Arrow /> <HoverCard.Arrow />
</HoverCard.Content> </HoverCard.Content>
</HoverCard.Root> </HoverCard.Root>
); )
}; }
export default WeaponHovercard; export default WeaponHovercard

View file

@ -1,39 +1,39 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import api from "~utils/api"; import api from '~utils/api'
import "./index.scss"; import './index.scss'
// Props // Props
interface Props { interface Props {
currentValue?: WeaponKey; currentValue?: WeaponKey
series: number; series: number
slot: number; slot: number
onChange?: (event: React.ChangeEvent<HTMLSelectElement>) => void; onChange?: (event: React.ChangeEvent<HTMLSelectElement>) => void
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void; onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void
} }
const WeaponKeyDropdown = React.forwardRef<HTMLSelectElement, Props>( const WeaponKeyDropdown = React.forwardRef<HTMLSelectElement, Props>(
function useFieldSet(props, ref) { function useFieldSet(props, ref) {
const [keys, setKeys] = useState<WeaponKey[][]>([]); const [keys, setKeys] = useState<WeaponKey[][]>([])
const [currentKey, setCurrentKey] = useState(""); const [currentKey, setCurrentKey] = useState('')
const pendulumNames = [ const pendulumNames = [
{ en: "Pendulum", jp: "" }, { en: 'Pendulum', jp: '' },
{ en: "Chain", jp: "" }, { en: 'Chain', jp: '' },
]; ]
const telumaNames = [{ en: "Teluma", jp: "" }]; const telumaNames = [{ en: 'Teluma', jp: '' }]
const emblemNames = [{ en: "Emblem", jp: "" }]; const emblemNames = [{ en: 'Emblem', jp: '' }]
const gauphNames = [ const gauphNames = [
{ en: "Gauph Key", jp: "" }, { en: 'Gauph Key', jp: '' },
{ en: "Ultima Key", jp: "" }, { en: 'Ultima Key', jp: '' },
{ en: "Gate of Omnipotence", jp: "" }, { en: 'Gate of Omnipotence', jp: '' },
]; ]
useEffect(() => { useEffect(() => {
if (props.currentValue) setCurrentKey(props.currentValue.id); if (props.currentValue) setCurrentKey(props.currentValue.id)
}, [props.currentValue]); }, [props.currentValue])
useEffect(() => { useEffect(() => {
const filterParams = { const filterParams = {
@ -41,36 +41,36 @@ const WeaponKeyDropdown = React.forwardRef<HTMLSelectElement, Props>(
series: props.series, series: props.series,
slot: props.slot, slot: props.slot,
}, },
}; }
function organizeWeaponKeys(weaponKeys: WeaponKey[]) { function organizeWeaponKeys(weaponKeys: WeaponKey[]) {
const numGroups = Math.max.apply( const numGroups = Math.max.apply(
Math, Math,
weaponKeys.map((key) => key.group) weaponKeys.map((key) => key.group)
); )
let groupedKeys = []; let groupedKeys = []
for (let i = 0; i <= numGroups; i++) { for (let i = 0; i <= numGroups; i++) {
groupedKeys[i] = weaponKeys.filter((key) => key.group == i); groupedKeys[i] = weaponKeys.filter((key) => key.group == i)
} }
setKeys(groupedKeys); setKeys(groupedKeys)
} }
function fetchWeaponKeys() { function fetchWeaponKeys() {
api.endpoints.weapon_keys.getAll(filterParams).then((response) => { api.endpoints.weapon_keys.getAll(filterParams).then((response) => {
const keys = response.data.map((k: any) => k.weapon_key); const keys = response.data.map((k: any) => k.weapon_key)
organizeWeaponKeys(keys); organizeWeaponKeys(keys)
}); })
} }
fetchWeaponKeys(); fetchWeaponKeys()
}, [props.series, props.slot]); }, [props.series, props.slot])
function weaponKeyGroup(index: number) { function weaponKeyGroup(index: number) {
["α", "β", "γ", "Δ"].sort((a, b) => a.localeCompare(b, "el")); ;['α', 'β', 'γ', 'Δ'].sort((a, b) => a.localeCompare(b, 'el'))
const sortByOrder = (a: WeaponKey, b: WeaponKey) => const sortByOrder = (a: WeaponKey, b: WeaponKey) =>
a.order > b.order ? 1 : -1; a.order > b.order ? 1 : -1
const options = const options =
keys && keys &&
@ -81,16 +81,16 @@ const WeaponKeyDropdown = React.forwardRef<HTMLSelectElement, Props>(
<option key={i} value={item.id}> <option key={i} value={item.id}>
{item.name.en} {item.name.en}
</option> </option>
); )
}); })
let name: { [key: string]: string } = {}; let name: { [key: string]: string } = {}
if (props.series == 2 && index == 0) name = pendulumNames[0]; if (props.series == 2 && index == 0) name = pendulumNames[0]
else if (props.series == 2 && props.slot == 1 && index == 1) else if (props.series == 2 && props.slot == 1 && index == 1)
name = pendulumNames[1]; name = pendulumNames[1]
else if (props.series == 3) name = telumaNames[index]; else if (props.series == 3) name = telumaNames[index]
else if (props.series == 17) name = gauphNames[props.slot]; else if (props.series == 17) name = gauphNames[props.slot]
else if (props.series == 22) name = emblemNames[index]; else if (props.series == 22) name = emblemNames[index]
return ( return (
<optgroup <optgroup
@ -101,24 +101,24 @@ const WeaponKeyDropdown = React.forwardRef<HTMLSelectElement, Props>(
> >
{options} {options}
</optgroup> </optgroup>
); )
} }
function handleChange(event: React.ChangeEvent<HTMLSelectElement>) { function handleChange(event: React.ChangeEvent<HTMLSelectElement>) {
if (props.onChange) props.onChange(event); if (props.onChange) props.onChange(event)
setCurrentKey(event.currentTarget.value); setCurrentKey(event.currentTarget.value)
} }
const emptyOption = () => { const emptyOption = () => {
let name = ""; let name = ''
if (props.series == 2) name = pendulumNames[0].en; if (props.series == 2) name = pendulumNames[0].en
else if (props.series == 3) name = telumaNames[0].en; else if (props.series == 3) name = telumaNames[0].en
else if (props.series == 17) name = gauphNames[props.slot].en; else if (props.series == 17) name = gauphNames[props.slot].en
else if (props.series == 22) name = emblemNames[0].en; else if (props.series == 22) name = emblemNames[0].en
return `No ${name}`; return `No ${name}`
}; }
return ( return (
<select <select
@ -132,11 +132,11 @@ const WeaponKeyDropdown = React.forwardRef<HTMLSelectElement, Props>(
{emptyOption()} {emptyOption()}
</option> </option>
{Array.from(Array(keys?.length)).map((x, i) => { {Array.from(Array(keys?.length)).map((x, i) => {
return weaponKeyGroup(i); return weaponKeyGroup(i)
})} })}
</select> </select>
); )
} }
); )
export default WeaponKeyDropdown; export default WeaponKeyDropdown

View file

@ -7,140 +7,140 @@
/* Elements */ /* Elements */
&.fire.en { &.fire.en {
background-image: url("/labels/element/fire_en.png"); background-image: url('/labels/element/fire_en.png');
} }
&.fire.ja { &.fire.ja {
background-image: url("/labels/element/fire_ja.png"); background-image: url('/labels/element/fire_ja.png');
} }
&.water.en { &.water.en {
background-image: url("/labels/element/water_en.png"); background-image: url('/labels/element/water_en.png');
} }
&.water.ja { &.water.ja {
background-image: url("/labels/element/water_ja.png"); background-image: url('/labels/element/water_ja.png');
} }
&.earth.en { &.earth.en {
background-image: url("/labels/element/earth_en.png"); background-image: url('/labels/element/earth_en.png');
} }
&.earth.ja { &.earth.ja {
background-image: url("/labels/element/earth_ja.png"); background-image: url('/labels/element/earth_ja.png');
} }
&.wind.en { &.wind.en {
background-image: url("/labels/element/wind_en.png"); background-image: url('/labels/element/wind_en.png');
} }
&.wind.ja { &.wind.ja {
background-image: url("/labels/element/wind_ja.png"); background-image: url('/labels/element/wind_ja.png');
} }
&.dark.en { &.dark.en {
background-image: url("/labels/element/dark_en.png"); background-image: url('/labels/element/dark_en.png');
} }
&.dark.ja { &.dark.ja {
background-image: url("/labels/element/dark_ja.png"); background-image: url('/labels/element/dark_ja.png');
} }
&.light.en { &.light.en {
background-image: url("/labels/element/light_en.png"); background-image: url('/labels/element/light_en.png');
} }
&.light.ja { &.light.ja {
background-image: url("/labels/element/light_ja.png"); background-image: url('/labels/element/light_ja.png');
} }
&.null.en { &.null.en {
background-image: url("/labels/element/any_en.png"); background-image: url('/labels/element/any_en.png');
} }
&.null.ja { &.null.ja {
background-image: url("/labels/element/any_ja.png"); background-image: url('/labels/element/any_ja.png');
} }
/* Proficiencies */ /* Proficiencies */
&.sword.en { &.sword.en {
background-image: url("/labels/proficiency/sabre_en.png"); background-image: url('/labels/proficiency/sabre_en.png');
} }
&.sword.ja { &.sword.ja {
background-image: url("/labels/proficiency/sabre_ja.png"); background-image: url('/labels/proficiency/sabre_ja.png');
} }
&.dagger.en { &.dagger.en {
background-image: url("/labels/proficiency/dagger_en.png"); background-image: url('/labels/proficiency/dagger_en.png');
} }
&.dagger.ja { &.dagger.ja {
background-image: url("/labels/proficiency/dagger_ja.png"); background-image: url('/labels/proficiency/dagger_ja.png');
} }
&.axe.en { &.axe.en {
background-image: url("/labels/proficiency/axe_en.png"); background-image: url('/labels/proficiency/axe_en.png');
} }
&.axe.ja { &.axe.ja {
background-image: url("/labels/proficiency/axe_ja.png"); background-image: url('/labels/proficiency/axe_ja.png');
} }
&.spear.en { &.spear.en {
background-image: url("/labels/proficiency/spear_en.png"); background-image: url('/labels/proficiency/spear_en.png');
} }
&.spear.ja { &.spear.ja {
background-image: url("/labels/proficiency/spear_ja.png"); background-image: url('/labels/proficiency/spear_ja.png');
} }
&.staff.en { &.staff.en {
background-image: url("/labels/proficiency/staff_en.png"); background-image: url('/labels/proficiency/staff_en.png');
} }
&.staff.ja { &.staff.ja {
background-image: url("/labels/proficiency/staff_ja.png"); background-image: url('/labels/proficiency/staff_ja.png');
} }
&.fist.en { &.fist.en {
background-image: url("/labels/proficiency/melee_en.png"); background-image: url('/labels/proficiency/melee_en.png');
} }
&.fist.ja { &.fist.ja {
background-image: url("/labels/proficiency/melee_ja.png"); background-image: url('/labels/proficiency/melee_ja.png');
} }
&.harp.en { &.harp.en {
background-image: url("/labels/proficiency/harp_en.png"); background-image: url('/labels/proficiency/harp_en.png');
} }
&.harp.ja { &.harp.ja {
background-image: url("/labels/proficiency/harp_ja.png"); background-image: url('/labels/proficiency/harp_ja.png');
} }
&.gun.en { &.gun.en {
background-image: url("/labels/proficiency/gun_en.png"); background-image: url('/labels/proficiency/gun_en.png');
} }
&.gun.ja { &.gun.ja {
background-image: url("/labels/proficiency/gun_ja.png"); background-image: url('/labels/proficiency/gun_ja.png');
} }
&.bow.en { &.bow.en {
background-image: url("/labels/proficiency/bow_en.png"); background-image: url('/labels/proficiency/bow_en.png');
} }
&.bow.ja { &.bow.ja {
background-image: url("/labels/proficiency/bow_ja.png"); background-image: url('/labels/proficiency/bow_ja.png');
} }
&.katana.en { &.katana.en {
background-image: url("/labels/proficiency/katana_en.png"); background-image: url('/labels/proficiency/katana_en.png');
} }
&.katana.ja { &.katana.ja {
background-image: url("/labels/proficiency/katana_ja.png"); background-image: url('/labels/proficiency/katana_ja.png');
} }
} }

View file

@ -1,18 +1,16 @@
import React from "react"; import React from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
labelType: string; labelType: string
} }
const WeaponLabelIcon = (props: Props) => { const WeaponLabelIcon = (props: Props) => {
const router = useRouter(); const router = useRouter()
return ( return <i className={`WeaponLabelIcon ${props.labelType} ${router.locale}`} />
<i className={`WeaponLabelIcon ${props.labelType} ${router.locale}`} /> }
);
};
export default WeaponLabelIcon; export default WeaponLabelIcon

View file

@ -1,71 +1,69 @@
import React, { useState } from "react"; import React, { useState } from 'react'
import { getCookie } from "cookies-next"; import { getCookie } from 'cookies-next'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import { AxiosResponse } from "axios"; import { AxiosResponse } from 'axios'
import * as Dialog from "@radix-ui/react-dialog"; import * as Dialog from '@radix-ui/react-dialog'
import AXSelect from "~components/AxSelect"; import AXSelect from '~components/AxSelect'
import ElementToggle from "~components/ElementToggle"; import ElementToggle from '~components/ElementToggle'
import WeaponKeyDropdown from "~components/WeaponKeyDropdown"; import WeaponKeyDropdown from '~components/WeaponKeyDropdown'
import Button from "~components/Button"; import Button from '~components/Button'
import api from "~utils/api"; import api from '~utils/api'
import { appState } from "~utils/appState"; import { appState } from '~utils/appState'
import CrossIcon from "~public/icons/Cross.svg"; import CrossIcon from '~public/icons/Cross.svg'
import "./index.scss"; import './index.scss'
interface GridWeaponObject { interface GridWeaponObject {
weapon: { weapon: {
element?: number; element?: number
weapon_key1_id?: string; weapon_key1_id?: string
weapon_key2_id?: string; weapon_key2_id?: string
weapon_key3_id?: string; weapon_key3_id?: string
ax_modifier1?: number; ax_modifier1?: number
ax_modifier2?: number; ax_modifier2?: number
ax_strength1?: number; ax_strength1?: number
ax_strength2?: number; ax_strength2?: number
}; }
} }
interface Props { interface Props {
gridWeapon: GridWeapon; gridWeapon: GridWeapon
children: React.ReactNode; children: React.ReactNode
} }
const WeaponModal = (props: Props) => { const WeaponModal = (props: Props) => {
const router = useRouter(); const router = useRouter()
const locale = const locale =
router.locale && ["en", "ja"].includes(router.locale) router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
? router.locale const { t } = useTranslation('common')
: "en";
const { t } = useTranslation("common");
// Cookies // Cookies
const cookie = getCookie("account"); const cookie = getCookie('account')
const accountData: AccountCookie = cookie const accountData: AccountCookie = cookie
? JSON.parse(cookie as string) ? JSON.parse(cookie as string)
: null; : null
const headers = accountData const headers = accountData
? { Authorization: `Bearer ${accountData.token}` } ? { Authorization: `Bearer ${accountData.token}` }
: {}; : {}
// Refs // Refs
const weaponKey1Select = React.createRef<HTMLSelectElement>(); const weaponKey1Select = React.createRef<HTMLSelectElement>()
const weaponKey2Select = React.createRef<HTMLSelectElement>(); const weaponKey2Select = React.createRef<HTMLSelectElement>()
const weaponKey3Select = React.createRef<HTMLSelectElement>(); const weaponKey3Select = React.createRef<HTMLSelectElement>()
// State // State
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false)
const [formValid, setFormValid] = useState(false); const [formValid, setFormValid] = useState(false)
const [element, setElement] = useState(-1); const [element, setElement] = useState(-1)
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1); const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1); const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1)
const [primaryAxValue, setPrimaryAxValue] = useState(0.0); const [primaryAxValue, setPrimaryAxValue] = useState(0.0)
const [secondaryAxValue, setSecondaryAxValue] = useState(0.0); const [secondaryAxValue, setSecondaryAxValue] = useState(0.0)
function receiveAxValues( function receiveAxValues(
primaryAxModifier: number, primaryAxModifier: number,
@ -73,82 +71,82 @@ const WeaponModal = (props: Props) => {
secondaryAxModifier: number, secondaryAxModifier: number,
secondaryAxValue: number secondaryAxValue: number
) { ) {
setPrimaryAxModifier(primaryAxModifier); setPrimaryAxModifier(primaryAxModifier)
setSecondaryAxModifier(secondaryAxModifier); setSecondaryAxModifier(secondaryAxModifier)
setPrimaryAxValue(primaryAxValue); setPrimaryAxValue(primaryAxValue)
setSecondaryAxValue(secondaryAxValue); setSecondaryAxValue(secondaryAxValue)
} }
function receiveAxValidity(isValid: boolean) { function receiveAxValidity(isValid: boolean) {
setFormValid(isValid); setFormValid(isValid)
} }
function receiveElementValue(element: string) { function receiveElementValue(element: string) {
setElement(parseInt(element)); setElement(parseInt(element))
} }
function prepareObject() { function prepareObject() {
let object: GridWeaponObject = { weapon: {} }; let object: GridWeaponObject = { weapon: {} }
if (props.gridWeapon.object.element == 0) object.weapon.element = element; if (props.gridWeapon.object.element == 0) object.weapon.element = element
if ([2, 3, 17, 24].includes(props.gridWeapon.object.series)) if ([2, 3, 17, 24].includes(props.gridWeapon.object.series))
object.weapon.weapon_key1_id = weaponKey1Select.current?.value; object.weapon.weapon_key1_id = weaponKey1Select.current?.value
if ([2, 3, 17].includes(props.gridWeapon.object.series)) if ([2, 3, 17].includes(props.gridWeapon.object.series))
object.weapon.weapon_key2_id = weaponKey2Select.current?.value; object.weapon.weapon_key2_id = weaponKey2Select.current?.value
if (props.gridWeapon.object.series == 17) if (props.gridWeapon.object.series == 17)
object.weapon.weapon_key3_id = weaponKey3Select.current?.value; object.weapon.weapon_key3_id = weaponKey3Select.current?.value
if (props.gridWeapon.object.ax > 0) { if (props.gridWeapon.object.ax > 0) {
object.weapon.ax_modifier1 = primaryAxModifier; object.weapon.ax_modifier1 = primaryAxModifier
object.weapon.ax_modifier2 = secondaryAxModifier; object.weapon.ax_modifier2 = secondaryAxModifier
object.weapon.ax_strength1 = primaryAxValue; object.weapon.ax_strength1 = primaryAxValue
object.weapon.ax_strength2 = secondaryAxValue; object.weapon.ax_strength2 = secondaryAxValue
} }
return object; return object
} }
async function updateWeapon() { async function updateWeapon() {
const updateObject = prepareObject(); const updateObject = prepareObject()
return await api.endpoints.grid_weapons return await api.endpoints.grid_weapons
.update(props.gridWeapon.id, updateObject, headers) .update(props.gridWeapon.id, updateObject, headers)
.then((response) => processResult(response)) .then((response) => processResult(response))
.catch((error) => processError(error)); .catch((error) => processError(error))
} }
function processResult(response: AxiosResponse) { function processResult(response: AxiosResponse) {
const gridWeapon: GridWeapon = response.data.grid_weapon; const gridWeapon: GridWeapon = response.data.grid_weapon
if (gridWeapon.mainhand) appState.grid.weapons.mainWeapon = gridWeapon; if (gridWeapon.mainhand) appState.grid.weapons.mainWeapon = gridWeapon
else appState.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon; else appState.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon
setOpen(false); setOpen(false)
} }
function processError(error: any) { function processError(error: any) {
console.error(error); console.error(error)
} }
const elementSelect = () => { const elementSelect = () => {
return ( return (
<section> <section>
<h3>{t("modals.weapon.subtitles.element")}</h3> <h3>{t('modals.weapon.subtitles.element')}</h3>
<ElementToggle <ElementToggle
currentElement={props.gridWeapon.element} currentElement={props.gridWeapon.element}
sendValue={receiveElementValue} sendValue={receiveElementValue}
/> />
</section> </section>
); )
}; }
const keySelect = () => { const keySelect = () => {
return ( return (
<section> <section>
<h3>{t("modals.weapon.subtitles.weapon_keys")}</h3> <h3>{t('modals.weapon.subtitles.weapon_keys')}</h3>
{[2, 3, 17, 22].includes(props.gridWeapon.object.series) ? ( {[2, 3, 17, 22].includes(props.gridWeapon.object.series) ? (
<WeaponKeyDropdown <WeaponKeyDropdown
currentValue={ currentValue={
@ -161,7 +159,7 @@ const WeaponModal = (props: Props) => {
ref={weaponKey1Select} ref={weaponKey1Select}
/> />
) : ( ) : (
"" ''
)} )}
{[2, 3, 17].includes(props.gridWeapon.object.series) ? ( {[2, 3, 17].includes(props.gridWeapon.object.series) ? (
@ -176,7 +174,7 @@ const WeaponModal = (props: Props) => {
ref={weaponKey2Select} ref={weaponKey2Select}
/> />
) : ( ) : (
"" ''
)} )}
{props.gridWeapon.object.series == 17 ? ( {props.gridWeapon.object.series == 17 ? (
@ -191,16 +189,16 @@ const WeaponModal = (props: Props) => {
ref={weaponKey3Select} ref={weaponKey3Select}
/> />
) : ( ) : (
"" ''
)} )}
</section> </section>
); )
}; }
const axSelect = () => { const axSelect = () => {
return ( return (
<section> <section>
<h3>{t("modals.weapon.subtitles.ax_skills")}</h3> <h3>{t('modals.weapon.subtitles.ax_skills')}</h3>
<AXSelect <AXSelect
axType={props.gridWeapon.object.ax} axType={props.gridWeapon.object.ax}
currentSkills={props.gridWeapon.ax} currentSkills={props.gridWeapon.ax}
@ -208,12 +206,12 @@ const WeaponModal = (props: Props) => {
sendValues={receiveAxValues} sendValues={receiveAxValues}
/> />
</section> </section>
); )
}; }
function openChange(open: boolean) { function openChange(open: boolean) {
setFormValid(false); setFormValid(false)
setOpen(open); setOpen(open)
} }
return ( return (
@ -227,7 +225,7 @@ const WeaponModal = (props: Props) => {
<div className="DialogHeader"> <div className="DialogHeader">
<div className="DialogTop"> <div className="DialogTop">
<Dialog.Title className="SubTitle"> <Dialog.Title className="SubTitle">
{t("modals.weapon.title")} {t('modals.weapon.title')}
</Dialog.Title> </Dialog.Title>
<Dialog.Title className="DialogTitle"> <Dialog.Title className="DialogTitle">
{props.gridWeapon.object.name[locale]} {props.gridWeapon.object.name[locale]}
@ -241,23 +239,23 @@ const WeaponModal = (props: Props) => {
</div> </div>
<div className="mods"> <div className="mods">
{props.gridWeapon.object.element == 0 ? elementSelect() : ""} {props.gridWeapon.object.element == 0 ? elementSelect() : ''}
{[2, 3, 17, 24].includes(props.gridWeapon.object.series) {[2, 3, 17, 24].includes(props.gridWeapon.object.series)
? keySelect() ? keySelect()
: ""} : ''}
{props.gridWeapon.object.ax > 0 ? axSelect() : ""} {props.gridWeapon.object.ax > 0 ? axSelect() : ''}
<Button <Button
onClick={updateWeapon} onClick={updateWeapon}
disabled={props.gridWeapon.object.ax > 0 && !formValid} disabled={props.gridWeapon.object.ax > 0 && !formValid}
> >
{t("modals.weapon.buttons.confirm")} {t('modals.weapon.buttons.confirm')}
</Button> </Button>
</div> </div>
</Dialog.Content> </Dialog.Content>
<Dialog.Overlay className="Overlay" /> <Dialog.Overlay className="Overlay" />
</Dialog.Portal> </Dialog.Portal>
</Dialog.Root> </Dialog.Root>
); )
}; }
export default WeaponModal; export default WeaponModal

View file

@ -1,65 +1,63 @@
import React from "react"; import React from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import UncapIndicator from "~components/UncapIndicator"; import UncapIndicator from '~components/UncapIndicator'
import WeaponLabelIcon from "~components/WeaponLabelIcon"; import WeaponLabelIcon from '~components/WeaponLabelIcon'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
data: Weapon; data: Weapon
onClick: () => void; onClick: () => void
} }
const Element = ["null", "wind", "fire", "water", "earth", "dark", "light"]; const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
const Proficiency = [ const Proficiency = [
"none", 'none',
"sword", 'sword',
"dagger", 'dagger',
"axe", 'axe',
"spear", 'spear',
"bow", 'bow',
"staff", 'staff',
"fist", 'fist',
"harp", 'harp',
"gun", 'gun',
"katana", 'katana',
]; ]
const Series = [ const Series = [
"seraphic", 'seraphic',
"grand", 'grand',
"opus", 'opus',
"draconic", 'draconic',
"revenant", 'revenant',
"primal", 'primal',
"beast", 'beast',
"regalia", 'regalia',
"omega", 'omega',
"olden_primal", 'olden_primal',
"hollowsky", 'hollowsky',
"xeno", 'xeno',
"astral", 'astral',
"rose", 'rose',
"ultima", 'ultima',
"bahamut", 'bahamut',
"epic", 'epic',
"ennead", 'ennead',
"cosmos", 'cosmos',
"ancestral", 'ancestral',
"superlative", 'superlative',
"vintage", 'vintage',
"class_champion", 'class_champion',
"sephira", 'sephira',
"new_world_foundation", 'new_world_foundation',
]; ]
const WeaponResult = (props: Props) => { const WeaponResult = (props: Props) => {
const router = useRouter(); const router = useRouter()
const locale = const locale =
router.locale && ["en", "ja"].includes(router.locale) router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
? router.locale const weapon = props.data
: "en";
const weapon = props.data;
return ( return (
<li className="WeaponResult" onClick={props.onClick}> <li className="WeaponResult" onClick={props.onClick}>
@ -81,7 +79,7 @@ const WeaponResult = (props: Props) => {
</div> </div>
</div> </div>
</li> </li>
); )
}; }
export default WeaponResult; export default WeaponResult

View file

@ -1,141 +1,141 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import cloneDeep from "lodash.clonedeep"; import cloneDeep from 'lodash.clonedeep'
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import SearchFilter from "~components/SearchFilter"; import SearchFilter from '~components/SearchFilter'
import SearchFilterCheckboxItem from "~components/SearchFilterCheckboxItem"; import SearchFilterCheckboxItem from '~components/SearchFilterCheckboxItem'
import "./index.scss"; import './index.scss'
import { import {
emptyElementState, emptyElementState,
emptyProficiencyState, emptyProficiencyState,
emptyRarityState, emptyRarityState,
emptyWeaponSeriesState, emptyWeaponSeriesState,
} from "~utils/emptyStates"; } from '~utils/emptyStates'
import { import {
elements, elements,
proficiencies, proficiencies,
rarities, rarities,
weaponSeries, weaponSeries,
} from "~utils/stateValues"; } from '~utils/stateValues'
interface Props { interface Props {
sendFilters: (filters: { [key: string]: number[] }) => void; sendFilters: (filters: { [key: string]: number[] }) => void
} }
const WeaponSearchFilterBar = (props: Props) => { const WeaponSearchFilterBar = (props: Props) => {
const { t } = useTranslation("common"); const { t } = useTranslation('common')
const [rarityMenu, setRarityMenu] = useState(false); const [rarityMenu, setRarityMenu] = useState(false)
const [elementMenu, setElementMenu] = useState(false); const [elementMenu, setElementMenu] = useState(false)
const [proficiencyMenu, setProficiencyMenu] = useState(false); const [proficiencyMenu, setProficiencyMenu] = useState(false)
const [seriesMenu, setSeriesMenu] = useState(false); const [seriesMenu, setSeriesMenu] = useState(false)
const [rarityState, setRarityState] = useState<RarityState>(emptyRarityState); const [rarityState, setRarityState] = useState<RarityState>(emptyRarityState)
const [elementState, setElementState] = const [elementState, setElementState] =
useState<ElementState>(emptyElementState); useState<ElementState>(emptyElementState)
const [proficiencyState, setProficiencyState] = useState<ProficiencyState>( const [proficiencyState, setProficiencyState] = useState<ProficiencyState>(
emptyProficiencyState emptyProficiencyState
); )
const [seriesState, setSeriesState] = useState<WeaponSeriesState>( const [seriesState, setSeriesState] = useState<WeaponSeriesState>(
emptyWeaponSeriesState emptyWeaponSeriesState
); )
function rarityMenuOpened(open: boolean) { function rarityMenuOpened(open: boolean) {
if (open) { if (open) {
setRarityMenu(true); setRarityMenu(true)
setElementMenu(false); setElementMenu(false)
setProficiencyMenu(false); setProficiencyMenu(false)
setSeriesMenu(false); setSeriesMenu(false)
} else setRarityMenu(false); } else setRarityMenu(false)
} }
function elementMenuOpened(open: boolean) { function elementMenuOpened(open: boolean) {
if (open) { if (open) {
setRarityMenu(false); setRarityMenu(false)
setElementMenu(true); setElementMenu(true)
setProficiencyMenu(false); setProficiencyMenu(false)
setSeriesMenu(false); setSeriesMenu(false)
} else setElementMenu(false); } else setElementMenu(false)
} }
function proficiencyMenuOpened(open: boolean) { function proficiencyMenuOpened(open: boolean) {
if (open) { if (open) {
setRarityMenu(false); setRarityMenu(false)
setElementMenu(false); setElementMenu(false)
setProficiencyMenu(true); setProficiencyMenu(true)
setSeriesMenu(false); setSeriesMenu(false)
} else setProficiencyMenu(false); } else setProficiencyMenu(false)
} }
function seriesMenuOpened(open: boolean) { function seriesMenuOpened(open: boolean) {
if (open) { if (open) {
setRarityMenu(false); setRarityMenu(false)
setElementMenu(false); setElementMenu(false)
setProficiencyMenu(false); setProficiencyMenu(false)
setSeriesMenu(true); setSeriesMenu(true)
} else setSeriesMenu(false); } else setSeriesMenu(false)
} }
function handleRarityChange(checked: boolean, key: string) { function handleRarityChange(checked: boolean, key: string) {
let newRarityState = cloneDeep(rarityState); let newRarityState = cloneDeep(rarityState)
newRarityState[key].checked = checked; newRarityState[key].checked = checked
setRarityState(newRarityState); setRarityState(newRarityState)
} }
function handleElementChange(checked: boolean, key: string) { function handleElementChange(checked: boolean, key: string) {
let newElementState = cloneDeep(elementState); let newElementState = cloneDeep(elementState)
newElementState[key].checked = checked; newElementState[key].checked = checked
setElementState(newElementState); setElementState(newElementState)
} }
function handleProficiencyChange(checked: boolean, key: string) { function handleProficiencyChange(checked: boolean, key: string) {
let newProficiencyState = cloneDeep(proficiencyState); let newProficiencyState = cloneDeep(proficiencyState)
newProficiencyState[key].checked = checked; newProficiencyState[key].checked = checked
setProficiencyState(newProficiencyState); setProficiencyState(newProficiencyState)
} }
function handleSeriesChange(checked: boolean, key: string) { function handleSeriesChange(checked: boolean, key: string) {
let newSeriesState = cloneDeep(seriesState); let newSeriesState = cloneDeep(seriesState)
newSeriesState[key].checked = checked; newSeriesState[key].checked = checked
setSeriesState(newSeriesState); setSeriesState(newSeriesState)
} }
function sendFilters() { function sendFilters() {
const checkedRarityFilters = Object.values(rarityState) const checkedRarityFilters = Object.values(rarityState)
.filter((x) => x.checked) .filter((x) => x.checked)
.map((x, i) => x.id); .map((x, i) => x.id)
const checkedElementFilters = Object.values(elementState) const checkedElementFilters = Object.values(elementState)
.filter((x) => x.checked) .filter((x) => x.checked)
.map((x, i) => x.id); .map((x, i) => x.id)
const checkedProficiencyFilters = Object.values(proficiencyState) const checkedProficiencyFilters = Object.values(proficiencyState)
.filter((x) => x.checked) .filter((x) => x.checked)
.map((x, i) => x.id); .map((x, i) => x.id)
const checkedSeriesFilters = Object.values(seriesState) const checkedSeriesFilters = Object.values(seriesState)
.filter((x) => x.checked) .filter((x) => x.checked)
.map((x, i) => x.id); .map((x, i) => x.id)
const filters = { const filters = {
rarity: checkedRarityFilters, rarity: checkedRarityFilters,
element: checkedElementFilters, element: checkedElementFilters,
proficiency1: checkedProficiencyFilters, proficiency1: checkedProficiencyFilters,
series: checkedSeriesFilters, series: checkedSeriesFilters,
}; }
props.sendFilters(filters); props.sendFilters(filters)
} }
useEffect(() => { useEffect(() => {
sendFilters(); sendFilters()
}, [rarityState, elementState, proficiencyState, seriesState]); }, [rarityState, elementState, proficiencyState, seriesState])
return ( return (
<div className="SearchFilterBar"> <div className="SearchFilterBar">
<SearchFilter <SearchFilter
label={t("filters.labels.rarity")} label={t('filters.labels.rarity')}
numSelected={ numSelected={
Object.values(rarityState) Object.values(rarityState)
.map((x) => x.checked) .map((x) => x.checked)
@ -145,7 +145,7 @@ const WeaponSearchFilterBar = (props: Props) => {
onOpenChange={rarityMenuOpened} onOpenChange={rarityMenuOpened}
> >
<DropdownMenu.Label className="Label"> <DropdownMenu.Label className="Label">
{t("filters.labels.rarity")} {t('filters.labels.rarity')}
</DropdownMenu.Label> </DropdownMenu.Label>
{Array.from(Array(rarities.length)).map((x, i) => { {Array.from(Array(rarities.length)).map((x, i) => {
return ( return (
@ -157,12 +157,12 @@ const WeaponSearchFilterBar = (props: Props) => {
> >
{t(`rarities.${rarities[i]}`)} {t(`rarities.${rarities[i]}`)}
</SearchFilterCheckboxItem> </SearchFilterCheckboxItem>
); )
})} })}
</SearchFilter> </SearchFilter>
<SearchFilter <SearchFilter
label={t("filters.labels.element")} label={t('filters.labels.element')}
numSelected={ numSelected={
Object.values(elementState) Object.values(elementState)
.map((x) => x.checked) .map((x) => x.checked)
@ -172,7 +172,7 @@ const WeaponSearchFilterBar = (props: Props) => {
onOpenChange={elementMenuOpened} onOpenChange={elementMenuOpened}
> >
<DropdownMenu.Label className="Label"> <DropdownMenu.Label className="Label">
{t("filters.labels.element")} {t('filters.labels.element')}
</DropdownMenu.Label> </DropdownMenu.Label>
{Array.from(Array(elements.length)).map((x, i) => { {Array.from(Array(elements.length)).map((x, i) => {
return ( return (
@ -184,12 +184,12 @@ const WeaponSearchFilterBar = (props: Props) => {
> >
{t(`elements.${elements[i]}`)} {t(`elements.${elements[i]}`)}
</SearchFilterCheckboxItem> </SearchFilterCheckboxItem>
); )
})} })}
</SearchFilter> </SearchFilter>
<SearchFilter <SearchFilter
label={t("filters.labels.proficiency")} label={t('filters.labels.proficiency')}
numSelected={ numSelected={
Object.values(proficiencyState) Object.values(proficiencyState)
.map((x) => x.checked) .map((x) => x.checked)
@ -199,7 +199,7 @@ const WeaponSearchFilterBar = (props: Props) => {
onOpenChange={proficiencyMenuOpened} onOpenChange={proficiencyMenuOpened}
> >
<DropdownMenu.Label className="Label"> <DropdownMenu.Label className="Label">
{t("filters.labels.proficiency")} {t('filters.labels.proficiency')}
</DropdownMenu.Label> </DropdownMenu.Label>
<section> <section>
<DropdownMenu.Group className="Group"> <DropdownMenu.Group className="Group">
@ -213,7 +213,7 @@ const WeaponSearchFilterBar = (props: Props) => {
> >
{t(`proficiencies.${proficiencies[i]}`)} {t(`proficiencies.${proficiencies[i]}`)}
</SearchFilterCheckboxItem> </SearchFilterCheckboxItem>
); )
})} })}
</DropdownMenu.Group> </DropdownMenu.Group>
<DropdownMenu.Group className="Group"> <DropdownMenu.Group className="Group">
@ -235,14 +235,14 @@ const WeaponSearchFilterBar = (props: Props) => {
}` }`
)} )}
</SearchFilterCheckboxItem> </SearchFilterCheckboxItem>
); )
})} })}
</DropdownMenu.Group> </DropdownMenu.Group>
</section> </section>
</SearchFilter> </SearchFilter>
<SearchFilter <SearchFilter
label={t("filters.labels.series")} label={t('filters.labels.series')}
numSelected={ numSelected={
Object.values(seriesState) Object.values(seriesState)
.map((x) => x.checked) .map((x) => x.checked)
@ -252,7 +252,7 @@ const WeaponSearchFilterBar = (props: Props) => {
onOpenChange={seriesMenuOpened} onOpenChange={seriesMenuOpened}
> >
<DropdownMenu.Label className="Label"> <DropdownMenu.Label className="Label">
{t("filters.labels.series")} {t('filters.labels.series')}
</DropdownMenu.Label> </DropdownMenu.Label>
<section> <section>
<DropdownMenu.Group className="Group"> <DropdownMenu.Group className="Group">
@ -266,7 +266,7 @@ const WeaponSearchFilterBar = (props: Props) => {
> >
{t(`series.${weaponSeries[i]}`)} {t(`series.${weaponSeries[i]}`)}
</SearchFilterCheckboxItem> </SearchFilterCheckboxItem>
); )
})} })}
</DropdownMenu.Group> </DropdownMenu.Group>
<DropdownMenu.Group className="Group"> <DropdownMenu.Group className="Group">
@ -283,7 +283,7 @@ const WeaponSearchFilterBar = (props: Props) => {
> >
{t(`series.${weaponSeries[i + weaponSeries.length / 3]}`)} {t(`series.${weaponSeries[i + weaponSeries.length / 3]}`)}
</SearchFilterCheckboxItem> </SearchFilterCheckboxItem>
); )
})} })}
</DropdownMenu.Group> </DropdownMenu.Group>
<DropdownMenu.Group className="Group"> <DropdownMenu.Group className="Group">
@ -302,13 +302,13 @@ const WeaponSearchFilterBar = (props: Props) => {
`series.${weaponSeries[i + 2 * (weaponSeries.length / 3)]}` `series.${weaponSeries[i + 2 * (weaponSeries.length / 3)]}`
)} )}
</SearchFilterCheckboxItem> </SearchFilterCheckboxItem>
); )
})} })}
</DropdownMenu.Group> </DropdownMenu.Group>
</section> </section>
</SearchFilter> </SearchFilter>
</div> </div>
); )
}; }
export default WeaponSearchFilterBar; export default WeaponSearchFilterBar

View file

@ -1,39 +1,37 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import classnames from "classnames"; import classnames from 'classnames'
import SearchModal from "~components/SearchModal"; import SearchModal from '~components/SearchModal'
import WeaponModal from "~components/WeaponModal"; import WeaponModal from '~components/WeaponModal'
import WeaponHovercard from "~components/WeaponHovercard"; import WeaponHovercard from '~components/WeaponHovercard'
import UncapIndicator from "~components/UncapIndicator"; import UncapIndicator from '~components/UncapIndicator'
import Button from "~components/Button"; import Button from '~components/Button'
import { ButtonType } from "~utils/enums"; import { ButtonType } from '~utils/enums'
import type { SearchableObject } from "~types"; import type { SearchableObject } from '~types'
import PlusIcon from "~public/icons/Add.svg"; import PlusIcon from '~public/icons/Add.svg'
import "./index.scss"; import './index.scss'
interface Props { interface Props {
gridWeapon: GridWeapon | undefined; gridWeapon: GridWeapon | undefined
unitType: 0 | 1; unitType: 0 | 1
position: number; position: number
editable: boolean; editable: boolean
updateObject: (object: SearchableObject, position: number) => void; updateObject: (object: SearchableObject, position: number) => void
updateUncap: (id: string, position: number, uncap: number) => void; updateUncap: (id: string, position: number, uncap: number) => void
} }
const WeaponUnit = (props: Props) => { const WeaponUnit = (props: Props) => {
const { t } = useTranslation("common"); const { t } = useTranslation('common')
const [imageUrl, setImageUrl] = useState(""); const [imageUrl, setImageUrl] = useState('')
const router = useRouter(); const router = useRouter()
const locale = const locale =
router.locale && ["en", "ja"].includes(router.locale) router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
? router.locale
: "en";
const classes = classnames({ const classes = classnames({
WeaponUnit: true, WeaponUnit: true,
@ -41,48 +39,48 @@ const WeaponUnit = (props: Props) => {
grid: props.unitType == 1, grid: props.unitType == 1,
editable: props.editable, editable: props.editable,
filled: props.gridWeapon !== undefined, filled: props.gridWeapon !== undefined,
}); })
const gridWeapon = props.gridWeapon; const gridWeapon = props.gridWeapon
const weapon = gridWeapon?.object; const weapon = gridWeapon?.object
useEffect(() => { useEffect(() => {
generateImageUrl(); generateImageUrl()
}); })
function generateImageUrl() { function generateImageUrl() {
let imgSrc = ""; let imgSrc = ''
if (props.gridWeapon) { if (props.gridWeapon) {
const weapon = props.gridWeapon.object!; const weapon = props.gridWeapon.object!
if (props.unitType == 0) { if (props.unitType == 0) {
if (props.gridWeapon.object.element == 0 && props.gridWeapon.element) if (props.gridWeapon.object.element == 0 && props.gridWeapon.element)
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblue_id}_${props.gridWeapon.element}.jpg`; imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblue_id}_${props.gridWeapon.element}.jpg`
else else
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblue_id}.jpg`; imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblue_id}.jpg`
} else { } else {
if (props.gridWeapon.object.element == 0 && props.gridWeapon.element) if (props.gridWeapon.object.element == 0 && props.gridWeapon.element)
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}_${props.gridWeapon.element}.jpg`; imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}_${props.gridWeapon.element}.jpg`
else else
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`; imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`
} }
} }
setImageUrl(imgSrc); setImageUrl(imgSrc)
} }
function passUncapData(uncap: number) { function passUncapData(uncap: number) {
if (props.gridWeapon) if (props.gridWeapon)
props.updateUncap(props.gridWeapon.id, props.position, uncap); props.updateUncap(props.gridWeapon.id, props.position, uncap)
} }
function canBeModified(gridWeapon: GridWeapon) { function canBeModified(gridWeapon: GridWeapon) {
const weapon = gridWeapon.object; const weapon = gridWeapon.object
return ( return (
weapon.ax > 0 || weapon.ax > 0 ||
(weapon.series && [2, 3, 17, 22, 24].includes(weapon.series)) (weapon.series && [2, 3, 17, 22, 24].includes(weapon.series))
); )
} }
const image = ( const image = (
@ -93,21 +91,21 @@ const WeaponUnit = (props: Props) => {
<PlusIcon /> <PlusIcon />
</span> </span>
) : ( ) : (
"" ''
)} )}
</div> </div>
); )
const editableImage = ( const editableImage = (
<SearchModal <SearchModal
placeholderText={t("search.placeholders.weapon")} placeholderText={t('search.placeholders.weapon')}
fromPosition={props.position} fromPosition={props.position}
object="weapons" object="weapons"
send={props.updateObject} send={props.updateObject}
> >
{image} {image}
</SearchModal> </SearchModal>
); )
const unitContent = ( const unitContent = (
<div className={classes}> <div className={classes}>
@ -118,7 +116,7 @@ const WeaponUnit = (props: Props) => {
</div> </div>
</WeaponModal> </WeaponModal>
) : ( ) : (
"" ''
)} )}
{props.editable ? editableImage : image} {props.editable ? editableImage : image}
{gridWeapon ? ( {gridWeapon ? (
@ -131,17 +129,17 @@ const WeaponUnit = (props: Props) => {
special={false} special={false}
/> />
) : ( ) : (
"" ''
)} )}
<h3 className="WeaponName">{weapon?.name[locale]}</h3> <h3 className="WeaponName">{weapon?.name[locale]}</h3>
</div> </div>
); )
const withHovercard = ( const withHovercard = (
<WeaponHovercard gridWeapon={gridWeapon!}>{unitContent}</WeaponHovercard> <WeaponHovercard gridWeapon={gridWeapon!}>{unitContent}</WeaponHovercard>
); )
return gridWeapon && !props.editable ? withHovercard : unitContent; return gridWeapon && !props.editable ? withHovercard : unitContent
}; }
export default WeaponUnit; export default WeaponUnit

View file

@ -1,121 +1,121 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from 'react'
import Head from "next/head"; import Head from 'next/head'
import { getCookie } from "cookies-next"; import { getCookie } from 'cookies-next'
import { queryTypes, useQueryState } from "next-usequerystate"; import { queryTypes, useQueryState } from 'next-usequerystate'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import InfiniteScroll from "react-infinite-scroll-component"; import InfiniteScroll from 'react-infinite-scroll-component'
import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import api from "~utils/api"; import api from '~utils/api'
import useDidMountEffect from "~utils/useDidMountEffect"; import useDidMountEffect from '~utils/useDidMountEffect'
import { elements, allElement } from "~utils/Element"; import { elements, allElement } from '~utils/Element'
import GridRep from "~components/GridRep"; import GridRep from '~components/GridRep'
import GridRepCollection from "~components/GridRepCollection"; import GridRepCollection from '~components/GridRepCollection'
import FilterBar from "~components/FilterBar"; import FilterBar from '~components/FilterBar'
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from 'next'
interface Props { interface Props {
user?: User; user?: User
teams?: { count: number; total_pages: number; results: Party[] }; teams?: { count: number; total_pages: number; results: Party[] }
raids: Raid[]; raids: Raid[]
sortedRaids: Raid[][]; sortedRaids: Raid[][]
} }
const ProfileRoute: React.FC<Props> = (props: Props) => { const ProfileRoute: React.FC<Props> = (props: Props) => {
// Set up cookies // Set up cookies
const cookie = getCookie("account"); const cookie = getCookie('account')
const accountData: AccountCookie = cookie const accountData: AccountCookie = cookie
? JSON.parse(cookie as string) ? JSON.parse(cookie as string)
: null; : null
const headers = accountData const headers = accountData
? { Authorization: `Bearer ${accountData.token}` } ? { Authorization: `Bearer ${accountData.token}` }
: {}; : {}
// Set up router // Set up router
const router = useRouter(); const router = useRouter()
const { username } = router.query; const { username } = router.query
// Import translations // Import translations
const { t } = useTranslation("common"); const { t } = useTranslation('common')
// Set up app-specific states // Set up app-specific states
const [raidsLoading, setRaidsLoading] = useState(true); const [raidsLoading, setRaidsLoading] = useState(true)
const [scrolled, setScrolled] = useState(false); const [scrolled, setScrolled] = useState(false)
// Set up page-specific states // Set up page-specific states
const [parties, setParties] = useState<Party[]>([]); const [parties, setParties] = useState<Party[]>([])
const [raids, setRaids] = useState<Raid[]>(); const [raids, setRaids] = useState<Raid[]>()
const [raid, setRaid] = useState<Raid>(); const [raid, setRaid] = useState<Raid>()
// Set up infinite scrolling-related states // Set up infinite scrolling-related states
const [recordCount, setRecordCount] = useState(0); const [recordCount, setRecordCount] = useState(0)
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1)
const [totalPages, setTotalPages] = useState(1); const [totalPages, setTotalPages] = useState(1)
// Set up filter-specific query states // Set up filter-specific query states
// Recency is in seconds // Recency is in seconds
const [element, setElement] = useQueryState("element", { const [element, setElement] = useQueryState('element', {
defaultValue: -1, defaultValue: -1,
parse: (query: string) => parseElement(query), parse: (query: string) => parseElement(query),
serialize: (value) => serializeElement(value), serialize: (value) => serializeElement(value),
}); })
const [raidSlug, setRaidSlug] = useQueryState("raid", { const [raidSlug, setRaidSlug] = useQueryState('raid', {
defaultValue: "all", defaultValue: 'all',
}); })
const [recency, setRecency] = useQueryState( const [recency, setRecency] = useQueryState(
"recency", 'recency',
queryTypes.integer.withDefault(-1) queryTypes.integer.withDefault(-1)
); )
// Define transformers for element // Define transformers for element
function parseElement(query: string) { function parseElement(query: string) {
let element: TeamElement | undefined = let element: TeamElement | undefined =
query === "all" query === 'all'
? allElement ? allElement
: elements.find((element) => element.name.en.toLowerCase() === query); : elements.find((element) => element.name.en.toLowerCase() === query)
return element ? element.id : -1; return element ? element.id : -1
} }
function serializeElement(value: number | undefined) { function serializeElement(value: number | undefined) {
let name = ""; let name = ''
if (value != undefined) { if (value != undefined) {
if (value == -1) name = allElement.name.en.toLowerCase(); if (value == -1) name = allElement.name.en.toLowerCase()
else name = elements[value].name.en.toLowerCase(); else name = elements[value].name.en.toLowerCase()
} }
return name; return name
} }
// Set the initial parties from props // Set the initial parties from props
useEffect(() => { useEffect(() => {
if (props.teams) { if (props.teams) {
setTotalPages(props.teams.total_pages); setTotalPages(props.teams.total_pages)
setRecordCount(props.teams.count); setRecordCount(props.teams.count)
replaceResults(props.teams.count, props.teams.results); replaceResults(props.teams.count, props.teams.results)
} }
setCurrentPage(1); setCurrentPage(1)
}, []); }, [])
// Add scroll event listener for shadow on FilterBar on mount // Add scroll event listener for shadow on FilterBar on mount
useEffect(() => { useEffect(() => {
window.addEventListener("scroll", handleScroll); window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener("scroll", handleScroll); return () => window.removeEventListener('scroll', handleScroll)
}, []); }, [])
// Handle errors // Handle errors
const handleError = useCallback((error: any) => { const handleError = useCallback((error: any) => {
if (error.response != null) { if (error.response != null) {
console.error(error); console.error(error)
} else { } else {
console.error("There was an error."); console.error('There was an error.')
} }
}, []); }, [])
const fetchProfile = useCallback( const fetchProfile = useCallback(
({ replace }: { replace: boolean }) => { ({ replace }: { replace: boolean }) => {
@ -126,7 +126,7 @@ const ProfileRoute: React.FC<Props> = (props: Props) => {
recency: recency != -1 ? recency : undefined, recency: recency != -1 ? recency : undefined,
page: currentPage, page: currentPage,
}, },
}; }
if (username && !Array.isArray(username)) { if (username && !Array.isArray(username)) {
api.endpoints.users api.endpoints.users
@ -135,62 +135,62 @@ const ProfileRoute: React.FC<Props> = (props: Props) => {
params: { ...filters, ...{ headers: headers } }, params: { ...filters, ...{ headers: headers } },
}) })
.then((response) => { .then((response) => {
setTotalPages(response.data.parties.total_pages); setTotalPages(response.data.parties.total_pages)
setRecordCount(response.data.parties.count); setRecordCount(response.data.parties.count)
if (replace) if (replace)
replaceResults( replaceResults(
response.data.parties.count, response.data.parties.count,
response.data.parties.results response.data.parties.results
); )
else appendResults(response.data.parties.results); else appendResults(response.data.parties.results)
}) })
.catch((error) => handleError(error)); .catch((error) => handleError(error))
} }
}, },
[currentPage, parties, element, raid, recency] [currentPage, parties, element, raid, recency]
); )
function replaceResults(count: number, list: Party[]) { function replaceResults(count: number, list: Party[]) {
if (count > 0) { if (count > 0) {
setParties(list.sort((a, b) => (a.created_at > b.created_at ? -1 : 1))); setParties(list.sort((a, b) => (a.created_at > b.created_at ? -1 : 1)))
} else { } else {
setParties([]); setParties([])
} }
} }
function appendResults(list: Party[]) { function appendResults(list: Party[]) {
setParties([...parties, ...list]); setParties([...parties, ...list])
} }
// Fetch all raids on mount, then find the raid in the URL if present // Fetch all raids on mount, then find the raid in the URL if present
useEffect(() => { useEffect(() => {
api.endpoints.raids.getAll().then((response) => { api.endpoints.raids.getAll().then((response) => {
const cleanRaids: Raid[] = response.data.map((r: any) => r.raid); const cleanRaids: Raid[] = response.data.map((r: any) => r.raid)
setRaids(cleanRaids); setRaids(cleanRaids)
setRaidsLoading(false); setRaidsLoading(false)
const raid = cleanRaids.find((r) => r.slug === raidSlug); const raid = cleanRaids.find((r) => r.slug === raidSlug)
setRaid(raid); setRaid(raid)
return raid; return raid
}); })
}, [setRaids]); }, [setRaids])
// When the element, raid or recency filter changes, // When the element, raid or recency filter changes,
// fetch all teams again. // fetch all teams again.
useDidMountEffect(() => { useDidMountEffect(() => {
setCurrentPage(1); setCurrentPage(1)
fetchProfile({ replace: true }); fetchProfile({ replace: true })
}, [element, raid, recency]); }, [element, raid, recency])
// When the page changes, fetch all teams again. // When the page changes, fetch all teams again.
useDidMountEffect(() => { useDidMountEffect(() => {
// Current page changed // Current page changed
if (currentPage > 1) fetchProfile({ replace: false }); if (currentPage > 1) fetchProfile({ replace: false })
else if (currentPage == 1) fetchProfile({ replace: true }); else if (currentPage == 1) fetchProfile({ replace: true })
}, [currentPage]); }, [currentPage])
// Receive filters from the filter bar // Receive filters from the filter bar
function receiveFilters({ function receiveFilters({
@ -198,30 +198,30 @@ const ProfileRoute: React.FC<Props> = (props: Props) => {
raidSlug, raidSlug,
recency, recency,
}: { }: {
element?: number; element?: number
raidSlug?: string; raidSlug?: string
recency?: number; recency?: number
}) { }) {
if (element == 0) setElement(0); if (element == 0) setElement(0)
else if (element) setElement(element); else if (element) setElement(element)
if (raids && raidSlug) { if (raids && raidSlug) {
const raid = raids.find((raid) => raid.slug === raidSlug); const raid = raids.find((raid) => raid.slug === raidSlug)
setRaid(raid); setRaid(raid)
setRaidSlug(raidSlug); setRaidSlug(raidSlug)
} }
if (recency) setRecency(recency); if (recency) setRecency(recency)
} }
// Methods: Navigation // Methods: Navigation
function handleScroll() { function handleScroll() {
if (window.pageYOffset > 90) setScrolled(true); if (window.pageYOffset > 90) setScrolled(true)
else setScrolled(false); else setScrolled(false)
} }
function goTo(shortcode: string) { function goTo(shortcode: string) {
router.push(`/p/${shortcode}`); router.push(`/p/${shortcode}`)
} }
// TODO: Add save functions // TODO: Add save functions
@ -240,8 +240,8 @@ const ProfileRoute: React.FC<Props> = (props: Props) => {
key={`party-${i}`} key={`party-${i}`}
onClick={goTo} onClick={goTo}
/> />
); )
}); })
} }
return ( return (
@ -309,25 +309,25 @@ const ProfileRoute: React.FC<Props> = (props: Props) => {
{parties.length == 0 ? ( {parties.length == 0 ? (
<div id="NotFound"> <div id="NotFound">
<h2>{t("teams.not_found")}</h2> <h2>{t('teams.not_found')}</h2>
</div> </div>
) : ( ) : (
"" ''
)} )}
</section> </section>
</div> </div>
); )
}; }
export const getServerSidePaths = async () => { export const getServerSidePaths = async () => {
return { return {
paths: [ paths: [
// Object variant: // Object variant:
{ params: { party: "string" } }, { params: { party: 'string' } },
], ],
fallback: true, fallback: true,
}; }
}; }
// prettier-ignore // prettier-ignore
export const getServerSideProps = async ({ req, res, locale, query }: { req: NextApiRequest, res: NextApiResponse, locale: string, query: { [index: string]: string } }) => { export const getServerSideProps = async ({ req, res, locale, query }: { req: NextApiRequest, res: NextApiResponse, locale: string, query: { [index: string]: string } }) => {
@ -403,31 +403,31 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
const organizeRaids = (raids: Raid[]) => { const organizeRaids = (raids: Raid[]) => {
// Set up empty raid for "All raids" // Set up empty raid for "All raids"
const all = { const all = {
id: "0", id: '0',
name: { name: {
en: "All raids", en: 'All raids',
ja: "全て", ja: '全て',
}, },
slug: "all", slug: 'all',
level: 0, level: 0,
group: 0, group: 0,
element: 0, element: 0,
}; }
const numGroups = Math.max.apply( const numGroups = Math.max.apply(
Math, Math,
raids.map((raid) => raid.group) raids.map((raid) => raid.group)
); )
let groupedRaids = []; let groupedRaids = []
for (let i = 0; i <= numGroups; i++) { for (let i = 0; i <= numGroups; i++) {
groupedRaids[i] = raids.filter((raid) => raid.group == i); groupedRaids[i] = raids.filter((raid) => raid.group == i)
} }
return { return {
raids: raids, raids: raids,
sortedRaids: groupedRaids, sortedRaids: groupedRaids,
}; }
}; }
export default ProfileRoute; export default ProfileRoute

View file

@ -1,17 +1,17 @@
import { useEffect } from "react" import { useEffect } from 'react'
import { getCookie } from "cookies-next" import { getCookie } from 'cookies-next'
import { appWithTranslation } from "next-i18next" import { appWithTranslation } from 'next-i18next'
import { ThemeProvider } from "next-themes" import { ThemeProvider } from 'next-themes'
import type { AppProps } from "next/app" import type { AppProps } from 'next/app'
import Layout from "~components/Layout" import Layout from '~components/Layout'
import { accountState } from "~utils/accountState" import { accountState } from '~utils/accountState'
import "../styles/globals.scss" import '../styles/globals.scss'
function MyApp({ Component, pageProps }: AppProps) { function MyApp({ Component, pageProps }: AppProps) {
const cookie = getCookie("account") const cookie = getCookie('account')
const cookieData: AccountCookie = cookie ? JSON.parse(cookie as string) : null const cookieData: AccountCookie = cookie ? JSON.parse(cookie as string) : null
useEffect(() => { useEffect(() => {
@ -22,8 +22,8 @@ function MyApp({ Component, pageProps }: AppProps) {
accountState.account.user = { accountState.account.user = {
id: cookieData.userId, id: cookieData.userId,
username: cookieData.username, username: cookieData.username,
picture: "", picture: '',
element: "", element: '',
gender: 0, gender: 0,
} }
} else { } else {

View file

@ -1,53 +1,53 @@
import React, { useEffect } from "react"; import React, { useEffect } from 'react'
import { getCookie } from "cookies-next"; import { getCookie } from 'cookies-next'
import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import Party from "~components/Party"; import Party from '~components/Party'
import { appState } from "~utils/appState"; import { appState } from '~utils/appState'
import api from "~utils/api"; import api from '~utils/api'
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from 'next'
interface Props { interface Props {
jobs: Job[]; jobs: Job[]
jobSkills: JobSkill[]; jobSkills: JobSkill[]
raids: Raid[]; raids: Raid[]
sortedRaids: Raid[][]; sortedRaids: Raid[][]
} }
const NewRoute: React.FC<Props> = (props: Props) => { const NewRoute: React.FC<Props> = (props: Props) => {
function callback(path: string) { function callback(path: string) {
// This is scuffed, how do we do this natively? // This is scuffed, how do we do this natively?
window.history.replaceState(null, `Grid Tool`, `${path}`); window.history.replaceState(null, `Grid Tool`, `${path}`)
} }
useEffect(() => { useEffect(() => {
persistStaticData(); persistStaticData()
}, [persistStaticData]); }, [persistStaticData])
function persistStaticData() { function persistStaticData() {
appState.raids = props.raids; appState.raids = props.raids
appState.jobs = props.jobs; appState.jobs = props.jobs
appState.jobSkills = props.jobSkills; appState.jobSkills = props.jobSkills
} }
return ( return (
<div id="Content"> <div id="Content">
<Party new={true} raids={props.sortedRaids} pushHistory={callback} /> <Party new={true} raids={props.sortedRaids} pushHistory={callback} />
</div> </div>
); )
}; }
export const getServerSidePaths = async () => { export const getServerSidePaths = async () => {
return { return {
paths: [ paths: [
// Object variant: // Object variant:
{ params: { party: "string" } }, { params: { party: 'string' } },
], ],
fallback: true, fallback: true,
}; }
}; }
// prettier-ignore // prettier-ignore
export const getServerSideProps = async ({ req, res, locale, query }: { req: NextApiRequest, res: NextApiResponse, locale: string, query: { [index: string]: string } }) => { export const getServerSideProps = async ({ req, res, locale, query }: { req: NextApiRequest, res: NextApiResponse, locale: string, query: { [index: string]: string } }) => {
@ -87,31 +87,31 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
const organizeRaids = (raids: Raid[]) => { const organizeRaids = (raids: Raid[]) => {
// Set up empty raid for "All raids" // Set up empty raid for "All raids"
const all = { const all = {
id: "0", id: '0',
name: { name: {
en: "All raids", en: 'All raids',
ja: "全て", ja: '全て',
}, },
slug: "all", slug: 'all',
level: 0, level: 0,
group: 0, group: 0,
element: 0, element: 0,
}; }
const numGroups = Math.max.apply( const numGroups = Math.max.apply(
Math, Math,
raids.map((raid) => raid.group) raids.map((raid) => raid.group)
); )
let groupedRaids = []; let groupedRaids = []
for (let i = 0; i <= numGroups; i++) { for (let i = 0; i <= numGroups; i++) {
groupedRaids[i] = raids.filter((raid) => raid.group == i); groupedRaids[i] = raids.filter((raid) => raid.group == i)
} }
return { return {
raids: raids, raids: raids,
sortedRaids: groupedRaids, sortedRaids: groupedRaids,
}; }
}; }
export default NewRoute; export default NewRoute

View file

@ -1,120 +1,120 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from 'react'
import Head from "next/head"; import Head from 'next/head'
import { getCookie } from "cookies-next"; import { getCookie } from 'cookies-next'
import { queryTypes, useQueryState } from "next-usequerystate"; import { queryTypes, useQueryState } from 'next-usequerystate'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import InfiniteScroll from "react-infinite-scroll-component"; import InfiniteScroll from 'react-infinite-scroll-component'
import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import clonedeep from "lodash.clonedeep"; import clonedeep from 'lodash.clonedeep'
import api from "~utils/api"; import api from '~utils/api'
import useDidMountEffect from "~utils/useDidMountEffect"; import useDidMountEffect from '~utils/useDidMountEffect'
import { elements, allElement } from "~utils/Element"; import { elements, allElement } from '~utils/Element'
import GridRep from "~components/GridRep"; import GridRep from '~components/GridRep'
import GridRepCollection from "~components/GridRepCollection"; import GridRepCollection from '~components/GridRepCollection'
import FilterBar from "~components/FilterBar"; import FilterBar from '~components/FilterBar'
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from 'next'
interface Props { interface Props {
teams?: { count: number; total_pages: number; results: Party[] }; teams?: { count: number; total_pages: number; results: Party[] }
raids: Raid[]; raids: Raid[]
sortedRaids: Raid[][]; sortedRaids: Raid[][]
} }
const SavedRoute: React.FC<Props> = (props: Props) => { const SavedRoute: React.FC<Props> = (props: Props) => {
// Set up cookies // Set up cookies
const cookie = getCookie("account"); const cookie = getCookie('account')
const accountData: AccountCookie = cookie const accountData: AccountCookie = cookie
? JSON.parse(cookie as string) ? JSON.parse(cookie as string)
: null; : null
const headers = accountData const headers = accountData
? { Authorization: `Bearer ${accountData.token}` } ? { Authorization: `Bearer ${accountData.token}` }
: {}; : {}
// Set up router // Set up router
const router = useRouter(); const router = useRouter()
// Import translations // Import translations
const { t } = useTranslation("common"); const { t } = useTranslation('common')
// Set up app-specific states // Set up app-specific states
const [raidsLoading, setRaidsLoading] = useState(true); const [raidsLoading, setRaidsLoading] = useState(true)
const [scrolled, setScrolled] = useState(false); const [scrolled, setScrolled] = useState(false)
// Set up page-specific states // Set up page-specific states
const [parties, setParties] = useState<Party[]>([]); const [parties, setParties] = useState<Party[]>([])
const [raids, setRaids] = useState<Raid[]>(); const [raids, setRaids] = useState<Raid[]>()
const [raid, setRaid] = useState<Raid>(); const [raid, setRaid] = useState<Raid>()
// Set up infinite scrolling-related states // Set up infinite scrolling-related states
const [recordCount, setRecordCount] = useState(0); const [recordCount, setRecordCount] = useState(0)
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1)
const [totalPages, setTotalPages] = useState(1); const [totalPages, setTotalPages] = useState(1)
// Set up filter-specific query states // Set up filter-specific query states
// Recency is in seconds // Recency is in seconds
const [element, setElement] = useQueryState("element", { const [element, setElement] = useQueryState('element', {
defaultValue: -1, defaultValue: -1,
parse: (query: string) => parseElement(query), parse: (query: string) => parseElement(query),
serialize: (value) => serializeElement(value), serialize: (value) => serializeElement(value),
}); })
const [raidSlug, setRaidSlug] = useQueryState("raid", { const [raidSlug, setRaidSlug] = useQueryState('raid', {
defaultValue: "all", defaultValue: 'all',
}); })
const [recency, setRecency] = useQueryState( const [recency, setRecency] = useQueryState(
"recency", 'recency',
queryTypes.integer.withDefault(-1) queryTypes.integer.withDefault(-1)
); )
// Define transformers for element // Define transformers for element
function parseElement(query: string) { function parseElement(query: string) {
let element: TeamElement | undefined = let element: TeamElement | undefined =
query === "all" query === 'all'
? allElement ? allElement
: elements.find((element) => element.name.en.toLowerCase() === query); : elements.find((element) => element.name.en.toLowerCase() === query)
return element ? element.id : -1; return element ? element.id : -1
} }
function serializeElement(value: number | undefined) { function serializeElement(value: number | undefined) {
let name = ""; let name = ''
if (value != undefined) { if (value != undefined) {
if (value == -1) name = allElement.name.en.toLowerCase(); if (value == -1) name = allElement.name.en.toLowerCase()
else name = elements[value].name.en.toLowerCase(); else name = elements[value].name.en.toLowerCase()
} }
return name; return name
} }
// Set the initial parties from props // Set the initial parties from props
useEffect(() => { useEffect(() => {
if (props.teams) { if (props.teams) {
setTotalPages(props.teams.total_pages); setTotalPages(props.teams.total_pages)
setRecordCount(props.teams.count); setRecordCount(props.teams.count)
replaceResults(props.teams.count, props.teams.results); replaceResults(props.teams.count, props.teams.results)
} }
setCurrentPage(1); setCurrentPage(1)
}, []); }, [])
// Add scroll event listener for shadow on FilterBar on mount // Add scroll event listener for shadow on FilterBar on mount
useEffect(() => { useEffect(() => {
window.addEventListener("scroll", handleScroll); window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener("scroll", handleScroll); return () => window.removeEventListener('scroll', handleScroll)
}, []); }, [])
// Handle errors // Handle errors
const handleError = useCallback((error: any) => { const handleError = useCallback((error: any) => {
if (error.response != null) { if (error.response != null) {
console.error(error); console.error(error)
} else { } else {
console.error("There was an error."); console.error('There was an error.')
} }
}, []); }, [])
const fetchTeams = useCallback( const fetchTeams = useCallback(
({ replace }: { replace: boolean }) => { ({ replace }: { replace: boolean }) => {
@ -125,63 +125,63 @@ const SavedRoute: React.FC<Props> = (props: Props) => {
recency: recency != -1 ? recency : undefined, recency: recency != -1 ? recency : undefined,
page: currentPage, page: currentPage,
}, },
}; }
api api
.savedTeams({ ...filters, ...{ headers: headers } }) .savedTeams({ ...filters, ...{ headers: headers } })
.then((response) => { .then((response) => {
setTotalPages(response.data.total_pages); setTotalPages(response.data.total_pages)
setRecordCount(response.data.count); setRecordCount(response.data.count)
if (replace) if (replace)
replaceResults(response.data.count, response.data.results); replaceResults(response.data.count, response.data.results)
else appendResults(response.data.results); else appendResults(response.data.results)
}) })
.catch((error) => handleError(error)); .catch((error) => handleError(error))
}, },
[currentPage, parties, element, raid, recency] [currentPage, parties, element, raid, recency]
); )
function replaceResults(count: number, list: Party[]) { function replaceResults(count: number, list: Party[]) {
if (count > 0) { if (count > 0) {
setParties(list); setParties(list)
} else { } else {
setParties([]); setParties([])
} }
} }
function appendResults(list: Party[]) { function appendResults(list: Party[]) {
setParties([...parties, ...list]); setParties([...parties, ...list])
} }
// Fetch all raids on mount, then find the raid in the URL if present // Fetch all raids on mount, then find the raid in the URL if present
useEffect(() => { useEffect(() => {
api.endpoints.raids.getAll().then((response) => { api.endpoints.raids.getAll().then((response) => {
const cleanRaids: Raid[] = response.data.map((r: any) => r.raid); const cleanRaids: Raid[] = response.data.map((r: any) => r.raid)
setRaids(cleanRaids); setRaids(cleanRaids)
setRaidsLoading(false); setRaidsLoading(false)
const raid = cleanRaids.find((r) => r.slug === raidSlug); const raid = cleanRaids.find((r) => r.slug === raidSlug)
setRaid(raid); setRaid(raid)
return raid; return raid
}); })
}, [setRaids]); }, [setRaids])
// When the element, raid or recency filter changes, // When the element, raid or recency filter changes,
// fetch all teams again. // fetch all teams again.
useDidMountEffect(() => { useDidMountEffect(() => {
setCurrentPage(1); setCurrentPage(1)
fetchTeams({ replace: true }); fetchTeams({ replace: true })
}, [element, raid, recency]); }, [element, raid, recency])
// When the page changes, fetch all teams again. // When the page changes, fetch all teams again.
useDidMountEffect(() => { useDidMountEffect(() => {
// Current page changed // Current page changed
if (currentPage > 1) fetchTeams({ replace: false }); if (currentPage > 1) fetchTeams({ replace: false })
else if (currentPage == 1) fetchTeams({ replace: true }); else if (currentPage == 1) fetchTeams({ replace: true })
}, [currentPage]); }, [currentPage])
// Receive filters from the filter bar // Receive filters from the filter bar
function receiveFilters({ function receiveFilters({
@ -189,68 +189,68 @@ const SavedRoute: React.FC<Props> = (props: Props) => {
raidSlug, raidSlug,
recency, recency,
}: { }: {
element?: number; element?: number
raidSlug?: string; raidSlug?: string
recency?: number; recency?: number
}) { }) {
if (element == 0) setElement(0); if (element == 0) setElement(0)
else if (element) setElement(element); else if (element) setElement(element)
if (raids && raidSlug) { if (raids && raidSlug) {
const raid = raids.find((raid) => raid.slug === raidSlug); const raid = raids.find((raid) => raid.slug === raidSlug)
setRaid(raid); setRaid(raid)
setRaidSlug(raidSlug); setRaidSlug(raidSlug)
} }
if (recency) setRecency(recency); if (recency) setRecency(recency)
} }
// Methods: Favorites // Methods: Favorites
function toggleFavorite(teamId: string, favorited: boolean) { function toggleFavorite(teamId: string, favorited: boolean) {
if (favorited) unsaveFavorite(teamId); if (favorited) unsaveFavorite(teamId)
else saveFavorite(teamId); else saveFavorite(teamId)
} }
function saveFavorite(teamId: string) { function saveFavorite(teamId: string) {
api.saveTeam({ id: teamId, params: headers }).then((response) => { api.saveTeam({ id: teamId, params: headers }).then((response) => {
if (response.status == 201) { if (response.status == 201) {
const index = parties.findIndex((p) => p.id === teamId); const index = parties.findIndex((p) => p.id === teamId)
const party = parties[index]; const party = parties[index]
party.favorited = true; party.favorited = true
let clonedParties = clonedeep(parties); let clonedParties = clonedeep(parties)
clonedParties[index] = party; clonedParties[index] = party
setParties(clonedParties); setParties(clonedParties)
} }
}); })
} }
function unsaveFavorite(teamId: string) { function unsaveFavorite(teamId: string) {
api.unsaveTeam({ id: teamId, params: headers }).then((response) => { api.unsaveTeam({ id: teamId, params: headers }).then((response) => {
if (response.status == 200) { if (response.status == 200) {
const index = parties.findIndex((p) => p.id === teamId); const index = parties.findIndex((p) => p.id === teamId)
const party = parties[index]; const party = parties[index]
party.favorited = false; party.favorited = false
let clonedParties = clonedeep(parties); let clonedParties = clonedeep(parties)
clonedParties.splice(index, 1); clonedParties.splice(index, 1)
setParties(clonedParties); setParties(clonedParties)
} }
}); })
} }
// Methods: Navigation // Methods: Navigation
function handleScroll() { function handleScroll() {
if (window.pageYOffset > 90) setScrolled(true); if (window.pageYOffset > 90) setScrolled(true)
else setScrolled(false); else setScrolled(false)
} }
function goTo(shortcode: string) { function goTo(shortcode: string) {
router.push(`/p/${shortcode}`); router.push(`/p/${shortcode}`)
} }
function renderParties() { function renderParties() {
@ -270,14 +270,14 @@ const SavedRoute: React.FC<Props> = (props: Props) => {
onClick={goTo} onClick={goTo}
onSave={toggleFavorite} onSave={toggleFavorite}
/> />
); )
}); })
} }
return ( return (
<div id="Teams"> <div id="Teams">
<Head> <Head>
<title>{t("saved.title")}</title> <title>{t('saved.title')}</title>
<meta property="og:title" content="Your saved Teams" /> <meta property="og:title" content="Your saved Teams" />
<meta property="og:url" content="https://app.granblue.team/saved" /> <meta property="og:url" content="https://app.granblue.team/saved" />
@ -295,7 +295,7 @@ const SavedRoute: React.FC<Props> = (props: Props) => {
raidSlug={raidSlug ? raidSlug : undefined} raidSlug={raidSlug ? raidSlug : undefined}
recency={recency} recency={recency}
> >
<h1>{t("saved.title")}</h1> <h1>{t('saved.title')}</h1>
</FilterBar> </FilterBar>
<section> <section>
@ -314,25 +314,25 @@ const SavedRoute: React.FC<Props> = (props: Props) => {
{parties.length == 0 ? ( {parties.length == 0 ? (
<div id="NotFound"> <div id="NotFound">
<h2>{t("saved.not_found")}</h2> <h2>{t('saved.not_found')}</h2>
</div> </div>
) : ( ) : (
"" ''
)} )}
</section> </section>
</div> </div>
); )
}; }
export const getServerSidePaths = async () => { export const getServerSidePaths = async () => {
return { return {
paths: [ paths: [
// Object variant: // Object variant:
{ params: { party: "string" } }, { params: { party: 'string' } },
], ],
fallback: true, fallback: true,
}; }
}; }
// prettier-ignore // prettier-ignore
export const getServerSideProps = async ({ req, res, locale, query }: { req: NextApiRequest, res: NextApiResponse, locale: string, query: { [index: string]: string } }) => { export const getServerSideProps = async ({ req, res, locale, query }: { req: NextApiRequest, res: NextApiResponse, locale: string, query: { [index: string]: string } }) => {
@ -399,31 +399,31 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
const organizeRaids = (raids: Raid[]) => { const organizeRaids = (raids: Raid[]) => {
// Set up empty raid for "All raids" // Set up empty raid for "All raids"
const all = { const all = {
id: "0", id: '0',
name: { name: {
en: "All raids", en: 'All raids',
ja: "全て", ja: '全て',
}, },
slug: "all", slug: 'all',
level: 0, level: 0,
group: 0, group: 0,
element: 0, element: 0,
}; }
const numGroups = Math.max.apply( const numGroups = Math.max.apply(
Math, Math,
raids.map((raid) => raid.group) raids.map((raid) => raid.group)
); )
let groupedRaids = []; let groupedRaids = []
for (let i = 0; i <= numGroups; i++) { for (let i = 0; i <= numGroups; i++) {
groupedRaids[i] = raids.filter((raid) => raid.group == i); groupedRaids[i] = raids.filter((raid) => raid.group == i)
} }
return { return {
raids: raids, raids: raids,
sortedRaids: groupedRaids, sortedRaids: groupedRaids,
}; }
}; }
export default SavedRoute; export default SavedRoute

View file

@ -1,120 +1,120 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from 'react'
import Head from "next/head"; import Head from 'next/head'
import { getCookie } from "cookies-next"; import { getCookie } from 'cookies-next'
import { queryTypes, useQueryState } from "next-usequerystate"; import { queryTypes, useQueryState } from 'next-usequerystate'
import { useRouter } from "next/router"; import { useRouter } from 'next/router'
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next'
import InfiniteScroll from "react-infinite-scroll-component"; import InfiniteScroll from 'react-infinite-scroll-component'
import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import clonedeep from "lodash.clonedeep"; import clonedeep from 'lodash.clonedeep'
import api from "~utils/api"; import api from '~utils/api'
import useDidMountEffect from "~utils/useDidMountEffect"; import useDidMountEffect from '~utils/useDidMountEffect'
import { elements, allElement } from "~utils/Element"; import { elements, allElement } from '~utils/Element'
import GridRep from "~components/GridRep"; import GridRep from '~components/GridRep'
import GridRepCollection from "~components/GridRepCollection"; import GridRepCollection from '~components/GridRepCollection'
import FilterBar from "~components/FilterBar"; import FilterBar from '~components/FilterBar'
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from 'next'
interface Props { interface Props {
teams?: { count: number; total_pages: number; results: Party[] }; teams?: { count: number; total_pages: number; results: Party[] }
raids: Raid[]; raids: Raid[]
sortedRaids: Raid[][]; sortedRaids: Raid[][]
} }
const TeamsRoute: React.FC<Props> = (props: Props) => { const TeamsRoute: React.FC<Props> = (props: Props) => {
// Set up cookies // Set up cookies
const cookie = getCookie("account"); const cookie = getCookie('account')
const accountData: AccountCookie = cookie const accountData: AccountCookie = cookie
? JSON.parse(cookie as string) ? JSON.parse(cookie as string)
: null; : null
const headers = accountData const headers = accountData
? { Authorization: `Bearer ${accountData.token}` } ? { Authorization: `Bearer ${accountData.token}` }
: {}; : {}
// Set up router // Set up router
const router = useRouter(); const router = useRouter()
// Import translations // Import translations
const { t } = useTranslation("common"); const { t } = useTranslation('common')
// Set up app-specific states // Set up app-specific states
const [raidsLoading, setRaidsLoading] = useState(true); const [raidsLoading, setRaidsLoading] = useState(true)
const [scrolled, setScrolled] = useState(false); const [scrolled, setScrolled] = useState(false)
// Set up page-specific states // Set up page-specific states
const [parties, setParties] = useState<Party[]>([]); const [parties, setParties] = useState<Party[]>([])
const [raids, setRaids] = useState<Raid[]>(); const [raids, setRaids] = useState<Raid[]>()
const [raid, setRaid] = useState<Raid>(); const [raid, setRaid] = useState<Raid>()
// Set up infinite scrolling-related states // Set up infinite scrolling-related states
const [recordCount, setRecordCount] = useState(0); const [recordCount, setRecordCount] = useState(0)
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1)
const [totalPages, setTotalPages] = useState(1); const [totalPages, setTotalPages] = useState(1)
// Set up filter-specific query states // Set up filter-specific query states
// Recency is in seconds // Recency is in seconds
const [element, setElement] = useQueryState("element", { const [element, setElement] = useQueryState('element', {
defaultValue: -1, defaultValue: -1,
parse: (query: string) => parseElement(query), parse: (query: string) => parseElement(query),
serialize: (value) => serializeElement(value), serialize: (value) => serializeElement(value),
}); })
const [raidSlug, setRaidSlug] = useQueryState("raid", { const [raidSlug, setRaidSlug] = useQueryState('raid', {
defaultValue: "all", defaultValue: 'all',
}); })
const [recency, setRecency] = useQueryState( const [recency, setRecency] = useQueryState(
"recency", 'recency',
queryTypes.integer.withDefault(-1) queryTypes.integer.withDefault(-1)
); )
// Define transformers for element // Define transformers for element
function parseElement(query: string) { function parseElement(query: string) {
let element: TeamElement | undefined = let element: TeamElement | undefined =
query === "all" query === 'all'
? allElement ? allElement
: elements.find((element) => element.name.en.toLowerCase() === query); : elements.find((element) => element.name.en.toLowerCase() === query)
return element ? element.id : -1; return element ? element.id : -1
} }
function serializeElement(value: number | undefined) { function serializeElement(value: number | undefined) {
let name = ""; let name = ''
if (value != undefined) { if (value != undefined) {
if (value == -1) name = allElement.name.en.toLowerCase(); if (value == -1) name = allElement.name.en.toLowerCase()
else name = elements[value].name.en.toLowerCase(); else name = elements[value].name.en.toLowerCase()
} }
return name; return name
} }
// Set the initial parties from props // Set the initial parties from props
useEffect(() => { useEffect(() => {
if (props.teams) { if (props.teams) {
setTotalPages(props.teams.total_pages); setTotalPages(props.teams.total_pages)
setRecordCount(props.teams.count); setRecordCount(props.teams.count)
replaceResults(props.teams.count, props.teams.results); replaceResults(props.teams.count, props.teams.results)
} }
setCurrentPage(1); setCurrentPage(1)
}, []); }, [])
// Add scroll event listener for shadow on FilterBar on mount // Add scroll event listener for shadow on FilterBar on mount
useEffect(() => { useEffect(() => {
window.addEventListener("scroll", handleScroll); window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener("scroll", handleScroll); return () => window.removeEventListener('scroll', handleScroll)
}, []); }, [])
// Handle errors // Handle errors
const handleError = useCallback((error: any) => { const handleError = useCallback((error: any) => {
if (error.response != null) { if (error.response != null) {
console.error(error); console.error(error)
} else { } else {
console.error("There was an error."); console.error('There was an error.')
} }
}, []); }, [])
const fetchTeams = useCallback( const fetchTeams = useCallback(
({ replace }: { replace: boolean }) => { ({ replace }: { replace: boolean }) => {
@ -125,63 +125,63 @@ const TeamsRoute: React.FC<Props> = (props: Props) => {
recency: recency != -1 ? recency : undefined, recency: recency != -1 ? recency : undefined,
page: currentPage, page: currentPage,
}, },
}; }
api.endpoints.parties api.endpoints.parties
.getAll({ ...filters, ...{ headers: headers } }) .getAll({ ...filters, ...{ headers: headers } })
.then((response) => { .then((response) => {
setTotalPages(response.data.total_pages); setTotalPages(response.data.total_pages)
setRecordCount(response.data.count); setRecordCount(response.data.count)
if (replace) if (replace)
replaceResults(response.data.count, response.data.results); replaceResults(response.data.count, response.data.results)
else appendResults(response.data.results); else appendResults(response.data.results)
}) })
.catch((error) => handleError(error)); .catch((error) => handleError(error))
}, },
[currentPage, parties, element, raid, recency] [currentPage, parties, element, raid, recency]
); )
function replaceResults(count: number, list: Party[]) { function replaceResults(count: number, list: Party[]) {
if (count > 0) { if (count > 0) {
setParties(list.sort((a, b) => (a.created_at > b.created_at ? -1 : 1))); setParties(list.sort((a, b) => (a.created_at > b.created_at ? -1 : 1)))
} else { } else {
setParties([]); setParties([])
} }
} }
function appendResults(list: Party[]) { function appendResults(list: Party[]) {
setParties([...parties, ...list]); setParties([...parties, ...list])
} }
// Fetch all raids on mount, then find the raid in the URL if present // Fetch all raids on mount, then find the raid in the URL if present
useEffect(() => { useEffect(() => {
api.endpoints.raids.getAll().then((response) => { api.endpoints.raids.getAll().then((response) => {
const cleanRaids: Raid[] = response.data.map((r: any) => r.raid); const cleanRaids: Raid[] = response.data.map((r: any) => r.raid)
setRaids(cleanRaids); setRaids(cleanRaids)
setRaidsLoading(false); setRaidsLoading(false)
const raid = cleanRaids.find((r) => r.slug === raidSlug); const raid = cleanRaids.find((r) => r.slug === raidSlug)
setRaid(raid); setRaid(raid)
return raid; return raid
}); })
}, [setRaids]); }, [setRaids])
// When the element, raid or recency filter changes, // When the element, raid or recency filter changes,
// fetch all teams again. // fetch all teams again.
useDidMountEffect(() => { useDidMountEffect(() => {
setCurrentPage(1); setCurrentPage(1)
fetchTeams({ replace: true }); fetchTeams({ replace: true })
}, [element, raid, recency]); }, [element, raid, recency])
// When the page changes, fetch all teams again. // When the page changes, fetch all teams again.
useDidMountEffect(() => { useDidMountEffect(() => {
// Current page changed // Current page changed
if (currentPage > 1) fetchTeams({ replace: false }); if (currentPage > 1) fetchTeams({ replace: false })
else if (currentPage == 1) fetchTeams({ replace: true }); else if (currentPage == 1) fetchTeams({ replace: true })
}, [currentPage]); }, [currentPage])
// Receive filters from the filter bar // Receive filters from the filter bar
function receiveFilters({ function receiveFilters({
@ -189,68 +189,68 @@ const TeamsRoute: React.FC<Props> = (props: Props) => {
raidSlug, raidSlug,
recency, recency,
}: { }: {
element?: number; element?: number
raidSlug?: string; raidSlug?: string
recency?: number; recency?: number
}) { }) {
if (element == 0) setElement(0); if (element == 0) setElement(0)
else if (element) setElement(element); else if (element) setElement(element)
if (raids && raidSlug) { if (raids && raidSlug) {
const raid = raids.find((raid) => raid.slug === raidSlug); const raid = raids.find((raid) => raid.slug === raidSlug)
setRaid(raid); setRaid(raid)
setRaidSlug(raidSlug); setRaidSlug(raidSlug)
} }
if (recency) setRecency(recency); if (recency) setRecency(recency)
} }
// Methods: Favorites // Methods: Favorites
function toggleFavorite(teamId: string, favorited: boolean) { function toggleFavorite(teamId: string, favorited: boolean) {
if (favorited) unsaveFavorite(teamId); if (favorited) unsaveFavorite(teamId)
else saveFavorite(teamId); else saveFavorite(teamId)
} }
function saveFavorite(teamId: string) { function saveFavorite(teamId: string) {
api.saveTeam({ id: teamId, params: headers }).then((response) => { api.saveTeam({ id: teamId, params: headers }).then((response) => {
if (response.status == 201) { if (response.status == 201) {
const index = parties.findIndex((p) => p.id === teamId); const index = parties.findIndex((p) => p.id === teamId)
const party = parties[index]; const party = parties[index]
party.favorited = true; party.favorited = true
let clonedParties = clonedeep(parties); let clonedParties = clonedeep(parties)
clonedParties[index] = party; clonedParties[index] = party
setParties(clonedParties); setParties(clonedParties)
} }
}); })
} }
function unsaveFavorite(teamId: string) { function unsaveFavorite(teamId: string) {
api.unsaveTeam({ id: teamId, params: headers }).then((response) => { api.unsaveTeam({ id: teamId, params: headers }).then((response) => {
if (response.status == 200) { if (response.status == 200) {
const index = parties.findIndex((p) => p.id === teamId); const index = parties.findIndex((p) => p.id === teamId)
const party = parties[index]; const party = parties[index]
party.favorited = false; party.favorited = false
let clonedParties = clonedeep(parties); let clonedParties = clonedeep(parties)
clonedParties[index] = party; clonedParties[index] = party
setParties(clonedParties); setParties(clonedParties)
} }
}); })
} }
// Methods: Navigation // Methods: Navigation
function handleScroll() { function handleScroll() {
if (window.pageYOffset > 90) setScrolled(true); if (window.pageYOffset > 90) setScrolled(true)
else setScrolled(false); else setScrolled(false)
} }
function goTo(shortcode: string) { function goTo(shortcode: string) {
router.push(`/p/${shortcode}`); router.push(`/p/${shortcode}`)
} }
function renderParties() { function renderParties() {
@ -270,14 +270,14 @@ const TeamsRoute: React.FC<Props> = (props: Props) => {
onClick={goTo} onClick={goTo}
onSave={toggleFavorite} onSave={toggleFavorite}
/> />
); )
}); })
} }
return ( return (
<div id="Teams"> <div id="Teams">
<Head> <Head>
<title>{t("teams.title")}</title> <title>{t('teams.title')}</title>
<meta property="og:title" content="Discover Teams" /> <meta property="og:title" content="Discover Teams" />
<meta <meta
@ -303,7 +303,7 @@ const TeamsRoute: React.FC<Props> = (props: Props) => {
raidSlug={raidSlug ? raidSlug : undefined} raidSlug={raidSlug ? raidSlug : undefined}
recency={recency} recency={recency}
> >
<h1>{t("teams.title")}</h1> <h1>{t('teams.title')}</h1>
</FilterBar> </FilterBar>
<section> <section>
@ -322,25 +322,25 @@ const TeamsRoute: React.FC<Props> = (props: Props) => {
{parties.length == 0 ? ( {parties.length == 0 ? (
<div id="NotFound"> <div id="NotFound">
<h2>{t("teams.not_found")}</h2> <h2>{t('teams.not_found')}</h2>
</div> </div>
) : ( ) : (
"" ''
)} )}
</section> </section>
</div> </div>
); )
}; }
export const getServerSidePaths = async () => { export const getServerSidePaths = async () => {
return { return {
paths: [ paths: [
// Object variant: // Object variant:
{ params: { party: "string" } }, { params: { party: 'string' } },
], ],
fallback: true, fallback: true,
}; }
}; }
// prettier-ignore // prettier-ignore
export const getServerSideProps = async ({ req, res, locale, query }: { req: NextApiRequest, res: NextApiResponse, locale: string, query: { [index: string]: string } }) => { export const getServerSideProps = async ({ req, res, locale, query }: { req: NextApiRequest, res: NextApiResponse, locale: string, query: { [index: string]: string } }) => {
@ -407,31 +407,31 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
const organizeRaids = (raids: Raid[]) => { const organizeRaids = (raids: Raid[]) => {
// Set up empty raid for "All raids" // Set up empty raid for "All raids"
const all = { const all = {
id: "0", id: '0',
name: { name: {
en: "All raids", en: 'All raids',
ja: "全て", ja: '全て',
}, },
slug: "all", slug: 'all',
level: 0, level: 0,
group: 0, group: 0,
element: 0, element: 0,
}; }
const numGroups = Math.max.apply( const numGroups = Math.max.apply(
Math, Math,
raids.map((raid) => raid.group) raids.map((raid) => raid.group)
); )
let groupedRaids = []; let groupedRaids = []
for (let i = 0; i <= numGroups; i++) { for (let i = 0; i <= numGroups; i++) {
groupedRaids[i] = raids.filter((raid) => raid.group == i); groupedRaids[i] = raids.filter((raid) => raid.group == i)
} }
return { return {
raids: raids, raids: raids,
sortedRaids: groupedRaids, sortedRaids: groupedRaids,
}; }
}; }
export default TeamsRoute; export default TeamsRoute

View file

@ -1,5 +1,5 @@
interface AccountCookie { interface AccountCookie {
userId: string; userId: string
username: string; username: string
token: string; token: string
} }

18
types/AxSkill.d.ts vendored
View file

@ -1,12 +1,12 @@
interface AxSkill { interface AxSkill {
name: { name: {
[key: string]: string; [key: string]: string
en: string; en: string
ja: string; ja: string
}; }
id: number; id: number
minValue: number; minValue: number
maxValue: number; maxValue: number
suffix?: string; suffix?: string
secondary?: AxSkill[]; secondary?: AxSkill[]
} }

62
types/Character.d.ts vendored
View file

@ -1,40 +1,40 @@
interface Character { interface Character {
type: "character"; type: 'character'
id: string; id: string
granblue_id: string; granblue_id: string
character_id: readonly number[]; character_id: readonly number[]
element: number; element: number
rarity: number; rarity: number
gender: number; gender: number
max_level: number; max_level: number
name: { name: {
[key: string]: string; [key: string]: string
en: string; en: string
ja: string; ja: string
}; }
hp: { hp: {
min_hp: number; min_hp: number
max_hp: number; max_hp: number
max_hp_flb: number; max_hp_flb: number
}; }
atk: { atk: {
min_atk: number; min_atk: number
max_atk: number; max_atk: number
max_atk_flb: number; max_atk_flb: number
}; }
uncap: { uncap: {
flb: boolean; flb: boolean
ulb: boolean; ulb: boolean
}; }
race: { race: {
race1: number; race1: number
race2: number; race2: number
}; }
proficiency: { proficiency: {
proficiency1: number; proficiency1: number
proficiency2: number; proficiency2: number
}; }
position?: number; position?: number
special: boolean; special: boolean
} }

View file

@ -1,4 +1,4 @@
interface CheckedState { interface CheckedState {
id: number; id: number
checked: boolean; checked: boolean
} }

View file

@ -1,10 +1,10 @@
interface ElementState { interface ElementState {
[key: string]: CheckedState; [key: string]: CheckedState
null: CheckedState; null: CheckedState
wind: CheckedState; wind: CheckedState
fire: CheckedState; fire: CheckedState
water: CheckedState; water: CheckedState
earth: CheckedState; earth: CheckedState
dark: CheckedState; dark: CheckedState
light: CheckedState; light: CheckedState
} }

View file

@ -1 +1 @@
type GridArray<T> = { [key: number]: T | undefined }; type GridArray<T> = { [key: number]: T | undefined }

View file

@ -1,6 +1,6 @@
interface GridCharacter { interface GridCharacter {
id: string; id: string
position: number; position: number
object: Character; object: Character
uncap_level: number; uncap_level: number
} }

12
types/GridSummon.d.ts vendored
View file

@ -1,8 +1,8 @@
interface GridSummon { interface GridSummon {
id: string; id: string
main: boolean; main: boolean
friend: boolean; friend: boolean
position: number; position: number
object: Summon; object: Summon
uncap_level: number; uncap_level: number
} }

16
types/GridWeapon.d.ts vendored
View file

@ -1,10 +1,10 @@
interface GridWeapon { interface GridWeapon {
id: string; id: string
mainhand: boolean; mainhand: boolean
position: number; position: number
object: Weapon; object: Weapon
uncap_level: number; uncap_level: number
element: number; element: number
weapon_keys?: Array<WeaponKey>; weapon_keys?: Array<WeaponKey>
ax?: Array<SimpleAxSkill>; ax?: Array<SimpleAxSkill>
} }

24
types/Job.d.ts vendored
View file

@ -1,16 +1,16 @@
interface Job { interface Job {
id: string; id: string
row: string; row: string
ml: boolean; ml: boolean
order: number; order: number
name: { name: {
[key: string]: string; [key: string]: string
en: string; en: string
ja: string; ja: string
}; }
proficiency: { proficiency: {
proficiency1: number; proficiency1: number
proficiency2: number; proficiency2: number
}; }
base_job?: Job; base_job?: Job
} }

26
types/JobSkill.d.ts vendored
View file

@ -1,16 +1,16 @@
interface JobSkill { interface JobSkill {
id: string; id: string
job: Job; job: Job
name: { name: {
[key: string]: string; [key: string]: string
en: string; en: string
ja: string; ja: string
}; }
slug: string; slug: string
color: number; color: number
main: boolean; main: boolean
base: boolean; base: boolean
sub: boolean; sub: boolean
emp: boolean; emp: boolean
order: number; order: number
} }

View file

@ -1,3 +1,3 @@
type OnClickEvent = ( type OnClickEvent = (
event: React.MouseEvent<HTMLDivElement, MouseEvent> event: React.MouseEvent<HTMLDivElement, MouseEvent>
) => void; ) => void

42
types/Party.d.ts vendored
View file

@ -1,25 +1,25 @@
type JobSkillObject = { type JobSkillObject = {
[key: number]: JobSkill | undefined; [key: number]: JobSkill | undefined
0: JobSkill | undefined; 0: JobSkill | undefined
1: JobSkill | undefined; 1: JobSkill | undefined
2: JobSkill | undefined; 2: JobSkill | undefined
3: JobSkill | undefined; 3: JobSkill | undefined
}; }
interface Party { interface Party {
id: string; id: string
name: string; name: string
description: string; description: string
raid: Raid; raid: Raid
job: Job; job: Job
job_skills: JobSkillObject; job_skills: JobSkillObject
shortcode: string; shortcode: string
extra: boolean; extra: boolean
favorited: boolean; favorited: boolean
characters: Array<GridCharacter>; characters: Array<GridCharacter>
weapons: Array<GridWeapon>; weapons: Array<GridWeapon>
summons: Array<GridSummon>; summons: Array<GridSummon>
user: User; user: User
created_at: string; created_at: string
updated_at: string; updated_at: string
} }

View file

@ -1,13 +1,13 @@
interface ProficiencyState { interface ProficiencyState {
[key: string]: CheckedState; [key: string]: CheckedState
sabre: CheckedState; sabre: CheckedState
dagger: CheckedState; dagger: CheckedState
spear: CheckedState; spear: CheckedState
axe: CheckedState; axe: CheckedState
staff: CheckedState; staff: CheckedState
melee: CheckedState; melee: CheckedState
gun: CheckedState; gun: CheckedState
bow: CheckedState; bow: CheckedState
harp: CheckedState; harp: CheckedState
katana: CheckedState; katana: CheckedState
} }

18
types/Raid.d.ts vendored
View file

@ -1,12 +1,12 @@
interface Raid { interface Raid {
id: string; id: string
name: { name: {
[key: string]: string; [key: string]: string
en: string; en: string
ja: string; ja: string
}; }
slug: string; slug: string
level: number; level: number
group: number; group: number
element: number; element: number
} }

View file

@ -1,5 +1,5 @@
interface RarityState { interface RarityState {
[key: string]: CheckedState; [key: string]: CheckedState
sr: CheckedState; sr: CheckedState
ssr: CheckedState; ssr: CheckedState
} }

View file

@ -1,4 +1,4 @@
interface SimpleAxSkill { interface SimpleAxSkill {
modifier: number; modifier: number
strength: number; strength: number
} }

46
types/Summon.d.ts vendored
View file

@ -1,30 +1,30 @@
interface Summon { interface Summon {
type: "summon"; type: 'summon'
id: string; id: string
granblue_id: number; granblue_id: number
element: number; element: number
max_level: number; max_level: number
name: { name: {
[key: string]: string; [key: string]: string
en: string; en: string
ja: string; ja: string
}; }
hp: { hp: {
min_hp: number; min_hp: number
max_hp: number; max_hp: number
max_hp_flb: number; max_hp_flb: number
max_hp_ulb: number; max_hp_ulb: number
}; }
atk: { atk: {
min_atk: number; min_atk: number
max_atk: number; max_atk: number
max_atk_flb: number; max_atk_flb: number
max_atk_ulb: number; max_atk_ulb: number
}; }
uncap: { uncap: {
flb: boolean; flb: boolean
ulb: boolean; ulb: boolean
}; }
position?: number; position?: number
} }

View file

@ -1,7 +1,7 @@
interface TeamElement { interface TeamElement {
id: number; id: number
name: { name: {
en: string; en: string
ja: string; ja: string
}; }
} }

16
types/User.d.ts vendored
View file

@ -1,11 +1,11 @@
interface User { interface User {
id: string; id: string
username: string; username: string
granblueId: number; granblueId: number
picture: { picture: {
picture: string; picture: string
element: string; element: string
}; }
gender: number; gender: number
private: boolean; private: boolean
} }

View file

@ -1,6 +1,6 @@
interface UserCookie { interface UserCookie {
picture: string; picture: string
element: string; element: string
language: string; language: string
gender: number; gender: number
} }

54
types/Weapon.d.ts vendored
View file

@ -1,34 +1,34 @@
interface Weapon { interface Weapon {
type: "weapon"; type: 'weapon'
id: string; id: string
granblue_id: number; granblue_id: number
element: number; element: number
proficiency: number; proficiency: number
max_level: number; max_level: number
max_skill_level: number; max_skill_level: number
series: number; series: number
ax: number; ax: number
name: { name: {
[key: string]: string; [key: string]: string
en: string; en: string
ja: string; ja: string
}; }
hp: { hp: {
min_hp: number; min_hp: number
max_hp: number; max_hp: number
max_hp_flb: number; max_hp_flb: number
max_hp_ulb: number; max_hp_ulb: number
}; }
atk: { atk: {
min_atk: number; min_atk: number
max_atk: number; max_atk: number
max_atk_flb: number; max_atk_flb: number
max_atk_ulb: number; max_atk_ulb: number
}; }
uncap: { uncap: {
flb: boolean; flb: boolean
ulb: boolean; ulb: boolean
}; }
position?: number; position?: number
} }

18
types/WeaponKey.d.ts vendored
View file

@ -1,12 +1,12 @@
interface WeaponKey { interface WeaponKey {
id: string; id: string
name: { name: {
[key: string]: string; [key: string]: string
en: string; en: string
ja: string; ja: string
}; }
series: integer; series: integer
slot: integer; slot: integer
group: integer; group: integer
order: integer; order: integer
} }

View file

@ -1,27 +1,27 @@
interface WeaponSeriesState { interface WeaponSeriesState {
[key: string]: CheckedState; [key: string]: CheckedState
seraphic: CheckedState; seraphic: CheckedState
grand: CheckedState; grand: CheckedState
opus: CheckedState; opus: CheckedState
draconic: CheckedState; draconic: CheckedState
ultima: CheckedState; ultima: CheckedState
bahamut: CheckedState; bahamut: CheckedState
omega: CheckedState; omega: CheckedState
primal: CheckedState; primal: CheckedState
olden_primal: CheckedState; olden_primal: CheckedState
militis: CheckedState; militis: CheckedState
beast: CheckedState; beast: CheckedState
rose: CheckedState; rose: CheckedState
xeno: CheckedState; xeno: CheckedState
hollowsky: CheckedState; hollowsky: CheckedState
astral: CheckedState; astral: CheckedState
epic: CheckedState; epic: CheckedState
ennead: CheckedState; ennead: CheckedState
cosmos: CheckedState; cosmos: CheckedState
ancestral: CheckedState; ancestral: CheckedState
superlative: CheckedState; superlative: CheckedState
vintage: CheckedState; vintage: CheckedState
class_champion: CheckedState; class_champion: CheckedState
sephira: CheckedState; sephira: CheckedState
new_world: CheckedState; new_world: CheckedState
} }

View file

@ -1,2 +1,2 @@
declare module "*.jpg"; declare module '*.jpg'
declare module "*.svg"; declare module '*.svg'

16
types/index.d.ts vendored
View file

@ -1,9 +1,9 @@
export type SearchableObject = Character | Weapon | Summon | JobSkill; export type SearchableObject = Character | Weapon | Summon | JobSkill
export type SearchableObjectArray = (Character | Weapon | Summon | JobSkill)[]; export type SearchableObjectArray = (Character | Weapon | Summon | JobSkill)[]
export type JobSkillObject = { export type JobSkillObject = {
[key: number]: JobSkill | undefined; [key: number]: JobSkill | undefined
0: JobSkill | undefined; 0: JobSkill | undefined
1: JobSkill | undefined; 1: JobSkill | undefined
2: JobSkill | undefined; 2: JobSkill | undefined
3: JobSkill | undefined; 3: JobSkill | undefined
}; }

View file

@ -1,59 +1,59 @@
export const allElement: TeamElement = { export const allElement: TeamElement = {
id: -1, id: -1,
name: { name: {
en: "All", en: 'All',
ja: "全s", ja: '全s',
}, },
}; }
export const elements: TeamElement[] = [ export const elements: TeamElement[] = [
{ {
id: 0, id: 0,
name: { name: {
en: "Null", en: 'Null',
ja: "無", ja: '無',
}, },
}, },
{ {
id: 1, id: 1,
name: { name: {
en: "Wind", en: 'Wind',
ja: "風", ja: '風',
}, },
}, },
{ {
id: 2, id: 2,
name: { name: {
en: "Fire", en: 'Fire',
ja: "火", ja: '火',
}, },
}, },
{ {
id: 3, id: 3,
name: { name: {
en: "Water", en: 'Water',
ja: "水", ja: '水',
}, },
}, },
{ {
id: 4, id: 4,
name: { name: {
en: "Earth", en: 'Earth',
ja: "土", ja: '土',
}, },
}, },
{ {
id: 5, id: 5,
name: { name: {
en: "Dark", en: 'Dark',
ja: "闇", ja: '闇',
}, },
}, },
{ {
id: 6, id: 6,
name: { name: {
en: "Light", en: 'Light',
ja: "光", ja: '光',
}, },
}, },
]; ]

View file

@ -1,20 +1,20 @@
import { proxy } from "valtio"; import { proxy } from 'valtio'
interface AccountState { interface AccountState {
[key: string]: any; [key: string]: any
account: { account: {
authorized: boolean; authorized: boolean
user: user:
| { | {
id: string; id: string
username: string; username: string
picture: string; picture: string
element: string; element: string
gender: number; gender: number
}
| undefined
} }
| undefined;
};
} }
export const initialAccountState: AccountState = { export const initialAccountState: AccountState = {
@ -22,6 +22,6 @@ export const initialAccountState: AccountState = {
authorized: false, authorized: false,
user: undefined, user: undefined,
}, },
}; }
export const accountState = proxy(initialAccountState); export const accountState = proxy(initialAccountState)

View file

@ -1,62 +1,62 @@
import { proxy } from "valtio"; import { proxy } from 'valtio'
import { JobSkillObject } from "~types"; import { JobSkillObject } from '~types'
const emptyJob: Job = { const emptyJob: Job = {
id: "-1", id: '-1',
row: "", row: '',
ml: false, ml: false,
order: 0, order: 0,
name: { name: {
en: "", en: '',
ja: "", ja: '',
}, },
proficiency: { proficiency: {
proficiency1: 0, proficiency1: 0,
proficiency2: 0, proficiency2: 0,
}, },
}; }
interface AppState { interface AppState {
[key: string]: any; [key: string]: any
party: { party: {
id: string | undefined; id: string | undefined
editable: boolean; editable: boolean
detailsVisible: boolean; detailsVisible: boolean
name: string | undefined; name: string | undefined
description: string | undefined; description: string | undefined
job: Job; job: Job
jobSkills: JobSkillObject; jobSkills: JobSkillObject
raid: Raid | undefined; raid: Raid | undefined
element: number; element: number
extra: boolean; extra: boolean
user: User | undefined; user: User | undefined
favorited: boolean; favorited: boolean
created_at: string; created_at: string
updated_at: string; updated_at: string
}; }
grid: { grid: {
weapons: { weapons: {
mainWeapon: GridWeapon | undefined; mainWeapon: GridWeapon | undefined
allWeapons: GridArray<GridWeapon>; allWeapons: GridArray<GridWeapon>
}; }
summons: { summons: {
mainSummon: GridSummon | undefined; mainSummon: GridSummon | undefined
friendSummon: GridSummon | undefined; friendSummon: GridSummon | undefined
allSummons: GridArray<GridSummon>; allSummons: GridArray<GridSummon>
}; }
characters: GridArray<GridCharacter>; characters: GridArray<GridCharacter>
}; }
search: { search: {
recents: { recents: {
characters: Character[]; characters: Character[]
weapons: Weapon[]; weapons: Weapon[]
summons: Summon[]; summons: Summon[]
}; }
}; }
raids: Raid[]; raids: Raid[]
jobs: Job[]; jobs: Job[]
jobSkills: JobSkill[]; jobSkills: JobSkill[]
} }
export const initialAppState: AppState = { export const initialAppState: AppState = {
@ -103,6 +103,6 @@ export const initialAppState: AppState = {
raids: [], raids: [],
jobs: [], jobs: [],
jobSkills: [], jobSkills: [],
}; }
export const appState = proxy(initialAppState); export const appState = proxy(initialAppState)

View file

@ -2,100 +2,100 @@ export const axData: AxSkill[][] = [
[ [
{ {
name: { name: {
en: "ATK", en: 'ATK',
ja: "攻撃", ja: '攻撃',
}, },
id: 0, id: 0,
minValue: 1, minValue: 1,
maxValue: 3.5, maxValue: 3.5,
suffix: "%", suffix: '%',
secondary: [ secondary: [
{ {
name: { name: {
en: "C.A. DMG", en: 'C.A. DMG',
ja: "奥義ダメ", ja: '奥義ダメ',
}, },
id: 3, id: 3,
minValue: 2, minValue: 2,
maxValue: 4, maxValue: 4,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Double Attack Rate", en: 'Double Attack Rate',
ja: "DA確率", ja: 'DA確率',
}, },
id: 5, id: 5,
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Triple Attack Rate", en: 'Triple Attack Rate',
ja: "TA確率", ja: 'TA確率',
}, },
id: 6, id: 6,
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Skill DMG Cap", en: 'Skill DMG Cap',
ja: "アビ上限", ja: 'アビ上限',
}, },
id: 7, id: 7,
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
suffix: "%", suffix: '%',
}, },
], ],
}, },
{ {
name: { name: {
en: "DEF", en: 'DEF',
ja: "防御", ja: '防御',
}, },
id: 1, id: 1,
minValue: 1, minValue: 1,
maxValue: 8, maxValue: 8,
suffix: "%", suffix: '%',
secondary: [ secondary: [
{ {
name: { name: {
en: "HP", en: 'HP',
ja: "HP", ja: 'HP',
}, },
id: 2, id: 2,
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Debuff Resistance", en: 'Debuff Resistance',
ja: "弱体耐性", ja: '弱体耐性',
}, },
id: 9, id: 9,
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Healing", en: 'Healing',
ja: "回復性能", ja: '回復性能',
}, },
id: 10, id: 10,
minValue: 2, minValue: 2,
maxValue: 5, maxValue: 5,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Enmity", en: 'Enmity',
ja: "背水", ja: '背水',
}, },
id: 11, id: 11,
minValue: 1, minValue: 1,
@ -105,48 +105,48 @@ export const axData: AxSkill[][] = [
}, },
{ {
name: { name: {
en: "HP", en: 'HP',
ja: "HP", ja: 'HP',
}, },
id: 2, id: 2,
minValue: 1, minValue: 1,
maxValue: 11, maxValue: 11,
suffix: "%", suffix: '%',
secondary: [ secondary: [
{ {
name: { name: {
en: "DEF", en: 'DEF',
ja: "防御", ja: '防御',
}, },
id: 1, id: 1,
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Debuff Resistance", en: 'Debuff Resistance',
ja: "弱体耐性", ja: '弱体耐性',
}, },
id: 9, id: 9,
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Healing", en: 'Healing',
ja: "回復性能", ja: '回復性能',
}, },
id: 10, id: 10,
minValue: 2, minValue: 2,
maxValue: 5, maxValue: 5,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Stamina", en: 'Stamina',
ja: "渾身", ja: '渾身',
}, },
id: 12, id: 12,
minValue: 1, minValue: 1,
@ -156,48 +156,48 @@ export const axData: AxSkill[][] = [
}, },
{ {
name: { name: {
en: "C.A. DMG", en: 'C.A. DMG',
ja: "奥義ダメ", ja: '奥義ダメ',
}, },
id: 3, id: 3,
minValue: 2, minValue: 2,
maxValue: 8.5, maxValue: 8.5,
suffix: "%", suffix: '%',
secondary: [ secondary: [
{ {
name: { name: {
en: "ATK", en: 'ATK',
ja: "攻撃", ja: '攻撃',
}, },
id: 0, id: 0,
minValue: 1, minValue: 1,
maxValue: 1.5, maxValue: 1.5,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Elemental ATK", en: 'Elemental ATK',
ja: "全属性攻撃力", ja: '全属性攻撃力',
}, },
id: 13, id: 13,
minValue: 1, minValue: 1,
maxValue: 5, maxValue: 5,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "C.A. DMG Cap", en: 'C.A. DMG Cap',
ja: "奥義上限", ja: '奥義上限',
}, },
id: 8, id: 8,
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Stamina", en: 'Stamina',
ja: "渾身", ja: '渾身',
}, },
id: 12, id: 12,
minValue: 1, minValue: 1,
@ -207,53 +207,53 @@ export const axData: AxSkill[][] = [
}, },
{ {
name: { name: {
en: "Multiattack Rate", en: 'Multiattack Rate',
ja: "連撃率", ja: '連撃率',
}, },
id: 4, id: 4,
minValue: 1, minValue: 1,
maxValue: 4, maxValue: 4,
suffix: "%", suffix: '%',
secondary: [ secondary: [
{ {
name: { name: {
en: "C.A. DMG", en: 'C.A. DMG',
ja: "奥義ダメ", ja: '奥義ダメ',
}, },
id: 3, id: 3,
minValue: 2, minValue: 2,
maxValue: 4, maxValue: 4,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Elemental ATK", en: 'Elemental ATK',
ja: "全属性攻撃力", ja: '全属性攻撃力',
}, },
id: 13, id: 13,
minValue: 1, minValue: 1,
maxValue: 5, maxValue: 5,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Double Attack Rate", en: 'Double Attack Rate',
ja: "DA確率", ja: 'DA確率',
}, },
id: 5, id: 5,
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Triple Attack Rate", en: 'Triple Attack Rate',
ja: "TA確率", ja: 'TA確率',
}, },
id: 6, id: 6,
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
suffix: "%", suffix: '%',
}, },
], ],
}, },
@ -261,48 +261,48 @@ export const axData: AxSkill[][] = [
[ [
{ {
name: { name: {
en: "ATK", en: 'ATK',
ja: "攻撃", ja: '攻撃',
}, },
id: 0, id: 0,
minValue: 1, minValue: 1,
maxValue: 3.5, maxValue: 3.5,
suffix: "%", suffix: '%',
secondary: [ secondary: [
{ {
name: { name: {
en: "C.A. DMG", en: 'C.A. DMG',
ja: "奥義ダメ", ja: '奥義ダメ',
}, },
id: 3, id: 3,
minValue: 2, minValue: 2,
maxValue: 8.5, maxValue: 8.5,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Multiattack Rate", en: 'Multiattack Rate',
ja: "連撃確率", ja: '連撃確率',
}, },
id: 4, id: 4,
minValue: 1.5, minValue: 1.5,
maxValue: 4, maxValue: 4,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Normal ATK DMG Cap", en: 'Normal ATK DMG Cap',
ja: "通常ダメ上限", ja: '通常ダメ上限',
}, },
id: 14, id: 14,
minValue: 0.5, minValue: 0.5,
maxValue: 1.5, maxValue: 1.5,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Supplemental Skill DMG", en: 'Supplemental Skill DMG',
ja: "アビ与ダメ上昇", ja: 'アビ与ダメ上昇',
}, },
id: 15, id: 15,
minValue: 1, minValue: 1,
@ -312,48 +312,48 @@ export const axData: AxSkill[][] = [
}, },
{ {
name: { name: {
en: "DEF", en: 'DEF',
ja: "防御", ja: '防御',
}, },
id: 1, id: 1,
minValue: 1, minValue: 1,
maxValue: 8, maxValue: 8,
suffix: "%", suffix: '%',
secondary: [ secondary: [
{ {
name: { name: {
en: "Elemental DMG Reduction", en: 'Elemental DMG Reduction',
ja: "属性ダメ軽減", ja: '属性ダメ軽減',
}, },
id: 17, id: 17,
minValue: 1, minValue: 1,
maxValue: 5, maxValue: 5,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Debuff Resistance", en: 'Debuff Resistance',
ja: "弱体耐性", ja: '弱体耐性',
}, },
id: 9, id: 9,
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Healing", en: 'Healing',
ja: "回復性能", ja: '回復性能',
}, },
id: 10, id: 10,
minValue: 2, minValue: 2,
maxValue: 5, maxValue: 5,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Enmity", en: 'Enmity',
ja: "背水", ja: '背水',
}, },
id: 11, id: 11,
minValue: 1, minValue: 1,
@ -363,48 +363,48 @@ export const axData: AxSkill[][] = [
}, },
{ {
name: { name: {
en: "HP", en: 'HP',
ja: "HP", ja: 'HP',
}, },
id: 2, id: 2,
minValue: 1, minValue: 1,
maxValue: 11, maxValue: 11,
suffix: "%", suffix: '%',
secondary: [ secondary: [
{ {
name: { name: {
en: "Elemental DMG Reduction", en: 'Elemental DMG Reduction',
ja: "属性ダメ軽減", ja: '属性ダメ軽減',
}, },
id: 17, id: 17,
minValue: 1, minValue: 1,
maxValue: 5, maxValue: 5,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Debuff Resistance", en: 'Debuff Resistance',
ja: "弱体耐性", ja: '弱体耐性',
}, },
id: 9, id: 9,
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Healing", en: 'Healing',
ja: "回復性能", ja: '回復性能',
}, },
id: 10, id: 10,
minValue: 2, minValue: 2,
maxValue: 5, maxValue: 5,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Stamina", en: 'Stamina',
ja: "渾身", ja: '渾身',
}, },
id: 12, id: 12,
minValue: 1, minValue: 1,
@ -414,28 +414,28 @@ export const axData: AxSkill[][] = [
}, },
{ {
name: { name: {
en: "C.A. DMG", en: 'C.A. DMG',
ja: "奥義ダメ", ja: '奥義ダメ',
}, },
id: 3, id: 3,
minValue: 2, minValue: 2,
maxValue: 8.5, maxValue: 8.5,
suffix: "%", suffix: '%',
secondary: [ secondary: [
{ {
name: { name: {
en: "Multiattack Rate", en: 'Multiattack Rate',
ja: "連撃率", ja: '連撃率',
}, },
id: 4, id: 4,
minValue: 1.5, minValue: 1.5,
maxValue: 4, maxValue: 4,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Supplemental Skill DMG", en: 'Supplemental Skill DMG',
ja: "アビ与ダメ上昇", ja: 'アビ与ダメ上昇',
}, },
id: 15, id: 15,
minValue: 1, minValue: 1,
@ -443,8 +443,8 @@ export const axData: AxSkill[][] = [
}, },
{ {
name: { name: {
en: "Supplemental C.A. DMG", en: 'Supplemental C.A. DMG',
ja: "奥義与ダメ上昇", ja: '奥義与ダメ上昇',
}, },
id: 16, id: 16,
minValue: 1, minValue: 1,
@ -452,8 +452,8 @@ export const axData: AxSkill[][] = [
}, },
{ {
name: { name: {
en: "Stamina", en: 'Stamina',
ja: "渾身", ja: '渾身',
}, },
id: 12, id: 12,
minValue: 1, minValue: 1,
@ -463,18 +463,18 @@ export const axData: AxSkill[][] = [
}, },
{ {
name: { name: {
en: "Multiattack Rate", en: 'Multiattack Rate',
ja: "連撃率", ja: '連撃率',
}, },
id: 4, id: 4,
minValue: 1, minValue: 1,
maxValue: 4, maxValue: 4,
suffix: "%", suffix: '%',
secondary: [ secondary: [
{ {
name: { name: {
en: "Supplemental C.A. DMG", en: 'Supplemental C.A. DMG',
ja: "奥義与ダメ上昇", ja: '奥義与ダメ上昇',
}, },
id: 16, id: 16,
minValue: 1, minValue: 1,
@ -482,18 +482,18 @@ export const axData: AxSkill[][] = [
}, },
{ {
name: { name: {
en: "Normal ATK DMG Cap", en: 'Normal ATK DMG Cap',
ja: "通常ダメ上限", ja: '通常ダメ上限',
}, },
id: 14, id: 14,
minValue: 0.5, minValue: 0.5,
maxValue: 1.5, maxValue: 1.5,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Stamina", en: 'Stamina',
ja: "渾身", ja: '渾身',
}, },
id: 12, id: 12,
minValue: 1, minValue: 1,
@ -501,8 +501,8 @@ export const axData: AxSkill[][] = [
}, },
{ {
name: { name: {
en: "Enmity", en: 'Enmity',
ja: "背水", ja: '背水',
}, },
id: 11, id: 11,
minValue: 1, minValue: 1,
@ -514,100 +514,100 @@ export const axData: AxSkill[][] = [
[ [
{ {
name: { name: {
en: "ATK", en: 'ATK',
ja: "攻撃", ja: '攻撃',
}, },
id: 0, id: 0,
minValue: 1, minValue: 1,
maxValue: 3.5, maxValue: 3.5,
suffix: "%", suffix: '%',
secondary: [ secondary: [
{ {
name: { name: {
en: "C.A. DMG", en: 'C.A. DMG',
ja: "奥義ダメ", ja: '奥義ダメ',
}, },
id: 3, id: 3,
minValue: 2, minValue: 2,
maxValue: 4, maxValue: 4,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Double Attack Rate", en: 'Double Attack Rate',
ja: "DA確率", ja: 'DA確率',
}, },
id: 5, id: 5,
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Triple Attack Rate", en: 'Triple Attack Rate',
ja: "TA確率", ja: 'TA確率',
}, },
id: 6, id: 6,
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Skill DMG Cap", en: 'Skill DMG Cap',
ja: "アビ上限", ja: 'アビ上限',
}, },
id: 7, id: 7,
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
suffix: "%", suffix: '%',
}, },
], ],
}, },
{ {
name: { name: {
en: "DEF", en: 'DEF',
ja: "防御", ja: '防御',
}, },
id: 1, id: 1,
minValue: 1, minValue: 1,
maxValue: 8, maxValue: 8,
suffix: "%", suffix: '%',
secondary: [ secondary: [
{ {
name: { name: {
en: "HP", en: 'HP',
ja: "HP", ja: 'HP',
}, },
id: 2, id: 2,
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Debuff Resistance", en: 'Debuff Resistance',
ja: "弱体耐性", ja: '弱体耐性',
}, },
id: 9, id: 9,
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Healing", en: 'Healing',
ja: "回復性能", ja: '回復性能',
}, },
id: 10, id: 10,
minValue: 2, minValue: 2,
maxValue: 5, maxValue: 5,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Enmity", en: 'Enmity',
ja: "背水", ja: '背水',
}, },
id: 11, id: 11,
minValue: 1, minValue: 1,
@ -617,48 +617,48 @@ export const axData: AxSkill[][] = [
}, },
{ {
name: { name: {
en: "HP", en: 'HP',
ja: "HP", ja: 'HP',
}, },
id: 2, id: 2,
minValue: 1, minValue: 1,
maxValue: 11, maxValue: 11,
suffix: "%", suffix: '%',
secondary: [ secondary: [
{ {
name: { name: {
en: "DEF", en: 'DEF',
ja: "防御", ja: '防御',
}, },
id: 1, id: 1,
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Debuff Resistance", en: 'Debuff Resistance',
ja: "弱体耐性", ja: '弱体耐性',
}, },
id: 9, id: 9,
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Healing", en: 'Healing',
ja: "回復性能", ja: '回復性能',
}, },
id: 10, id: 10,
minValue: 2, minValue: 2,
maxValue: 5, maxValue: 5,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Stamina", en: 'Stamina',
ja: "渾身", ja: '渾身',
}, },
id: 12, id: 12,
minValue: 1, minValue: 1,
@ -668,48 +668,48 @@ export const axData: AxSkill[][] = [
}, },
{ {
name: { name: {
en: "C.A. DMG", en: 'C.A. DMG',
ja: "奥義ダメ", ja: '奥義ダメ',
}, },
id: 3, id: 3,
minValue: 2, minValue: 2,
maxValue: 8.5, maxValue: 8.5,
suffix: "%", suffix: '%',
secondary: [ secondary: [
{ {
name: { name: {
en: "ATK", en: 'ATK',
ja: "攻撃", ja: '攻撃',
}, },
id: 0, id: 0,
minValue: 1, minValue: 1,
maxValue: 1.5, maxValue: 1.5,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Elemental ATK", en: 'Elemental ATK',
ja: "全属性攻撃力", ja: '全属性攻撃力',
}, },
id: 13, id: 13,
minValue: 1, minValue: 1,
maxValue: 5, maxValue: 5,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "C.A. DMG Cap", en: 'C.A. DMG Cap',
ja: "奥義上限", ja: '奥義上限',
}, },
id: 8, id: 8,
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Stamina", en: 'Stamina',
ja: "渾身", ja: '渾身',
}, },
id: 12, id: 12,
minValue: 1, minValue: 1,
@ -719,75 +719,75 @@ export const axData: AxSkill[][] = [
}, },
{ {
name: { name: {
en: "Multiattack Rate", en: 'Multiattack Rate',
ja: "連撃率", ja: '連撃率',
}, },
id: 4, id: 4,
minValue: 1, minValue: 1,
maxValue: 4, maxValue: 4,
suffix: "%", suffix: '%',
secondary: [ secondary: [
{ {
name: { name: {
en: "C.A. DMG", en: 'C.A. DMG',
ja: "奥義ダメ", ja: '奥義ダメ',
}, },
id: 3, id: 3,
minValue: 2, minValue: 2,
maxValue: 4, maxValue: 4,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Elemental ATK", en: 'Elemental ATK',
ja: "全属性攻撃力", ja: '全属性攻撃力',
}, },
id: 13, id: 13,
minValue: 1, minValue: 1,
maxValue: 5, maxValue: 5,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Double Attack Rate", en: 'Double Attack Rate',
ja: "DA確率", ja: 'DA確率',
}, },
id: 5, id: 5,
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Triple Attack Rate", en: 'Triple Attack Rate',
ja: "TA確率", ja: 'TA確率',
}, },
id: 6, id: 6,
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
suffix: "%", suffix: '%',
}, },
], ],
}, },
{ {
name: { name: {
en: "EXP Up", en: 'EXP Up',
ja: "EXP UP", ja: 'EXP UP',
}, },
id: 18, id: 18,
minValue: 5, minValue: 5,
maxValue: 10, maxValue: 10,
suffix: "%", suffix: '%',
}, },
{ {
name: { name: {
en: "Rupies", en: 'Rupies',
ja: "獲得ルピ", ja: '獲得ルピ',
}, },
id: 19, id: 19,
minValue: 10, minValue: 10,
maxValue: 20, maxValue: 20,
suffix: "%", suffix: '%',
}, },
], ],
]; ]

View file

@ -7,7 +7,7 @@ export const emptyRarityState: RarityState = {
id: 3, id: 3,
checked: true, checked: true,
}, },
}; }
export const emptyElementState: ElementState = { export const emptyElementState: ElementState = {
null: { null: {
@ -38,7 +38,7 @@ export const emptyElementState: ElementState = {
id: 6, id: 6,
checked: false, checked: false,
}, },
}; }
export const emptyProficiencyState: ProficiencyState = { export const emptyProficiencyState: ProficiencyState = {
sabre: { sabre: {
@ -81,7 +81,7 @@ export const emptyProficiencyState: ProficiencyState = {
id: 10, id: 10,
checked: false, checked: false,
}, },
}; }
export const emptyWeaponSeriesState: WeaponSeriesState = { export const emptyWeaponSeriesState: WeaponSeriesState = {
seraphic: { seraphic: {
@ -184,4 +184,4 @@ export const emptyWeaponSeriesState: WeaponSeriesState = {
id: 29, id: 29,
checked: false, checked: false,
}, },
}; }

View file

@ -1,60 +1,60 @@
interface JobGroup { interface JobGroup {
slug: string; slug: string
name: { name: {
[key: string]: string; [key: string]: string
en: string; en: string
ja: string; ja: string
}; }
} }
export const jobGroups: JobGroup[] = [ export const jobGroups: JobGroup[] = [
{ {
slug: "1", slug: '1',
name: { name: {
en: "Row I", en: 'Row I',
ja: "Class I", ja: 'Class I',
}, },
}, },
{ {
slug: "2", slug: '2',
name: { name: {
en: "Row II", en: 'Row II',
ja: "Class II", ja: 'Class II',
}, },
}, },
{ {
slug: "3", slug: '3',
name: { name: {
en: "Row III", en: 'Row III',
ja: "Class III", ja: 'Class III',
}, },
}, },
{ {
slug: "4", slug: '4',
name: { name: {
en: "Row IV", en: 'Row IV',
ja: "Class IV", ja: 'Class IV',
}, },
}, },
{ {
slug: "5", slug: '5',
name: { name: {
en: "Row V", en: 'Row V',
ja: "Class V", ja: 'Class V',
}, },
}, },
{ {
slug: "ex1", slug: 'ex1',
name: { name: {
en: "Extra I", en: 'Extra I',
ja: "EXTRA I", ja: 'EXTRA I',
}, },
}, },
{ {
slug: "ex2", slug: 'ex2',
name: { name: {
en: "Extra II", en: 'Extra II',
ja: "EXTRA II", ja: 'EXTRA II',
}, },
}, },
]; ]

Some files were not shown because too many files have changed in this diff Show more