Remove trailing semicolons
This commit is contained in:
parent
6d6eaf59ee
commit
e8843699c7
106 changed files with 3690 additions and 3731 deletions
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"editor.formatOnSave": true,
|
"semi": false,
|
||||||
"prettier.semi": false,
|
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"singleQuote": true
|
"singleQuote": true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&[data-state="on"] {
|
&[data-state='on'] {
|
||||||
background: $grey-80;
|
background: $grey-80;
|
||||||
color: $grey-10;
|
color: $grey-10;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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%;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-state="checked"] {
|
&[data-state='checked'] {
|
||||||
background: $grey-90;
|
background: $grey-90;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
262
pages/saved.tsx
262
pages/saved.tsx
|
|
@ -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
|
||||||
|
|
|
||||||
262
pages/teams.tsx
262
pages/teams.tsx
|
|
@ -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
|
||||||
|
|
|
||||||
6
types/AccountCookie.d.ts
vendored
6
types/AccountCookie.d.ts
vendored
|
|
@ -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
18
types/AxSkill.d.ts
vendored
|
|
@ -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
62
types/Character.d.ts
vendored
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
types/CheckedState.d.ts
vendored
4
types/CheckedState.d.ts
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
interface CheckedState {
|
interface CheckedState {
|
||||||
id: number;
|
id: number
|
||||||
checked: boolean;
|
checked: boolean
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
types/ElementState.d.ts
vendored
16
types/ElementState.d.ts
vendored
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
types/GridArray.d.ts
vendored
2
types/GridArray.d.ts
vendored
|
|
@ -1 +1 @@
|
||||||
type GridArray<T> = { [key: number]: T | undefined };
|
type GridArray<T> = { [key: number]: T | undefined }
|
||||||
|
|
|
||||||
8
types/GridCharacter.d.ts
vendored
8
types/GridCharacter.d.ts
vendored
|
|
@ -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
12
types/GridSummon.d.ts
vendored
|
|
@ -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
16
types/GridWeapon.d.ts
vendored
|
|
@ -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
24
types/Job.d.ts
vendored
|
|
@ -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
26
types/JobSkill.d.ts
vendored
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
types/OnClickEvent.d.ts
vendored
2
types/OnClickEvent.d.ts
vendored
|
|
@ -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
42
types/Party.d.ts
vendored
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
22
types/ProficiencyState.d.ts
vendored
22
types/ProficiencyState.d.ts
vendored
|
|
@ -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
18
types/Raid.d.ts
vendored
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
6
types/RarityState.d.ts
vendored
6
types/RarityState.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
interface RarityState {
|
interface RarityState {
|
||||||
[key: string]: CheckedState;
|
[key: string]: CheckedState
|
||||||
sr: CheckedState;
|
sr: CheckedState
|
||||||
ssr: CheckedState;
|
ssr: CheckedState
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
types/SimpleAxSkill.d.ts
vendored
4
types/SimpleAxSkill.d.ts
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
interface SimpleAxSkill {
|
interface SimpleAxSkill {
|
||||||
modifier: number;
|
modifier: number
|
||||||
strength: number;
|
strength: number
|
||||||
}
|
}
|
||||||
|
|
|
||||||
46
types/Summon.d.ts
vendored
46
types/Summon.d.ts
vendored
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
types/TeamElement.d.ts
vendored
8
types/TeamElement.d.ts
vendored
|
|
@ -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
16
types/User.d.ts
vendored
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
types/UserCookie.d.ts
vendored
8
types/UserCookie.d.ts
vendored
|
|
@ -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
54
types/Weapon.d.ts
vendored
|
|
@ -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
18
types/WeaponKey.d.ts
vendored
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
50
types/WeaponSeries.d.ts
vendored
50
types/WeaponSeries.d.ts
vendored
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
types/declarations.d.ts
vendored
4
types/declarations.d.ts
vendored
|
|
@ -1,2 +1,2 @@
|
||||||
declare module "*.jpg";
|
declare module '*.jpg'
|
||||||
declare module "*.svg";
|
declare module '*.svg'
|
||||||
|
|
|
||||||
16
types/index.d.ts
vendored
16
types/index.d.ts
vendored
|
|
@ -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
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -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: '光',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
]
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
434
utils/axData.tsx
434
utils/axData.tsx
|
|
@ -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: '%',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
];
|
]
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Reference in a new issue