Run prettier on src
This commit is contained in:
parent
3ccb80d33b
commit
efa864fb80
142 changed files with 8617 additions and 7923 deletions
|
|
@ -1,24 +1,29 @@
|
|||
import React from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import * as Dialog from '@radix-ui/react-dialog'
|
||||
import React from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import * as Dialog from "@radix-ui/react-dialog";
|
||||
|
||||
import CrossIcon from '~public/icons/Cross.svg'
|
||||
import './index.scss'
|
||||
import CrossIcon from "~public/icons/Cross.svg";
|
||||
import "./index.scss";
|
||||
|
||||
const AboutModal = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
return (
|
||||
<Dialog.Root>
|
||||
<Dialog.Trigger asChild>
|
||||
<li className="MenuItem">
|
||||
<span>{t('modals.about.title')}</span>
|
||||
<span>{t("modals.about.title")}</span>
|
||||
</li>
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Content className="About Dialog" onOpenAutoFocus={ (event) => event.preventDefault() }>
|
||||
<Dialog.Content
|
||||
className="About Dialog"
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
>
|
||||
<div className="DialogHeader">
|
||||
<Dialog.Title className="DialogTitle">{t('menu.about')}</Dialog.Title>
|
||||
<Dialog.Title className="DialogTitle">
|
||||
{t("menu.about")}
|
||||
</Dialog.Title>
|
||||
<Dialog.Close className="DialogClose" asChild>
|
||||
<span>
|
||||
<CrossIcon />
|
||||
|
|
@ -28,20 +33,27 @@ const AboutModal = () => {
|
|||
|
||||
<section>
|
||||
<Dialog.Description className="DialogDescription">
|
||||
Granblue.team is a tool to save and share team compositions for <a href="https://game.granbluefantasy.jp">Granblue Fantasy.</a>
|
||||
Granblue.team is a tool to save and share team compositions for{" "}
|
||||
<a href="https://game.granbluefantasy.jp">Granblue Fantasy.</a>
|
||||
</Dialog.Description>
|
||||
<Dialog.Description className="DialogDescription">
|
||||
Start adding things to a team and a URL will be created for you to share it wherever you like, no account needed.
|
||||
Start adding things to a team and a URL will be created for you to
|
||||
share it wherever you like, no account needed.
|
||||
</Dialog.Description>
|
||||
<Dialog.Description className="DialogDescription">
|
||||
You can make an account to save any teams you find for future reference, or to keep all of your teams together in one place.
|
||||
You can make an account to save any teams you find for future
|
||||
reference, or to keep all of your teams together in one place.
|
||||
</Dialog.Description>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<Dialog.Title className="DialogTitle">Credits</Dialog.Title>
|
||||
<Dialog.Description className="DialogDescription">
|
||||
Granblue.team was built by <a href="https://twitter.com/jedmund">@jedmund</a> with a lot of help from <a href="https://twitter.com/lalalalinna">@lalalalinna</a> and <a href="https://twitter.com/tarngerine">@tarngerine</a>.
|
||||
Granblue.team was built by{" "}
|
||||
<a href="https://twitter.com/jedmund">@jedmund</a> with a lot of
|
||||
help from{" "}
|
||||
<a href="https://twitter.com/lalalalinna">@lalalalinna</a> and{" "}
|
||||
<a href="https://twitter.com/tarngerine">@tarngerine</a>.
|
||||
</Dialog.Description>
|
||||
</section>
|
||||
|
||||
|
|
@ -55,7 +67,7 @@ const AboutModal = () => {
|
|||
<Dialog.Overlay className="Overlay" />
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutModal
|
||||
export default AboutModal;
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@
|
|||
gap: $unit * 2;
|
||||
|
||||
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-x: 95%;
|
||||
margin: 0;
|
||||
|
|
|
|||
|
|
@ -1,33 +1,35 @@
|
|||
import React, { useEffect, useState } from "react"
|
||||
import { getCookie } from "cookies-next"
|
||||
import { useRouter } from "next/router"
|
||||
import { useSnapshot } from "valtio"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { getCookie } from "cookies-next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useSnapshot } from "valtio";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import * as Dialog from "@radix-ui/react-dialog"
|
||||
import * as Switch from "@radix-ui/react-switch"
|
||||
import * as Dialog from "@radix-ui/react-dialog";
|
||||
import * as Switch from "@radix-ui/react-switch";
|
||||
|
||||
import api from "~utils/api"
|
||||
import { accountState } from "~utils/accountState"
|
||||
import { pictureData } from "~utils/pictureData"
|
||||
import api from "~utils/api";
|
||||
import { accountState } from "~utils/accountState";
|
||||
import { pictureData } from "~utils/pictureData";
|
||||
|
||||
import Button from "~components/Button"
|
||||
import Button from "~components/Button";
|
||||
|
||||
import CrossIcon from "~public/icons/Cross.svg"
|
||||
import "./index.scss"
|
||||
import CrossIcon from "~public/icons/Cross.svg";
|
||||
import "./index.scss";
|
||||
|
||||
const AccountModal = () => {
|
||||
const { account } = useSnapshot(accountState)
|
||||
const { account } = useSnapshot(accountState);
|
||||
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation("common")
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation("common");
|
||||
const locale =
|
||||
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
||||
router.locale && ["en", "ja"].includes(router.locale)
|
||||
? router.locale
|
||||
: "en";
|
||||
|
||||
// Cookies
|
||||
const cookie = getCookie("account")
|
||||
const cookie = getCookie("account");
|
||||
|
||||
const headers = {}
|
||||
const headers = {};
|
||||
// cookies.account != null
|
||||
// ? {
|
||||
// headers: {
|
||||
|
|
@ -37,17 +39,17 @@ const AccountModal = () => {
|
|||
// : {}
|
||||
|
||||
// State
|
||||
const [open, setOpen] = useState(false)
|
||||
const [picture, setPicture] = useState("")
|
||||
const [language, setLanguage] = useState("")
|
||||
const [gender, setGender] = useState(0)
|
||||
const [privateProfile, setPrivateProfile] = useState(false)
|
||||
const [open, setOpen] = useState(false);
|
||||
const [picture, setPicture] = useState("");
|
||||
const [language, setLanguage] = useState("");
|
||||
const [gender, setGender] = useState(0);
|
||||
const [privateProfile, setPrivateProfile] = useState(false);
|
||||
|
||||
// Refs
|
||||
const pictureSelect = React.createRef<HTMLSelectElement>()
|
||||
const languageSelect = React.createRef<HTMLSelectElement>()
|
||||
const genderSelect = React.createRef<HTMLSelectElement>()
|
||||
const privateSelect = React.createRef<HTMLInputElement>()
|
||||
const pictureSelect = React.createRef<HTMLSelectElement>();
|
||||
const languageSelect = React.createRef<HTMLSelectElement>();
|
||||
const genderSelect = React.createRef<HTMLSelectElement>();
|
||||
const privateSelect = React.createRef<HTMLInputElement>();
|
||||
|
||||
// useEffect(() => {
|
||||
// if (cookies.user) setPicture(cookies.user.picture)
|
||||
|
|
@ -62,27 +64,27 @@ const AccountModal = () => {
|
|||
<option key={`picture-${i}`} value={item.filename}>
|
||||
{item.name[locale]}
|
||||
</option>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
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>) {
|
||||
if (languageSelect.current) setLanguage(languageSelect.current.value)
|
||||
if (languageSelect.current) setLanguage(languageSelect.current.value);
|
||||
}
|
||||
|
||||
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) {
|
||||
setPrivateProfile(checked)
|
||||
setPrivateProfile(checked);
|
||||
}
|
||||
|
||||
function update(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
const object = {
|
||||
user: {
|
||||
|
|
@ -92,7 +94,7 @@ const AccountModal = () => {
|
|||
gender: gender,
|
||||
private: privateProfile,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// api.endpoints.users
|
||||
// .update(cookies.account.user_id, object, headers)
|
||||
|
|
@ -129,7 +131,7 @@ const AccountModal = () => {
|
|||
}
|
||||
|
||||
function openChange(open: boolean) {
|
||||
setOpen(open)
|
||||
setOpen(open);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -249,7 +251,7 @@ const AccountModal = () => {
|
|||
<Dialog.Overlay className="Overlay" />
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountModal
|
||||
export default AccountModal;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import React from "react"
|
||||
import * as AlertDialog from "@radix-ui/react-alert-dialog"
|
||||
import React from "react";
|
||||
import * as AlertDialog from "@radix-ui/react-alert-dialog";
|
||||
|
||||
import "./index.scss"
|
||||
import Button from "~components/Button"
|
||||
import { ButtonType } from "~utils/enums"
|
||||
import "./index.scss";
|
||||
import Button from "~components/Button";
|
||||
import { ButtonType } from "~utils/enums";
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
open: boolean
|
||||
title?: string
|
||||
message: string
|
||||
primaryAction?: () => void
|
||||
primaryActionText?: string
|
||||
cancelAction: () => void
|
||||
cancelActionText: string
|
||||
open: boolean;
|
||||
title?: string;
|
||||
message: string;
|
||||
primaryAction?: () => void;
|
||||
primaryActionText?: string;
|
||||
cancelAction: () => void;
|
||||
cancelActionText: string;
|
||||
}
|
||||
|
||||
const Alert = (props: Props) => {
|
||||
|
|
@ -45,7 +45,7 @@ const Alert = (props: Props) => {
|
|||
</div>
|
||||
</AlertDialog.Portal>
|
||||
</AlertDialog.Root>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default Alert
|
||||
export default Alert;
|
||||
|
|
|
|||
|
|
@ -1,243 +1,284 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
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 {
|
||||
[index: string]: string
|
||||
axValue1: string
|
||||
axValue2: string
|
||||
[index: string]: string;
|
||||
axValue1: string;
|
||||
axValue2: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
axType: number
|
||||
currentSkills?: SimpleAxSkill[],
|
||||
sendValidity: (isValid: boolean) => void
|
||||
sendValues: (primaryAxModifier: number, primaryAxValue: number, secondaryAxModifier: number, secondaryAxValue: number) => void
|
||||
axType: number;
|
||||
currentSkills?: SimpleAxSkill[];
|
||||
sendValidity: (isValid: boolean) => void;
|
||||
sendValues: (
|
||||
primaryAxModifier: number,
|
||||
primaryAxValue: number,
|
||||
secondaryAxModifier: number,
|
||||
secondaryAxValue: number
|
||||
) => void;
|
||||
}
|
||||
|
||||
const AXSelect = (props: Props) => {
|
||||
const router = useRouter()
|
||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
||||
const { t } = useTranslation('common')
|
||||
const router = useRouter();
|
||||
const locale =
|
||||
router.locale && ["en", "ja"].includes(router.locale)
|
||||
? router.locale
|
||||
: "en";
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
// Set up form states and error handling
|
||||
const [errors, setErrors] = useState<ErrorMap>({
|
||||
axValue1: '',
|
||||
axValue2: ''
|
||||
})
|
||||
axValue1: "",
|
||||
axValue2: "",
|
||||
});
|
||||
|
||||
const primaryErrorClasses = classNames({
|
||||
'errors': true,
|
||||
'visible': errors.axValue1.length > 0
|
||||
})
|
||||
errors: true,
|
||||
visible: errors.axValue1.length > 0,
|
||||
});
|
||||
|
||||
const secondaryErrorClasses = classNames({
|
||||
'errors': true,
|
||||
'visible': errors.axValue2.length > 0
|
||||
})
|
||||
errors: true,
|
||||
visible: errors.axValue2.length > 0,
|
||||
});
|
||||
|
||||
// Refs
|
||||
const primaryAxModifierSelect = React.createRef<HTMLSelectElement>()
|
||||
const primaryAxValueInput = React.createRef<HTMLInputElement>()
|
||||
const secondaryAxModifierSelect = React.createRef<HTMLSelectElement>()
|
||||
const secondaryAxValueInput = React.createRef<HTMLInputElement>()
|
||||
const primaryAxModifierSelect = React.createRef<HTMLSelectElement>();
|
||||
const primaryAxValueInput = React.createRef<HTMLInputElement>();
|
||||
const secondaryAxModifierSelect = React.createRef<HTMLSelectElement>();
|
||||
const secondaryAxValueInput = React.createRef<HTMLInputElement>();
|
||||
|
||||
// States
|
||||
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
|
||||
const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1)
|
||||
const [primaryAxValue, setPrimaryAxValue] = useState(0.0)
|
||||
const [secondaryAxValue, setSecondaryAxValue] = useState(0.0)
|
||||
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1);
|
||||
const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1);
|
||||
const [primaryAxValue, setPrimaryAxValue] = useState(0.0);
|
||||
const [secondaryAxValue, setSecondaryAxValue] = useState(0.0);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.currentSkills && props.currentSkills[0]) {
|
||||
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[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(() => {
|
||||
props.sendValues(primaryAxModifier, primaryAxValue, secondaryAxModifier, secondaryAxValue)
|
||||
}, [props, primaryAxModifier, primaryAxValue, secondaryAxModifier, secondaryAxValue])
|
||||
props.sendValues(
|
||||
primaryAxModifier,
|
||||
primaryAxValue,
|
||||
secondaryAxModifier,
|
||||
secondaryAxValue
|
||||
);
|
||||
}, [
|
||||
props,
|
||||
primaryAxModifier,
|
||||
primaryAxValue,
|
||||
secondaryAxModifier,
|
||||
secondaryAxValue,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
props.sendValidity(primaryAxValue > 0 && errors.axValue1 === '' && errors.axValue2 === '')
|
||||
}, [props, primaryAxValue, errors])
|
||||
props.sendValidity(
|
||||
primaryAxValue > 0 && errors.axValue1 === "" && errors.axValue2 === ""
|
||||
);
|
||||
}, [props, primaryAxValue, errors]);
|
||||
|
||||
// Classes
|
||||
const secondarySetClasses = classNames({
|
||||
'AXSet': true,
|
||||
'hidden': primaryAxModifier < 0
|
||||
})
|
||||
AXSet: true,
|
||||
hidden: primaryAxModifier < 0,
|
||||
});
|
||||
|
||||
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) {
|
||||
axOptionElements = axOptions.map((ax, i) => {
|
||||
return (
|
||||
<option key={i} value={ax.id}>{ax.name[locale]}</option>
|
||||
)
|
||||
})
|
||||
<option key={i} value={ax.id}>
|
||||
{ax.name[locale]}
|
||||
</option>
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// If we are loading data from the server, state doesn't set before render,
|
||||
// so our defaultValue is undefined.
|
||||
let modifier = -1;
|
||||
if (primaryAxModifier >= 0)
|
||||
modifier = primaryAxModifier
|
||||
else if (props.currentSkills)
|
||||
modifier = props.currentSkills[0].modifier
|
||||
if (primaryAxModifier >= 0) modifier = primaryAxModifier;
|
||||
else if (props.currentSkills) modifier = props.currentSkills[0].modifier;
|
||||
|
||||
if (modifier >= 0 && axOptions[modifier]) {
|
||||
const primarySkill = axOptions[modifier]
|
||||
const primarySkill = axOptions[modifier];
|
||||
|
||||
if (primarySkill.secondary) {
|
||||
const secondaryAxOptions = primarySkill.secondary
|
||||
const secondaryAxOptions = primarySkill.secondary;
|
||||
axOptionElements = secondaryAxOptions.map((ax, i) => {
|
||||
return (
|
||||
<option key={i} value={ax.id}>{ax.name[locale]}</option>
|
||||
)
|
||||
})
|
||||
<option key={i} value={ax.id}>
|
||||
{ax.name[locale]}
|
||||
</option>
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
axOptionElements?.unshift(<option key={-1} value={-1}>{t('ax.no_skill')}</option>)
|
||||
return axOptionElements
|
||||
axOptionElements?.unshift(
|
||||
<option key={-1} value={-1}>
|
||||
{t("ax.no_skill")}
|
||||
</option>
|
||||
);
|
||||
return axOptionElements;
|
||||
}
|
||||
|
||||
function handleSelectChange(event: React.ChangeEvent<HTMLSelectElement>) {
|
||||
const value = parseInt(event.target.value)
|
||||
const value = parseInt(event.target.value);
|
||||
|
||||
if (primaryAxModifierSelect.current == event.target) {
|
||||
setPrimaryAxModifier(value)
|
||||
setPrimaryAxModifier(value);
|
||||
|
||||
if (primaryAxValueInput.current && secondaryAxModifierSelect.current && secondaryAxValueInput.current) {
|
||||
setupInput(axData[props.axType - 1][value], primaryAxValueInput.current)
|
||||
if (
|
||||
primaryAxValueInput.current &&
|
||||
secondaryAxModifierSelect.current &&
|
||||
secondaryAxValueInput.current
|
||||
) {
|
||||
setupInput(
|
||||
axData[props.axType - 1][value],
|
||||
primaryAxValueInput.current
|
||||
);
|
||||
|
||||
secondaryAxModifierSelect.current.value = "-1"
|
||||
secondaryAxValueInput.current.value = ""
|
||||
secondaryAxModifierSelect.current.value = "-1";
|
||||
secondaryAxValueInput.current.value = "";
|
||||
}
|
||||
} else {
|
||||
setSecondaryAxModifier(value)
|
||||
setSecondaryAxModifier(value);
|
||||
|
||||
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
|
||||
const currentAxSkill = (primaryAxSkill.secondary) ?
|
||||
primaryAxSkill.secondary.find(skill => skill.id == value) : undefined
|
||||
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier];
|
||||
const currentAxSkill = primaryAxSkill.secondary
|
||||
? primaryAxSkill.secondary.find((skill) => skill.id == value)
|
||||
: undefined;
|
||||
|
||||
if (secondaryAxValueInput.current)
|
||||
setupInput(currentAxSkill, secondaryAxValueInput.current)
|
||||
setupInput(currentAxSkill, secondaryAxValueInput.current);
|
||||
}
|
||||
}
|
||||
|
||||
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const value = parseFloat(event.target.value)
|
||||
let newErrors = {...errors}
|
||||
const value = parseFloat(event.target.value);
|
||||
let newErrors = { ...errors };
|
||||
|
||||
if (primaryAxValueInput.current == event.target) {
|
||||
if (handlePrimaryErrors(value))
|
||||
setPrimaryAxValue(value)
|
||||
if (handlePrimaryErrors(value)) setPrimaryAxValue(value);
|
||||
} else {
|
||||
if (handleSecondaryErrors(value))
|
||||
setSecondaryAxValue(value)
|
||||
if (handleSecondaryErrors(value)) setSecondaryAxValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
function handlePrimaryErrors(value: number) {
|
||||
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
|
||||
let newErrors = {...errors}
|
||||
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier];
|
||||
let newErrors = { ...errors };
|
||||
|
||||
if (value < primaryAxSkill.minValue) {
|
||||
newErrors.axValue1 = t('ax.errors.value_too_low', {
|
||||
newErrors.axValue1 = t("ax.errors.value_too_low", {
|
||||
name: primaryAxSkill.name[locale],
|
||||
minValue: primaryAxSkill.minValue,
|
||||
suffix: (primaryAxSkill.suffix) ? primaryAxSkill.suffix : ''
|
||||
})
|
||||
suffix: primaryAxSkill.suffix ? primaryAxSkill.suffix : "",
|
||||
});
|
||||
} 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],
|
||||
maxValue: primaryAxSkill.minValue,
|
||||
suffix: (primaryAxSkill.suffix) ? primaryAxSkill.suffix : ''
|
||||
})
|
||||
suffix: primaryAxSkill.suffix ? primaryAxSkill.suffix : "",
|
||||
});
|
||||
} else if (!value || value <= 0) {
|
||||
newErrors.axValue1 = t('ax.errors.value_empty', { name: primaryAxSkill.name[locale] })
|
||||
newErrors.axValue1 = t("ax.errors.value_empty", {
|
||||
name: primaryAxSkill.name[locale],
|
||||
});
|
||||
} else {
|
||||
newErrors.axValue1 = ''
|
||||
newErrors.axValue1 = "";
|
||||
}
|
||||
|
||||
setErrors(newErrors)
|
||||
setErrors(newErrors);
|
||||
|
||||
return newErrors.axValue1.length === 0
|
||||
return newErrors.axValue1.length === 0;
|
||||
}
|
||||
|
||||
function handleSecondaryErrors(value: number) {
|
||||
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
|
||||
let newErrors = {...errors}
|
||||
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier];
|
||||
let newErrors = { ...errors };
|
||||
|
||||
if (primaryAxSkill.secondary) {
|
||||
const secondaryAxSkill = primaryAxSkill.secondary.find(skill => skill.id == secondaryAxModifier)
|
||||
const secondaryAxSkill = primaryAxSkill.secondary.find(
|
||||
(skill) => skill.id == secondaryAxModifier
|
||||
);
|
||||
|
||||
if (secondaryAxSkill) {
|
||||
if (value < secondaryAxSkill.minValue) {
|
||||
newErrors.axValue2 = t('ax.errors.value_too_low', {
|
||||
newErrors.axValue2 = t("ax.errors.value_too_low", {
|
||||
name: secondaryAxSkill.name[locale],
|
||||
minValue: secondaryAxSkill.minValue,
|
||||
suffix: (secondaryAxSkill.suffix) ? secondaryAxSkill.suffix : ''
|
||||
})
|
||||
suffix: secondaryAxSkill.suffix ? secondaryAxSkill.suffix : "",
|
||||
});
|
||||
} 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],
|
||||
maxValue: secondaryAxSkill.minValue,
|
||||
suffix: (secondaryAxSkill.suffix) ? secondaryAxSkill.suffix : ''
|
||||
})
|
||||
suffix: secondaryAxSkill.suffix ? secondaryAxSkill.suffix : "",
|
||||
});
|
||||
} else if (!secondaryAxSkill.suffix && value % 1 !== 0) {
|
||||
newErrors.axValue2 = t('ax.errors.value_not_whole', { name: secondaryAxSkill.name[locale] })
|
||||
newErrors.axValue2 = t("ax.errors.value_not_whole", {
|
||||
name: secondaryAxSkill.name[locale],
|
||||
});
|
||||
} else if (primaryAxValue <= 0) {
|
||||
newErrors.axValue1 = t('ax.errors.value_empty', { name: primaryAxSkill.name[locale] })
|
||||
newErrors.axValue1 = t("ax.errors.value_empty", {
|
||||
name: primaryAxSkill.name[locale],
|
||||
});
|
||||
} 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) {
|
||||
if (ax) {
|
||||
const rangeString = `${ax.minValue}~${ax.maxValue}${ax.suffix || ''}`
|
||||
const rangeString = `${ax.minValue}~${ax.maxValue}${ax.suffix || ""}`;
|
||||
|
||||
element.disabled = false
|
||||
element.placeholder = rangeString
|
||||
element.min = `${ax.minValue}`
|
||||
element.max = `${ax.maxValue}`
|
||||
element.step = (ax.suffix) ? "0.5" : "1"
|
||||
element.disabled = false;
|
||||
element.placeholder = rangeString;
|
||||
element.min = `${ax.minValue}`;
|
||||
element.max = `${ax.maxValue}`;
|
||||
element.step = ax.suffix ? "0.5" : "1";
|
||||
} else {
|
||||
if (primaryAxValueInput.current && secondaryAxValueInput.current) {
|
||||
if (primaryAxValueInput.current == element) {
|
||||
primaryAxValueInput.current.disabled = true
|
||||
primaryAxValueInput.current.placeholder = ''
|
||||
primaryAxValueInput.current.disabled = true;
|
||||
primaryAxValueInput.current.placeholder = "";
|
||||
}
|
||||
|
||||
secondaryAxValueInput.current.disabled = true
|
||||
secondaryAxValueInput.current.placeholder = ''
|
||||
secondaryAxValueInput.current.disabled = true;
|
||||
secondaryAxValueInput.current.placeholder = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -246,21 +287,65 @@ const AXSelect = (props: Props) => {
|
|||
<div className="AXSelect">
|
||||
<div className="AXSet">
|
||||
<div className="fields">
|
||||
<select key="ax1" defaultValue={ (props.currentSkills && props.currentSkills[0]) ? props.currentSkills[0].modifier : -1 } onChange={handleSelectChange} ref={primaryAxModifierSelect}>{ generateOptions(0) }</select>
|
||||
<input defaultValue={ (props.currentSkills && props.currentSkills[0]) ? props.currentSkills[0].strength : 0 } className="Input" type="number" onChange={handleInputChange} ref={primaryAxValueInput} disabled={primaryAxValue != 0} />
|
||||
<select
|
||||
key="ax1"
|
||||
defaultValue={
|
||||
props.currentSkills && props.currentSkills[0]
|
||||
? props.currentSkills[0].modifier
|
||||
: -1
|
||||
}
|
||||
onChange={handleSelectChange}
|
||||
ref={primaryAxModifierSelect}
|
||||
>
|
||||
{generateOptions(0)}
|
||||
</select>
|
||||
<input
|
||||
defaultValue={
|
||||
props.currentSkills && props.currentSkills[0]
|
||||
? props.currentSkills[0].strength
|
||||
: 0
|
||||
}
|
||||
className="Input"
|
||||
type="number"
|
||||
onChange={handleInputChange}
|
||||
ref={primaryAxValueInput}
|
||||
disabled={primaryAxValue != 0}
|
||||
/>
|
||||
</div>
|
||||
<p className={primaryErrorClasses}>{errors.axValue1}</p>
|
||||
</div>
|
||||
|
||||
<div className={secondarySetClasses}>
|
||||
<div className="fields">
|
||||
<select key="ax2" defaultValue={ (props.currentSkills && props.currentSkills[1]) ? props.currentSkills[1].modifier : -1 } onChange={handleSelectChange} ref={secondaryAxModifierSelect}>{ generateOptions(1) }</select>
|
||||
<input defaultValue={ (props.currentSkills && props.currentSkills[1]) ? props.currentSkills[1].strength : 0 } className="Input" type="number" onChange={handleInputChange} ref={secondaryAxValueInput} disabled={secondaryAxValue != 0} />
|
||||
<select
|
||||
key="ax2"
|
||||
defaultValue={
|
||||
props.currentSkills && props.currentSkills[1]
|
||||
? props.currentSkills[1].modifier
|
||||
: -1
|
||||
}
|
||||
onChange={handleSelectChange}
|
||||
ref={secondaryAxModifierSelect}
|
||||
>
|
||||
{generateOptions(1)}
|
||||
</select>
|
||||
<input
|
||||
defaultValue={
|
||||
props.currentSkills && props.currentSkills[1]
|
||||
? props.currentSkills[1].strength
|
||||
: 0
|
||||
}
|
||||
className="Input"
|
||||
type="number"
|
||||
onChange={handleInputChange}
|
||||
ref={secondaryAxValueInput}
|
||||
disabled={secondaryAxValue != 0}
|
||||
/>
|
||||
</div>
|
||||
<p className={secondaryErrorClasses}>{errors.axValue2}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default AXSelect
|
||||
export default AXSelect;
|
||||
|
|
|
|||
|
|
@ -35,28 +35,28 @@
|
|||
}
|
||||
|
||||
&.save:hover {
|
||||
color: #FF4D4D;
|
||||
color: #ff4d4d;
|
||||
|
||||
.icon svg {
|
||||
fill: #FF4D4D;
|
||||
stroke: #FF4D4D;
|
||||
fill: #ff4d4d;
|
||||
stroke: #ff4d4d;
|
||||
}
|
||||
}
|
||||
|
||||
&.save.Active {
|
||||
color: #FF4D4D;
|
||||
color: #ff4d4d;
|
||||
|
||||
.icon svg {
|
||||
fill: #FF4D4D;
|
||||
stroke: #FF4D4D;
|
||||
fill: #ff4d4d;
|
||||
stroke: #ff4d4d;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: darken(#FF4D4D, 30);
|
||||
color: darken(#ff4d4d, 30);
|
||||
|
||||
.icon svg {
|
||||
fill: darken(#FF4D4D, 30);
|
||||
stroke: darken(#FF4D4D, 30);
|
||||
fill: darken(#ff4d4d, 30);
|
||||
stroke: darken(#ff4d4d, 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
color: $error;
|
||||
|
||||
&:hover {
|
||||
color: darken($error, 10)
|
||||
color: darken($error, 10);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,8 +108,8 @@
|
|||
color: #8b8b8b;
|
||||
|
||||
&:hover {
|
||||
background: #4B9BE5;
|
||||
color: #233E56;
|
||||
background: #4b9be5;
|
||||
color: #233e56;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -196,7 +196,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
&.light {
|
||||
background: $light-bg-light;
|
||||
color: $light-text-dark;
|
||||
|
|
|
|||
|
|
@ -1,124 +1,143 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import React, { useEffect, useState } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
import Link from 'next/link'
|
||||
import Link from "next/link";
|
||||
|
||||
import AddIcon from '~public/icons/Add.svg'
|
||||
import CheckIcon from '~public/icons/LargeCheck.svg'
|
||||
import CrossIcon from '~public/icons/Cross.svg'
|
||||
import EditIcon from '~public/icons/Edit.svg'
|
||||
import LinkIcon from '~public/icons/Link.svg'
|
||||
import MenuIcon from '~public/icons/Menu.svg'
|
||||
import SaveIcon from '~public/icons/Save.svg'
|
||||
import SettingsIcon from '~public/icons/Settings.svg'
|
||||
import AddIcon from "~public/icons/Add.svg";
|
||||
import CheckIcon from "~public/icons/LargeCheck.svg";
|
||||
import CrossIcon from "~public/icons/Cross.svg";
|
||||
import EditIcon from "~public/icons/Edit.svg";
|
||||
import LinkIcon from "~public/icons/Link.svg";
|
||||
import MenuIcon from "~public/icons/Menu.svg";
|
||||
import SaveIcon from "~public/icons/Save.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 {
|
||||
active?: boolean
|
||||
disabled?: boolean
|
||||
classes?: string[],
|
||||
icon?: string
|
||||
type?: ButtonType
|
||||
children?: React.ReactNode
|
||||
onClick?: (event: React.MouseEvent<HTMLElement>) => void
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
classes?: string[];
|
||||
icon?: string;
|
||||
type?: ButtonType;
|
||||
children?: React.ReactNode;
|
||||
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
||||
}
|
||||
|
||||
const Button = (props: Props) => {
|
||||
// States
|
||||
const [active, setActive] = useState(false)
|
||||
const [disabled, setDisabled] = useState(false)
|
||||
const [pressed, setPressed] = useState(false)
|
||||
const [buttonType, setButtonType] = useState(ButtonType.Base)
|
||||
const [active, setActive] = useState(false);
|
||||
const [disabled, setDisabled] = useState(false);
|
||||
const [pressed, setPressed] = useState(false);
|
||||
const [buttonType, setButtonType] = useState(ButtonType.Base);
|
||||
|
||||
const classes = classNames({
|
||||
const classes = classNames(
|
||||
{
|
||||
Button: true,
|
||||
'Active': active,
|
||||
'btn-pressed': pressed,
|
||||
'btn-disabled': disabled,
|
||||
'save': props.icon === 'save',
|
||||
'destructive': props.type == ButtonType.Destructive
|
||||
}, props.classes)
|
||||
Active: active,
|
||||
"btn-pressed": pressed,
|
||||
"btn-disabled": disabled,
|
||||
save: props.icon === "save",
|
||||
destructive: props.type == ButtonType.Destructive,
|
||||
},
|
||||
props.classes
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.active) setActive(props.active)
|
||||
if (props.disabled) setDisabled(props.disabled)
|
||||
if (props.type) setButtonType(props.type)
|
||||
}, [props.active, props.disabled, props.type])
|
||||
if (props.active) setActive(props.active);
|
||||
if (props.disabled) setDisabled(props.disabled);
|
||||
if (props.type) setButtonType(props.type);
|
||||
}, [props.active, props.disabled, props.type]);
|
||||
|
||||
const addIcon = (
|
||||
<span className='icon'>
|
||||
<span className="icon">
|
||||
<AddIcon />
|
||||
</span>
|
||||
)
|
||||
);
|
||||
|
||||
const menuIcon = (
|
||||
<span className='icon'>
|
||||
<span className="icon">
|
||||
<MenuIcon />
|
||||
</span>
|
||||
)
|
||||
);
|
||||
|
||||
const linkIcon = (
|
||||
<span className='icon stroke'>
|
||||
<span className="icon stroke">
|
||||
<LinkIcon />
|
||||
</span>
|
||||
)
|
||||
);
|
||||
|
||||
const checkIcon = (
|
||||
<span className='icon check'>
|
||||
<span className="icon check">
|
||||
<CheckIcon />
|
||||
</span>
|
||||
)
|
||||
);
|
||||
|
||||
const crossIcon = (
|
||||
<span className='icon'>
|
||||
<span className="icon">
|
||||
<CrossIcon />
|
||||
</span>
|
||||
)
|
||||
);
|
||||
|
||||
const editIcon = (
|
||||
<span className='icon'>
|
||||
<span className="icon">
|
||||
<EditIcon />
|
||||
</span>
|
||||
)
|
||||
);
|
||||
|
||||
const saveIcon = (
|
||||
<span className='icon stroke'>
|
||||
<span className="icon stroke">
|
||||
<SaveIcon />
|
||||
</span>
|
||||
)
|
||||
);
|
||||
|
||||
const settingsIcon = (
|
||||
<span className='icon settings'>
|
||||
<span className="icon settings">
|
||||
<SettingsIcon />
|
||||
</span>
|
||||
)
|
||||
);
|
||||
|
||||
function getIcon() {
|
||||
let icon: React.ReactNode
|
||||
let icon: React.ReactNode;
|
||||
|
||||
switch(props.icon) {
|
||||
case 'new': icon = addIcon; break
|
||||
case 'menu': icon = menuIcon; break
|
||||
case 'link': icon = linkIcon; break
|
||||
case 'check': icon = checkIcon; break
|
||||
case 'cross': icon = crossIcon; break
|
||||
case 'edit': icon = editIcon; break
|
||||
case 'save': icon = saveIcon; break
|
||||
case 'settings': icon = settingsIcon; break
|
||||
switch (props.icon) {
|
||||
case "new":
|
||||
icon = addIcon;
|
||||
break;
|
||||
case "menu":
|
||||
icon = menuIcon;
|
||||
break;
|
||||
case "link":
|
||||
icon = linkIcon;
|
||||
break;
|
||||
case "check":
|
||||
icon = checkIcon;
|
||||
break;
|
||||
case "cross":
|
||||
icon = crossIcon;
|
||||
break;
|
||||
case "edit":
|
||||
icon = editIcon;
|
||||
break;
|
||||
case "save":
|
||||
icon = saveIcon;
|
||||
break;
|
||||
case "settings":
|
||||
icon = settingsIcon;
|
||||
break;
|
||||
}
|
||||
|
||||
return icon
|
||||
return icon;
|
||||
}
|
||||
|
||||
function handleMouseDown() {
|
||||
setPressed(true)
|
||||
setPressed(true);
|
||||
}
|
||||
|
||||
function handleMouseUp() {
|
||||
setPressed(false)
|
||||
setPressed(false);
|
||||
}
|
||||
return (
|
||||
<button
|
||||
|
|
@ -126,16 +145,17 @@ const Button = (props: Props) => {
|
|||
disabled={disabled}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseUp={handleMouseUp}
|
||||
onClick={props.onClick}>
|
||||
{ getIcon() }
|
||||
onClick={props.onClick}
|
||||
>
|
||||
{getIcon()}
|
||||
|
||||
{ (props.type != ButtonType.IconOnly) ?
|
||||
<span className='text'>
|
||||
{ props.children }
|
||||
</span> : ''
|
||||
}
|
||||
{props.type != ButtonType.IconOnly ? (
|
||||
<span className="text">{props.children}</span>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default Button
|
||||
export default Button;
|
||||
|
|
|
|||
|
|
@ -1,28 +1,33 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import './index.scss'
|
||||
import React, { useEffect, useState } from "react";
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
fieldName: string
|
||||
placeholder: string
|
||||
value?: string
|
||||
limit: number
|
||||
error: string
|
||||
onBlur?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
fieldName: string;
|
||||
placeholder: string;
|
||||
value?: string;
|
||||
limit: number;
|
||||
error: string;
|
||||
onBlur?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
const CharLimitedFieldset = React.forwardRef<HTMLInputElement, Props>(function useFieldSet(props, ref) {
|
||||
const fieldType = (['password', 'confirm_password'].includes(props.fieldName)) ? 'password' : 'text'
|
||||
const CharLimitedFieldset = React.forwardRef<HTMLInputElement, Props>(
|
||||
function useFieldSet(props, ref) {
|
||||
const fieldType = ["password", "confirm_password"].includes(props.fieldName)
|
||||
? "password"
|
||||
: "text";
|
||||
|
||||
const [currentCount, setCurrentCount] = useState(0)
|
||||
const [currentCount, setCurrentCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentCount((props.value) ? props.limit - props.value.length : props.limit)
|
||||
}, [props.limit, props.value])
|
||||
setCurrentCount(
|
||||
props.value ? props.limit - props.value.length : props.limit
|
||||
);
|
||||
}, [props.limit, props.value]);
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
setCurrentCount(props.limit - event.currentTarget.value.length)
|
||||
if (props.onChange) props.onChange(event)
|
||||
setCurrentCount(props.limit - event.currentTarget.value.length);
|
||||
if (props.onChange) props.onChange(event);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -34,7 +39,7 @@ const CharLimitedFieldset = React.forwardRef<HTMLInputElement, Props>(function u
|
|||
type={fieldType}
|
||||
name={props.fieldName}
|
||||
placeholder={props.placeholder}
|
||||
defaultValue={props.value || ''}
|
||||
defaultValue={props.value || ""}
|
||||
onBlur={props.onBlur}
|
||||
onChange={onChange}
|
||||
maxLength={props.limit}
|
||||
|
|
@ -43,12 +48,10 @@ const CharLimitedFieldset = React.forwardRef<HTMLInputElement, Props>(function u
|
|||
/>
|
||||
<span className="Counter">{currentCount}</span>
|
||||
</div>
|
||||
{
|
||||
props.error.length > 0 &&
|
||||
<p className='InputError'>{props.error}</p>
|
||||
}
|
||||
{props.error.length > 0 && <p className="InputError">{props.error}</p>}
|
||||
</fieldset>
|
||||
)
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default CharLimitedFieldset
|
||||
export default CharLimitedFieldset;
|
||||
|
|
|
|||
|
|
@ -1,70 +1,70 @@
|
|||
import React, { useEffect, useState } from "react"
|
||||
import { setCookie } from "cookies-next"
|
||||
import Router, { useRouter } from "next/router"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { AxiosResponse } from "axios"
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { setCookie } from "cookies-next";
|
||||
import Router, { useRouter } from "next/router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
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 { appState } from "~utils/appState"
|
||||
import { accountState } from "~utils/accountState"
|
||||
import api from "~utils/api";
|
||||
import { appState } from "~utils/appState";
|
||||
import { accountState } from "~utils/accountState";
|
||||
|
||||
import Button from "~components/Button"
|
||||
import Button from "~components/Button";
|
||||
|
||||
import "./index.scss"
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
incomingCharacter?: Character
|
||||
conflictingCharacters?: GridCharacter[]
|
||||
desiredPosition: number
|
||||
resolveConflict: () => void
|
||||
resetConflict: () => void
|
||||
open: boolean;
|
||||
incomingCharacter?: Character;
|
||||
conflictingCharacters?: GridCharacter[];
|
||||
desiredPosition: number;
|
||||
resolveConflict: () => void;
|
||||
resetConflict: () => void;
|
||||
}
|
||||
|
||||
const CharacterConflictModal = (props: Props) => {
|
||||
const { t } = useTranslation("common")
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
// States
|
||||
const [open, setOpen] = useState(false)
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(props.open)
|
||||
}, [setOpen, props.open])
|
||||
setOpen(props.open);
|
||||
}, [setOpen, props.open]);
|
||||
|
||||
function imageUrl(character?: Character, uncap: number = 0) {
|
||||
// Change the image based on the uncap level
|
||||
let suffix = "01"
|
||||
if (uncap == 6) suffix = "04"
|
||||
else if (uncap == 5) suffix = "03"
|
||||
else if (uncap > 2) suffix = "02"
|
||||
let suffix = "01";
|
||||
if (uncap == 6) suffix = "04";
|
||||
else if (uncap == 5) suffix = "03";
|
||||
else if (uncap > 2) suffix = "02";
|
||||
|
||||
// Special casing for Lyria (and Young Cat eventually)
|
||||
if (character?.granblue_id === "3030182000") {
|
||||
let element = 1
|
||||
let element = 1;
|
||||
if (
|
||||
appState.grid.weapons.mainWeapon &&
|
||||
appState.grid.weapons.mainWeapon.element
|
||||
) {
|
||||
element = appState.grid.weapons.mainWeapon.element
|
||||
element = appState.grid.weapons.mainWeapon.element;
|
||||
} 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) {
|
||||
setOpen(open)
|
||||
setOpen(open);
|
||||
}
|
||||
|
||||
function close() {
|
||||
setOpen(false)
|
||||
props.resetConflict()
|
||||
setOpen(false);
|
||||
props.resetConflict();
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -107,7 +107,7 @@ const CharacterConflictModal = (props: Props) => {
|
|||
<Dialog.Overlay className="Overlay" />
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default CharacterConflictModal
|
||||
export default CharacterConflictModal;
|
||||
|
|
|
|||
|
|
@ -1,68 +1,68 @@
|
|||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { getCookie } from "cookies-next"
|
||||
import { useSnapshot } from "valtio"
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { getCookie } from "cookies-next";
|
||||
import { useSnapshot } from "valtio";
|
||||
|
||||
import { AxiosResponse } from "axios"
|
||||
import debounce from "lodash.debounce"
|
||||
import { AxiosResponse } from "axios";
|
||||
import debounce from "lodash.debounce";
|
||||
|
||||
import Alert from "~components/Alert"
|
||||
import JobSection from "~components/JobSection"
|
||||
import CharacterUnit from "~components/CharacterUnit"
|
||||
import CharacterConflictModal from "~components/CharacterConflictModal"
|
||||
import Alert from "~components/Alert";
|
||||
import JobSection from "~components/JobSection";
|
||||
import CharacterUnit from "~components/CharacterUnit";
|
||||
import CharacterConflictModal from "~components/CharacterConflictModal";
|
||||
|
||||
import type { JobSkillObject, SearchableObject } from "~types"
|
||||
import type { JobSkillObject, SearchableObject } from "~types";
|
||||
|
||||
import api from "~utils/api"
|
||||
import { appState } from "~utils/appState"
|
||||
import api from "~utils/api";
|
||||
import { appState } from "~utils/appState";
|
||||
|
||||
import "./index.scss"
|
||||
import "./index.scss";
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
new: boolean
|
||||
characters?: GridCharacter[]
|
||||
createParty: () => Promise<AxiosResponse<any, any>>
|
||||
pushHistory?: (path: string) => void
|
||||
new: boolean;
|
||||
characters?: GridCharacter[];
|
||||
createParty: () => Promise<AxiosResponse<any, any>>;
|
||||
pushHistory?: (path: string) => void;
|
||||
}
|
||||
|
||||
const CharacterGrid = (props: Props) => {
|
||||
// Constants
|
||||
const numCharacters: number = 5
|
||||
const numCharacters: number = 5;
|
||||
|
||||
// Cookies
|
||||
const cookie = getCookie("account")
|
||||
const cookie = getCookie("account");
|
||||
const accountData: AccountCookie = cookie
|
||||
? JSON.parse(cookie as string)
|
||||
: null
|
||||
: null;
|
||||
const headers = accountData
|
||||
? { headers: { Authorization: `Bearer ${accountData.token}` } }
|
||||
: {}
|
||||
: {};
|
||||
|
||||
// Set up state for view management
|
||||
const { party, grid } = useSnapshot(appState)
|
||||
const [slug, setSlug] = useState()
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const { party, grid } = useSnapshot(appState);
|
||||
const [slug, setSlug] = useState();
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
|
||||
// Set up state for conflict management
|
||||
const [incoming, setIncoming] = useState<Character>()
|
||||
const [conflicts, setConflicts] = useState<GridCharacter[]>([])
|
||||
const [position, setPosition] = useState(0)
|
||||
const [incoming, setIncoming] = useState<Character>();
|
||||
const [conflicts, setConflicts] = useState<GridCharacter[]>([]);
|
||||
const [position, setPosition] = useState(0);
|
||||
|
||||
// Set up state for data
|
||||
const [job, setJob] = useState<Job | undefined>()
|
||||
const [job, setJob] = useState<Job | undefined>();
|
||||
const [jobSkills, setJobSkills] = useState<JobSkillObject>({
|
||||
0: undefined,
|
||||
1: undefined,
|
||||
2: undefined,
|
||||
3: undefined,
|
||||
})
|
||||
const [errorMessage, setErrorMessage] = useState("")
|
||||
});
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
|
||||
// Create a temporary state to store previous character uncap values
|
||||
const [previousUncapValues, setPreviousUncapValues] = useState<{
|
||||
[key: number]: number | undefined
|
||||
}>({})
|
||||
[key: number]: number | undefined;
|
||||
}>({});
|
||||
|
||||
// Set the editable flag only on first load
|
||||
useEffect(() => {
|
||||
|
|
@ -71,58 +71,58 @@ const CharacterGrid = (props: Props) => {
|
|||
(accountData && party.user && accountData.userId === party.user.id) ||
|
||||
props.new
|
||||
)
|
||||
appState.party.editable = true
|
||||
else appState.party.editable = false
|
||||
}, [props.new, accountData, party])
|
||||
appState.party.editable = true;
|
||||
else appState.party.editable = false;
|
||||
}, [props.new, accountData, party]);
|
||||
|
||||
useEffect(() => {
|
||||
setJob(appState.party.job)
|
||||
setJobSkills(appState.party.jobSkills)
|
||||
}, [appState])
|
||||
setJob(appState.party.job);
|
||||
setJobSkills(appState.party.jobSkills);
|
||||
}, [appState]);
|
||||
|
||||
// Initialize an array of current uncap values for each characters
|
||||
useEffect(() => {
|
||||
let initialPreviousUncapValues: { [key: number]: number } = {}
|
||||
let initialPreviousUncapValues: { [key: number]: number } = {};
|
||||
Object.values(appState.grid.characters).map((o) => {
|
||||
o ? (initialPreviousUncapValues[o.position] = o.uncap_level) : 0
|
||||
})
|
||||
setPreviousUncapValues(initialPreviousUncapValues)
|
||||
}, [appState.grid.characters])
|
||||
o ? (initialPreviousUncapValues[o.position] = o.uncap_level) : 0;
|
||||
});
|
||||
setPreviousUncapValues(initialPreviousUncapValues);
|
||||
}, [appState.grid.characters]);
|
||||
|
||||
// Methods: Adding an object from search
|
||||
function receiveCharacterFromSearch(
|
||||
object: SearchableObject,
|
||||
position: number
|
||||
) {
|
||||
const character = object as Character
|
||||
const character = object as Character;
|
||||
|
||||
if (!party.id) {
|
||||
props.createParty().then((response) => {
|
||||
const party = response.data.party
|
||||
appState.party.id = party.id
|
||||
setSlug(party.shortcode)
|
||||
const party = response.data.party;
|
||||
appState.party.id = party.id;
|
||||
setSlug(party.shortcode);
|
||||
|
||||
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`)
|
||||
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`);
|
||||
saveCharacter(party.id, character, position)
|
||||
.then((response) => storeGridCharacter(response.data.grid_character))
|
||||
.catch((error) => console.error(error))
|
||||
})
|
||||
.catch((error) => console.error(error));
|
||||
});
|
||||
} else {
|
||||
if (party.editable)
|
||||
saveCharacter(party.id, character, position)
|
||||
.then((response) => handleCharacterResponse(response.data))
|
||||
.catch((error) => console.error(error))
|
||||
.catch((error) => console.error(error));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCharacterResponse(data: any) {
|
||||
if (data.hasOwnProperty("conflicts")) {
|
||||
setIncoming(data.incoming)
|
||||
setConflicts(data.conflicts)
|
||||
setPosition(data.position)
|
||||
setModalOpen(true)
|
||||
setIncoming(data.incoming);
|
||||
setConflicts(data.conflicts);
|
||||
setPosition(data.position);
|
||||
setModalOpen(true);
|
||||
} else {
|
||||
storeGridCharacter(data.grid_character)
|
||||
storeGridCharacter(data.grid_character);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,11 +141,11 @@ const CharacterGrid = (props: Props) => {
|
|||
},
|
||||
},
|
||||
headers
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function storeGridCharacter(gridCharacter: GridCharacter) {
|
||||
appState.grid.characters[gridCharacter.position] = gridCharacter
|
||||
appState.grid.characters[gridCharacter.position] = gridCharacter;
|
||||
}
|
||||
|
||||
async function resolveConflict() {
|
||||
|
|
@ -159,26 +159,26 @@ const CharacterGrid = (props: Props) => {
|
|||
})
|
||||
.then((response) => {
|
||||
// Store new character in state
|
||||
storeGridCharacter(response.data.grid_character)
|
||||
storeGridCharacter(response.data.grid_character);
|
||||
|
||||
// Remove conflicting characters from state
|
||||
conflicts.forEach(
|
||||
(c) => (appState.grid.characters[c.position] = undefined)
|
||||
)
|
||||
);
|
||||
|
||||
// Reset conflict
|
||||
resetConflict()
|
||||
resetConflict();
|
||||
|
||||
// Close modal
|
||||
setModalOpen(false)
|
||||
})
|
||||
setModalOpen(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function resetConflict() {
|
||||
setPosition(-1)
|
||||
setConflicts([])
|
||||
setIncoming(undefined)
|
||||
setPosition(-1);
|
||||
setConflicts([]);
|
||||
setIncoming(undefined);
|
||||
}
|
||||
|
||||
// Methods: Saving job and job skills
|
||||
|
|
@ -188,99 +188,99 @@ const CharacterGrid = (props: Props) => {
|
|||
job_id: job ? job.id : "",
|
||||
},
|
||||
...headers,
|
||||
}
|
||||
};
|
||||
|
||||
if (party.id && appState.party.editable) {
|
||||
api.updateJob({ partyId: party.id, params: payload }).then((response) => {
|
||||
const newParty = response.data.party
|
||||
const newParty = response.data.party;
|
||||
|
||||
setJob(newParty.job)
|
||||
appState.party.job = newParty.job
|
||||
setJob(newParty.job);
|
||||
appState.party.job = newParty.job;
|
||||
|
||||
setJobSkills(newParty.job_skills)
|
||||
appState.party.jobSkills = newParty.job_skills
|
||||
})
|
||||
}
|
||||
setJobSkills(newParty.job_skills);
|
||||
appState.party.jobSkills = newParty.job_skills;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const saveJobSkill = function (skill: JobSkill, position: number) {
|
||||
if (party.id && appState.party.editable) {
|
||||
const positionedKey = `skill${position}_id`
|
||||
const positionedKey = `skill${position}_id`;
|
||||
|
||||
let skillObject: {
|
||||
[key: string]: string | undefined
|
||||
skill0_id?: string
|
||||
skill1_id?: string
|
||||
skill2_id?: string
|
||||
skill3_id?: string
|
||||
} = {}
|
||||
[key: string]: string | undefined;
|
||||
skill0_id?: string;
|
||||
skill1_id?: string;
|
||||
skill2_id?: string;
|
||||
skill3_id?: string;
|
||||
} = {};
|
||||
|
||||
const payload = {
|
||||
party: skillObject,
|
||||
...headers,
|
||||
}
|
||||
};
|
||||
|
||||
skillObject[positionedKey] = skill.id
|
||||
skillObject[positionedKey] = skill.id;
|
||||
api
|
||||
.updateJobSkills({ partyId: party.id, params: payload })
|
||||
.then((response) => {
|
||||
// Update the current skills
|
||||
const newSkills = response.data.party.job_skills
|
||||
setJobSkills(newSkills)
|
||||
appState.party.jobSkills = newSkills
|
||||
const newSkills = response.data.party.job_skills;
|
||||
setJobSkills(newSkills);
|
||||
appState.party.jobSkills = newSkills;
|
||||
})
|
||||
.catch((error) => {
|
||||
const data = error.response.data
|
||||
const data = error.response.data;
|
||||
if (data.code == "too_many_skills_of_type") {
|
||||
const message = `You can only add up to 2 ${
|
||||
data.skill_type === "emp"
|
||||
? data.skill_type.toUpperCase()
|
||||
: data.skill_type
|
||||
} skills to your party at once.`
|
||||
setErrorMessage(message)
|
||||
}
|
||||
console.log(error.response.data)
|
||||
})
|
||||
} skills to your party at once.`;
|
||||
setErrorMessage(message);
|
||||
}
|
||||
console.log(error.response.data);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Methods: Helpers
|
||||
function characterUncapLevel(character: Character) {
|
||||
let uncapLevel
|
||||
let uncapLevel;
|
||||
|
||||
if (character.special) {
|
||||
uncapLevel = 3
|
||||
if (character.uncap.ulb) uncapLevel = 5
|
||||
else if (character.uncap.flb) uncapLevel = 4
|
||||
uncapLevel = 3;
|
||||
if (character.uncap.ulb) uncapLevel = 5;
|
||||
else if (character.uncap.flb) uncapLevel = 4;
|
||||
} else {
|
||||
uncapLevel = 4
|
||||
if (character.uncap.ulb) uncapLevel = 6
|
||||
else if (character.uncap.flb) uncapLevel = 5
|
||||
uncapLevel = 4;
|
||||
if (character.uncap.ulb) uncapLevel = 6;
|
||||
else if (character.uncap.flb) uncapLevel = 5;
|
||||
}
|
||||
|
||||
return uncapLevel
|
||||
return uncapLevel;
|
||||
}
|
||||
|
||||
// Methods: Updating uncap level
|
||||
// Note: Saves, but debouncing is not working properly
|
||||
async function saveUncap(id: string, position: number, uncapLevel: number) {
|
||||
storePreviousUncapValue(position)
|
||||
storePreviousUncapValue(position);
|
||||
|
||||
try {
|
||||
if (uncapLevel != previousUncapValues[position])
|
||||
await api.updateUncap("character", id, uncapLevel).then((response) => {
|
||||
storeGridCharacter(response.data.grid_character)
|
||||
})
|
||||
storeGridCharacter(response.data.grid_character);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
console.error(error);
|
||||
|
||||
// Revert optimistic UI
|
||||
updateUncapLevel(position, previousUncapValues[position])
|
||||
updateUncapLevel(position, previousUncapValues[position]);
|
||||
|
||||
// Remove optimistic key
|
||||
let newPreviousValues = { ...previousUncapValues }
|
||||
delete newPreviousValues[position]
|
||||
setPreviousUncapValues(newPreviousValues)
|
||||
let newPreviousValues = { ...previousUncapValues };
|
||||
delete newPreviousValues[position];
|
||||
setPreviousUncapValues(newPreviousValues);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -289,50 +289,50 @@ const CharacterGrid = (props: Props) => {
|
|||
position: number,
|
||||
uncapLevel: number
|
||||
) {
|
||||
memoizeAction(id, position, uncapLevel)
|
||||
memoizeAction(id, position, uncapLevel);
|
||||
|
||||
// Optimistically update UI
|
||||
updateUncapLevel(position, uncapLevel)
|
||||
updateUncapLevel(position, uncapLevel);
|
||||
}
|
||||
|
||||
const memoizeAction = useCallback(
|
||||
(id: string, position: number, uncapLevel: number) => {
|
||||
debouncedAction(id, position, uncapLevel)
|
||||
debouncedAction(id, position, uncapLevel);
|
||||
},
|
||||
[props, previousUncapValues]
|
||||
)
|
||||
);
|
||||
|
||||
const debouncedAction = useMemo(
|
||||
() =>
|
||||
debounce((id, position, number) => {
|
||||
saveUncap(id, position, number)
|
||||
saveUncap(id, position, number);
|
||||
}, 500),
|
||||
[props, saveUncap]
|
||||
)
|
||||
);
|
||||
|
||||
const updateUncapLevel = (
|
||||
position: number,
|
||||
uncapLevel: number | undefined
|
||||
) => {
|
||||
const character = appState.grid.characters[position]
|
||||
const character = appState.grid.characters[position];
|
||||
if (character && uncapLevel) {
|
||||
character.uncap_level = uncapLevel
|
||||
appState.grid.characters[position] = character
|
||||
}
|
||||
character.uncap_level = uncapLevel;
|
||||
appState.grid.characters[position] = character;
|
||||
}
|
||||
};
|
||||
|
||||
function storePreviousUncapValue(position: number) {
|
||||
// Save the current value in case of an unexpected result
|
||||
let newPreviousValues = { ...previousUncapValues }
|
||||
let newPreviousValues = { ...previousUncapValues };
|
||||
|
||||
if (grid.characters[position]) {
|
||||
newPreviousValues[position] = grid.characters[position]?.uncap_level
|
||||
setPreviousUncapValues(newPreviousValues)
|
||||
newPreviousValues[position] = grid.characters[position]?.uncap_level;
|
||||
setPreviousUncapValues(newPreviousValues);
|
||||
}
|
||||
}
|
||||
|
||||
function cancelAlert() {
|
||||
setErrorMessage("")
|
||||
setErrorMessage("");
|
||||
}
|
||||
|
||||
// Render: JSX components
|
||||
|
|
@ -372,12 +372,12 @@ const CharacterGrid = (props: Props) => {
|
|||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default CharacterGrid
|
||||
export default CharacterGrid;
|
||||
|
|
|
|||
|
|
@ -1,76 +1,108 @@
|
|||
import React from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
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 UncapIndicator from '~components/UncapIndicator'
|
||||
import WeaponLabelIcon from "~components/WeaponLabelIcon";
|
||||
import UncapIndicator from "~components/UncapIndicator";
|
||||
|
||||
import './index.scss'
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
gridCharacter: GridCharacter
|
||||
children: React.ReactNode
|
||||
gridCharacter: GridCharacter;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface KeyNames {
|
||||
[key: string]: {
|
||||
en: string,
|
||||
jp: string
|
||||
}
|
||||
en: string;
|
||||
jp: string;
|
||||
};
|
||||
}
|
||||
|
||||
const CharacterHovercard = (props: Props) => {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation('common')
|
||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation("common");
|
||||
const locale =
|
||||
router.locale && ["en", "ja"].includes(router.locale)
|
||||
? router.locale
|
||||
: "en";
|
||||
|
||||
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
|
||||
const Proficiency = ['none', 'sword', 'dagger', 'axe', 'spear', 'bow', 'staff', 'fist', 'harp', 'gun', 'katana']
|
||||
const Element = ["null", "wind", "fire", "water", "earth", "dark", "light"];
|
||||
const Proficiency = [
|
||||
"none",
|
||||
"sword",
|
||||
"dagger",
|
||||
"axe",
|
||||
"spear",
|
||||
"bow",
|
||||
"staff",
|
||||
"fist",
|
||||
"harp",
|
||||
"gun",
|
||||
"katana",
|
||||
];
|
||||
|
||||
const tintElement = Element[props.gridCharacter.object.element]
|
||||
const wikiUrl = `https://gbf.wiki/${props.gridCharacter.object.name.en.replaceAll(' ', '_')}`
|
||||
const tintElement = Element[props.gridCharacter.object.element];
|
||||
const wikiUrl = `https://gbf.wiki/${props.gridCharacter.object.name.en.replaceAll(
|
||||
" ",
|
||||
"_"
|
||||
)}`;
|
||||
|
||||
function characterImage() {
|
||||
let imgSrc = ""
|
||||
let imgSrc = "";
|
||||
|
||||
if (props.gridCharacter) {
|
||||
const character = props.gridCharacter.object
|
||||
const character = props.gridCharacter.object;
|
||||
|
||||
// Change the image based on the uncap level
|
||||
let suffix = '01'
|
||||
if (props.gridCharacter.uncap_level == 6)
|
||||
suffix = '04'
|
||||
else if (props.gridCharacter.uncap_level == 5)
|
||||
suffix = '03'
|
||||
else if (props.gridCharacter.uncap_level > 2)
|
||||
suffix = '02'
|
||||
let suffix = "01";
|
||||
if (props.gridCharacter.uncap_level == 6) suffix = "04";
|
||||
else if (props.gridCharacter.uncap_level == 5) suffix = "03";
|
||||
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 (
|
||||
<HoverCard.Root>
|
||||
<HoverCard.Trigger>
|
||||
{ props.children }
|
||||
</HoverCard.Trigger>
|
||||
<HoverCard.Trigger>{props.children}</HoverCard.Trigger>
|
||||
<HoverCard.Content className="Weapon Hovercard">
|
||||
<div className="top">
|
||||
<div className="title">
|
||||
<h4>{ props.gridCharacter.object.name[locale] }</h4>
|
||||
<img alt={props.gridCharacter.object.name[locale]} src={characterImage()} />
|
||||
<h4>{props.gridCharacter.object.name[locale]}</h4>
|
||||
<img
|
||||
alt={props.gridCharacter.object.name[locale]}
|
||||
src={characterImage()}
|
||||
/>
|
||||
</div>
|
||||
<div className="subInfo">
|
||||
<div className="icons">
|
||||
<WeaponLabelIcon labelType={Element[props.gridCharacter.object.element]} />
|
||||
<WeaponLabelIcon labelType={ Proficiency[props.gridCharacter.object.proficiency.proficiency1] } />
|
||||
{ (props.gridCharacter.object.proficiency.proficiency2) ?
|
||||
<WeaponLabelIcon labelType={ Proficiency[props.gridCharacter.object.proficiency.proficiency2] } />
|
||||
: ''}
|
||||
<WeaponLabelIcon
|
||||
labelType={Element[props.gridCharacter.object.element]}
|
||||
/>
|
||||
<WeaponLabelIcon
|
||||
labelType={
|
||||
Proficiency[
|
||||
props.gridCharacter.object.proficiency.proficiency1
|
||||
]
|
||||
}
|
||||
/>
|
||||
{props.gridCharacter.object.proficiency.proficiency2 ? (
|
||||
<WeaponLabelIcon
|
||||
labelType={
|
||||
Proficiency[
|
||||
props.gridCharacter.object.proficiency.proficiency2
|
||||
]
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
<UncapIndicator
|
||||
type="character"
|
||||
|
|
@ -81,12 +113,13 @@ const CharacterHovercard = (props: Props) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">{t('buttons.wiki')}</a>
|
||||
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">
|
||||
{t("buttons.wiki")}
|
||||
</a>
|
||||
<HoverCard.Arrow />
|
||||
</HoverCard.Content>
|
||||
</HoverCard.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export default CharacterHovercard
|
||||
);
|
||||
};
|
||||
|
||||
export default CharacterHovercard;
|
||||
|
|
|
|||
|
|
@ -37,11 +37,11 @@
|
|||
|
||||
.stars {
|
||||
display: inline-block;
|
||||
color: #FFA15E;
|
||||
color: #ffa15e;
|
||||
font-size: $font-xlarge;
|
||||
|
||||
& > span {
|
||||
color: #65DAFF;
|
||||
color: #65daff;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +1,36 @@
|
|||
import React from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import UncapIndicator from '~components/UncapIndicator'
|
||||
import WeaponLabelIcon from '~components/WeaponLabelIcon'
|
||||
import UncapIndicator from "~components/UncapIndicator";
|
||||
import WeaponLabelIcon from "~components/WeaponLabelIcon";
|
||||
|
||||
import './index.scss'
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
data: Character
|
||||
onClick: () => void
|
||||
data: Character;
|
||||
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 router = useRouter()
|
||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
||||
const router = useRouter();
|
||||
const locale =
|
||||
router.locale && ["en", "ja"].includes(router.locale)
|
||||
? router.locale
|
||||
: "en";
|
||||
|
||||
const character = props.data
|
||||
const character = props.data;
|
||||
|
||||
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') {
|
||||
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_01_01.jpg`
|
||||
if (character.granblue_id === "3030182000") {
|
||||
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_01_01.jpg`;
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
return (
|
||||
<li className="CharacterResult" onClick={props.onClick}>
|
||||
|
|
@ -45,7 +48,7 @@ const CharacterResult = (props: Props) => {
|
|||
</div>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default CharacterResult
|
||||
export default CharacterResult;
|
||||
|
|
|
|||
|
|
@ -1,205 +1,268 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import React, { useEffect, useState } from "react";
|
||||
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 SearchFilterCheckboxItem from '~components/SearchFilterCheckboxItem'
|
||||
import SearchFilter from "~components/SearchFilter";
|
||||
import SearchFilterCheckboxItem from "~components/SearchFilterCheckboxItem";
|
||||
|
||||
import './index.scss'
|
||||
import { emptyElementState, emptyProficiencyState, emptyRarityState } from '~utils/emptyStates'
|
||||
import { elements, proficiencies, rarities } from '~utils/stateValues'
|
||||
import "./index.scss";
|
||||
import {
|
||||
emptyElementState,
|
||||
emptyProficiencyState,
|
||||
emptyRarityState,
|
||||
} from "~utils/emptyStates";
|
||||
import { elements, proficiencies, rarities } from "~utils/stateValues";
|
||||
|
||||
interface Props {
|
||||
sendFilters: (filters: { [key: string]: number[] }) => void
|
||||
sendFilters: (filters: { [key: string]: number[] }) => void;
|
||||
}
|
||||
|
||||
const CharacterSearchFilterBar = (props: Props) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
const [rarityMenu, setRarityMenu] = useState(false)
|
||||
const [elementMenu, setElementMenu] = useState(false)
|
||||
const [proficiency1Menu, setProficiency1Menu] = useState(false)
|
||||
const [proficiency2Menu, setProficiency2Menu] = useState(false)
|
||||
const [rarityMenu, setRarityMenu] = useState(false);
|
||||
const [elementMenu, setElementMenu] = useState(false);
|
||||
const [proficiency1Menu, setProficiency1Menu] = useState(false);
|
||||
const [proficiency2Menu, setProficiency2Menu] = useState(false);
|
||||
|
||||
const [rarityState, setRarityState] = useState<RarityState>(emptyRarityState)
|
||||
const [elementState, setElementState] = useState<ElementState>(emptyElementState)
|
||||
const [proficiency1State, setProficiency1State] = useState<ProficiencyState>(emptyProficiencyState)
|
||||
const [proficiency2State, setProficiency2State] = useState<ProficiencyState>(emptyProficiencyState)
|
||||
const [rarityState, setRarityState] = useState<RarityState>(emptyRarityState);
|
||||
const [elementState, setElementState] =
|
||||
useState<ElementState>(emptyElementState);
|
||||
const [proficiency1State, setProficiency1State] = useState<ProficiencyState>(
|
||||
emptyProficiencyState
|
||||
);
|
||||
const [proficiency2State, setProficiency2State] = useState<ProficiencyState>(
|
||||
emptyProficiencyState
|
||||
);
|
||||
|
||||
function rarityMenuOpened(open: boolean) {
|
||||
if (open) {
|
||||
setRarityMenu(true)
|
||||
setElementMenu(false)
|
||||
setProficiency1Menu(false)
|
||||
setProficiency2Menu(false)
|
||||
} else setRarityMenu(false)
|
||||
setRarityMenu(true);
|
||||
setElementMenu(false);
|
||||
setProficiency1Menu(false);
|
||||
setProficiency2Menu(false);
|
||||
} else setRarityMenu(false);
|
||||
}
|
||||
|
||||
function elementMenuOpened(open: boolean) {
|
||||
if (open) {
|
||||
setRarityMenu(false)
|
||||
setElementMenu(true)
|
||||
setProficiency1Menu(false)
|
||||
setProficiency2Menu(false)
|
||||
} else setElementMenu(false)
|
||||
setRarityMenu(false);
|
||||
setElementMenu(true);
|
||||
setProficiency1Menu(false);
|
||||
setProficiency2Menu(false);
|
||||
} else setElementMenu(false);
|
||||
}
|
||||
|
||||
function proficiency1MenuOpened(open: boolean) {
|
||||
if (open) {
|
||||
setRarityMenu(false)
|
||||
setElementMenu(false)
|
||||
setProficiency1Menu(true)
|
||||
setProficiency2Menu(false)
|
||||
} else setProficiency1Menu(false)
|
||||
setRarityMenu(false);
|
||||
setElementMenu(false);
|
||||
setProficiency1Menu(true);
|
||||
setProficiency2Menu(false);
|
||||
} else setProficiency1Menu(false);
|
||||
}
|
||||
|
||||
function proficiency2MenuOpened(open: boolean) {
|
||||
if (open) {
|
||||
setRarityMenu(false)
|
||||
setElementMenu(false)
|
||||
setProficiency1Menu(false)
|
||||
setProficiency2Menu(true)
|
||||
} else setProficiency2Menu(false)
|
||||
setRarityMenu(false);
|
||||
setElementMenu(false);
|
||||
setProficiency1Menu(false);
|
||||
setProficiency2Menu(true);
|
||||
} else setProficiency2Menu(false);
|
||||
}
|
||||
|
||||
function handleRarityChange(checked: boolean, key: string) {
|
||||
let newRarityState = cloneDeep(rarityState)
|
||||
newRarityState[key].checked = checked
|
||||
setRarityState(newRarityState)
|
||||
let newRarityState = cloneDeep(rarityState);
|
||||
newRarityState[key].checked = checked;
|
||||
setRarityState(newRarityState);
|
||||
}
|
||||
|
||||
function handleElementChange(checked: boolean, key: string) {
|
||||
let newElementState = cloneDeep(elementState)
|
||||
newElementState[key].checked = checked
|
||||
setElementState(newElementState)
|
||||
let newElementState = cloneDeep(elementState);
|
||||
newElementState[key].checked = checked;
|
||||
setElementState(newElementState);
|
||||
}
|
||||
|
||||
function handleProficiency1Change(checked: boolean, key: string) {
|
||||
let newProficiencyState = cloneDeep(proficiency1State)
|
||||
newProficiencyState[key].checked = checked
|
||||
setProficiency1State(newProficiencyState)
|
||||
let newProficiencyState = cloneDeep(proficiency1State);
|
||||
newProficiencyState[key].checked = checked;
|
||||
setProficiency1State(newProficiencyState);
|
||||
}
|
||||
|
||||
function handleProficiency2Change(checked: boolean, key: string) {
|
||||
let newProficiencyState = cloneDeep(proficiency2State)
|
||||
newProficiencyState[key].checked = checked
|
||||
setProficiency2State(newProficiencyState)
|
||||
let newProficiencyState = cloneDeep(proficiency2State);
|
||||
newProficiencyState[key].checked = checked;
|
||||
setProficiency2State(newProficiencyState);
|
||||
}
|
||||
|
||||
function sendFilters() {
|
||||
const checkedRarityFilters = Object.values(rarityState).filter(x => x.checked).map((x, i) => x.id)
|
||||
const checkedElementFilters = Object.values(elementState).filter(x => x.checked).map((x, i) => x.id)
|
||||
const checkedProficiency1Filters = Object.values(proficiency1State).filter(x => x.checked).map((x, i) => x.id)
|
||||
const checkedProficiency2Filters = Object.values(proficiency2State).filter(x => x.checked).map((x, i) => x.id)
|
||||
const checkedRarityFilters = Object.values(rarityState)
|
||||
.filter((x) => x.checked)
|
||||
.map((x, i) => x.id);
|
||||
const checkedElementFilters = Object.values(elementState)
|
||||
.filter((x) => x.checked)
|
||||
.map((x, i) => x.id);
|
||||
const checkedProficiency1Filters = Object.values(proficiency1State)
|
||||
.filter((x) => x.checked)
|
||||
.map((x, i) => x.id);
|
||||
const checkedProficiency2Filters = Object.values(proficiency2State)
|
||||
.filter((x) => x.checked)
|
||||
.map((x, i) => x.id);
|
||||
|
||||
const filters = {
|
||||
rarity: checkedRarityFilters,
|
||||
element: checkedElementFilters,
|
||||
proficiency1: checkedProficiency1Filters,
|
||||
proficiency2: checkedProficiency2Filters
|
||||
}
|
||||
proficiency2: checkedProficiency2Filters,
|
||||
};
|
||||
|
||||
props.sendFilters(filters)
|
||||
props.sendFilters(filters);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
sendFilters()
|
||||
}, [rarityState, elementState, proficiency1State, proficiency2State])
|
||||
sendFilters();
|
||||
}, [rarityState, elementState, proficiency1State, proficiency2State]);
|
||||
|
||||
function renderProficiencyFilter(proficiency: 1 | 2) {
|
||||
const onCheckedChange = (proficiency == 1) ? handleProficiency1Change : handleProficiency2Change
|
||||
const numSelected = (proficiency == 1)
|
||||
? Object.values(proficiency1State).map(x => x.checked).filter(Boolean).length
|
||||
: Object.values(proficiency2State).map(x => x.checked).filter(Boolean).length
|
||||
const open = (proficiency == 1) ? proficiency1Menu : proficiency2Menu
|
||||
const onOpenChange = (proficiency == 1) ? proficiency1MenuOpened : proficiency2MenuOpened
|
||||
const onCheckedChange =
|
||||
proficiency == 1 ? handleProficiency1Change : handleProficiency2Change;
|
||||
const numSelected =
|
||||
proficiency == 1
|
||||
? Object.values(proficiency1State)
|
||||
.map((x) => x.checked)
|
||||
.filter(Boolean).length
|
||||
: Object.values(proficiency2State)
|
||||
.map((x) => x.checked)
|
||||
.filter(Boolean).length;
|
||||
const open = proficiency == 1 ? proficiency1Menu : proficiency2Menu;
|
||||
const onOpenChange =
|
||||
proficiency == 1 ? proficiency1MenuOpened : proficiency2MenuOpened;
|
||||
|
||||
return (
|
||||
<SearchFilter
|
||||
label={`${t('filters.labels.proficiency')} ${proficiency}`}
|
||||
label={`${t("filters.labels.proficiency")} ${proficiency}`}
|
||||
numSelected={numSelected}
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}>
|
||||
<DropdownMenu.Label className="Label">{`${t('filters.labels.proficiency')} ${proficiency}`}</DropdownMenu.Label>
|
||||
onOpenChange={onOpenChange}
|
||||
>
|
||||
<DropdownMenu.Label className="Label">{`${t(
|
||||
"filters.labels.proficiency"
|
||||
)} ${proficiency}`}</DropdownMenu.Label>
|
||||
<section>
|
||||
<DropdownMenu.Group className="Group">
|
||||
{ Array.from(Array(proficiencies.length / 2)).map((x, i) => {
|
||||
const checked = (proficiency == 1)
|
||||
{Array.from(Array(proficiencies.length / 2)).map((x, i) => {
|
||||
const checked =
|
||||
proficiency == 1
|
||||
? proficiency1State[proficiencies[i]].checked
|
||||
: proficiency2State[proficiencies[i]].checked
|
||||
: proficiency2State[proficiencies[i]].checked;
|
||||
|
||||
return (
|
||||
<SearchFilterCheckboxItem
|
||||
key={proficiencies[i]}
|
||||
onCheckedChange={onCheckedChange}
|
||||
checked={checked}
|
||||
valueKey={proficiencies[i]}>
|
||||
valueKey={proficiencies[i]}
|
||||
>
|
||||
{t(`proficiencies.${proficiencies[i]}`)}
|
||||
</SearchFilterCheckboxItem>
|
||||
)}
|
||||
) }
|
||||
);
|
||||
})}
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Group className="Group">
|
||||
{ Array.from(Array(proficiencies.length / 2)).map((x, i) => {
|
||||
const checked = (proficiency == 1)
|
||||
? proficiency1State[proficiencies[i + (proficiencies.length / 2)]].checked
|
||||
: proficiency2State[proficiencies[i + (proficiencies.length / 2)]].checked
|
||||
{Array.from(Array(proficiencies.length / 2)).map((x, i) => {
|
||||
const checked =
|
||||
proficiency == 1
|
||||
? proficiency1State[
|
||||
proficiencies[i + proficiencies.length / 2]
|
||||
].checked
|
||||
: proficiency2State[
|
||||
proficiencies[i + proficiencies.length / 2]
|
||||
].checked;
|
||||
|
||||
return (
|
||||
<SearchFilterCheckboxItem
|
||||
key={proficiencies[i + (proficiencies.length / 2)]}
|
||||
key={proficiencies[i + proficiencies.length / 2]}
|
||||
onCheckedChange={onCheckedChange}
|
||||
checked={checked}
|
||||
valueKey={proficiencies[i + (proficiencies.length / 2)]}>
|
||||
{t(`proficiencies.${proficiencies[i + (proficiencies.length / 2)]}`)}
|
||||
</SearchFilterCheckboxItem>
|
||||
valueKey={proficiencies[i + proficiencies.length / 2]}
|
||||
>
|
||||
{t(
|
||||
`proficiencies.${
|
||||
proficiencies[i + proficiencies.length / 2]
|
||||
}`
|
||||
)}
|
||||
) }
|
||||
</SearchFilterCheckboxItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenu.Group>
|
||||
</section>
|
||||
</SearchFilter>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="SearchFilterBar">
|
||||
<SearchFilter label={t('filters.labels.rarity')} numSelected={Object.values(rarityState).map(x => x.checked).filter(Boolean).length} open={rarityMenu} onOpenChange={rarityMenuOpened}>
|
||||
<DropdownMenu.Label className="Label">{t('filters.labels.rarity')}</DropdownMenu.Label>
|
||||
{ Array.from(Array(rarities.length)).map((x, i) => {
|
||||
<SearchFilter
|
||||
label={t("filters.labels.rarity")}
|
||||
numSelected={
|
||||
Object.values(rarityState)
|
||||
.map((x) => x.checked)
|
||||
.filter(Boolean).length
|
||||
}
|
||||
open={rarityMenu}
|
||||
onOpenChange={rarityMenuOpened}
|
||||
>
|
||||
<DropdownMenu.Label className="Label">
|
||||
{t("filters.labels.rarity")}
|
||||
</DropdownMenu.Label>
|
||||
{Array.from(Array(rarities.length)).map((x, i) => {
|
||||
return (
|
||||
<SearchFilterCheckboxItem
|
||||
key={rarities[i]}
|
||||
onCheckedChange={handleRarityChange}
|
||||
checked={rarityState[rarities[i]].checked}
|
||||
valueKey={rarities[i]}>
|
||||
valueKey={rarities[i]}
|
||||
>
|
||||
{t(`rarities.${rarities[i]}`)}
|
||||
</SearchFilterCheckboxItem>
|
||||
)}
|
||||
) }
|
||||
);
|
||||
})}
|
||||
</SearchFilter>
|
||||
|
||||
<SearchFilter label={t('filters.labels.element')} numSelected={Object.values(elementState).map(x => x.checked).filter(Boolean).length} open={elementMenu} onOpenChange={elementMenuOpened}>
|
||||
<DropdownMenu.Label className="Label">{t('filters.labels.element')}</DropdownMenu.Label>
|
||||
{ Array.from(Array(elements.length)).map((x, i) => {
|
||||
<SearchFilter
|
||||
label={t("filters.labels.element")}
|
||||
numSelected={
|
||||
Object.values(elementState)
|
||||
.map((x) => x.checked)
|
||||
.filter(Boolean).length
|
||||
}
|
||||
open={elementMenu}
|
||||
onOpenChange={elementMenuOpened}
|
||||
>
|
||||
<DropdownMenu.Label className="Label">
|
||||
{t("filters.labels.element")}
|
||||
</DropdownMenu.Label>
|
||||
{Array.from(Array(elements.length)).map((x, i) => {
|
||||
return (
|
||||
<SearchFilterCheckboxItem
|
||||
key={elements[i]}
|
||||
onCheckedChange={handleElementChange}
|
||||
checked={elementState[elements[i]].checked}
|
||||
valueKey={elements[i]}>
|
||||
valueKey={elements[i]}
|
||||
>
|
||||
{t(`elements.${elements[i]}`)}
|
||||
</SearchFilterCheckboxItem>
|
||||
)}
|
||||
) }
|
||||
);
|
||||
})}
|
||||
</SearchFilter>
|
||||
|
||||
{ renderProficiencyFilter(1) }
|
||||
{ renderProficiencyFilter(2) }
|
||||
{renderProficiencyFilter(1)}
|
||||
{renderProficiencyFilter(2)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default CharacterSearchFilterBar
|
||||
export default CharacterSearchFilterBar;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@
|
|||
z-index: 2;
|
||||
}
|
||||
|
||||
|
||||
.CharacterImage {
|
||||
aspect-ratio: 131 / 273;
|
||||
background: white;
|
||||
|
|
|
|||
|
|
@ -1,85 +1,87 @@
|
|||
import React, { useEffect, useState } from "react"
|
||||
import { useRouter } from "next/router"
|
||||
import { useSnapshot } from "valtio"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import classnames from "classnames"
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useSnapshot } from "valtio";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import classnames from "classnames";
|
||||
|
||||
import { appState } from "~utils/appState"
|
||||
import { appState } from "~utils/appState";
|
||||
|
||||
import CharacterHovercard from "~components/CharacterHovercard"
|
||||
import SearchModal from "~components/SearchModal"
|
||||
import UncapIndicator from "~components/UncapIndicator"
|
||||
import PlusIcon from "~public/icons/Add.svg"
|
||||
import CharacterHovercard from "~components/CharacterHovercard";
|
||||
import SearchModal from "~components/SearchModal";
|
||||
import UncapIndicator from "~components/UncapIndicator";
|
||||
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 {
|
||||
gridCharacter?: GridCharacter
|
||||
position: number
|
||||
editable: boolean
|
||||
updateObject: (object: SearchableObject, position: number) => void
|
||||
updateUncap: (id: string, position: number, uncap: number) => void
|
||||
gridCharacter?: GridCharacter;
|
||||
position: number;
|
||||
editable: boolean;
|
||||
updateObject: (object: SearchableObject, position: number) => void;
|
||||
updateUncap: (id: string, position: number, uncap: number) => void;
|
||||
}
|
||||
|
||||
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 =
|
||||
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
||||
router.locale && ["en", "ja"].includes(router.locale)
|
||||
? router.locale
|
||||
: "en";
|
||||
|
||||
const [imageUrl, setImageUrl] = useState("")
|
||||
const [imageUrl, setImageUrl] = useState("");
|
||||
|
||||
const classes = classnames({
|
||||
CharacterUnit: true,
|
||||
editable: props.editable,
|
||||
filled: props.gridCharacter !== undefined,
|
||||
})
|
||||
});
|
||||
|
||||
const gridCharacter = props.gridCharacter
|
||||
const character = gridCharacter?.object
|
||||
const gridCharacter = props.gridCharacter;
|
||||
const character = gridCharacter?.object;
|
||||
|
||||
useEffect(() => {
|
||||
generateImageUrl()
|
||||
})
|
||||
generateImageUrl();
|
||||
});
|
||||
|
||||
function generateImageUrl() {
|
||||
let imgSrc = ""
|
||||
let imgSrc = "";
|
||||
|
||||
if (props.gridCharacter) {
|
||||
const character = props.gridCharacter.object!
|
||||
const character = props.gridCharacter.object!;
|
||||
|
||||
// Change the image based on the uncap level
|
||||
let suffix = "01"
|
||||
if (props.gridCharacter.uncap_level == 6) suffix = "04"
|
||||
else if (props.gridCharacter.uncap_level == 5) suffix = "03"
|
||||
else if (props.gridCharacter.uncap_level > 2) suffix = "02"
|
||||
let suffix = "01";
|
||||
if (props.gridCharacter.uncap_level == 6) suffix = "04";
|
||||
else if (props.gridCharacter.uncap_level == 5) suffix = "03";
|
||||
else if (props.gridCharacter.uncap_level > 2) suffix = "02";
|
||||
|
||||
// Special casing for Lyria (and Young Cat eventually)
|
||||
if (props.gridCharacter.object.granblue_id === "3030182000") {
|
||||
let element = 1
|
||||
let element = 1;
|
||||
if (grid.weapons.mainWeapon && grid.weapons.mainWeapon.element) {
|
||||
element = grid.weapons.mainWeapon.element
|
||||
element = grid.weapons.mainWeapon.element;
|
||||
} 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) {
|
||||
if (props.gridCharacter)
|
||||
props.updateUncap(props.gridCharacter.id, props.position, uncap)
|
||||
props.updateUncap(props.gridCharacter.id, props.position, uncap);
|
||||
}
|
||||
|
||||
const image = (
|
||||
|
|
@ -93,7 +95,7 @@ const CharacterUnit = (props: Props) => {
|
|||
""
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
const editableImage = (
|
||||
<SearchModal
|
||||
|
|
@ -104,7 +106,7 @@ const CharacterUnit = (props: Props) => {
|
|||
>
|
||||
{image}
|
||||
</SearchModal>
|
||||
)
|
||||
);
|
||||
|
||||
const unitContent = (
|
||||
<div className={classes}>
|
||||
|
|
@ -123,15 +125,15 @@ const CharacterUnit = (props: Props) => {
|
|||
)}
|
||||
<h3 className="CharacterName">{character?.name[locale]}</h3>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
const withHovercard = (
|
||||
<CharacterHovercard gridCharacter={gridCharacter!}>
|
||||
{unitContent}
|
||||
</CharacterHovercard>
|
||||
)
|
||||
);
|
||||
|
||||
return gridCharacter && !props.editable ? withHovercard : unitContent
|
||||
}
|
||||
return gridCharacter && !props.editable ? withHovercard : unitContent;
|
||||
};
|
||||
|
||||
export default CharacterUnit
|
||||
export default CharacterUnit;
|
||||
|
|
|
|||
|
|
@ -26,8 +26,9 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover, &[data-state="on"] {
|
||||
background:$grey-80;
|
||||
&:hover,
|
||||
&[data-state="on"] {
|
||||
background: $grey-80;
|
||||
color: $grey-00;
|
||||
|
||||
&.fire {
|
||||
|
|
|
|||
|
|
@ -1,46 +1,83 @@
|
|||
import React from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
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 {
|
||||
currentElement: number
|
||||
sendValue: (value: string) => void
|
||||
currentElement: number;
|
||||
sendValue: (value: string) => void;
|
||||
}
|
||||
|
||||
const ElementToggle = (props: Props) => {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation('common')
|
||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation("common");
|
||||
const locale =
|
||||
router.locale && ["en", "ja"].includes(router.locale)
|
||||
? router.locale
|
||||
: "en";
|
||||
|
||||
return (
|
||||
<ToggleGroup.Root className="ToggleGroup" type="single" defaultValue={`${props.currentElement}`} aria-label="Element" onValueChange={props.sendValue}>
|
||||
<ToggleGroup.Item className={`ToggleItem ${locale}`} value="0" aria-label="null">
|
||||
{t('elements.null')}
|
||||
<ToggleGroup.Root
|
||||
className="ToggleGroup"
|
||||
type="single"
|
||||
defaultValue={`${props.currentElement}`}
|
||||
aria-label="Element"
|
||||
onValueChange={props.sendValue}
|
||||
>
|
||||
<ToggleGroup.Item
|
||||
className={`ToggleItem ${locale}`}
|
||||
value="0"
|
||||
aria-label="null"
|
||||
>
|
||||
{t("elements.null")}
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item className={`ToggleItem wind ${locale}`} value="1" aria-label="wind">
|
||||
{t('elements.wind')}
|
||||
<ToggleGroup.Item
|
||||
className={`ToggleItem wind ${locale}`}
|
||||
value="1"
|
||||
aria-label="wind"
|
||||
>
|
||||
{t("elements.wind")}
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item className={`ToggleItem fire ${locale}`} value="2" aria-label="fire">
|
||||
{t('elements.fire')}
|
||||
<ToggleGroup.Item
|
||||
className={`ToggleItem fire ${locale}`}
|
||||
value="2"
|
||||
aria-label="fire"
|
||||
>
|
||||
{t("elements.fire")}
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item className={`ToggleItem water ${locale}`} value="3" aria-label="water">
|
||||
{t('elements.water')}
|
||||
<ToggleGroup.Item
|
||||
className={`ToggleItem water ${locale}`}
|
||||
value="3"
|
||||
aria-label="water"
|
||||
>
|
||||
{t("elements.water")}
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item className={`ToggleItem earth ${locale}`} value="4" aria-label="earth">
|
||||
{t('elements.earth')}
|
||||
<ToggleGroup.Item
|
||||
className={`ToggleItem earth ${locale}`}
|
||||
value="4"
|
||||
aria-label="earth"
|
||||
>
|
||||
{t("elements.earth")}
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item className={`ToggleItem dark ${locale}`} value="5" aria-label="dark">
|
||||
{t('elements.dark')}
|
||||
<ToggleGroup.Item
|
||||
className={`ToggleItem dark ${locale}`}
|
||||
value="5"
|
||||
aria-label="dark"
|
||||
>
|
||||
{t("elements.dark")}
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item className={`ToggleItem light ${locale}`} value="6" aria-label="light">
|
||||
{t('elements.light')}
|
||||
<ToggleGroup.Item
|
||||
className={`ToggleItem light ${locale}`}
|
||||
value="6"
|
||||
aria-label="light"
|
||||
>
|
||||
{t("elements.light")}
|
||||
</ToggleGroup.Item>
|
||||
</ToggleGroup.Root>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default ElementToggle
|
||||
export default ElementToggle;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#ExtraSummons {
|
||||
background: #FFEBD9;
|
||||
background: #ffebd9;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
}
|
||||
|
||||
& > span {
|
||||
color: #825B39;
|
||||
color: #825b39;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import React from "react"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import SummonUnit from "~components/SummonUnit"
|
||||
import { SearchableObject } from "~types"
|
||||
import "./index.scss"
|
||||
import React from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import SummonUnit from "~components/SummonUnit";
|
||||
import { SearchableObject } from "~types";
|
||||
import "./index.scss";
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
grid: GridArray<GridSummon>
|
||||
editable: boolean
|
||||
exists: boolean
|
||||
found?: boolean
|
||||
offset: number
|
||||
updateObject: (object: SearchableObject, position: number) => void
|
||||
updateUncap: (id: string, position: number, uncap: number) => void
|
||||
grid: GridArray<GridSummon>;
|
||||
editable: boolean;
|
||||
exists: boolean;
|
||||
found?: boolean;
|
||||
offset: number;
|
||||
updateObject: (object: SearchableObject, position: number) => void;
|
||||
updateUncap: (id: string, position: number, uncap: number) => void;
|
||||
}
|
||||
|
||||
const ExtraSummons = (props: Props) => {
|
||||
const numSummons: number = 2
|
||||
const numSummons: number = 2;
|
||||
|
||||
const { t } = useTranslation("common")
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
return (
|
||||
<div id="ExtraSummons">
|
||||
|
|
@ -36,11 +36,11 @@ const ExtraSummons = (props: Props) => {
|
|||
updateUncap={props.updateUncap}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtraSummons
|
||||
export default ExtraSummons;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#ExtraGrid {
|
||||
background: #ECEBFF;
|
||||
background: #ecebff;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
}
|
||||
|
||||
& > span {
|
||||
color: #4F3C79;
|
||||
color: #4f3c79;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
|
|
@ -38,10 +38,10 @@
|
|||
}
|
||||
|
||||
.WeaponUnit .WeaponImage {
|
||||
background: #D5D3F6;
|
||||
background: #d5d3f6;
|
||||
}
|
||||
|
||||
.WeaponUnit .WeaponImage .icon svg {
|
||||
fill: #8F8AC6;
|
||||
fill: #8f8ac6;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +1,24 @@
|
|||
import React from "react"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import WeaponUnit from "~components/WeaponUnit"
|
||||
import React from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import WeaponUnit from "~components/WeaponUnit";
|
||||
|
||||
import type { SearchableObject } from "~types"
|
||||
import type { SearchableObject } from "~types";
|
||||
|
||||
import "./index.scss"
|
||||
import "./index.scss";
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
grid: GridArray<GridWeapon>
|
||||
editable: boolean
|
||||
found?: boolean
|
||||
offset: number
|
||||
updateObject: (object: SearchableObject, position: number) => void
|
||||
updateUncap: (id: string, position: number, uncap: number) => void
|
||||
grid: GridArray<GridWeapon>;
|
||||
editable: boolean;
|
||||
found?: boolean;
|
||||
offset: number;
|
||||
updateObject: (object: SearchableObject, position: number) => void;
|
||||
updateUncap: (id: string, position: number, uncap: number) => void;
|
||||
}
|
||||
|
||||
const ExtraWeapons = (props: Props) => {
|
||||
const numWeapons: number = 3
|
||||
const { t } = useTranslation("common")
|
||||
const numWeapons: number = 3;
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
return (
|
||||
<div id="ExtraGrid">
|
||||
|
|
@ -36,11 +36,11 @@ const ExtraWeapons = (props: Props) => {
|
|||
updateUncap={props.updateUncap}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtraWeapons
|
||||
export default ExtraWeapons;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
|
||||
::placeholder {
|
||||
/* Chrome, Firefox, Opera, Safari 10.1+ */
|
||||
color: #a9a9a9 !important;
|
||||
opacity: 1; /* Firefox */
|
||||
}
|
||||
|
|
@ -1,17 +1,22 @@
|
|||
import React from 'react'
|
||||
import './index.scss'
|
||||
import React from "react";
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
fieldName: string
|
||||
placeholder: string
|
||||
value?: string
|
||||
error: string
|
||||
onBlur?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
fieldName: string;
|
||||
placeholder: string;
|
||||
value?: string;
|
||||
error: string;
|
||||
onBlur?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
const Fieldset = React.forwardRef<HTMLInputElement, Props>(function fieldSet(props, ref) {
|
||||
const fieldType = (['password', 'confirm_password'].includes(props.fieldName)) ? 'password' : 'text'
|
||||
const Fieldset = React.forwardRef<HTMLInputElement, Props>(function fieldSet(
|
||||
props,
|
||||
ref
|
||||
) {
|
||||
const fieldType = ["password", "confirm_password"].includes(props.fieldName)
|
||||
? "password"
|
||||
: "text";
|
||||
|
||||
return (
|
||||
<fieldset className="Fieldset">
|
||||
|
|
@ -21,18 +26,15 @@ const Fieldset = React.forwardRef<HTMLInputElement, Props>(function fieldSet(pro
|
|||
type={fieldType}
|
||||
name={props.fieldName}
|
||||
placeholder={props.placeholder}
|
||||
defaultValue={props.value || ''}
|
||||
defaultValue={props.value || ""}
|
||||
onBlur={props.onBlur}
|
||||
onChange={props.onChange}
|
||||
ref={ref}
|
||||
formNoValidate
|
||||
/>
|
||||
{
|
||||
props.error.length > 0 &&
|
||||
<p className='InputError'>{props.error}</p>
|
||||
}
|
||||
{props.error.length > 0 && <p className="InputError">{props.error}</p>}
|
||||
</fieldset>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
export default Fieldset
|
||||
export default Fieldset;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
}
|
||||
|
||||
select {
|
||||
background: url('/icons/Arrow.svg'), $grey-90;
|
||||
background: url("/icons/Arrow.svg"), $grey-90;
|
||||
background-repeat: no-repeat;
|
||||
background-position-y: center;
|
||||
background-position-x: 95%;
|
||||
|
|
@ -37,7 +37,6 @@
|
|||
max-width: 200px;
|
||||
}
|
||||
|
||||
|
||||
.UserInfo {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
|
@ -52,11 +51,11 @@
|
|||
width: $diameter;
|
||||
|
||||
&.gran {
|
||||
background-color: #CEE7FE;
|
||||
background-color: #cee7fe;
|
||||
}
|
||||
|
||||
&.djeeta {
|
||||
background-color: #FFE1FE;
|
||||
background-color: #ffe1fe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,61 +1,93 @@
|
|||
import React from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import classNames from 'classnames'
|
||||
import React from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import classNames from "classnames";
|
||||
|
||||
import RaidDropdown from '~components/RaidDropdown'
|
||||
import RaidDropdown from "~components/RaidDropdown";
|
||||
|
||||
import './index.scss'
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode
|
||||
scrolled: boolean
|
||||
element?: number
|
||||
raidSlug?: string
|
||||
recency?: number
|
||||
onFilter: ({element, raidSlug, recency} : { element?: number, raidSlug?: string, recency?: number}) => void
|
||||
children: React.ReactNode;
|
||||
scrolled: boolean;
|
||||
element?: number;
|
||||
raidSlug?: string;
|
||||
recency?: number;
|
||||
onFilter: ({
|
||||
element,
|
||||
raidSlug,
|
||||
recency,
|
||||
}: {
|
||||
element?: number;
|
||||
raidSlug?: string;
|
||||
recency?: number;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
const FilterBar = (props: Props) => {
|
||||
// Set up translation
|
||||
const { t } = useTranslation('common')
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
// Set up refs for filter dropdowns
|
||||
const elementSelect = React.createRef<HTMLSelectElement>()
|
||||
const raidSelect = React.createRef<HTMLSelectElement>()
|
||||
const recencySelect = React.createRef<HTMLSelectElement>()
|
||||
const elementSelect = React.createRef<HTMLSelectElement>();
|
||||
const raidSelect = React.createRef<HTMLSelectElement>();
|
||||
const recencySelect = React.createRef<HTMLSelectElement>();
|
||||
|
||||
// Set up classes object for showing shadow on scroll
|
||||
const classes = classNames({
|
||||
'FilterBar': true,
|
||||
'shadow': props.scrolled
|
||||
})
|
||||
FilterBar: true,
|
||||
shadow: props.scrolled,
|
||||
});
|
||||
|
||||
function elementSelectChanged() {
|
||||
const elementValue = (elementSelect.current) ? parseInt(elementSelect.current.value) : -1
|
||||
props.onFilter({ element: elementValue })
|
||||
const elementValue = elementSelect.current
|
||||
? parseInt(elementSelect.current.value)
|
||||
: -1;
|
||||
props.onFilter({ element: elementValue });
|
||||
}
|
||||
|
||||
function recencySelectChanged() {
|
||||
const recencyValue = (recencySelect.current) ? parseInt(recencySelect.current.value) : -1
|
||||
props.onFilter({ recency: recencyValue })
|
||||
const recencyValue = recencySelect.current
|
||||
? parseInt(recencySelect.current.value)
|
||||
: -1;
|
||||
props.onFilter({ recency: recencyValue });
|
||||
}
|
||||
|
||||
function raidSelectChanged(slug?: string) {
|
||||
props.onFilter({ raidSlug: slug })
|
||||
props.onFilter({ raidSlug: slug });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
{props.children}
|
||||
<select onChange={elementSelectChanged} ref={elementSelect} value={props.element}>
|
||||
<option data-element="all" key={-1} value={-1}>{t('elements.full.all')}</option>
|
||||
<option data-element="null" key={0} value={0}>{t('elements.full.null')}</option>
|
||||
<option data-element="wind" key={1} value={1}>{t('elements.full.wind')}</option>
|
||||
<option data-element="fire" key={2} value={2}>{t('elements.full.fire')}</option>
|
||||
<option data-element="water" key={3} value={3}>{t('elements.full.water')}</option>
|
||||
<option data-element="earth" key={4} value={4}>{t('elements.full.earth')}</option>
|
||||
<option data-element="dark" key={5} value={5}>{t('elements.full.dark')}</option>
|
||||
<option data-element="light" key={6} value={6}>{t('elements.full.light')}</option>
|
||||
<select
|
||||
onChange={elementSelectChanged}
|
||||
ref={elementSelect}
|
||||
value={props.element}
|
||||
>
|
||||
<option data-element="all" key={-1} value={-1}>
|
||||
{t("elements.full.all")}
|
||||
</option>
|
||||
<option data-element="null" key={0} value={0}>
|
||||
{t("elements.full.null")}
|
||||
</option>
|
||||
<option data-element="wind" key={1} value={1}>
|
||||
{t("elements.full.wind")}
|
||||
</option>
|
||||
<option data-element="fire" key={2} value={2}>
|
||||
{t("elements.full.fire")}
|
||||
</option>
|
||||
<option data-element="water" key={3} value={3}>
|
||||
{t("elements.full.water")}
|
||||
</option>
|
||||
<option data-element="earth" key={4} value={4}>
|
||||
{t("elements.full.earth")}
|
||||
</option>
|
||||
<option data-element="dark" key={5} value={5}>
|
||||
{t("elements.full.dark")}
|
||||
</option>
|
||||
<option data-element="light" key={6} value={6}>
|
||||
{t("elements.full.light")}
|
||||
</option>
|
||||
</select>
|
||||
<RaidDropdown
|
||||
currentRaid={props.raidSlug}
|
||||
|
|
@ -64,16 +96,30 @@ const FilterBar = (props: Props) => {
|
|||
ref={raidSelect}
|
||||
/>
|
||||
<select onChange={recencySelectChanged} ref={recencySelect}>
|
||||
<option key={-1} value={-1}>{t('recency.all_time')}</option>
|
||||
<option key={86400} value={86400}>{t('recency.last_day')}</option>
|
||||
<option key={604800} value={604800}>{t('recency.last_week')}</option>
|
||||
<option key={2629746} value={2629746}>{t('recency.last_month')}</option>
|
||||
<option key={7889238} value={7889238}>{t('recency.last_3_months')}</option>
|
||||
<option key={15778476} value={15778476}>{t('recency.last_6_months')}</option>
|
||||
<option key={31556952} value={31556952}>{t('recency.last_year')}</option>
|
||||
<option key={-1} value={-1}>
|
||||
{t("recency.all_time")}
|
||||
</option>
|
||||
<option key={86400} value={86400}>
|
||||
{t("recency.last_day")}
|
||||
</option>
|
||||
<option key={604800} value={604800}>
|
||||
{t("recency.last_week")}
|
||||
</option>
|
||||
<option key={2629746} value={2629746}>
|
||||
{t("recency.last_month")}
|
||||
</option>
|
||||
<option key={7889238} value={7889238}>
|
||||
{t("recency.last_3_months")}
|
||||
</option>
|
||||
<option key={15778476} value={15778476}>
|
||||
{t("recency.last_6_months")}
|
||||
</option>
|
||||
<option key={31556952} value={31556952}>
|
||||
{t("recency.last_year")}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterBar
|
||||
export default FilterBar;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
&:hover {
|
||||
background: white;
|
||||
|
||||
h2, .Grid {
|
||||
h2,
|
||||
.Grid {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
@ -105,12 +106,15 @@
|
|||
flex-direction: row;
|
||||
}
|
||||
|
||||
.raid, .user, time {
|
||||
.raid,
|
||||
.user,
|
||||
time {
|
||||
color: $grey-50;
|
||||
font-size: $font-small;
|
||||
}
|
||||
|
||||
.raid, .user {
|
||||
.raid,
|
||||
.user {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
|
|
@ -123,8 +127,8 @@
|
|||
gap: calc($unit / 2);
|
||||
align-items: center;
|
||||
|
||||
|
||||
img, .no-user {
|
||||
img,
|
||||
.no-user {
|
||||
$diameter: 18px;
|
||||
|
||||
border-radius: calc($diameter / 2);
|
||||
|
|
@ -133,11 +137,11 @@
|
|||
}
|
||||
|
||||
img.gran {
|
||||
background-color: #CEE7FE;
|
||||
background-color: #cee7fe;
|
||||
}
|
||||
|
||||
img.djeeta {
|
||||
background-color: #FFE1FE;
|
||||
background-color: #ffe1fe;
|
||||
}
|
||||
|
||||
.no-user {
|
||||
|
|
|
|||
|
|
@ -1,87 +1,89 @@
|
|||
import React, { useEffect, useState } from "react"
|
||||
import { useRouter } from "next/router"
|
||||
import { useSnapshot } from "valtio"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import classNames from "classnames"
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useSnapshot } from "valtio";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import classNames from "classnames";
|
||||
|
||||
import { accountState } from "~utils/accountState"
|
||||
import { formatTimeAgo } from "~utils/timeAgo"
|
||||
import { accountState } from "~utils/accountState";
|
||||
import { formatTimeAgo } from "~utils/timeAgo";
|
||||
|
||||
import Button from "~components/Button"
|
||||
import { ButtonType } from "~utils/enums"
|
||||
import Button from "~components/Button";
|
||||
import { ButtonType } from "~utils/enums";
|
||||
|
||||
import "./index.scss"
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
shortcode: string
|
||||
id: string
|
||||
name: string
|
||||
raid: Raid
|
||||
grid: GridWeapon[]
|
||||
user?: User
|
||||
favorited: boolean
|
||||
createdAt: Date
|
||||
displayUser?: boolean | false
|
||||
onClick: (shortcode: string) => void
|
||||
onSave?: (partyId: string, favorited: boolean) => void
|
||||
shortcode: string;
|
||||
id: string;
|
||||
name: string;
|
||||
raid: Raid;
|
||||
grid: GridWeapon[];
|
||||
user?: User;
|
||||
favorited: boolean;
|
||||
createdAt: Date;
|
||||
displayUser?: boolean | false;
|
||||
onClick: (shortcode: string) => void;
|
||||
onSave?: (partyId: string, favorited: boolean) => void;
|
||||
}
|
||||
|
||||
const GridRep = (props: Props) => {
|
||||
const numWeapons: number = 9
|
||||
const numWeapons: number = 9;
|
||||
|
||||
const { account } = useSnapshot(accountState)
|
||||
const { account } = useSnapshot(accountState);
|
||||
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation("common")
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation("common");
|
||||
const locale =
|
||||
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
||||
router.locale && ["en", "ja"].includes(router.locale)
|
||||
? router.locale
|
||||
: "en";
|
||||
|
||||
const [mainhand, setMainhand] = useState<Weapon>()
|
||||
const [weapons, setWeapons] = useState<GridArray<Weapon>>({})
|
||||
const [grid, setGrid] = useState<GridArray<GridWeapon>>({})
|
||||
const [mainhand, setMainhand] = useState<Weapon>();
|
||||
const [weapons, setWeapons] = useState<GridArray<Weapon>>({});
|
||||
const [grid, setGrid] = useState<GridArray<GridWeapon>>({});
|
||||
|
||||
const titleClass = classNames({
|
||||
empty: !props.name,
|
||||
})
|
||||
});
|
||||
|
||||
const raidClass = classNames({
|
||||
raid: true,
|
||||
empty: !props.raid,
|
||||
})
|
||||
});
|
||||
|
||||
const userClass = classNames({
|
||||
user: true,
|
||||
empty: !props.user,
|
||||
})
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const newWeapons = Array(numWeapons)
|
||||
const gridWeapons = Array(numWeapons)
|
||||
const newWeapons = Array(numWeapons);
|
||||
const gridWeapons = Array(numWeapons);
|
||||
|
||||
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) {
|
||||
newWeapons[value.position] = value.object
|
||||
gridWeapons[value.position] = value
|
||||
newWeapons[value.position] = value.object;
|
||||
gridWeapons[value.position] = value;
|
||||
}
|
||||
}
|
||||
|
||||
setWeapons(newWeapons)
|
||||
setGrid(gridWeapons)
|
||||
}, [props.grid])
|
||||
setWeapons(newWeapons);
|
||||
setGrid(gridWeapons);
|
||||
}, [props.grid]);
|
||||
|
||||
function navigate() {
|
||||
props.onClick(props.shortcode)
|
||||
props.onClick(props.shortcode);
|
||||
}
|
||||
|
||||
function generateMainhandImage() {
|
||||
let url = ""
|
||||
let url = "";
|
||||
|
||||
if (mainhand) {
|
||||
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 {
|
||||
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`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -89,20 +91,20 @@ const GridRep = (props: Props) => {
|
|||
<img alt={mainhand.name[locale]} src={url} />
|
||||
) : (
|
||||
""
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function generateGridImage(position: number) {
|
||||
let url = ""
|
||||
let url = "";
|
||||
|
||||
const weapon = weapons[position]
|
||||
const gridWeapon = grid[position]
|
||||
const weapon = weapons[position];
|
||||
const gridWeapon = grid[position];
|
||||
|
||||
if (weapon && gridWeapon) {
|
||||
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 {
|
||||
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`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -110,11 +112,11 @@ const GridRep = (props: Props) => {
|
|||
<img alt={weapons[position]?.name[locale]} src={url} />
|
||||
) : (
|
||||
""
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function sendSaveData() {
|
||||
if (props.onSave) props.onSave(props.id, props.favorited)
|
||||
if (props.onSave) props.onSave(props.id, props.favorited);
|
||||
}
|
||||
|
||||
const userImage = () => {
|
||||
|
|
@ -127,9 +129,9 @@ const GridRep = (props: Props) => {
|
|||
/profile/${props.user.picture.picture}@2x.png 2x`}
|
||||
src={`/profile/${props.user.picture.picture}.png`}
|
||||
/>
|
||||
)
|
||||
} else return <div className="no-user" />
|
||||
}
|
||||
);
|
||||
} else return <div className="no-user" />;
|
||||
};
|
||||
|
||||
const details = (
|
||||
<div className="Details">
|
||||
|
|
@ -145,7 +147,7 @@ const GridRep = (props: Props) => {
|
|||
</time>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
const detailsWithUsername = (
|
||||
<div className="Details">
|
||||
|
|
@ -181,7 +183,7 @@ const GridRep = (props: Props) => {
|
|||
</time>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="GridRep">
|
||||
|
|
@ -198,12 +200,12 @@ const GridRep = (props: Props) => {
|
|||
>
|
||||
{generateGridImage(i)}
|
||||
</li>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default GridRep
|
||||
export default GridRep;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import classNames from "classnames"
|
||||
import React from "react"
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import "./index.scss"
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const GridRepCollection = (props: Props) => {
|
||||
const classes = classNames({
|
||||
GridRepCollection: true,
|
||||
})
|
||||
});
|
||||
|
||||
return <div className={classes}>{props.children}</div>
|
||||
}
|
||||
return <div className={classes}>{props.children}</div>;
|
||||
};
|
||||
|
||||
export default GridRepCollection
|
||||
export default GridRepCollection;
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
import React from 'react'
|
||||
import React from "react";
|
||||
|
||||
import './index.scss'
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
position: 'top' | 'bottom'
|
||||
left: JSX.Element,
|
||||
right: JSX.Element
|
||||
position: "top" | "bottom";
|
||||
left: JSX.Element;
|
||||
right: JSX.Element;
|
||||
}
|
||||
|
||||
const Header = (props: Props) => {
|
||||
return (
|
||||
<nav className={`Header ${props.position}`}>
|
||||
<div id="left">{ props.left }</div>
|
||||
<div id="left">{props.left}</div>
|
||||
<div className="push" />
|
||||
<div id="right">{ props.right }</div>
|
||||
<div id="right">{props.right}</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default Header
|
||||
export default Header;
|
||||
|
|
|
|||
|
|
@ -73,7 +73,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
.left, .right {
|
||||
.left,
|
||||
.right {
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
font-weight: $bold;
|
||||
|
|
@ -97,7 +98,8 @@
|
|||
color: $grey-40;
|
||||
}
|
||||
|
||||
& > a, & > span {
|
||||
& > a,
|
||||
& > span {
|
||||
display: block;
|
||||
padding: 12px 12px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,51 +1,51 @@
|
|||
import React, { useEffect, useState } from "react"
|
||||
import { getCookie, setCookie } from "cookies-next"
|
||||
import { useRouter } from "next/router"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { getCookie, setCookie } from "cookies-next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import Link from "next/link"
|
||||
import * as Switch from "@radix-ui/react-switch"
|
||||
import Link from "next/link";
|
||||
import * as Switch from "@radix-ui/react-switch";
|
||||
|
||||
import AboutModal from "~components/AboutModal"
|
||||
import AccountModal from "~components/AccountModal"
|
||||
import LoginModal from "~components/LoginModal"
|
||||
import SignupModal from "~components/SignupModal"
|
||||
import AboutModal from "~components/AboutModal";
|
||||
import AccountModal from "~components/AccountModal";
|
||||
import LoginModal from "~components/LoginModal";
|
||||
import SignupModal from "~components/SignupModal";
|
||||
|
||||
import "./index.scss"
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
authenticated: boolean
|
||||
username?: string
|
||||
logout?: () => void
|
||||
authenticated: boolean;
|
||||
username?: string;
|
||||
logout?: () => void;
|
||||
}
|
||||
|
||||
const HeaderMenu = (props: Props) => {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation("common")
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
const accountCookie = getCookie("account")
|
||||
const accountCookie = getCookie("account");
|
||||
const accountData: AccountCookie = accountCookie
|
||||
? JSON.parse(accountCookie as string)
|
||||
: null
|
||||
: null;
|
||||
|
||||
const userCookie = getCookie("user")
|
||||
const userCookie = getCookie("user");
|
||||
const userData: UserCookie = userCookie
|
||||
? 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(() => {
|
||||
const locale = localeCookie
|
||||
setChecked(locale === "ja" ? true : false)
|
||||
}, [localeCookie])
|
||||
const locale = localeCookie;
|
||||
setChecked(locale === "ja" ? true : false);
|
||||
}, [localeCookie]);
|
||||
|
||||
function handleCheckedChange(value: boolean) {
|
||||
const language = value ? "ja" : "en"
|
||||
setCookie("NEXT_LOCALE", language, { path: "/" })
|
||||
router.push(router.asPath, undefined, { locale: language })
|
||||
const language = value ? "ja" : "en";
|
||||
setCookie("NEXT_LOCALE", language, { path: "/" });
|
||||
router.push(router.asPath, undefined, { locale: language });
|
||||
}
|
||||
|
||||
function authItems() {
|
||||
|
|
@ -92,7 +92,7 @@ const HeaderMenu = (props: Props) => {
|
|||
</div>
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function unauthItems() {
|
||||
|
|
@ -132,10 +132,10 @@ const HeaderMenu = (props: Props) => {
|
|||
<SignupModal />
|
||||
</div>
|
||||
</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 { useRouter } from "next/router"
|
||||
import { useSnapshot } from "valtio"
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useSnapshot } from "valtio";
|
||||
|
||||
import { appState } from "~utils/appState"
|
||||
import { jobGroups } from "~utils/jobGroups"
|
||||
import { appState } from "~utils/appState";
|
||||
import { jobGroups } from "~utils/jobGroups";
|
||||
|
||||
import "./index.scss"
|
||||
import "./index.scss";
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
currentJob?: string
|
||||
onChange?: (job?: Job) => void
|
||||
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
||||
currentJob?: string;
|
||||
onChange?: (job?: Job) => void;
|
||||
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||
}
|
||||
|
||||
type GroupedJob = { [key: string]: Job[] }
|
||||
type GroupedJob = { [key: string]: Job[] };
|
||||
|
||||
const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
|
||||
function useFieldSet(props, ref) {
|
||||
// Set up router for locale
|
||||
const router = useRouter()
|
||||
const locale = router.locale || "en"
|
||||
const router = useRouter();
|
||||
const locale = router.locale || "en";
|
||||
|
||||
// Create snapshot of app state
|
||||
const { party } = useSnapshot(appState)
|
||||
const { party } = useSnapshot(appState);
|
||||
|
||||
// Set up local states for storing jobs
|
||||
const [currentJob, setCurrentJob] = useState<Job>()
|
||||
const [jobs, setJobs] = useState<Job[]>()
|
||||
const [sortedJobs, setSortedJobs] = useState<GroupedJob>()
|
||||
const [currentJob, setCurrentJob] = useState<Job>();
|
||||
const [jobs, setJobs] = useState<Job[]>();
|
||||
const [sortedJobs, setSortedJobs] = useState<GroupedJob>();
|
||||
|
||||
// Set current job from state on mount
|
||||
useEffect(() => {
|
||||
setCurrentJob(party.job)
|
||||
}, [])
|
||||
setCurrentJob(party.job);
|
||||
}, []);
|
||||
|
||||
// Organize jobs into groups on mount
|
||||
useEffect(() => {
|
||||
const jobGroups = appState.jobs
|
||||
.map((job) => job.row)
|
||||
.filter((value, index, self) => self.indexOf(value) === index)
|
||||
let groupedJobs: GroupedJob = {}
|
||||
.filter((value, index, self) => self.indexOf(value) === index);
|
||||
let groupedJobs: GroupedJob = {};
|
||||
|
||||
jobGroups.forEach((group) => {
|
||||
groupedJobs[group] = appState.jobs.filter((job) => job.row === group)
|
||||
})
|
||||
groupedJobs[group] = appState.jobs.filter((job) => job.row === group);
|
||||
});
|
||||
|
||||
setJobs(appState.jobs)
|
||||
setSortedJobs(groupedJobs)
|
||||
}, [appState])
|
||||
setJobs(appState.jobs);
|
||||
setSortedJobs(groupedJobs);
|
||||
}, [appState]);
|
||||
|
||||
// Set current job on mount
|
||||
useEffect(() => {
|
||||
if (jobs && props.currentJob) {
|
||||
const job = appState.jobs.find((job) => job.id === props.currentJob)
|
||||
setCurrentJob(job)
|
||||
const job = appState.jobs.find((job) => job.id === props.currentJob);
|
||||
setCurrentJob(job);
|
||||
}
|
||||
}, [appState, props.currentJob])
|
||||
}, [appState, props.currentJob]);
|
||||
|
||||
// Enable changing select value
|
||||
function handleChange(event: React.ChangeEvent<HTMLSelectElement>) {
|
||||
if (jobs) {
|
||||
const job = jobs.find((job) => job.id === event.target.value)
|
||||
if (props.onChange) props.onChange(job)
|
||||
setCurrentJob(job)
|
||||
const job = jobs.find((job) => job.id === event.target.value);
|
||||
if (props.onChange) props.onChange(job);
|
||||
setCurrentJob(job);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,16 +79,16 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
|
|||
<option key={i} value={item.id}>
|
||||
{item.name[locale]}
|
||||
</option>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const groupName = jobGroups.find((g) => g.slug === group)?.name[locale]
|
||||
const groupName = jobGroups.find((g) => g.slug === group)?.name[locale];
|
||||
|
||||
return (
|
||||
<optgroup key={group} label={groupName}>
|
||||
{options}
|
||||
</optgroup>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -106,8 +106,8 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
|
|||
? Object.keys(sortedJobs).map((x) => renderJobGroup(x))
|
||||
: ""}
|
||||
</select>
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export default JobDropdown
|
||||
export default JobDropdown;
|
||||
|
|
|
|||
|
|
@ -1,99 +1,101 @@
|
|||
import React, { ForwardedRef, useEffect, useState } from "react"
|
||||
import { useRouter } from "next/router"
|
||||
import { useSnapshot } from "valtio"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import React, { ForwardedRef, useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useSnapshot } from "valtio";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import JobDropdown from "~components/JobDropdown"
|
||||
import JobSkillItem from "~components/JobSkillItem"
|
||||
import SearchModal from "~components/SearchModal"
|
||||
import JobDropdown from "~components/JobDropdown";
|
||||
import JobSkillItem from "~components/JobSkillItem";
|
||||
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
|
||||
interface Props {
|
||||
job?: Job
|
||||
jobSkills: JobSkillObject
|
||||
editable: boolean
|
||||
saveJob: (job: Job) => void
|
||||
saveSkill: (skill: JobSkill, position: number) => void
|
||||
job?: Job;
|
||||
jobSkills: JobSkillObject;
|
||||
editable: boolean;
|
||||
saveJob: (job: Job) => void;
|
||||
saveSkill: (skill: JobSkill, position: number) => void;
|
||||
}
|
||||
|
||||
const JobSection = (props: Props) => {
|
||||
const { party } = useSnapshot(appState)
|
||||
const { t } = useTranslation("common")
|
||||
const { party } = useSnapshot(appState);
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
const locale =
|
||||
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
||||
router.locale && ["en", "ja"].includes(router.locale)
|
||||
? router.locale
|
||||
: "en";
|
||||
|
||||
const [job, setJob] = useState<Job>()
|
||||
const [imageUrl, setImageUrl] = useState("")
|
||||
const [numSkills, setNumSkills] = useState(4)
|
||||
const [job, setJob] = useState<Job>();
|
||||
const [imageUrl, setImageUrl] = useState("");
|
||||
const [numSkills, setNumSkills] = useState(4);
|
||||
const [skills, setSkills] = useState<{ [key: number]: JobSkill | undefined }>(
|
||||
[]
|
||||
)
|
||||
);
|
||||
|
||||
const selectRef = React.createRef<HTMLSelectElement>()
|
||||
const selectRef = React.createRef<HTMLSelectElement>();
|
||||
|
||||
useEffect(() => {
|
||||
// Set current job based on ID
|
||||
if (props.job) {
|
||||
setJob(props.job)
|
||||
setJob(props.job);
|
||||
setSkills({
|
||||
0: props.jobSkills[0],
|
||||
1: props.jobSkills[1],
|
||||
2: props.jobSkills[2],
|
||||
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(() => {
|
||||
generateImageUrl()
|
||||
})
|
||||
generateImageUrl();
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (job) {
|
||||
if ((party.job && job.id != party.job.id) || !party.job)
|
||||
appState.party.job = job
|
||||
if (job.row === "1") setNumSkills(3)
|
||||
else setNumSkills(4)
|
||||
appState.party.job = job;
|
||||
if (job.row === "1") setNumSkills(3);
|
||||
else setNumSkills(4);
|
||||
}
|
||||
}, [job])
|
||||
}, [job]);
|
||||
|
||||
function receiveJob(job?: Job) {
|
||||
if (job) {
|
||||
setJob(job)
|
||||
props.saveJob(job)
|
||||
setJob(job);
|
||||
props.saveJob(job);
|
||||
}
|
||||
}
|
||||
|
||||
function generateImageUrl() {
|
||||
let imgSrc = ""
|
||||
let imgSrc = "";
|
||||
|
||||
if (job) {
|
||||
const slug = job?.name.en.replaceAll(" ", "-").toLowerCase()
|
||||
const gender = party.user && party.user.gender == 1 ? "b" : "a"
|
||||
const slug = job?.name.en.replaceAll(" ", "-").toLowerCase();
|
||||
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) => {
|
||||
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) => {
|
||||
return (
|
||||
|
|
@ -103,8 +105,8 @@ const JobSection = (props: Props) => {
|
|||
key={`skill-${index}`}
|
||||
hasJob={job != undefined && job.id != "-1"}
|
||||
/>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const editableSkillItem = (index: number) => {
|
||||
return (
|
||||
|
|
@ -117,17 +119,17 @@ const JobSection = (props: Props) => {
|
|||
>
|
||||
{skillItem(index, true)}
|
||||
</SearchModal>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function saveJobSkill(object: SearchableObject, position: number) {
|
||||
const skill = object as JobSkill
|
||||
const skill = object as JobSkill;
|
||||
|
||||
const newSkills = skills
|
||||
newSkills[position] = skill
|
||||
setSkills(newSkills)
|
||||
const newSkills = skills;
|
||||
newSkills[position] = skill;
|
||||
setSkills(newSkills);
|
||||
|
||||
props.saveSkill(skill, position)
|
||||
props.saveSkill(skill, position);
|
||||
}
|
||||
|
||||
// Render: JSX components
|
||||
|
|
@ -159,7 +161,7 @@ const JobSection = (props: Props) => {
|
|||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default JobSection
|
||||
export default JobSection;
|
||||
|
|
|
|||
|
|
@ -1,40 +1,40 @@
|
|||
import React from "react"
|
||||
import { useRouter } from "next/router"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import classNames from "classnames"
|
||||
import PlusIcon from "~public/icons/Add.svg"
|
||||
import classNames from "classnames";
|
||||
import PlusIcon from "~public/icons/Add.svg";
|
||||
|
||||
import "./index.scss"
|
||||
import "./index.scss";
|
||||
|
||||
// Props
|
||||
interface Props extends React.ComponentPropsWithoutRef<"div"> {
|
||||
skill?: JobSkill
|
||||
editable: boolean
|
||||
hasJob: boolean
|
||||
skill?: JobSkill;
|
||||
editable: boolean;
|
||||
hasJob: boolean;
|
||||
}
|
||||
|
||||
const JobSkillItem = React.forwardRef<HTMLDivElement, Props>(
|
||||
function useJobSkillItem({ ...props }, forwardedRef) {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation("common")
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation("common");
|
||||
const locale =
|
||||
router.locale && ["en", "ja"].includes(router.locale)
|
||||
? router.locale
|
||||
: "en"
|
||||
: "en";
|
||||
|
||||
const classes = classNames({
|
||||
JobSkill: true,
|
||||
editable: props.editable,
|
||||
})
|
||||
});
|
||||
|
||||
const imageClasses = classNames({
|
||||
placeholder: !props.skill,
|
||||
editable: props.editable && props.hasJob,
|
||||
})
|
||||
});
|
||||
|
||||
const skillImage = () => {
|
||||
let jsx: React.ReactNode
|
||||
let jsx: React.ReactNode;
|
||||
|
||||
if (props.skill) {
|
||||
jsx = (
|
||||
|
|
@ -43,39 +43,39 @@ const JobSkillItem = React.forwardRef<HTMLDivElement, Props>(
|
|||
className={imageClasses}
|
||||
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-skills/${props.skill.slug}.png`}
|
||||
/>
|
||||
)
|
||||
);
|
||||
} else {
|
||||
jsx = (
|
||||
<div className={imageClasses}>
|
||||
{props.editable && props.hasJob ? <PlusIcon /> : ""}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return jsx
|
||||
}
|
||||
return jsx;
|
||||
};
|
||||
|
||||
const label = () => {
|
||||
let jsx: React.ReactNode
|
||||
let jsx: React.ReactNode;
|
||||
|
||||
if (props.skill) {
|
||||
jsx = <p>{props.skill.name[locale]}</p>
|
||||
jsx = <p>{props.skill.name[locale]}</p>;
|
||||
} 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 {
|
||||
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 (
|
||||
<div className={classes} onClick={props.onClick} ref={forwardedRef}>
|
||||
{skillImage()}
|
||||
{label()}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export default JobSkillItem
|
||||
export default JobSkillItem;
|
||||
|
|
|
|||
|
|
@ -1,29 +1,31 @@
|
|||
import React, { useEffect, useState } from "react"
|
||||
import { useRouter } from "next/router"
|
||||
import { SkillGroup, skillClassification } from "~utils/skillGroups"
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { SkillGroup, skillClassification } from "~utils/skillGroups";
|
||||
|
||||
import "./index.scss"
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
data: JobSkill
|
||||
onClick: () => void
|
||||
data: JobSkill;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const JobSkillResult = (props: Props) => {
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
const locale =
|
||||
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
||||
router.locale && ["en", "ja"].includes(router.locale)
|
||||
? router.locale
|
||||
: "en";
|
||||
|
||||
const skill = props.data
|
||||
const skill = props.data;
|
||||
|
||||
const [group, setGroup] = useState<SkillGroup | undefined>()
|
||||
const [group, setGroup] = useState<SkillGroup | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
setGroup(skillClassification.find((group) => group.id === skill.color))
|
||||
}, [skill, setGroup, skillClassification])
|
||||
setGroup(skillClassification.find((group) => group.id === skill.color));
|
||||
}, [skill, setGroup, skillClassification]);
|
||||
|
||||
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 (
|
||||
<li className="JobSkillResult" onClick={props.onClick}>
|
||||
|
|
@ -35,7 +37,7 @@ const JobSkillResult = (props: Props) => {
|
|||
</div>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default JobSkillResult
|
||||
export default JobSkillResult;
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
import React, { useEffect, useState } from "react"
|
||||
import { useRouter } from "next/router"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { skillGroups } from "~utils/skillGroups"
|
||||
import { skillGroups } from "~utils/skillGroups";
|
||||
|
||||
import "./index.scss"
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
sendFilters: (filters: { [key: string]: number }) => void
|
||||
sendFilters: (filters: { [key: string]: number }) => void;
|
||||
}
|
||||
|
||||
const JobSkillSearchFilterBar = (props: Props) => {
|
||||
// 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>) {
|
||||
setCurrentGroup(parseInt(event.target.value))
|
||||
setCurrentGroup(parseInt(event.target.value));
|
||||
}
|
||||
|
||||
function onBlur(event: React.ChangeEvent<HTMLSelectElement>) {}
|
||||
|
|
@ -25,14 +25,14 @@ const JobSkillSearchFilterBar = (props: Props) => {
|
|||
function sendFilters() {
|
||||
const filters = {
|
||||
group: currentGroup,
|
||||
}
|
||||
};
|
||||
|
||||
props.sendFilters(filters)
|
||||
props.sendFilters(filters);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
sendFilters()
|
||||
}, [currentGroup])
|
||||
sendFilters();
|
||||
}, [currentGroup]);
|
||||
|
||||
return (
|
||||
<div className="SearchFilterBar">
|
||||
|
|
@ -65,7 +65,7 @@ const JobSkillSearchFilterBar = (props: Props) => {
|
|||
</option>
|
||||
</select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default JobSkillSearchFilterBar
|
||||
export default JobSkillSearchFilterBar;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import type { ReactElement } from 'react'
|
||||
import TopHeader from '~components/TopHeader'
|
||||
import type { ReactElement } from "react";
|
||||
import TopHeader from "~components/TopHeader";
|
||||
|
||||
interface Props {
|
||||
children: ReactElement
|
||||
children: ReactElement;
|
||||
}
|
||||
|
||||
const Layout = ({children}: Props) => {
|
||||
const Layout = ({ children }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<TopHeader />
|
||||
<main>{children}</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout
|
||||
export default Layout;
|
||||
|
|
|
|||
|
|
@ -1,138 +1,138 @@
|
|||
import React, { useState } from "react"
|
||||
import { setCookie } from "cookies-next"
|
||||
import Router, { useRouter } from "next/router"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { AxiosResponse } from "axios"
|
||||
import React, { useState } from "react";
|
||||
import { setCookie } from "cookies-next";
|
||||
import Router, { useRouter } from "next/router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
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 { accountState } from "~utils/accountState"
|
||||
import api from "~utils/api";
|
||||
import { accountState } from "~utils/accountState";
|
||||
|
||||
import Button from "~components/Button"
|
||||
import Fieldset from "~components/Fieldset"
|
||||
import Button from "~components/Button";
|
||||
import Fieldset from "~components/Fieldset";
|
||||
|
||||
import CrossIcon from "~public/icons/Cross.svg"
|
||||
import "./index.scss"
|
||||
import CrossIcon from "~public/icons/Cross.svg";
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {}
|
||||
|
||||
interface ErrorMap {
|
||||
[index: string]: string
|
||||
email: string
|
||||
password: string
|
||||
[index: string]: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
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 router = useRouter()
|
||||
const { t } = useTranslation("common")
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
// Set up form states and error handling
|
||||
const [formValid, setFormValid] = useState(false)
|
||||
const [formValid, setFormValid] = useState(false);
|
||||
const [errors, setErrors] = useState<ErrorMap>({
|
||||
email: "",
|
||||
password: "",
|
||||
})
|
||||
});
|
||||
|
||||
// States
|
||||
const [open, setOpen] = useState(false)
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
// Set up form refs
|
||||
const emailInput: React.RefObject<HTMLInputElement> = React.createRef()
|
||||
const passwordInput: React.RefObject<HTMLInputElement> = React.createRef()
|
||||
const form: React.RefObject<HTMLInputElement>[] = [emailInput, passwordInput]
|
||||
const emailInput: React.RefObject<HTMLInputElement> = React.createRef();
|
||||
const passwordInput: React.RefObject<HTMLInputElement> = React.createRef();
|
||||
const form: React.RefObject<HTMLInputElement>[] = [emailInput, passwordInput];
|
||||
|
||||
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const { name, value } = event.target
|
||||
let newErrors = { ...errors }
|
||||
const { name, value } = event.target;
|
||||
let newErrors = { ...errors };
|
||||
|
||||
switch (name) {
|
||||
case "email":
|
||||
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))
|
||||
newErrors.email = t("modals.login.errors.invalid_email")
|
||||
else newErrors.email = ""
|
||||
break
|
||||
newErrors.email = t("modals.login.errors.invalid_email");
|
||||
else newErrors.email = "";
|
||||
break;
|
||||
|
||||
case "password":
|
||||
newErrors.password =
|
||||
value.length == 0 ? t("modals.login.errors.empty_password") : ""
|
||||
break
|
||||
value.length == 0 ? t("modals.login.errors.empty_password") : "";
|
||||
break;
|
||||
|
||||
default:
|
||||
break
|
||||
break;
|
||||
}
|
||||
|
||||
setErrors(newErrors)
|
||||
setFormValid(validateForm(newErrors))
|
||||
setErrors(newErrors);
|
||||
setFormValid(validateForm(newErrors));
|
||||
}
|
||||
|
||||
function validateForm(errors: ErrorMap) {
|
||||
let valid = true
|
||||
let valid = true;
|
||||
|
||||
Object.values(form).forEach(
|
||||
(input) => input.current?.value.length == 0 && (valid = false)
|
||||
)
|
||||
);
|
||||
|
||||
Object.values(errors).forEach(
|
||||
(error) => error.length > 0 && (valid = false)
|
||||
)
|
||||
);
|
||||
|
||||
return valid
|
||||
return valid;
|
||||
}
|
||||
|
||||
function login(event: React.FormEvent) {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
const body = {
|
||||
email: emailInput.current?.value,
|
||||
password: passwordInput.current?.value,
|
||||
grant_type: "password",
|
||||
}
|
||||
};
|
||||
|
||||
if (formValid) {
|
||||
api
|
||||
.login(body)
|
||||
.then((response) => {
|
||||
storeCookieInfo(response)
|
||||
return response.data.user.id
|
||||
storeCookieInfo(response);
|
||||
return response.data.user.id;
|
||||
})
|
||||
.then((id) => fetchUserInfo(id))
|
||||
.then((infoResponse) => storeUserInfo(infoResponse))
|
||||
.then((infoResponse) => storeUserInfo(infoResponse));
|
||||
}
|
||||
}
|
||||
|
||||
function fetchUserInfo(id: string) {
|
||||
return api.userInfo(id)
|
||||
return api.userInfo(id);
|
||||
}
|
||||
|
||||
function storeCookieInfo(response: AxiosResponse) {
|
||||
const user = response.data.user
|
||||
const user = response.data.user;
|
||||
|
||||
const cookieObj: AccountCookie = {
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
token: response.data.access_token,
|
||||
}
|
||||
};
|
||||
|
||||
setCookie("account", cookieObj, { path: "/" })
|
||||
setCookie("account", cookieObj, { path: "/" });
|
||||
}
|
||||
|
||||
function storeUserInfo(response: AxiosResponse) {
|
||||
const user = response.data.user
|
||||
const user = response.data.user;
|
||||
|
||||
const cookieObj: UserCookie = {
|
||||
picture: user.picture.picture,
|
||||
element: user.picture.element,
|
||||
language: user.language,
|
||||
gender: user.gender,
|
||||
}
|
||||
};
|
||||
|
||||
setCookie("user", cookieObj, { path: "/" })
|
||||
setCookie("user", cookieObj, { path: "/" });
|
||||
|
||||
accountState.account.user = {
|
||||
id: user.id,
|
||||
|
|
@ -140,28 +140,28 @@ const LoginModal = (props: Props) => {
|
|||
picture: user.picture.picture,
|
||||
element: user.picture.element,
|
||||
gender: user.gender,
|
||||
}
|
||||
};
|
||||
|
||||
console.log("Authorizing account...")
|
||||
accountState.account.authorized = true
|
||||
console.log("Authorizing account...");
|
||||
accountState.account.authorized = true;
|
||||
|
||||
setOpen(false)
|
||||
changeLanguage(user.language)
|
||||
setOpen(false);
|
||||
changeLanguage(user.language);
|
||||
}
|
||||
|
||||
function changeLanguage(newLanguage: string) {
|
||||
if (newLanguage !== router.locale) {
|
||||
setCookie("NEXT_LOCALE", newLanguage, { path: "/" })
|
||||
router.push(router.asPath, undefined, { locale: newLanguage })
|
||||
setCookie("NEXT_LOCALE", newLanguage, { path: "/" });
|
||||
router.push(router.asPath, undefined, { locale: newLanguage });
|
||||
}
|
||||
}
|
||||
|
||||
function openChange(open: boolean) {
|
||||
setOpen(open)
|
||||
setOpen(open);
|
||||
setErrors({
|
||||
email: "",
|
||||
password: "",
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -210,7 +210,7 @@ const LoginModal = (props: Props) => {
|
|||
<Dialog.Overlay className="Overlay" />
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginModal
|
||||
export default LoginModal;
|
||||
|
|
|
|||
|
|
@ -1,55 +1,55 @@
|
|||
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { useRouter } from "next/router"
|
||||
import { useSnapshot } from "valtio"
|
||||
import { getCookie } from "cookies-next"
|
||||
import clonedeep from "lodash.clonedeep"
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useSnapshot } from "valtio";
|
||||
import { getCookie } from "cookies-next";
|
||||
import clonedeep from "lodash.clonedeep";
|
||||
|
||||
import PartySegmentedControl from "~components/PartySegmentedControl"
|
||||
import PartyDetails from "~components/PartyDetails"
|
||||
import WeaponGrid from "~components/WeaponGrid"
|
||||
import SummonGrid from "~components/SummonGrid"
|
||||
import CharacterGrid from "~components/CharacterGrid"
|
||||
import PartySegmentedControl from "~components/PartySegmentedControl";
|
||||
import PartyDetails from "~components/PartyDetails";
|
||||
import WeaponGrid from "~components/WeaponGrid";
|
||||
import SummonGrid from "~components/SummonGrid";
|
||||
import CharacterGrid from "~components/CharacterGrid";
|
||||
|
||||
import api from "~utils/api"
|
||||
import { appState, initialAppState } from "~utils/appState"
|
||||
import { GridType, TeamElement } from "~utils/enums"
|
||||
import api from "~utils/api";
|
||||
import { appState, initialAppState } from "~utils/appState";
|
||||
import { GridType, TeamElement } from "~utils/enums";
|
||||
|
||||
import "./index.scss"
|
||||
import "./index.scss";
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
new?: boolean
|
||||
team?: Party
|
||||
raids: Raid[][]
|
||||
pushHistory?: (path: string) => void
|
||||
new?: boolean;
|
||||
team?: Party;
|
||||
raids: Raid[][];
|
||||
pushHistory?: (path: string) => void;
|
||||
}
|
||||
|
||||
const Party = (props: Props) => {
|
||||
// Cookies
|
||||
const cookie = getCookie("account")
|
||||
const cookie = getCookie("account");
|
||||
const accountData: AccountCookie = cookie
|
||||
? JSON.parse(cookie as string)
|
||||
: null
|
||||
: null;
|
||||
|
||||
const headers = useMemo(() => {
|
||||
return accountData
|
||||
? { headers: { Authorization: `Bearer ${accountData.token}` } }
|
||||
: {}
|
||||
}, [accountData])
|
||||
: {};
|
||||
}, [accountData]);
|
||||
|
||||
// Set up router
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
|
||||
// Set up states
|
||||
const { party } = useSnapshot(appState)
|
||||
const [currentTab, setCurrentTab] = useState<GridType>(GridType.Weapon)
|
||||
const { party } = useSnapshot(appState);
|
||||
const [currentTab, setCurrentTab] = useState<GridType>(GridType.Weapon);
|
||||
|
||||
// Reset state on first load
|
||||
useEffect(() => {
|
||||
const resetState = clonedeep(initialAppState)
|
||||
appState.grid = resetState.grid
|
||||
if (props.team) storeParty(props.team)
|
||||
}, [])
|
||||
const resetState = clonedeep(initialAppState);
|
||||
appState.grid = resetState.grid;
|
||||
if (props.team) storeParty(props.team);
|
||||
}, []);
|
||||
|
||||
// Methods: Creating a new party
|
||||
async function createParty(extra: boolean = false) {
|
||||
|
|
@ -58,14 +58,14 @@ const Party = (props: Props) => {
|
|||
...(accountData && { user_id: accountData.userId }),
|
||||
extra: extra,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
return await api.endpoints.parties.create(body, headers)
|
||||
return await api.endpoints.parties.create(body, headers);
|
||||
}
|
||||
|
||||
// Methods: Updating the party's details
|
||||
function checkboxChanged(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
appState.party.extra = event.target.checked
|
||||
appState.party.extra = event.target.checked;
|
||||
|
||||
if (party.id) {
|
||||
api.endpoints.parties.update(
|
||||
|
|
@ -74,7 +74,7 @@ const Party = (props: Props) => {
|
|||
party: { extra: event.target.checked },
|
||||
},
|
||||
headers
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,11 +98,11 @@ const Party = (props: Props) => {
|
|||
headers
|
||||
)
|
||||
.then(() => {
|
||||
appState.party.name = name
|
||||
appState.party.description = description
|
||||
appState.party.raid = raid
|
||||
appState.party.updated_at = party.updated_at
|
||||
})
|
||||
appState.party.name = name;
|
||||
appState.party.description = description;
|
||||
appState.party.raid = raid;
|
||||
appState.party.updated_at = party.updated_at;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -113,95 +113,95 @@ const Party = (props: Props) => {
|
|||
.destroy({ id: appState.party.id, params: headers })
|
||||
.then(() => {
|
||||
// Push to route
|
||||
router.push("/")
|
||||
router.push("/");
|
||||
|
||||
// Clean state
|
||||
const resetState = clonedeep(initialAppState)
|
||||
const resetState = clonedeep(initialAppState);
|
||||
Object.keys(resetState).forEach((key) => {
|
||||
appState[key] = resetState[key]
|
||||
})
|
||||
appState[key] = resetState[key];
|
||||
});
|
||||
|
||||
// Set party to be editable
|
||||
appState.party.editable = true
|
||||
appState.party.editable = true;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
})
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Methods: Storing party data
|
||||
const storeParty = function (party: Party) {
|
||||
// Store the important party and state-keeping values
|
||||
appState.party.name = party.name
|
||||
appState.party.description = party.description
|
||||
appState.party.raid = party.raid
|
||||
appState.party.updated_at = party.updated_at
|
||||
appState.party.job = party.job
|
||||
appState.party.jobSkills = party.job_skills
|
||||
appState.party.name = party.name;
|
||||
appState.party.description = party.description;
|
||||
appState.party.raid = party.raid;
|
||||
appState.party.updated_at = party.updated_at;
|
||||
appState.party.job = party.job;
|
||||
appState.party.jobSkills = party.job_skills;
|
||||
|
||||
appState.party.id = party.id
|
||||
appState.party.extra = party.extra
|
||||
appState.party.user = party.user
|
||||
appState.party.favorited = party.favorited
|
||||
appState.party.created_at = party.created_at
|
||||
appState.party.updated_at = party.updated_at
|
||||
appState.party.id = party.id;
|
||||
appState.party.extra = party.extra;
|
||||
appState.party.user = party.user;
|
||||
appState.party.favorited = party.favorited;
|
||||
appState.party.created_at = party.created_at;
|
||||
appState.party.updated_at = party.updated_at;
|
||||
|
||||
// Populate state
|
||||
storeCharacters(party.characters)
|
||||
storeWeapons(party.weapons)
|
||||
storeSummons(party.summons)
|
||||
}
|
||||
storeCharacters(party.characters);
|
||||
storeWeapons(party.weapons);
|
||||
storeSummons(party.summons);
|
||||
};
|
||||
|
||||
const storeCharacters = (list: Array<GridCharacter>) => {
|
||||
list.forEach((object: GridCharacter) => {
|
||||
if (object.position != null)
|
||||
appState.grid.characters[object.position] = object
|
||||
})
|
||||
}
|
||||
appState.grid.characters[object.position] = object;
|
||||
});
|
||||
};
|
||||
|
||||
const storeWeapons = (list: Array<GridWeapon>) => {
|
||||
list.forEach((gridObject: GridWeapon) => {
|
||||
if (gridObject.mainhand) {
|
||||
appState.grid.weapons.mainWeapon = gridObject
|
||||
appState.party.element = gridObject.object.element
|
||||
appState.grid.weapons.mainWeapon = gridObject;
|
||||
appState.party.element = gridObject.object.element;
|
||||
} else if (!gridObject.mainhand && gridObject.position != null) {
|
||||
appState.grid.weapons.allWeapons[gridObject.position] = gridObject
|
||||
}
|
||||
})
|
||||
appState.grid.weapons.allWeapons[gridObject.position] = gridObject;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const storeSummons = (list: Array<GridSummon>) => {
|
||||
list.forEach((gridObject: GridSummon) => {
|
||||
if (gridObject.main) appState.grid.summons.mainSummon = gridObject
|
||||
if (gridObject.main) appState.grid.summons.mainSummon = gridObject;
|
||||
else if (gridObject.friend)
|
||||
appState.grid.summons.friendSummon = gridObject
|
||||
appState.grid.summons.friendSummon = gridObject;
|
||||
else if (
|
||||
!gridObject.main &&
|
||||
!gridObject.friend &&
|
||||
gridObject.position != null
|
||||
)
|
||||
appState.grid.summons.allSummons[gridObject.position] = gridObject
|
||||
})
|
||||
}
|
||||
appState.grid.summons.allSummons[gridObject.position] = gridObject;
|
||||
});
|
||||
};
|
||||
|
||||
// Methods: Navigating with segmented control
|
||||
function segmentClicked(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
switch (event.target.value) {
|
||||
case "class":
|
||||
setCurrentTab(GridType.Class)
|
||||
break
|
||||
setCurrentTab(GridType.Class);
|
||||
break;
|
||||
case "characters":
|
||||
setCurrentTab(GridType.Character)
|
||||
break
|
||||
setCurrentTab(GridType.Character);
|
||||
break;
|
||||
case "weapons":
|
||||
setCurrentTab(GridType.Weapon)
|
||||
break
|
||||
setCurrentTab(GridType.Weapon);
|
||||
break;
|
||||
case "summons":
|
||||
setCurrentTab(GridType.Summon)
|
||||
break
|
||||
setCurrentTab(GridType.Summon);
|
||||
break;
|
||||
default:
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +212,7 @@ const Party = (props: Props) => {
|
|||
onClick={segmentClicked}
|
||||
onCheckboxChange={checkboxChanged}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
const weaponGrid = (
|
||||
<WeaponGrid
|
||||
|
|
@ -221,7 +221,7 @@ const Party = (props: Props) => {
|
|||
createParty={createParty}
|
||||
pushHistory={props.pushHistory}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
const summonGrid = (
|
||||
<SummonGrid
|
||||
|
|
@ -230,7 +230,7 @@ const Party = (props: Props) => {
|
|||
createParty={createParty}
|
||||
pushHistory={props.pushHistory}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
const characterGrid = (
|
||||
<CharacterGrid
|
||||
|
|
@ -239,18 +239,18 @@ const Party = (props: Props) => {
|
|||
createParty={createParty}
|
||||
pushHistory={props.pushHistory}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
const currentGrid = () => {
|
||||
switch (currentTab) {
|
||||
case GridType.Character:
|
||||
return characterGrid
|
||||
return characterGrid;
|
||||
case GridType.Weapon:
|
||||
return weaponGrid
|
||||
return weaponGrid;
|
||||
case GridType.Summon:
|
||||
return summonGrid
|
||||
}
|
||||
return summonGrid;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -264,7 +264,7 @@ const Party = (props: Props) => {
|
|||
/>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default Party
|
||||
export default Party;
|
||||
|
|
|
|||
|
|
@ -10,9 +10,7 @@
|
|||
top: $unit;
|
||||
height: 0;
|
||||
z-index: 2;
|
||||
transition: opacity 0.2s ease-in-out,
|
||||
top 0.2s ease-in-out;
|
||||
|
||||
transition: opacity 0.2s ease-in-out, top 0.2s ease-in-out;
|
||||
|
||||
&.Visible {
|
||||
display: block;
|
||||
|
|
@ -51,8 +49,7 @@
|
|||
|
||||
&.ReadOnly {
|
||||
top: $unit * -1;
|
||||
transition: opacity 0.2s ease-in-out,
|
||||
top 0.2s ease-in-out;
|
||||
transition: opacity 0.2s ease-in-out, top 0.2s ease-in-out;
|
||||
|
||||
&.Visible {
|
||||
display: block;
|
||||
|
|
@ -71,7 +68,6 @@
|
|||
white-space: pre-line;
|
||||
}
|
||||
|
||||
|
||||
h1 {
|
||||
font-size: $font-xlarge;
|
||||
font-weight: $normal;
|
||||
|
|
@ -119,7 +115,8 @@
|
|||
gap: calc($unit / 2);
|
||||
margin-top: 1px;
|
||||
|
||||
img, .no-user {
|
||||
img,
|
||||
.no-user {
|
||||
$diameter: 24px;
|
||||
|
||||
border-radius: calc($diameter / 2);
|
||||
|
|
@ -128,11 +125,11 @@
|
|||
}
|
||||
|
||||
img.gran {
|
||||
background-color: #CEE7FE;
|
||||
background-color: #cee7fe;
|
||||
}
|
||||
|
||||
img.djeeta {
|
||||
background-color: #FFE1FE;
|
||||
background-color: #ffe1fe;
|
||||
}
|
||||
|
||||
.no-user {
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
import React, { useState } from "react"
|
||||
import Head from "next/head"
|
||||
import { useRouter } from "next/router"
|
||||
import { useSnapshot } from "valtio"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import React, { useState } from "react";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { useSnapshot } from "valtio";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import Linkify from "react-linkify"
|
||||
import classNames from "classnames"
|
||||
import Linkify from "react-linkify";
|
||||
import classNames from "classnames";
|
||||
|
||||
import * as AlertDialog from "@radix-ui/react-alert-dialog"
|
||||
import CrossIcon from "~public/icons/Cross.svg"
|
||||
import * as AlertDialog from "@radix-ui/react-alert-dialog";
|
||||
import CrossIcon from "~public/icons/Cross.svg";
|
||||
|
||||
import Button from "~components/Button"
|
||||
import CharLimitedFieldset from "~components/CharLimitedFieldset"
|
||||
import RaidDropdown from "~components/RaidDropdown"
|
||||
import TextFieldset from "~components/TextFieldset"
|
||||
import Button from "~components/Button";
|
||||
import CharLimitedFieldset from "~components/CharLimitedFieldset";
|
||||
import RaidDropdown from "~components/RaidDropdown";
|
||||
import TextFieldset from "~components/TextFieldset";
|
||||
|
||||
import { accountState } from "~utils/accountState"
|
||||
import { appState } from "~utils/appState"
|
||||
import { accountState } from "~utils/accountState";
|
||||
import { appState } from "~utils/appState";
|
||||
|
||||
import "./index.scss"
|
||||
import Link from "next/link"
|
||||
import { formatTimeAgo } from "~utils/timeAgo"
|
||||
import "./index.scss";
|
||||
import Link from "next/link";
|
||||
import { formatTimeAgo } from "~utils/timeAgo";
|
||||
|
||||
const emptyRaid: Raid = {
|
||||
id: "",
|
||||
|
|
@ -32,50 +32,50 @@ const emptyRaid: Raid = {
|
|||
level: 0,
|
||||
group: 0,
|
||||
element: 0,
|
||||
}
|
||||
};
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
editable: boolean
|
||||
updateCallback: (name?: string, description?: string, raid?: Raid) => void
|
||||
editable: boolean;
|
||||
updateCallback: (name?: string, description?: string, raid?: Raid) => void;
|
||||
deleteCallback: (
|
||||
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
||||
) => void
|
||||
) => void;
|
||||
}
|
||||
|
||||
const PartyDetails = (props: Props) => {
|
||||
const { party, raids } = useSnapshot(appState)
|
||||
const { account } = useSnapshot(accountState)
|
||||
const { party, raids } = useSnapshot(appState);
|
||||
const { account } = useSnapshot(accountState);
|
||||
|
||||
const { t } = useTranslation("common")
|
||||
const router = useRouter()
|
||||
const locale = router.locale || "en"
|
||||
const { t } = useTranslation("common");
|
||||
const router = useRouter();
|
||||
const locale = router.locale || "en";
|
||||
|
||||
const nameInput = React.createRef<HTMLInputElement>()
|
||||
const descriptionInput = React.createRef<HTMLTextAreaElement>()
|
||||
const raidSelect = React.createRef<HTMLSelectElement>()
|
||||
const nameInput = React.createRef<HTMLInputElement>();
|
||||
const descriptionInput = React.createRef<HTMLTextAreaElement>();
|
||||
const raidSelect = React.createRef<HTMLSelectElement>();
|
||||
|
||||
const readOnlyClasses = classNames({
|
||||
PartyDetails: true,
|
||||
ReadOnly: true,
|
||||
Visible: !party.detailsVisible,
|
||||
})
|
||||
});
|
||||
|
||||
const editableClasses = classNames({
|
||||
PartyDetails: true,
|
||||
Editable: true,
|
||||
Visible: party.detailsVisible,
|
||||
})
|
||||
});
|
||||
|
||||
const emptyClasses = classNames({
|
||||
EmptyDetails: true,
|
||||
Visible: !party.detailsVisible,
|
||||
})
|
||||
});
|
||||
|
||||
const userClass = classNames({
|
||||
user: true,
|
||||
empty: !party.user,
|
||||
})
|
||||
});
|
||||
|
||||
const linkClass = classNames({
|
||||
wind: party && party.element == 1,
|
||||
|
|
@ -84,42 +84,42 @@ const PartyDetails = (props: Props) => {
|
|||
earth: party && party.element == 4,
|
||||
dark: party && party.element == 5,
|
||||
light: party && party.element == 6,
|
||||
})
|
||||
});
|
||||
|
||||
const [errors, setErrors] = useState<{ [key: string]: string }>({
|
||||
name: "",
|
||||
description: "",
|
||||
})
|
||||
});
|
||||
|
||||
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
const { name, value } = event.target
|
||||
let newErrors = errors
|
||||
const { name, value } = event.target;
|
||||
let newErrors = errors;
|
||||
|
||||
setErrors(newErrors)
|
||||
setErrors(newErrors);
|
||||
}
|
||||
|
||||
function handleTextAreaChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
const { name, value } = event.target
|
||||
let newErrors = errors
|
||||
const { name, value } = event.target;
|
||||
let newErrors = errors;
|
||||
|
||||
setErrors(newErrors)
|
||||
setErrors(newErrors);
|
||||
}
|
||||
|
||||
function toggleDetails() {
|
||||
appState.party.detailsVisible = !appState.party.detailsVisible
|
||||
appState.party.detailsVisible = !appState.party.detailsVisible;
|
||||
}
|
||||
|
||||
function updateDetails(event: React.MouseEvent) {
|
||||
const nameValue = nameInput.current?.value
|
||||
const descriptionValue = descriptionInput.current?.value
|
||||
const raid = raids.find((raid) => raid.slug === raidSelect.current?.value)
|
||||
const nameValue = nameInput.current?.value;
|
||||
const descriptionValue = descriptionInput.current?.value;
|
||||
const raid = raids.find((raid) => raid.slug === raidSelect.current?.value);
|
||||
|
||||
props.updateCallback(nameValue, descriptionValue, raid)
|
||||
toggleDetails()
|
||||
props.updateCallback(nameValue, descriptionValue, raid);
|
||||
toggleDetails();
|
||||
}
|
||||
|
||||
const userImage = () => {
|
||||
|
|
@ -132,9 +132,9 @@ const PartyDetails = (props: Props) => {
|
|||
/profile/${party.user.picture.picture}@2x.png 2x`}
|
||||
src={`/profile/${party.user.picture.picture}.png`}
|
||||
/>
|
||||
)
|
||||
else return <div className="no-user" />
|
||||
}
|
||||
);
|
||||
else return <div className="no-user" />;
|
||||
};
|
||||
|
||||
const userBlock = () => {
|
||||
return (
|
||||
|
|
@ -142,8 +142,8 @@ const PartyDetails = (props: Props) => {
|
|||
{userImage()}
|
||||
{party.user ? party.user.username : t("no_user")}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const linkedUserBlock = (user: User) => {
|
||||
return (
|
||||
|
|
@ -152,8 +152,8 @@ const PartyDetails = (props: Props) => {
|
|||
<a className={linkClass}>{userBlock()}</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const linkedRaidBlock = (raid: Raid) => {
|
||||
return (
|
||||
|
|
@ -162,8 +162,8 @@ const PartyDetails = (props: Props) => {
|
|||
<a className={`Raid ${linkClass}`}>{raid.name[locale]}</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const deleteButton = () => {
|
||||
if (party.editable) {
|
||||
|
|
@ -198,11 +198,11 @@ const PartyDetails = (props: Props) => {
|
|||
</AlertDialog.Content>
|
||||
</AlertDialog.Portal>
|
||||
</AlertDialog.Root>
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const editable = (
|
||||
<section className={editableClasses}>
|
||||
|
|
@ -246,7 +246,7 @@ const PartyDetails = (props: Props) => {
|
|||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
);
|
||||
|
||||
const readOnly = (
|
||||
<section className={readOnlyClasses}>
|
||||
|
|
@ -286,7 +286,7 @@ const PartyDetails = (props: Props) => {
|
|||
""
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
);
|
||||
|
||||
const emptyDetails = (
|
||||
<div className={emptyClasses}>
|
||||
|
|
@ -298,25 +298,28 @@ const PartyDetails = (props: Props) => {
|
|||
<div />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
const generateTitle = () => {
|
||||
let title = party.raid ? `[${party.raid?.name[locale]}] ` : ""
|
||||
let title = party.raid ? `[${party.raid?.name[locale]}] ` : "";
|
||||
|
||||
const username =
|
||||
party.user != null ? `@${party.user?.username}` : t("header.anonymous")
|
||||
party.user != null ? `@${party.user?.username}` : t("header.anonymous");
|
||||
|
||||
if (party.name != null)
|
||||
title += t("header.byline", { partyName: party.name, username: username })
|
||||
title += t("header.byline", {
|
||||
partyName: party.name,
|
||||
username: username,
|
||||
});
|
||||
else if (party.name == null && party.editable && router.route === "/new")
|
||||
title = t("header.new_team")
|
||||
title = t("header.new_team");
|
||||
else
|
||||
title += t("header.untitled_team", {
|
||||
username: username,
|
||||
})
|
||||
});
|
||||
|
||||
return title
|
||||
}
|
||||
return title;
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -344,7 +347,7 @@ const PartyDetails = (props: Props) => {
|
|||
: emptyDetails}
|
||||
{editable}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default PartyDetails
|
||||
export default PartyDetails;
|
||||
|
|
|
|||
|
|
@ -1,47 +1,57 @@
|
|||
import React from 'react'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import React from "react";
|
||||
import { useSnapshot } from "valtio";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import { appState } from '~utils/appState'
|
||||
import { appState } from "~utils/appState";
|
||||
|
||||
import SegmentedControl from '~components/SegmentedControl'
|
||||
import Segment from '~components/Segment'
|
||||
import ToggleSwitch from '~components/ToggleSwitch'
|
||||
import SegmentedControl from "~components/SegmentedControl";
|
||||
import Segment from "~components/Segment";
|
||||
import ToggleSwitch from "~components/ToggleSwitch";
|
||||
|
||||
import { GridType } from '~utils/enums'
|
||||
import { GridType } from "~utils/enums";
|
||||
|
||||
|
||||
import './index.scss'
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
selectedTab: GridType
|
||||
onClick: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
onCheckboxChange: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
selectedTab: GridType;
|
||||
onClick: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onCheckboxChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
const PartySegmentedControl = (props: Props) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
const { party, grid } = useSnapshot(appState)
|
||||
const { party, grid } = useSnapshot(appState);
|
||||
|
||||
function getElement() {
|
||||
let element: number = 0
|
||||
let element: number = 0;
|
||||
if (party.element == 0 && grid.weapons.mainWeapon)
|
||||
element = grid.weapons.mainWeapon.element
|
||||
else
|
||||
element = party.element
|
||||
element = grid.weapons.mainWeapon.element;
|
||||
else element = party.element;
|
||||
|
||||
switch(element) {
|
||||
case 1: return "wind"; break
|
||||
case 2: return "fire"; break
|
||||
case 3: return "water"; break
|
||||
case 4: return "earth"; break
|
||||
case 5: return "dark"; break
|
||||
case 6: return "light"; break
|
||||
switch (element) {
|
||||
case 1:
|
||||
return "wind";
|
||||
break;
|
||||
case 2:
|
||||
return "fire";
|
||||
break;
|
||||
case 3:
|
||||
return "water";
|
||||
break;
|
||||
case 4:
|
||||
return "earth";
|
||||
break;
|
||||
case 5:
|
||||
return "dark";
|
||||
break;
|
||||
case 6:
|
||||
return "light";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const extraToggle =
|
||||
const extraToggle = (
|
||||
<div className="ExtraSwitch">
|
||||
Extra
|
||||
<ToggleSwitch
|
||||
|
|
@ -51,6 +61,7 @@ const PartySegmentedControl = (props: Props) => {
|
|||
onChange={props.onCheckboxChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="PartyNavigation">
|
||||
|
|
@ -67,32 +78,36 @@ const PartySegmentedControl = (props: Props) => {
|
|||
name="characters"
|
||||
selected={props.selectedTab == GridType.Character}
|
||||
onClick={props.onClick}
|
||||
>{t('party.segmented_control.characters')}</Segment>
|
||||
>
|
||||
{t("party.segmented_control.characters")}
|
||||
</Segment>
|
||||
|
||||
<Segment
|
||||
groupName="grid"
|
||||
name="weapons"
|
||||
selected={props.selectedTab == GridType.Weapon}
|
||||
onClick={props.onClick}
|
||||
>{t('party.segmented_control.weapons')}</Segment>
|
||||
>
|
||||
{t("party.segmented_control.weapons")}
|
||||
</Segment>
|
||||
|
||||
<Segment
|
||||
groupName="grid"
|
||||
name="summons"
|
||||
selected={props.selectedTab == GridType.Summon}
|
||||
onClick={props.onClick}
|
||||
>{t('party.segmented_control.summons')}</Segment>
|
||||
>
|
||||
{t("party.segmented_control.summons")}
|
||||
</Segment>
|
||||
</SegmentedControl>
|
||||
|
||||
{
|
||||
(() => {
|
||||
{(() => {
|
||||
if (party.editable && props.selectedTab == GridType.Weapon) {
|
||||
return extraToggle
|
||||
}
|
||||
})()
|
||||
return extraToggle;
|
||||
}
|
||||
})()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default PartySegmentedControl
|
||||
export default PartySegmentedControl;
|
||||
|
|
|
|||
|
|
@ -1,100 +1,117 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import api from '~utils/api'
|
||||
import { appState } from '~utils/appState'
|
||||
import { raidGroups } from '~utils/raidGroups'
|
||||
import api from "~utils/api";
|
||||
import { appState } from "~utils/appState";
|
||||
import { raidGroups } from "~utils/raidGroups";
|
||||
|
||||
import './index.scss'
|
||||
import "./index.scss";
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
showAllRaidsOption: boolean
|
||||
currentRaid?: string
|
||||
onChange?: (slug?: string) => void
|
||||
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
||||
showAllRaidsOption: boolean;
|
||||
currentRaid?: string;
|
||||
onChange?: (slug?: string) => void;
|
||||
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||
}
|
||||
|
||||
const RaidDropdown = React.forwardRef<HTMLSelectElement, Props>(function useFieldSet(props, ref) {
|
||||
const RaidDropdown = React.forwardRef<HTMLSelectElement, Props>(
|
||||
function useFieldSet(props, ref) {
|
||||
// Set up router for locale
|
||||
const router = useRouter()
|
||||
const locale = router.locale || 'en'
|
||||
const router = useRouter();
|
||||
const locale = router.locale || "en";
|
||||
|
||||
// Set up local states for storing raids
|
||||
const [currentRaid, setCurrentRaid] = useState<Raid>()
|
||||
const [raids, setRaids] = useState<Raid[]>()
|
||||
const [sortedRaids, setSortedRaids] = useState<Raid[][]>()
|
||||
const [currentRaid, setCurrentRaid] = useState<Raid>();
|
||||
const [raids, setRaids] = useState<Raid[]>();
|
||||
const [sortedRaids, setSortedRaids] = useState<Raid[][]>();
|
||||
|
||||
// Organize raids into groups on mount
|
||||
const organizeRaids = useCallback((raids: Raid[]) => {
|
||||
const organizeRaids = useCallback(
|
||||
(raids: Raid[]) => {
|
||||
// Set up empty raid for "All raids"
|
||||
const all = {
|
||||
id: '0',
|
||||
id: "0",
|
||||
name: {
|
||||
en: 'All raids',
|
||||
ja: '全て'
|
||||
en: "All raids",
|
||||
ja: "全て",
|
||||
},
|
||||
slug: 'all',
|
||||
slug: "all",
|
||||
level: 0,
|
||||
group: 0,
|
||||
element: 0
|
||||
}
|
||||
element: 0,
|
||||
};
|
||||
|
||||
const numGroups = Math.max.apply(Math, raids.map(raid => raid.group))
|
||||
let groupedRaids = []
|
||||
const numGroups = Math.max.apply(
|
||||
Math,
|
||||
raids.map((raid) => raid.group)
|
||||
);
|
||||
let groupedRaids = [];
|
||||
|
||||
for (let i = 0; i <= numGroups; i++) {
|
||||
groupedRaids[i] = raids.filter(raid => raid.group == i)
|
||||
groupedRaids[i] = raids.filter((raid) => raid.group == i);
|
||||
}
|
||||
|
||||
if (props.showAllRaidsOption) {
|
||||
raids.unshift(all)
|
||||
groupedRaids[0].unshift(all)
|
||||
raids.unshift(all);
|
||||
groupedRaids[0].unshift(all);
|
||||
}
|
||||
|
||||
setRaids(raids)
|
||||
setSortedRaids(groupedRaids)
|
||||
appState.raids = raids
|
||||
}, [props.showAllRaidsOption])
|
||||
setRaids(raids);
|
||||
setSortedRaids(groupedRaids);
|
||||
appState.raids = raids;
|
||||
},
|
||||
[props.showAllRaidsOption]
|
||||
);
|
||||
|
||||
// Fetch all raids on mount
|
||||
useEffect(() => {
|
||||
api.endpoints.raids.getAll()
|
||||
.then(response => organizeRaids(response.data.map((r: any) => r.raid)))
|
||||
}, [organizeRaids])
|
||||
api.endpoints.raids
|
||||
.getAll()
|
||||
.then((response) =>
|
||||
organizeRaids(response.data.map((r: any) => r.raid))
|
||||
);
|
||||
}, [organizeRaids]);
|
||||
|
||||
// Set current raid on mount
|
||||
useEffect(() => {
|
||||
if (raids && props.currentRaid) {
|
||||
const raid = raids.find(raid => raid.slug === props.currentRaid)
|
||||
setCurrentRaid(raid)
|
||||
const raid = raids.find((raid) => raid.slug === props.currentRaid);
|
||||
setCurrentRaid(raid);
|
||||
}
|
||||
}, [raids, props.currentRaid])
|
||||
}, [raids, props.currentRaid]);
|
||||
|
||||
// Enable changing select value
|
||||
function handleChange(event: React.ChangeEvent<HTMLSelectElement>) {
|
||||
if (props.onChange) props.onChange(event.target.value)
|
||||
if (props.onChange) props.onChange(event.target.value);
|
||||
|
||||
if (raids) {
|
||||
const raid = raids.find(raid => raid.slug === event.target.value)
|
||||
setCurrentRaid(raid)
|
||||
const raid = raids.find((raid) => raid.slug === event.target.value);
|
||||
setCurrentRaid(raid);
|
||||
}
|
||||
}
|
||||
|
||||
// Render JSX for each raid option, sorted into optgroups
|
||||
function renderRaidGroup(index: number) {
|
||||
const options = sortedRaids && sortedRaids.length > 0 && sortedRaids[index].length > 0 &&
|
||||
sortedRaids[index].sort((a, b) => a.element - b.element).map((item, i) => {
|
||||
const options =
|
||||
sortedRaids &&
|
||||
sortedRaids.length > 0 &&
|
||||
sortedRaids[index].length > 0 &&
|
||||
sortedRaids[index]
|
||||
.sort((a, b) => a.element - b.element)
|
||||
.map((item, i) => {
|
||||
return (
|
||||
<option key={i} value={item.slug}>{item.name[locale]}</option>
|
||||
)
|
||||
})
|
||||
<option key={i} value={item.slug}>
|
||||
{item.name[locale]}
|
||||
</option>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<optgroup key={index} label={raidGroups[index].name[locale]}>
|
||||
{options}
|
||||
</optgroup>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -103,10 +120,14 @@ const RaidDropdown = React.forwardRef<HTMLSelectElement, Props>(function useFiel
|
|||
value={currentRaid?.slug}
|
||||
onBlur={props.onBlur}
|
||||
onChange={handleChange}
|
||||
ref={ref}>
|
||||
{ Array.from(Array(sortedRaids?.length)).map((x, i) => renderRaidGroup(i)) }
|
||||
ref={ref}
|
||||
>
|
||||
{Array.from(Array(sortedRaids?.length)).map((x, i) =>
|
||||
renderRaidGroup(i)
|
||||
)}
|
||||
</select>
|
||||
)
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default RaidDropdown
|
||||
export default RaidDropdown;
|
||||
|
|
|
|||
|
|
@ -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 './index.scss'
|
||||
import ArrowIcon from "~public/icons/Arrow.svg";
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
label: string
|
||||
open: boolean
|
||||
numSelected: number
|
||||
onOpenChange: (open: boolean) => void
|
||||
children: React.ReactNode
|
||||
label: string;
|
||||
open: boolean;
|
||||
numSelected: number;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const SearchFilter = (props: Props) => {
|
||||
|
|
@ -28,7 +28,7 @@ const SearchFilter = (props: Props) => {
|
|||
<DropdownMenu.Arrow />
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchFilter
|
||||
export default SearchFilter;
|
||||
|
|
|
|||
|
|
@ -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 './index.scss'
|
||||
import CheckIcon from "~public/icons/Check.svg";
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
checked?: boolean
|
||||
valueKey: string
|
||||
onCheckedChange: (open: boolean, key: string) => void
|
||||
children: React.ReactNode
|
||||
checked?: boolean;
|
||||
valueKey: string;
|
||||
onCheckedChange: (open: boolean, key: string) => void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const SearchFilterCheckboxItem = (props: Props) => {
|
||||
function handleCheckedChange(checked: boolean) {
|
||||
props.onCheckedChange(checked, props.valueKey)
|
||||
props.onCheckedChange(checked, props.valueKey);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -22,13 +22,14 @@ const SearchFilterCheckboxItem = (props: Props) => {
|
|||
className="Item"
|
||||
checked={props.checked || false}
|
||||
onCheckedChange={handleCheckedChange}
|
||||
onSelect={ (event) => event.preventDefault() }>
|
||||
onSelect={(event) => event.preventDefault()}
|
||||
>
|
||||
<DropdownMenu.ItemIndicator className="Indicator">
|
||||
<CheckIcon />
|
||||
</DropdownMenu.ItemIndicator>
|
||||
{props.children}
|
||||
</DropdownMenu.CheckboxItem>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchFilterCheckboxItem
|
||||
export default SearchFilterCheckboxItem;
|
||||
|
|
|
|||
|
|
@ -1,70 +1,70 @@
|
|||
import React, { useEffect, useState } from "react"
|
||||
import { getCookie, setCookie } from "cookies-next"
|
||||
import { useRouter } from "next/router"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import InfiniteScroll from "react-infinite-scroll-component"
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { getCookie, setCookie } from "cookies-next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
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 WeaponSearchFilterBar from "~components/WeaponSearchFilterBar"
|
||||
import SummonSearchFilterBar from "~components/SummonSearchFilterBar"
|
||||
import JobSkillSearchFilterBar from "~components/JobSkillSearchFilterBar"
|
||||
import CharacterSearchFilterBar from "~components/CharacterSearchFilterBar";
|
||||
import WeaponSearchFilterBar from "~components/WeaponSearchFilterBar";
|
||||
import SummonSearchFilterBar from "~components/SummonSearchFilterBar";
|
||||
import JobSkillSearchFilterBar from "~components/JobSkillSearchFilterBar";
|
||||
|
||||
import CharacterResult from "~components/CharacterResult"
|
||||
import WeaponResult from "~components/WeaponResult"
|
||||
import SummonResult from "~components/SummonResult"
|
||||
import JobSkillResult from "~components/JobSkillResult"
|
||||
import CharacterResult from "~components/CharacterResult";
|
||||
import WeaponResult from "~components/WeaponResult";
|
||||
import SummonResult from "~components/SummonResult";
|
||||
import JobSkillResult from "~components/JobSkillResult";
|
||||
|
||||
import type { SearchableObject, SearchableObjectArray } from "~types"
|
||||
import type { SearchableObject, SearchableObjectArray } from "~types";
|
||||
|
||||
import "./index.scss"
|
||||
import CrossIcon from "~public/icons/Cross.svg"
|
||||
import cloneDeep from "lodash.clonedeep"
|
||||
import "./index.scss";
|
||||
import CrossIcon from "~public/icons/Cross.svg";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
|
||||
interface Props {
|
||||
send: (object: SearchableObject, position: number) => any
|
||||
placeholderText: string
|
||||
fromPosition: number
|
||||
job?: Job
|
||||
object: "weapons" | "characters" | "summons" | "job_skills"
|
||||
children: React.ReactNode
|
||||
send: (object: SearchableObject, position: number) => any;
|
||||
placeholderText: string;
|
||||
fromPosition: number;
|
||||
job?: Job;
|
||||
object: "weapons" | "characters" | "summons" | "job_skills";
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const SearchModal = (props: Props) => {
|
||||
// Set up router
|
||||
const router = useRouter()
|
||||
const locale = router.locale
|
||||
const router = useRouter();
|
||||
const locale = router.locale;
|
||||
|
||||
// Set up translation
|
||||
const { t } = useTranslation("common")
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
let searchInput = React.createRef<HTMLInputElement>()
|
||||
let scrollContainer = React.createRef<HTMLDivElement>()
|
||||
let searchInput = React.createRef<HTMLInputElement>();
|
||||
let scrollContainer = React.createRef<HTMLDivElement>();
|
||||
|
||||
const [firstLoad, setFirstLoad] = useState(true)
|
||||
const [filters, setFilters] = useState<{ [key: string]: any }>()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [query, setQuery] = useState("")
|
||||
const [results, setResults] = useState<SearchableObjectArray>([])
|
||||
const [firstLoad, setFirstLoad] = useState(true);
|
||||
const [filters, setFilters] = useState<{ [key: string]: any }>();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [query, setQuery] = useState("");
|
||||
const [results, setResults] = useState<SearchableObjectArray>([]);
|
||||
|
||||
// Pagination states
|
||||
const [recordCount, setRecordCount] = useState(0)
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [totalPages, setTotalPages] = useState(1)
|
||||
const [recordCount, setRecordCount] = useState(0);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
if (searchInput.current) searchInput.current.focus()
|
||||
}, [searchInput])
|
||||
if (searchInput.current) searchInput.current.focus();
|
||||
}, [searchInput]);
|
||||
|
||||
function inputChanged(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const text = event.target.value
|
||||
const text = event.target.value;
|
||||
if (text.length) {
|
||||
setQuery(text)
|
||||
setQuery(text);
|
||||
} else {
|
||||
setQuery("")
|
||||
setQuery("");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,131 +79,131 @@ const SearchModal = (props: Props) => {
|
|||
page: currentPage,
|
||||
})
|
||||
.then((response) => {
|
||||
setTotalPages(response.data.total_pages)
|
||||
setRecordCount(response.data.count)
|
||||
setTotalPages(response.data.total_pages);
|
||||
setRecordCount(response.data.count);
|
||||
|
||||
if (replace) {
|
||||
replaceResults(response.data.count, response.data.results)
|
||||
replaceResults(response.data.count, response.data.results);
|
||||
} else {
|
||||
appendResults(response.data.results)
|
||||
appendResults(response.data.results);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
})
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
function replaceResults(count: number, list: SearchableObjectArray) {
|
||||
if (count > 0) {
|
||||
setResults(list)
|
||||
setResults(list);
|
||||
} else {
|
||||
setResults([])
|
||||
setResults([]);
|
||||
}
|
||||
}
|
||||
|
||||
function appendResults(list: SearchableObjectArray) {
|
||||
setResults([...results, ...list])
|
||||
setResults([...results, ...list]);
|
||||
}
|
||||
|
||||
function storeRecentResult(result: SearchableObject) {
|
||||
const key = `recent_${props.object}`
|
||||
const cookie = getCookie(key)
|
||||
const key = `recent_${props.object}`;
|
||||
const cookie = getCookie(key);
|
||||
const cookieObj: SearchableObjectArray = cookie
|
||||
? JSON.parse(cookie as string)
|
||||
: []
|
||||
let recents: SearchableObjectArray = []
|
||||
: [];
|
||||
let recents: SearchableObjectArray = [];
|
||||
|
||||
if (props.object === "weapons") {
|
||||
recents = cloneDeep(cookieObj as Weapon[]) || []
|
||||
recents = cloneDeep(cookieObj as Weapon[]) || [];
|
||||
if (
|
||||
!recents.find(
|
||||
(item) =>
|
||||
(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") {
|
||||
recents = cloneDeep(cookieObj as Summon[]) || []
|
||||
recents = cloneDeep(cookieObj as Summon[]) || [];
|
||||
if (
|
||||
!recents.find(
|
||||
(item) =>
|
||||
(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()
|
||||
setCookie(`recent_${props.object}`, recents, { path: "/" })
|
||||
sendData(result)
|
||||
if (recents && recents.length > 5) recents.pop();
|
||||
setCookie(`recent_${props.object}`, recents, { path: "/" });
|
||||
sendData(result);
|
||||
}
|
||||
|
||||
function sendData(result: SearchableObject) {
|
||||
props.send(result, props.fromPosition)
|
||||
openChange()
|
||||
props.send(result, props.fromPosition);
|
||||
openChange();
|
||||
}
|
||||
|
||||
function receiveFilters(filters: { [key: string]: any }) {
|
||||
setCurrentPage(1)
|
||||
setResults([])
|
||||
setFilters(filters)
|
||||
setCurrentPage(1);
|
||||
setResults([]);
|
||||
setFilters(filters);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Current page changed
|
||||
if (open && currentPage > 1) {
|
||||
fetchResults({ replace: false })
|
||||
fetchResults({ replace: false });
|
||||
} else if (open && currentPage == 1) {
|
||||
fetchResults({ replace: true })
|
||||
fetchResults({ replace: true });
|
||||
}
|
||||
}, [currentPage])
|
||||
}, [currentPage]);
|
||||
|
||||
useEffect(() => {
|
||||
// Filters changed
|
||||
const key = `recent_${props.object}`
|
||||
const cookie = getCookie(key)
|
||||
const key = `recent_${props.object}`;
|
||||
const cookie = getCookie(key);
|
||||
const cookieObj: Weapon[] | Summon[] | Character[] = cookie
|
||||
? JSON.parse(cookie as string)
|
||||
: []
|
||||
: [];
|
||||
|
||||
if (open) {
|
||||
if (firstLoad && cookieObj && cookieObj.length > 0) {
|
||||
setResults(cookieObj)
|
||||
setRecordCount(cookieObj.length)
|
||||
setFirstLoad(false)
|
||||
setResults(cookieObj);
|
||||
setRecordCount(cookieObj.length);
|
||||
setFirstLoad(false);
|
||||
} else {
|
||||
setCurrentPage(1)
|
||||
fetchResults({ replace: true })
|
||||
setCurrentPage(1);
|
||||
fetchResults({ replace: true });
|
||||
}
|
||||
}
|
||||
}, [filters])
|
||||
}, [filters]);
|
||||
|
||||
useEffect(() => {
|
||||
// Query changed
|
||||
if (open && query.length != 1) {
|
||||
setCurrentPage(1)
|
||||
fetchResults({ replace: true })
|
||||
setCurrentPage(1);
|
||||
fetchResults({ replace: true });
|
||||
}
|
||||
}, [query])
|
||||
}, [query]);
|
||||
|
||||
function renderResults() {
|
||||
let jsx
|
||||
let jsx;
|
||||
|
||||
switch (props.object) {
|
||||
case "weapons":
|
||||
jsx = renderWeaponSearchResults()
|
||||
break
|
||||
jsx = renderWeaponSearchResults();
|
||||
break;
|
||||
case "summons":
|
||||
jsx = renderSummonSearchResults(results)
|
||||
break
|
||||
jsx = renderSummonSearchResults(results);
|
||||
break;
|
||||
case "characters":
|
||||
jsx = renderCharacterSearchResults(results)
|
||||
break
|
||||
jsx = renderCharacterSearchResults(results);
|
||||
break;
|
||||
case "job_skills":
|
||||
jsx = renderJobSkillSearchResults(results)
|
||||
break
|
||||
jsx = renderJobSkillSearchResults(results);
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -216,13 +216,13 @@ const SearchModal = (props: Props) => {
|
|||
>
|
||||
{jsx}
|
||||
</InfiniteScroll>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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) {
|
||||
jsx = castResults.map((result: Weapon) => {
|
||||
return (
|
||||
|
|
@ -230,20 +230,20 @@ const SearchModal = (props: Props) => {
|
|||
key={result.id}
|
||||
data={result}
|
||||
onClick={() => {
|
||||
storeRecentResult(result)
|
||||
storeRecentResult(result);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return jsx
|
||||
return jsx;
|
||||
}
|
||||
|
||||
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) {
|
||||
jsx = castResults.map((result: Summon) => {
|
||||
return (
|
||||
|
|
@ -251,20 +251,20 @@ const SearchModal = (props: Props) => {
|
|||
key={result.id}
|
||||
data={result}
|
||||
onClick={() => {
|
||||
storeRecentResult(result)
|
||||
storeRecentResult(result);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return jsx
|
||||
return jsx;
|
||||
}
|
||||
|
||||
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) {
|
||||
jsx = castResults.map((result: Character) => {
|
||||
return (
|
||||
|
|
@ -272,20 +272,20 @@ const SearchModal = (props: Props) => {
|
|||
key={result.id}
|
||||
data={result}
|
||||
onClick={() => {
|
||||
storeRecentResult(result)
|
||||
storeRecentResult(result);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return jsx
|
||||
return jsx;
|
||||
}
|
||||
|
||||
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) {
|
||||
jsx = castResults.map((result: JobSkill) => {
|
||||
return (
|
||||
|
|
@ -293,26 +293,26 @@ const SearchModal = (props: Props) => {
|
|||
key={result.id}
|
||||
data={result}
|
||||
onClick={() => {
|
||||
storeRecentResult(result)
|
||||
storeRecentResult(result);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return jsx
|
||||
return jsx;
|
||||
}
|
||||
|
||||
function openChange() {
|
||||
if (open) {
|
||||
setQuery("")
|
||||
setFirstLoad(true)
|
||||
setResults([])
|
||||
setRecordCount(0)
|
||||
setCurrentPage(1)
|
||||
setOpen(false)
|
||||
setQuery("");
|
||||
setFirstLoad(true);
|
||||
setResults([]);
|
||||
setRecordCount(0);
|
||||
setCurrentPage(1);
|
||||
setOpen(false);
|
||||
} else {
|
||||
setOpen(true)
|
||||
setOpen(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -372,7 +372,7 @@ const SearchModal = (props: Props) => {
|
|||
<Dialog.Overlay className="Overlay" />
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchModal
|
||||
export default SearchModal;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
import React from 'react'
|
||||
import React from "react";
|
||||
|
||||
import './index.scss'
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
groupName: string
|
||||
name: string
|
||||
selected: boolean
|
||||
children: string
|
||||
onClick: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
groupName: string;
|
||||
name: string;
|
||||
selected: boolean;
|
||||
children: string;
|
||||
onClick: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
const Segment: React.FC<Props> = (props: Props) => {
|
||||
|
||||
|
||||
return (
|
||||
<div className="Segment">
|
||||
<input
|
||||
|
|
@ -23,11 +21,9 @@ const Segment: React.FC<Props> = (props: Props) => {
|
|||
checked={props.selected}
|
||||
onChange={props.onClick}
|
||||
/>
|
||||
<label htmlFor={props.name}>
|
||||
{props.children}
|
||||
</label>
|
||||
<label htmlFor={props.name}>{props.children}</label>
|
||||
</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 {
|
||||
elementClass?: string
|
||||
elementClass?: string;
|
||||
}
|
||||
|
||||
const SegmentedControl: React.FC<Props> = ({ elementClass, children }) => {
|
||||
return (
|
||||
<div className="SegmentedControlWrapper">
|
||||
<div className={`SegmentedControl ${(elementClass) ? elementClass : ''}`}>
|
||||
<div className={`SegmentedControl ${elementClass ? elementClass : ""}`}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default SegmentedControl
|
||||
export default SegmentedControl;
|
||||
|
|
|
|||
|
|
@ -1,64 +1,64 @@
|
|||
import React, { useEffect, useState } from "react"
|
||||
import Link from "next/link"
|
||||
import { setCookie } from "cookies-next"
|
||||
import { useRouter } from "next/router"
|
||||
import { Trans, useTranslation } from "next-i18next"
|
||||
import { AxiosResponse } from "axios"
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { setCookie } from "cookies-next";
|
||||
import { useRouter } from "next/router";
|
||||
import { Trans, useTranslation } from "next-i18next";
|
||||
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 { accountState } from "~utils/accountState"
|
||||
import api from "~utils/api";
|
||||
import { accountState } from "~utils/accountState";
|
||||
|
||||
import Button from "~components/Button"
|
||||
import Fieldset from "~components/Fieldset"
|
||||
import Button from "~components/Button";
|
||||
import Fieldset from "~components/Fieldset";
|
||||
|
||||
import CrossIcon from "~public/icons/Cross.svg"
|
||||
import "./index.scss"
|
||||
import CrossIcon from "~public/icons/Cross.svg";
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {}
|
||||
|
||||
interface ErrorMap {
|
||||
[index: string]: string
|
||||
username: string
|
||||
email: string
|
||||
password: string
|
||||
passwordConfirmation: string
|
||||
[index: string]: string;
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
passwordConfirmation: string;
|
||||
}
|
||||
|
||||
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 router = useRouter()
|
||||
const { t } = useTranslation("common")
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
// Set up form states and error handling
|
||||
const [formValid, setFormValid] = useState(false)
|
||||
const [formValid, setFormValid] = useState(false);
|
||||
const [errors, setErrors] = useState<ErrorMap>({
|
||||
username: "",
|
||||
email: "",
|
||||
password: "",
|
||||
passwordConfirmation: "",
|
||||
})
|
||||
});
|
||||
|
||||
// States
|
||||
const [open, setOpen] = useState(false)
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
// Set up form refs
|
||||
const usernameInput = React.createRef<HTMLInputElement>()
|
||||
const emailInput = React.createRef<HTMLInputElement>()
|
||||
const passwordInput = React.createRef<HTMLInputElement>()
|
||||
const passwordConfirmationInput = React.createRef<HTMLInputElement>()
|
||||
const usernameInput = React.createRef<HTMLInputElement>();
|
||||
const emailInput = React.createRef<HTMLInputElement>();
|
||||
const passwordInput = React.createRef<HTMLInputElement>();
|
||||
const passwordConfirmationInput = React.createRef<HTMLInputElement>();
|
||||
const form = [
|
||||
usernameInput,
|
||||
emailInput,
|
||||
passwordInput,
|
||||
passwordConfirmationInput,
|
||||
]
|
||||
];
|
||||
|
||||
function register(event: React.FormEvent) {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
const body = {
|
||||
user: {
|
||||
|
|
@ -68,47 +68,47 @@ const SignupModal = (props: Props) => {
|
|||
password_confirmation: passwordConfirmationInput.current?.value,
|
||||
language: router.locale,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if (formValid)
|
||||
api.endpoints.users
|
||||
.create(body)
|
||||
.then((response) => {
|
||||
storeCookieInfo(response)
|
||||
return response.data.user.user_id
|
||||
storeCookieInfo(response);
|
||||
return response.data.user.user_id;
|
||||
})
|
||||
.then((id) => fetchUserInfo(id))
|
||||
.then((infoResponse) => storeUserInfo(infoResponse))
|
||||
.then((infoResponse) => storeUserInfo(infoResponse));
|
||||
}
|
||||
|
||||
function storeCookieInfo(response: AxiosResponse) {
|
||||
const user = response.data.user
|
||||
const user = response.data.user;
|
||||
|
||||
const cookieObj: AccountCookie = {
|
||||
userId: user.user_id,
|
||||
username: user.username,
|
||||
token: user.token,
|
||||
}
|
||||
};
|
||||
|
||||
setCookie("account", cookieObj, { path: "/" })
|
||||
setCookie("account", cookieObj, { path: "/" });
|
||||
}
|
||||
|
||||
function fetchUserInfo(id: string) {
|
||||
return api.userInfo(id)
|
||||
return api.userInfo(id);
|
||||
}
|
||||
|
||||
function storeUserInfo(response: AxiosResponse) {
|
||||
const user = response.data.user
|
||||
const user = response.data.user;
|
||||
|
||||
const cookieObj: UserCookie = {
|
||||
picture: user.picture.picture,
|
||||
element: user.picture.element,
|
||||
language: user.language,
|
||||
gender: user.gender,
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Set language
|
||||
setCookie("user", cookieObj, { path: "/" })
|
||||
setCookie("user", cookieObj, { path: "/" });
|
||||
|
||||
accountState.account.user = {
|
||||
id: user.id,
|
||||
|
|
@ -116,29 +116,29 @@ const SignupModal = (props: Props) => {
|
|||
picture: user.picture.picture,
|
||||
element: user.picture.element,
|
||||
gender: user.gender,
|
||||
}
|
||||
};
|
||||
|
||||
accountState.account.authorized = true
|
||||
setOpen(false)
|
||||
accountState.account.authorized = true;
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
function handleNameChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
const fieldName = event.target.name
|
||||
const value = event.target.value
|
||||
const fieldName = event.target.name;
|
||||
const value = event.target.value;
|
||||
|
||||
if (value.length >= 3) {
|
||||
api.check(fieldName, value).then(
|
||||
(response) => {
|
||||
processNameCheck(fieldName, value, response.data.available)
|
||||
processNameCheck(fieldName, value, response.data.available);
|
||||
},
|
||||
(error) => {
|
||||
console.error(error)
|
||||
console.error(error);
|
||||
}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
validateName(fieldName, value)
|
||||
validateName(fieldName, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,55 +147,55 @@ const SignupModal = (props: Props) => {
|
|||
value: string,
|
||||
available: boolean
|
||||
) {
|
||||
const newErrors = { ...errors }
|
||||
const newErrors = { ...errors };
|
||||
|
||||
if (available) {
|
||||
// Continue checking for errors
|
||||
newErrors[fieldName] = ""
|
||||
setErrors(newErrors)
|
||||
setFormValid(true)
|
||||
newErrors[fieldName] = "";
|
||||
setErrors(newErrors);
|
||||
setFormValid(true);
|
||||
|
||||
validateName(fieldName, value)
|
||||
validateName(fieldName, value);
|
||||
} else {
|
||||
newErrors[fieldName] = t("modals.signup.errors.field_in_use", {
|
||||
field: fieldName,
|
||||
})
|
||||
setErrors(newErrors)
|
||||
setFormValid(false)
|
||||
});
|
||||
setErrors(newErrors);
|
||||
setFormValid(false);
|
||||
}
|
||||
}
|
||||
|
||||
function validateName(fieldName: string, value: string) {
|
||||
let newErrors = { ...errors }
|
||||
let newErrors = { ...errors };
|
||||
|
||||
switch (fieldName) {
|
||||
case "username":
|
||||
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)
|
||||
newErrors.username = t("modals.signup.errors.username_too_long")
|
||||
else newErrors.username = ""
|
||||
newErrors.username = t("modals.signup.errors.username_too_long");
|
||||
else newErrors.username = "";
|
||||
|
||||
break
|
||||
break;
|
||||
|
||||
case "email":
|
||||
newErrors.email = emailRegex.test(value)
|
||||
? ""
|
||||
: t("modals.signup.errors.invalid_email")
|
||||
break
|
||||
: t("modals.signup.errors.invalid_email");
|
||||
break;
|
||||
|
||||
default:
|
||||
break
|
||||
break;
|
||||
}
|
||||
|
||||
setFormValid(validateForm(newErrors))
|
||||
setFormValid(validateForm(newErrors));
|
||||
}
|
||||
|
||||
function handlePasswordChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
const { name, value } = event.target
|
||||
let newErrors = { ...errors }
|
||||
const { name, value } = event.target;
|
||||
let newErrors = { ...errors };
|
||||
|
||||
switch (name) {
|
||||
case "password":
|
||||
|
|
@ -203,51 +203,51 @@ const SignupModal = (props: Props) => {
|
|||
usernameInput.current?.value!
|
||||
)
|
||||
? t("modals.signup.errors.password_contains_username")
|
||||
: ""
|
||||
break
|
||||
: "";
|
||||
break;
|
||||
|
||||
case "password":
|
||||
newErrors.password =
|
||||
value.length < 8 ? t("modals.signup.errors.password_too_short") : ""
|
||||
break
|
||||
value.length < 8 ? t("modals.signup.errors.password_too_short") : "";
|
||||
break;
|
||||
|
||||
case "confirm_password":
|
||||
newErrors.passwordConfirmation =
|
||||
passwordInput.current?.value ===
|
||||
passwordConfirmationInput.current?.value
|
||||
? ""
|
||||
: t("modals.signup.errors.passwords_dont_match")
|
||||
break
|
||||
: t("modals.signup.errors.passwords_dont_match");
|
||||
break;
|
||||
|
||||
default:
|
||||
break
|
||||
break;
|
||||
}
|
||||
|
||||
setFormValid(validateForm(newErrors))
|
||||
setFormValid(validateForm(newErrors));
|
||||
}
|
||||
|
||||
function validateForm(errors: ErrorMap) {
|
||||
let valid = true
|
||||
let valid = true;
|
||||
|
||||
Object.values(form).forEach(
|
||||
(input) => input.current?.value.length == 0 && (valid = false)
|
||||
)
|
||||
);
|
||||
|
||||
Object.values(errors).forEach(
|
||||
(error) => error.length > 0 && (valid = false)
|
||||
)
|
||||
);
|
||||
|
||||
return valid
|
||||
return valid;
|
||||
}
|
||||
|
||||
function openChange(open: boolean) {
|
||||
setOpen(open)
|
||||
setOpen(open);
|
||||
setErrors({
|
||||
username: "",
|
||||
email: "",
|
||||
password: "",
|
||||
passwordConfirmation: "",
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -318,7 +318,7 @@ const SignupModal = (props: Props) => {
|
|||
<Dialog.Overlay className="Overlay" />
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default SignupModal
|
||||
export default SignupModal;
|
||||
|
|
|
|||
|
|
@ -1,53 +1,53 @@
|
|||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { getCookie } from "cookies-next"
|
||||
import { useSnapshot } from "valtio"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { getCookie } from "cookies-next";
|
||||
import { useSnapshot } from "valtio";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import { AxiosResponse } from "axios"
|
||||
import debounce from "lodash.debounce"
|
||||
import { AxiosResponse } from "axios";
|
||||
import debounce from "lodash.debounce";
|
||||
|
||||
import SummonUnit from "~components/SummonUnit"
|
||||
import ExtraSummons from "~components/ExtraSummons"
|
||||
import SummonUnit from "~components/SummonUnit";
|
||||
import ExtraSummons from "~components/ExtraSummons";
|
||||
|
||||
import api from "~utils/api"
|
||||
import { appState } from "~utils/appState"
|
||||
import type { SearchableObject } from "~types"
|
||||
import api from "~utils/api";
|
||||
import { appState } from "~utils/appState";
|
||||
import type { SearchableObject } from "~types";
|
||||
|
||||
import "./index.scss"
|
||||
import "./index.scss";
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
new: boolean
|
||||
summons?: GridSummon[]
|
||||
createParty: () => Promise<AxiosResponse<any, any>>
|
||||
pushHistory?: (path: string) => void
|
||||
new: boolean;
|
||||
summons?: GridSummon[];
|
||||
createParty: () => Promise<AxiosResponse<any, any>>;
|
||||
pushHistory?: (path: string) => void;
|
||||
}
|
||||
|
||||
const SummonGrid = (props: Props) => {
|
||||
// Constants
|
||||
const numSummons: number = 4
|
||||
const numSummons: number = 4;
|
||||
|
||||
// Cookies
|
||||
const cookie = getCookie("account")
|
||||
const cookie = getCookie("account");
|
||||
const accountData: AccountCookie = cookie
|
||||
? JSON.parse(cookie as string)
|
||||
: null
|
||||
: null;
|
||||
const headers = accountData
|
||||
? { headers: { Authorization: `Bearer ${accountData.token}` } }
|
||||
: {}
|
||||
: {};
|
||||
|
||||
// Localization
|
||||
const { t } = useTranslation("common")
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
// Set up state for view management
|
||||
const { party, grid } = useSnapshot(appState)
|
||||
const [slug, setSlug] = useState()
|
||||
const { party, grid } = useSnapshot(appState);
|
||||
const [slug, setSlug] = useState();
|
||||
|
||||
// Create a temporary state to store previous weapon uncap value
|
||||
const [previousUncapValues, setPreviousUncapValues] = useState<{
|
||||
[key: number]: number
|
||||
}>({})
|
||||
[key: number]: number;
|
||||
}>({});
|
||||
|
||||
// Set the editable flag only on first load
|
||||
useEffect(() => {
|
||||
|
|
@ -56,61 +56,61 @@ const SummonGrid = (props: Props) => {
|
|||
(accountData && party.user && accountData.userId === party.user.id) ||
|
||||
props.new
|
||||
)
|
||||
appState.party.editable = true
|
||||
else appState.party.editable = false
|
||||
}, [props.new, accountData, party])
|
||||
appState.party.editable = true;
|
||||
else appState.party.editable = false;
|
||||
}, [props.new, accountData, party]);
|
||||
|
||||
// Initialize an array of current uncap values for each summon
|
||||
useEffect(() => {
|
||||
let initialPreviousUncapValues: { [key: number]: number } = {}
|
||||
let initialPreviousUncapValues: { [key: number]: number } = {};
|
||||
|
||||
if (appState.grid.summons.mainSummon)
|
||||
initialPreviousUncapValues[-1] =
|
||||
appState.grid.summons.mainSummon.uncap_level
|
||||
appState.grid.summons.mainSummon.uncap_level;
|
||||
|
||||
if (appState.grid.summons.friendSummon)
|
||||
initialPreviousUncapValues[6] =
|
||||
appState.grid.summons.friendSummon.uncap_level
|
||||
appState.grid.summons.friendSummon.uncap_level;
|
||||
|
||||
Object.values(appState.grid.summons.allSummons).map((o) =>
|
||||
o ? (initialPreviousUncapValues[o.position] = o.uncap_level) : 0
|
||||
)
|
||||
);
|
||||
|
||||
setPreviousUncapValues(initialPreviousUncapValues)
|
||||
setPreviousUncapValues(initialPreviousUncapValues);
|
||||
}, [
|
||||
appState.grid.summons.mainSummon,
|
||||
appState.grid.summons.friendSummon,
|
||||
appState.grid.summons.allSummons,
|
||||
])
|
||||
]);
|
||||
|
||||
// Methods: Adding an object from search
|
||||
function receiveSummonFromSearch(object: SearchableObject, position: number) {
|
||||
const summon = object as Summon
|
||||
const summon = object as Summon;
|
||||
|
||||
if (!party.id) {
|
||||
props.createParty().then((response) => {
|
||||
const party = response.data.party
|
||||
appState.party.id = party.id
|
||||
setSlug(party.shortcode)
|
||||
const party = response.data.party;
|
||||
appState.party.id = party.id;
|
||||
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) =>
|
||||
storeGridSummon(response.data.grid_summon)
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
} else {
|
||||
if (party.editable)
|
||||
saveSummon(party.id, summon, position).then((response) =>
|
||||
storeGridSummon(response.data.grid_summon)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSummon(partyId: string, summon: Summon, position: number) {
|
||||
let uncapLevel = 3
|
||||
if (summon.uncap.ulb) uncapLevel = 5
|
||||
else if (summon.uncap.flb) uncapLevel = 4
|
||||
let uncapLevel = 3;
|
||||
if (summon.uncap.ulb) uncapLevel = 5;
|
||||
else if (summon.uncap.flb) uncapLevel = 4;
|
||||
|
||||
return await api.endpoints.summons.create(
|
||||
{
|
||||
|
|
@ -124,36 +124,37 @@ const SummonGrid = (props: Props) => {
|
|||
},
|
||||
},
|
||||
headers
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function storeGridSummon(gridSummon: GridSummon) {
|
||||
if (gridSummon.position == -1) appState.grid.summons.mainSummon = gridSummon
|
||||
if (gridSummon.position == -1)
|
||||
appState.grid.summons.mainSummon = gridSummon;
|
||||
else if (gridSummon.position == 6)
|
||||
appState.grid.summons.friendSummon = gridSummon
|
||||
else appState.grid.summons.allSummons[gridSummon.position] = gridSummon
|
||||
appState.grid.summons.friendSummon = gridSummon;
|
||||
else appState.grid.summons.allSummons[gridSummon.position] = gridSummon;
|
||||
}
|
||||
|
||||
// Methods: Updating uncap level
|
||||
// Note: Saves, but debouncing is not working properly
|
||||
async function saveUncap(id: string, position: number, uncapLevel: number) {
|
||||
storePreviousUncapValue(position)
|
||||
storePreviousUncapValue(position);
|
||||
|
||||
try {
|
||||
if (uncapLevel != previousUncapValues[position])
|
||||
await api.updateUncap("summon", id, uncapLevel).then((response) => {
|
||||
storeGridSummon(response.data.grid_summon)
|
||||
})
|
||||
storeGridSummon(response.data.grid_summon);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
console.error(error);
|
||||
|
||||
// Revert optimistic UI
|
||||
updateUncapLevel(position, previousUncapValues[position])
|
||||
updateUncapLevel(position, previousUncapValues[position]);
|
||||
|
||||
// Remove optimistic key
|
||||
let newPreviousValues = { ...previousUncapValues }
|
||||
delete newPreviousValues[position]
|
||||
setPreviousUncapValues(newPreviousValues)
|
||||
let newPreviousValues = { ...previousUncapValues };
|
||||
delete newPreviousValues[position];
|
||||
setPreviousUncapValues(newPreviousValues);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -162,56 +163,57 @@ const SummonGrid = (props: Props) => {
|
|||
position: number,
|
||||
uncapLevel: number
|
||||
) {
|
||||
memoizeAction(id, position, uncapLevel)
|
||||
memoizeAction(id, position, uncapLevel);
|
||||
|
||||
// Optimistically update UI
|
||||
updateUncapLevel(position, uncapLevel)
|
||||
updateUncapLevel(position, uncapLevel);
|
||||
}
|
||||
|
||||
const memoizeAction = useCallback(
|
||||
(id: string, position: number, uncapLevel: number) => {
|
||||
debouncedAction(id, position, uncapLevel)
|
||||
debouncedAction(id, position, uncapLevel);
|
||||
},
|
||||
[props, previousUncapValues]
|
||||
)
|
||||
);
|
||||
|
||||
const debouncedAction = useMemo(
|
||||
() =>
|
||||
debounce((id, position, number) => {
|
||||
saveUncap(id, position, number)
|
||||
saveUncap(id, position, number);
|
||||
}, 500),
|
||||
[props, saveUncap]
|
||||
)
|
||||
);
|
||||
|
||||
const updateUncapLevel = (position: number, uncapLevel: number) => {
|
||||
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)
|
||||
appState.grid.summons.friendSummon.uncap_level = uncapLevel
|
||||
appState.grid.summons.friendSummon.uncap_level = uncapLevel;
|
||||
else {
|
||||
const summon = appState.grid.summons.allSummons[position]
|
||||
const summon = appState.grid.summons.allSummons[position];
|
||||
if (summon) {
|
||||
summon.uncap_level = uncapLevel
|
||||
appState.grid.summons.allSummons[position] = summon
|
||||
}
|
||||
summon.uncap_level = uncapLevel;
|
||||
appState.grid.summons.allSummons[position] = summon;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function storePreviousUncapValue(position: number) {
|
||||
// Save the current value in case of an unexpected result
|
||||
let newPreviousValues = { ...previousUncapValues }
|
||||
let newPreviousValues = { ...previousUncapValues };
|
||||
|
||||
if (appState.grid.summons.mainSummon && position == -1)
|
||||
newPreviousValues[position] = appState.grid.summons.mainSummon.uncap_level
|
||||
newPreviousValues[position] =
|
||||
appState.grid.summons.mainSummon.uncap_level;
|
||||
else if (appState.grid.summons.friendSummon && position == 6)
|
||||
newPreviousValues[position] =
|
||||
appState.grid.summons.friendSummon.uncap_level
|
||||
appState.grid.summons.friendSummon.uncap_level;
|
||||
else {
|
||||
const summon = appState.grid.summons.allSummons[position]
|
||||
newPreviousValues[position] = summon ? summon.uncap_level : 0
|
||||
const summon = appState.grid.summons.allSummons[position];
|
||||
newPreviousValues[position] = summon ? summon.uncap_level : 0;
|
||||
}
|
||||
|
||||
setPreviousUncapValues(newPreviousValues)
|
||||
setPreviousUncapValues(newPreviousValues);
|
||||
}
|
||||
|
||||
// Render: JSX components
|
||||
|
|
@ -228,7 +230,7 @@ const SummonGrid = (props: Props) => {
|
|||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
const friendSummonElement = (
|
||||
<div className="LabeledUnit">
|
||||
|
|
@ -243,7 +245,7 @@ const SummonGrid = (props: Props) => {
|
|||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
const summonGridElement = (
|
||||
<div id="LabeledGrid">
|
||||
<div className="Label">{t("summons.summons")}</div>
|
||||
|
|
@ -260,11 +262,11 @@ const SummonGrid = (props: Props) => {
|
|||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
const subAuraSummonElement = (
|
||||
<ExtraSummons
|
||||
grid={grid.summons.allSummons}
|
||||
|
|
@ -274,7 +276,7 @@ const SummonGrid = (props: Props) => {
|
|||
updateObject={receiveSummonFromSearch}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
)
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<div id="SummonGrid">
|
||||
|
|
@ -285,7 +287,7 @@ const SummonGrid = (props: Props) => {
|
|||
|
||||
{subAuraSummonElement}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default SummonGrid
|
||||
export default SummonGrid;
|
||||
|
|
|
|||
|
|
@ -1,65 +1,83 @@
|
|||
import React from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
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 UncapIndicator from '~components/UncapIndicator'
|
||||
import WeaponLabelIcon from "~components/WeaponLabelIcon";
|
||||
import UncapIndicator from "~components/UncapIndicator";
|
||||
|
||||
import './index.scss'
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
gridSummon: GridSummon
|
||||
children: React.ReactNode
|
||||
gridSummon: GridSummon;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const SummonHovercard = (props: Props) => {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation('common')
|
||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation("common");
|
||||
const locale =
|
||||
router.locale && ["en", "ja"].includes(router.locale)
|
||||
? 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 wikiUrl = `https://gbf.wiki/${props.gridSummon.object.name.en.replaceAll(' ', '_')}`
|
||||
const tintElement = Element[props.gridSummon.object.element];
|
||||
const wikiUrl = `https://gbf.wiki/${props.gridSummon.object.name.en.replaceAll(
|
||||
" ",
|
||||
"_"
|
||||
)}`;
|
||||
|
||||
function summonImage() {
|
||||
let imgSrc = ""
|
||||
let imgSrc = "";
|
||||
|
||||
if (props.gridSummon) {
|
||||
const summon = props.gridSummon.object
|
||||
const summon = props.gridSummon.object;
|
||||
|
||||
const upgradedSummons = [
|
||||
'2040094000', '2040100000', '2040080000', '2040098000',
|
||||
'2040090000', '2040084000', '2040003000', '2040056000'
|
||||
]
|
||||
"2040094000",
|
||||
"2040100000",
|
||||
"2040080000",
|
||||
"2040098000",
|
||||
"2040090000",
|
||||
"2040084000",
|
||||
"2040003000",
|
||||
"2040056000",
|
||||
];
|
||||
|
||||
let suffix = ''
|
||||
if (upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 && props.gridSummon.uncap_level == 5)
|
||||
suffix = '_02'
|
||||
let suffix = "";
|
||||
if (
|
||||
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 &&
|
||||
props.gridSummon.uncap_level == 5
|
||||
)
|
||||
suffix = "_02";
|
||||
|
||||
// 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 (
|
||||
<HoverCard.Root>
|
||||
<HoverCard.Trigger>
|
||||
{ props.children }
|
||||
</HoverCard.Trigger>
|
||||
<HoverCard.Trigger>{props.children}</HoverCard.Trigger>
|
||||
<HoverCard.Content className="Weapon Hovercard">
|
||||
<div className="top">
|
||||
<div className="title">
|
||||
<h4>{ props.gridSummon.object.name[locale] }</h4>
|
||||
<img alt={props.gridSummon.object.name[locale]} src={summonImage()} />
|
||||
<h4>{props.gridSummon.object.name[locale]}</h4>
|
||||
<img
|
||||
alt={props.gridSummon.object.name[locale]}
|
||||
src={summonImage()}
|
||||
/>
|
||||
</div>
|
||||
<div className="subInfo">
|
||||
<div className="icons">
|
||||
<WeaponLabelIcon labelType={Element[props.gridSummon.object.element]}/>
|
||||
<WeaponLabelIcon
|
||||
labelType={Element[props.gridSummon.object.element]}
|
||||
/>
|
||||
</div>
|
||||
<UncapIndicator
|
||||
type="summon"
|
||||
|
|
@ -69,12 +87,13 @@ const SummonHovercard = (props: Props) => {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">{t('buttons.wiki')}</a>
|
||||
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">
|
||||
{t("buttons.wiki")}
|
||||
</a>
|
||||
<HoverCard.Arrow />
|
||||
</HoverCard.Content>
|
||||
</HoverCard.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export default SummonHovercard
|
||||
);
|
||||
};
|
||||
|
||||
export default SummonHovercard;
|
||||
|
|
|
|||
|
|
@ -37,11 +37,11 @@
|
|||
|
||||
.stars {
|
||||
display: inline-block;
|
||||
color: #FFA15E;
|
||||
color: #ffa15e;
|
||||
font-size: $font-xlarge;
|
||||
|
||||
& > span {
|
||||
color: #65DAFF;
|
||||
color: #65daff;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,33 @@
|
|||
import React from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import UncapIndicator from '~components/UncapIndicator'
|
||||
import WeaponLabelIcon from '~components/WeaponLabelIcon'
|
||||
import UncapIndicator from "~components/UncapIndicator";
|
||||
import WeaponLabelIcon from "~components/WeaponLabelIcon";
|
||||
|
||||
import './index.scss'
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
data: Summon
|
||||
onClick: () => void
|
||||
data: Summon;
|
||||
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 router = useRouter()
|
||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
||||
const router = useRouter();
|
||||
const locale =
|
||||
router.locale && ["en", "ja"].includes(router.locale)
|
||||
? router.locale
|
||||
: "en";
|
||||
|
||||
const summon = props.data
|
||||
const summon = props.data;
|
||||
|
||||
return (
|
||||
<li className="SummonResult" onClick={props.onClick}>
|
||||
<img alt={summon.name[locale]} src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}.jpg`} />
|
||||
<img
|
||||
alt={summon.name[locale]}
|
||||
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}.jpg`}
|
||||
/>
|
||||
<div className="Info">
|
||||
<h5>{summon.name[locale]}</h5>
|
||||
<UncapIndicator
|
||||
|
|
@ -35,7 +41,7 @@ const SummonResult = (props: Props) => {
|
|||
</div>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default SummonResult
|
||||
export default SummonResult;
|
||||
|
|
|
|||
|
|
@ -1,105 +1,134 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import React, { useEffect, useState } from "react";
|
||||
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 SearchFilterCheckboxItem from '~components/SearchFilterCheckboxItem'
|
||||
import SearchFilter from "~components/SearchFilter";
|
||||
import SearchFilterCheckboxItem from "~components/SearchFilterCheckboxItem";
|
||||
|
||||
import './index.scss'
|
||||
import { emptyElementState, emptyRarityState } from '~utils/emptyStates'
|
||||
import { elements, rarities } from '~utils/stateValues'
|
||||
import "./index.scss";
|
||||
import { emptyElementState, emptyRarityState } from "~utils/emptyStates";
|
||||
import { elements, rarities } from "~utils/stateValues";
|
||||
|
||||
interface Props {
|
||||
sendFilters: (filters: { [key: string]: number[] }) => void
|
||||
sendFilters: (filters: { [key: string]: number[] }) => void;
|
||||
}
|
||||
|
||||
const SummonSearchFilterBar = (props: Props) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
const [rarityMenu, setRarityMenu] = useState(false)
|
||||
const [elementMenu, setElementMenu] = useState(false)
|
||||
const [rarityMenu, setRarityMenu] = useState(false);
|
||||
const [elementMenu, setElementMenu] = useState(false);
|
||||
|
||||
const [rarityState, setRarityState] = useState<RarityState>(emptyRarityState)
|
||||
const [elementState, setElementState] = useState<ElementState>(emptyElementState)
|
||||
const [rarityState, setRarityState] = useState<RarityState>(emptyRarityState);
|
||||
const [elementState, setElementState] =
|
||||
useState<ElementState>(emptyElementState);
|
||||
|
||||
function rarityMenuOpened(open: boolean) {
|
||||
if (open) {
|
||||
setRarityMenu(true)
|
||||
setElementMenu(false)
|
||||
} else setRarityMenu(false)
|
||||
setRarityMenu(true);
|
||||
setElementMenu(false);
|
||||
} else setRarityMenu(false);
|
||||
}
|
||||
|
||||
function elementMenuOpened(open: boolean) {
|
||||
if (open) {
|
||||
setRarityMenu(false)
|
||||
setElementMenu(true)
|
||||
} else setElementMenu(false)
|
||||
setRarityMenu(false);
|
||||
setElementMenu(true);
|
||||
} else setElementMenu(false);
|
||||
}
|
||||
|
||||
function handleRarityChange(checked: boolean, key: string) {
|
||||
let newRarityState = cloneDeep(rarityState)
|
||||
newRarityState[key].checked = checked
|
||||
setRarityState(newRarityState)
|
||||
let newRarityState = cloneDeep(rarityState);
|
||||
newRarityState[key].checked = checked;
|
||||
setRarityState(newRarityState);
|
||||
}
|
||||
|
||||
function handleElementChange(checked: boolean, key: string) {
|
||||
let newElementState = cloneDeep(elementState)
|
||||
newElementState[key].checked = checked
|
||||
setElementState(newElementState)
|
||||
let newElementState = cloneDeep(elementState);
|
||||
newElementState[key].checked = checked;
|
||||
setElementState(newElementState);
|
||||
}
|
||||
|
||||
function sendFilters() {
|
||||
const checkedRarityFilters = Object.values(rarityState).filter(x => x.checked).map((x, i) => x.id)
|
||||
const checkedElementFilters = Object.values(elementState).filter(x => x.checked).map((x, i) => x.id)
|
||||
const checkedRarityFilters = Object.values(rarityState)
|
||||
.filter((x) => x.checked)
|
||||
.map((x, i) => x.id);
|
||||
const checkedElementFilters = Object.values(elementState)
|
||||
.filter((x) => x.checked)
|
||||
.map((x, i) => x.id);
|
||||
|
||||
const filters = {
|
||||
rarity: checkedRarityFilters,
|
||||
element: checkedElementFilters
|
||||
}
|
||||
element: checkedElementFilters,
|
||||
};
|
||||
|
||||
props.sendFilters(filters)
|
||||
props.sendFilters(filters);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
sendFilters()
|
||||
}, [rarityState, elementState])
|
||||
sendFilters();
|
||||
}, [rarityState, elementState]);
|
||||
|
||||
return (
|
||||
<div className="SearchFilterBar">
|
||||
<SearchFilter label={t('filters.labels.rarity')} numSelected={Object.values(rarityState).map(x => x.checked).filter(Boolean).length} open={rarityMenu} onOpenChange={rarityMenuOpened}>
|
||||
<DropdownMenu.Label className="Label">{t('filters.labels.rarity')}</DropdownMenu.Label>
|
||||
{ Array.from(Array(rarities.length)).map((x, i) => {
|
||||
<SearchFilter
|
||||
label={t("filters.labels.rarity")}
|
||||
numSelected={
|
||||
Object.values(rarityState)
|
||||
.map((x) => x.checked)
|
||||
.filter(Boolean).length
|
||||
}
|
||||
open={rarityMenu}
|
||||
onOpenChange={rarityMenuOpened}
|
||||
>
|
||||
<DropdownMenu.Label className="Label">
|
||||
{t("filters.labels.rarity")}
|
||||
</DropdownMenu.Label>
|
||||
{Array.from(Array(rarities.length)).map((x, i) => {
|
||||
return (
|
||||
<SearchFilterCheckboxItem
|
||||
key={rarities[i]}
|
||||
onCheckedChange={handleRarityChange}
|
||||
checked={rarityState[rarities[i]].checked}
|
||||
valueKey={rarities[i]}>
|
||||
valueKey={rarities[i]}
|
||||
>
|
||||
{t(`rarities.${rarities[i]}`)}
|
||||
</SearchFilterCheckboxItem>
|
||||
)}
|
||||
) }
|
||||
);
|
||||
})}
|
||||
</SearchFilter>
|
||||
|
||||
<SearchFilter label={t('filters.labels.element')} numSelected={Object.values(elementState).map(x => x.checked).filter(Boolean).length} open={elementMenu} onOpenChange={elementMenuOpened}>
|
||||
<DropdownMenu.Label className="Label">{t('filters.labels.element')}</DropdownMenu.Label>
|
||||
{ Array.from(Array(elements.length)).map((x, i) => {
|
||||
<SearchFilter
|
||||
label={t("filters.labels.element")}
|
||||
numSelected={
|
||||
Object.values(elementState)
|
||||
.map((x) => x.checked)
|
||||
.filter(Boolean).length
|
||||
}
|
||||
open={elementMenu}
|
||||
onOpenChange={elementMenuOpened}
|
||||
>
|
||||
<DropdownMenu.Label className="Label">
|
||||
{t("filters.labels.element")}
|
||||
</DropdownMenu.Label>
|
||||
{Array.from(Array(elements.length)).map((x, i) => {
|
||||
return (
|
||||
<SearchFilterCheckboxItem
|
||||
key={elements[i]}
|
||||
onCheckedChange={handleElementChange}
|
||||
checked={elementState[elements[i]].checked}
|
||||
valueKey={elements[i]}>
|
||||
valueKey={elements[i]}
|
||||
>
|
||||
{t(`elements.${elements[i]}`)}
|
||||
</SearchFilterCheckboxItem>
|
||||
)}
|
||||
) }
|
||||
);
|
||||
})}
|
||||
</SearchFilter>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default SummonSearchFilterBar
|
||||
export default SummonSearchFilterBar;
|
||||
|
|
|
|||
|
|
@ -85,7 +85,8 @@
|
|||
display: flex;
|
||||
}
|
||||
|
||||
h3, ul {
|
||||
h3,
|
||||
ul {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +1,36 @@
|
|||
import React, { useEffect, useState } from "react"
|
||||
import { useRouter } from "next/router"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import classnames from "classnames"
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import classnames from "classnames";
|
||||
|
||||
import SearchModal from "~components/SearchModal"
|
||||
import SummonHovercard from "~components/SummonHovercard"
|
||||
import UncapIndicator from "~components/UncapIndicator"
|
||||
import PlusIcon from "~public/icons/Add.svg"
|
||||
import SearchModal from "~components/SearchModal";
|
||||
import SummonHovercard from "~components/SummonHovercard";
|
||||
import UncapIndicator from "~components/UncapIndicator";
|
||||
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 {
|
||||
gridSummon: GridSummon | undefined
|
||||
unitType: 0 | 1 | 2
|
||||
position: number
|
||||
editable: boolean
|
||||
updateObject: (object: SearchableObject, position: number) => void
|
||||
updateUncap: (id: string, position: number, uncap: number) => void
|
||||
gridSummon: GridSummon | undefined;
|
||||
unitType: 0 | 1 | 2;
|
||||
position: number;
|
||||
editable: boolean;
|
||||
updateObject: (object: SearchableObject, position: number) => void;
|
||||
updateUncap: (id: string, position: number, uncap: number) => void;
|
||||
}
|
||||
|
||||
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 =
|
||||
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
||||
router.locale && ["en", "ja"].includes(router.locale)
|
||||
? router.locale
|
||||
: "en";
|
||||
|
||||
const classes = classnames({
|
||||
SummonUnit: true,
|
||||
|
|
@ -37,19 +39,19 @@ const SummonUnit = (props: Props) => {
|
|||
friend: props.unitType == 2,
|
||||
editable: props.editable,
|
||||
filled: props.gridSummon !== undefined,
|
||||
})
|
||||
});
|
||||
|
||||
const gridSummon = props.gridSummon
|
||||
const summon = gridSummon?.object
|
||||
const gridSummon = props.gridSummon;
|
||||
const summon = gridSummon?.object;
|
||||
|
||||
useEffect(() => {
|
||||
generateImageUrl()
|
||||
})
|
||||
generateImageUrl();
|
||||
});
|
||||
|
||||
function generateImageUrl() {
|
||||
let imgSrc = ""
|
||||
let imgSrc = "";
|
||||
if (props.gridSummon) {
|
||||
const summon = props.gridSummon.object!
|
||||
const summon = props.gridSummon.object!;
|
||||
|
||||
const upgradedSummons = [
|
||||
"2040094000",
|
||||
|
|
@ -66,28 +68,28 @@ const SummonUnit = (props: Props) => {
|
|||
"2040027000",
|
||||
"2040046000",
|
||||
"2040047000",
|
||||
]
|
||||
];
|
||||
|
||||
let suffix = ""
|
||||
let suffix = "";
|
||||
if (
|
||||
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 &&
|
||||
props.gridSummon.uncap_level == 5
|
||||
)
|
||||
suffix = "_02"
|
||||
suffix = "_02";
|
||||
|
||||
// Generate the correct source for the summon
|
||||
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
|
||||
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) {
|
||||
if (props.gridSummon)
|
||||
props.updateUncap(props.gridSummon.id, props.position, uncap)
|
||||
props.updateUncap(props.gridSummon.id, props.position, uncap);
|
||||
}
|
||||
|
||||
const image = (
|
||||
|
|
@ -101,7 +103,7 @@ const SummonUnit = (props: Props) => {
|
|||
""
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
const editableImage = (
|
||||
<SearchModal
|
||||
|
|
@ -112,7 +114,7 @@ const SummonUnit = (props: Props) => {
|
|||
>
|
||||
{image}
|
||||
</SearchModal>
|
||||
)
|
||||
);
|
||||
|
||||
const unitContent = (
|
||||
<div className={classes}>
|
||||
|
|
@ -131,13 +133,13 @@ const SummonUnit = (props: Props) => {
|
|||
)}
|
||||
<h3 className="SummonName">{summon?.name[locale]}</h3>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
const withHovercard = (
|
||||
<SummonHovercard gridSummon={gridSummon!}>{unitContent}</SummonHovercard>
|
||||
)
|
||||
);
|
||||
|
||||
return gridSummon && !props.editable ? withHovercard : unitContent
|
||||
}
|
||||
return gridSummon && !props.editable ? withHovercard : unitContent;
|
||||
};
|
||||
|
||||
export default SummonUnit
|
||||
export default SummonUnit;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
.Fieldset textarea {
|
||||
color: $grey-00;
|
||||
font-family: system-ui, -apple-system, 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-family: system-ui, -apple-system, "Helvetica Neue", Helvetica, Arial,
|
||||
sans-serif;
|
||||
line-height: 21px;
|
||||
}
|
||||
|
|
@ -1,33 +1,32 @@
|
|||
import React from 'react'
|
||||
import './index.scss'
|
||||
import React from "react";
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
fieldName: string
|
||||
placeholder: string
|
||||
value?: string
|
||||
error: string
|
||||
onBlur?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
|
||||
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
|
||||
fieldName: string;
|
||||
placeholder: string;
|
||||
value?: string;
|
||||
error: string;
|
||||
onBlur?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||
}
|
||||
|
||||
const TextFieldset = React.forwardRef<HTMLTextAreaElement, Props>(function fieldSet(props, ref) {
|
||||
const TextFieldset = React.forwardRef<HTMLTextAreaElement, Props>(
|
||||
function fieldSet(props, ref) {
|
||||
return (
|
||||
<fieldset className="Fieldset">
|
||||
<textarea
|
||||
className="Input"
|
||||
name={props.fieldName}
|
||||
placeholder={props.placeholder}
|
||||
defaultValue={props.value || ''}
|
||||
defaultValue={props.value || ""}
|
||||
onBlur={props.onBlur}
|
||||
onChange={props.onChange}
|
||||
ref={ref}
|
||||
/>
|
||||
{
|
||||
props.error.length > 0 &&
|
||||
<p className='InputError'>{props.error}</p>
|
||||
}
|
||||
{props.error.length > 0 && <p className="InputError">{props.error}</p>}
|
||||
</fieldset>
|
||||
)
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default TextFieldset
|
||||
export default TextFieldset;
|
||||
|
|
|
|||
|
|
@ -43,11 +43,11 @@
|
|||
}
|
||||
|
||||
&-checkbox:checked + &-label {
|
||||
background: #ECEBFF;
|
||||
background: #ecebff;
|
||||
}
|
||||
|
||||
&-checkbox:checked + &-label &-switch {
|
||||
background: #8C86FF;
|
||||
background: #8c86ff;
|
||||
}
|
||||
|
||||
&-checkbox:checked + &-label {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import React from 'react'
|
||||
import React from "react";
|
||||
|
||||
import './index.scss'
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
name: string
|
||||
checked: boolean
|
||||
editable: boolean
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
name: string;
|
||||
checked: boolean;
|
||||
editable: boolean;
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
const ToggleSwitch: React.FC<Props> = (props: Props) => {
|
||||
|
|
@ -25,7 +25,7 @@ const ToggleSwitch: React.FC<Props> = (props: Props) => {
|
|||
<span className="toggle-switch-switch" />
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default ToggleSwitch
|
||||
export default ToggleSwitch;
|
||||
|
|
|
|||
|
|
@ -1,97 +1,97 @@
|
|||
import React from "react"
|
||||
import { useSnapshot } from "valtio"
|
||||
import { getCookie, deleteCookie } from "cookies-next"
|
||||
import { useRouter } from "next/router"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import React from "react";
|
||||
import { useSnapshot } from "valtio";
|
||||
import { getCookie, deleteCookie } from "cookies-next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import clonedeep from "lodash.clonedeep"
|
||||
import clonedeep from "lodash.clonedeep";
|
||||
|
||||
import api from "~utils/api"
|
||||
import { accountState, initialAccountState } from "~utils/accountState"
|
||||
import { appState, initialAppState } from "~utils/appState"
|
||||
import api from "~utils/api";
|
||||
import { accountState, initialAccountState } from "~utils/accountState";
|
||||
import { appState, initialAppState } from "~utils/appState";
|
||||
|
||||
import Header from "~components/Header"
|
||||
import Button from "~components/Button"
|
||||
import HeaderMenu from "~components/HeaderMenu"
|
||||
import Header from "~components/Header";
|
||||
import Button from "~components/Button";
|
||||
import HeaderMenu from "~components/HeaderMenu";
|
||||
|
||||
const TopHeader = () => {
|
||||
const { t } = useTranslation("common")
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
// Cookies
|
||||
const accountCookie = getCookie("account")
|
||||
const userCookie = getCookie("user")
|
||||
const accountCookie = getCookie("account");
|
||||
const userCookie = getCookie("user");
|
||||
|
||||
const headers = {}
|
||||
const headers = {};
|
||||
// accountCookies.account != null
|
||||
// ? {
|
||||
// Authorization: `Bearer ${accountCookies.account.access_token}`,
|
||||
// }
|
||||
// : {}
|
||||
|
||||
const { account } = useSnapshot(accountState)
|
||||
const { party } = useSnapshot(appState)
|
||||
const router = useRouter()
|
||||
const { account } = useSnapshot(accountState);
|
||||
const { party } = useSnapshot(appState);
|
||||
const router = useRouter();
|
||||
|
||||
function copyToClipboard() {
|
||||
const el = document.createElement("input")
|
||||
el.value = window.location.href
|
||||
el.id = "url-input"
|
||||
document.body.appendChild(el)
|
||||
const el = document.createElement("input");
|
||||
el.value = window.location.href;
|
||||
el.id = "url-input";
|
||||
document.body.appendChild(el);
|
||||
|
||||
el.select()
|
||||
document.execCommand("copy")
|
||||
el.remove()
|
||||
el.select();
|
||||
document.execCommand("copy");
|
||||
el.remove();
|
||||
}
|
||||
|
||||
function newParty() {
|
||||
// Push the root URL
|
||||
router.push("/")
|
||||
router.push("/");
|
||||
|
||||
// Clean state
|
||||
const resetState = clonedeep(initialAppState)
|
||||
const resetState = clonedeep(initialAppState);
|
||||
Object.keys(resetState).forEach((key) => {
|
||||
appState[key] = resetState[key]
|
||||
})
|
||||
appState[key] = resetState[key];
|
||||
});
|
||||
|
||||
// Set party to be editable
|
||||
appState.party.editable = true
|
||||
appState.party.editable = true;
|
||||
}
|
||||
|
||||
function logout() {
|
||||
deleteCookie("account")
|
||||
deleteCookie("user")
|
||||
deleteCookie("account");
|
||||
deleteCookie("user");
|
||||
|
||||
// Clean state
|
||||
const resetState = clonedeep(initialAccountState)
|
||||
const resetState = clonedeep(initialAccountState);
|
||||
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("/")
|
||||
return false
|
||||
router.push("/");
|
||||
return false;
|
||||
}
|
||||
|
||||
function toggleFavorite() {
|
||||
if (party.favorited) unsaveFavorite()
|
||||
else saveFavorite()
|
||||
if (party.favorited) unsaveFavorite();
|
||||
else saveFavorite();
|
||||
}
|
||||
|
||||
function saveFavorite() {
|
||||
if (party.id)
|
||||
api.saveTeam({ id: party.id, params: headers }).then((response) => {
|
||||
if (response.status == 201) appState.party.favorited = true
|
||||
})
|
||||
else console.error("Failed to save team: No party ID")
|
||||
if (response.status == 201) appState.party.favorited = true;
|
||||
});
|
||||
else console.error("Failed to save team: No party ID");
|
||||
}
|
||||
|
||||
function unsaveFavorite() {
|
||||
if (party.id)
|
||||
api.unsaveTeam({ id: party.id, params: headers }).then((response) => {
|
||||
if (response.status == 200) appState.party.favorited = false
|
||||
})
|
||||
else console.error("Failed to unsave team: No party ID")
|
||||
if (response.status == 200) appState.party.favorited = false;
|
||||
});
|
||||
else console.error("Failed to unsave team: No party ID");
|
||||
}
|
||||
|
||||
const leftNav = () => {
|
||||
|
|
@ -108,8 +108,8 @@ const TopHeader = () => {
|
|||
<HeaderMenu authenticated={account.authorized} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const saveButton = () => {
|
||||
if (party.favorited)
|
||||
|
|
@ -117,14 +117,14 @@ const TopHeader = () => {
|
|||
<Button icon="save" active={true} onClick={toggleFavorite}>
|
||||
Saved
|
||||
</Button>
|
||||
)
|
||||
);
|
||||
else
|
||||
return (
|
||||
<Button icon="save" onClick={toggleFavorite}>
|
||||
Save
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const rightNav = () => {
|
||||
return (
|
||||
|
|
@ -145,10 +145,10 @@ const TopHeader = () => {
|
|||
{t("buttons.new")}
|
||||
</Button>
|
||||
</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 UncapStar from "~components/UncapStar"
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import UncapStar from "~components/UncapStar";
|
||||
|
||||
import "./index.scss"
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
type: "character" | "weapon" | "summon"
|
||||
rarity?: number
|
||||
uncapLevel?: number
|
||||
flb: boolean
|
||||
ulb: boolean
|
||||
special: boolean
|
||||
updateUncap?: (uncap: number) => void
|
||||
type: "character" | "weapon" | "summon";
|
||||
rarity?: number;
|
||||
uncapLevel?: number;
|
||||
flb: boolean;
|
||||
ulb: boolean;
|
||||
special: boolean;
|
||||
updateUncap?: (uncap: number) => void;
|
||||
}
|
||||
|
||||
const UncapIndicator = (props: Props) => {
|
||||
const [uncap, setUncap] = useState(props.uncapLevel)
|
||||
const [uncap, setUncap] = useState(props.uncapLevel);
|
||||
|
||||
const numStars = setNumStars()
|
||||
const numStars = setNumStars();
|
||||
function setNumStars() {
|
||||
let numStars
|
||||
let numStars;
|
||||
|
||||
if (props.type === "character") {
|
||||
if (props.special) {
|
||||
if (props.ulb) {
|
||||
numStars = 5
|
||||
numStars = 5;
|
||||
} else if (props.flb) {
|
||||
numStars = 4
|
||||
numStars = 4;
|
||||
} else {
|
||||
numStars = 3
|
||||
numStars = 3;
|
||||
}
|
||||
} else {
|
||||
if (props.ulb) {
|
||||
numStars = 6
|
||||
numStars = 6;
|
||||
} else if (props.flb) {
|
||||
numStars = 5
|
||||
numStars = 5;
|
||||
} else {
|
||||
numStars = 4
|
||||
numStars = 4;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (props.ulb) {
|
||||
numStars = 5
|
||||
numStars = 5;
|
||||
} else if (props.flb) {
|
||||
numStars = 4
|
||||
numStars = 4;
|
||||
} else {
|
||||
numStars = 3
|
||||
numStars = 3;
|
||||
}
|
||||
}
|
||||
|
||||
return numStars
|
||||
return numStars;
|
||||
}
|
||||
|
||||
function toggleStar(index: number, empty: boolean) {
|
||||
if (props.updateUncap) {
|
||||
if (empty) props.updateUncap(index + 1)
|
||||
else props.updateUncap(index)
|
||||
if (empty) props.updateUncap(index + 1);
|
||||
else props.updateUncap(index);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,8 +67,8 @@ const UncapIndicator = (props: Props) => {
|
|||
index={i}
|
||||
onClick={toggleStar}
|
||||
/>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const ulb = (i: number) => {
|
||||
return (
|
||||
|
|
@ -79,8 +79,8 @@ const UncapIndicator = (props: Props) => {
|
|||
index={i}
|
||||
onClick={toggleStar}
|
||||
/>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const flb = (i: number) => {
|
||||
return (
|
||||
|
|
@ -91,8 +91,8 @@ const UncapIndicator = (props: Props) => {
|
|||
index={i}
|
||||
onClick={toggleStar}
|
||||
/>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const mlb = (i: number) => {
|
||||
// console.log("MLB; Number of stars:", props.uncapLevel)
|
||||
|
|
@ -103,27 +103,27 @@ const UncapIndicator = (props: Props) => {
|
|||
index={i}
|
||||
onClick={toggleStar}
|
||||
/>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ul className="UncapIndicator">
|
||||
{Array.from(Array(numStars)).map((x, i) => {
|
||||
if (props.type === "character" && i > 4) {
|
||||
if (props.special) return ulb(i)
|
||||
else return transcendence(i)
|
||||
if (props.special) return ulb(i);
|
||||
else return transcendence(i);
|
||||
} else if (
|
||||
(props.special && props.type === "character" && i == 3) ||
|
||||
(props.type === "character" && i == 4) ||
|
||||
(props.type !== "character" && i > 2)
|
||||
) {
|
||||
return flb(i)
|
||||
return flb(i);
|
||||
} else {
|
||||
return mlb(i)
|
||||
return mlb(i);
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default UncapIndicator
|
||||
export default UncapIndicator;
|
||||
|
|
|
|||
|
|
@ -14,42 +14,42 @@
|
|||
&.empty.flb,
|
||||
&.empty.ulb,
|
||||
&.empty.special {
|
||||
background: url('/icons/uncap/empty.svg');
|
||||
background: url("/icons/uncap/empty.svg");
|
||||
|
||||
&:hover {
|
||||
background: url('/icons/uncap/empty-hover.svg');
|
||||
background: url("/icons/uncap/empty-hover.svg");
|
||||
}
|
||||
}
|
||||
|
||||
&.mlb {
|
||||
background: url('/icons/uncap/yellow.svg');
|
||||
background: url("/icons/uncap/yellow.svg");
|
||||
|
||||
&:hover {
|
||||
background: url('/icons/uncap/yellow-hover.svg');
|
||||
background: url("/icons/uncap/yellow-hover.svg");
|
||||
}
|
||||
}
|
||||
|
||||
&.special {
|
||||
background: url('/icons/uncap/red.svg');
|
||||
background: url("/icons/uncap/red.svg");
|
||||
|
||||
&:hover {
|
||||
background: url('/icons/uncap/red-hover.svg');
|
||||
background: url("/icons/uncap/red-hover.svg");
|
||||
}
|
||||
}
|
||||
|
||||
&.flb {
|
||||
background: url('/icons/uncap/blue.svg');
|
||||
background: url("/icons/uncap/blue.svg");
|
||||
|
||||
&:hover {
|
||||
background: url('/icons/uncap/blue-hover.svg');
|
||||
background: url("/icons/uncap/blue-hover.svg");
|
||||
}
|
||||
}
|
||||
|
||||
&.ulb {
|
||||
background: url('/icons/uncap/purple.svg');
|
||||
background: url("/icons/uncap/purple.svg");
|
||||
|
||||
&:hover {
|
||||
background: url('/icons/uncap/purple-hover.svg');
|
||||
background: url("/icons/uncap/purple-hover.svg");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +1,39 @@
|
|||
import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
import React from "react";
|
||||
import classnames from "classnames";
|
||||
|
||||
import './index.scss'
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
empty: boolean
|
||||
special: boolean
|
||||
flb: boolean
|
||||
ulb: boolean
|
||||
index: number
|
||||
onClick: (index: number, empty: boolean) => void
|
||||
empty: boolean;
|
||||
special: boolean;
|
||||
flb: boolean;
|
||||
ulb: boolean;
|
||||
index: number;
|
||||
onClick: (index: number, empty: boolean) => void;
|
||||
}
|
||||
|
||||
const UncapStar = (props: Props) => {
|
||||
const classes = classnames({
|
||||
UncapStar: true,
|
||||
'empty': props.empty,
|
||||
'special': props.special,
|
||||
'mlb': !props.special,
|
||||
'flb': props.flb,
|
||||
'ulb': props.ulb
|
||||
|
||||
})
|
||||
empty: props.empty,
|
||||
special: props.special,
|
||||
mlb: !props.special,
|
||||
flb: props.flb,
|
||||
ulb: props.ulb,
|
||||
});
|
||||
|
||||
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 = {
|
||||
empty: false,
|
||||
special: false,
|
||||
flb: false,
|
||||
ulb: false
|
||||
}
|
||||
ulb: false,
|
||||
};
|
||||
|
||||
export default UncapStar
|
||||
export default UncapStar;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
#MainGrid, #ExtraGrid {
|
||||
#MainGrid,
|
||||
#ExtraGrid {
|
||||
.grid_weapons > * {
|
||||
margin-bottom: $unit * 3;
|
||||
margin-right: $unit * 3;
|
||||
|
|
@ -22,12 +23,12 @@
|
|||
margin-right: $unit * 2;
|
||||
}
|
||||
|
||||
&:nth-last-child(-n+3) {
|
||||
&:nth-last-child(-n + 3) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.grid_weapons > *:nth-child(3n+3) {
|
||||
.grid_weapons > *:nth-child(3n + 3) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
|
|
@ -39,4 +40,3 @@
|
|||
#ExtraWeapons #grid_weapons > * {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,50 +1,50 @@
|
|||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { getCookie } from "cookies-next"
|
||||
import { useSnapshot } from "valtio"
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { getCookie } from "cookies-next";
|
||||
import { useSnapshot } from "valtio";
|
||||
|
||||
import { AxiosResponse } from "axios"
|
||||
import debounce from "lodash.debounce"
|
||||
import { AxiosResponse } from "axios";
|
||||
import debounce from "lodash.debounce";
|
||||
|
||||
import WeaponUnit from "~components/WeaponUnit"
|
||||
import ExtraWeapons from "~components/ExtraWeapons"
|
||||
import WeaponUnit from "~components/WeaponUnit";
|
||||
import ExtraWeapons from "~components/ExtraWeapons";
|
||||
|
||||
import api from "~utils/api"
|
||||
import { appState } from "~utils/appState"
|
||||
import api from "~utils/api";
|
||||
import { appState } from "~utils/appState";
|
||||
|
||||
import type { SearchableObject } from "~types"
|
||||
import type { SearchableObject } from "~types";
|
||||
|
||||
import "./index.scss"
|
||||
import "./index.scss";
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
new: boolean
|
||||
weapons?: GridWeapon[]
|
||||
createParty: (extra: boolean) => Promise<AxiosResponse<any, any>>
|
||||
pushHistory?: (path: string) => void
|
||||
new: boolean;
|
||||
weapons?: GridWeapon[];
|
||||
createParty: (extra: boolean) => Promise<AxiosResponse<any, any>>;
|
||||
pushHistory?: (path: string) => void;
|
||||
}
|
||||
|
||||
const WeaponGrid = (props: Props) => {
|
||||
// Constants
|
||||
const numWeapons: number = 9
|
||||
const numWeapons: number = 9;
|
||||
|
||||
// Cookies
|
||||
const cookie = getCookie("account")
|
||||
const cookie = getCookie("account");
|
||||
const accountData: AccountCookie = cookie
|
||||
? JSON.parse(cookie as string)
|
||||
: null
|
||||
: null;
|
||||
const headers = accountData
|
||||
? { headers: { Authorization: `Bearer ${accountData.token}` } }
|
||||
: {}
|
||||
: {};
|
||||
|
||||
// Set up state for view management
|
||||
const { party, grid } = useSnapshot(appState)
|
||||
const [slug, setSlug] = useState()
|
||||
const { party, grid } = useSnapshot(appState);
|
||||
const [slug, setSlug] = useState();
|
||||
|
||||
// Create a temporary state to store previous weapon uncap values
|
||||
const [previousUncapValues, setPreviousUncapValues] = useState<{
|
||||
[key: number]: number
|
||||
}>({})
|
||||
[key: number]: number;
|
||||
}>({});
|
||||
|
||||
// Set the editable flag only on first load
|
||||
useEffect(() => {
|
||||
|
|
@ -53,53 +53,53 @@ const WeaponGrid = (props: Props) => {
|
|||
(accountData && party.user && accountData.userId === party.user.id) ||
|
||||
props.new
|
||||
)
|
||||
appState.party.editable = true
|
||||
else appState.party.editable = false
|
||||
}, [props.new, accountData, party])
|
||||
appState.party.editable = true;
|
||||
else appState.party.editable = false;
|
||||
}, [props.new, accountData, party]);
|
||||
|
||||
// Initialize an array of current uncap values for each weapon
|
||||
useEffect(() => {
|
||||
let initialPreviousUncapValues: { [key: number]: number } = {}
|
||||
let initialPreviousUncapValues: { [key: number]: number } = {};
|
||||
|
||||
if (appState.grid.weapons.mainWeapon)
|
||||
initialPreviousUncapValues[-1] =
|
||||
appState.grid.weapons.mainWeapon.uncap_level
|
||||
appState.grid.weapons.mainWeapon.uncap_level;
|
||||
|
||||
Object.values(appState.grid.weapons.allWeapons).map((o) =>
|
||||
o ? (initialPreviousUncapValues[o.position] = o.uncap_level) : 0
|
||||
)
|
||||
);
|
||||
|
||||
setPreviousUncapValues(initialPreviousUncapValues)
|
||||
}, [appState.grid.weapons.mainWeapon, appState.grid.weapons.allWeapons])
|
||||
setPreviousUncapValues(initialPreviousUncapValues);
|
||||
}, [appState.grid.weapons.mainWeapon, appState.grid.weapons.allWeapons]);
|
||||
|
||||
// Methods: Adding an object from search
|
||||
function receiveWeaponFromSearch(object: SearchableObject, position: number) {
|
||||
const weapon = object as Weapon
|
||||
if (position == 1) appState.party.element = weapon.element
|
||||
const weapon = object as Weapon;
|
||||
if (position == 1) appState.party.element = weapon.element;
|
||||
|
||||
if (!party.id) {
|
||||
props.createParty(party.extra).then((response) => {
|
||||
const party = response.data.party
|
||||
appState.party.id = party.id
|
||||
setSlug(party.shortcode)
|
||||
const party = response.data.party;
|
||||
appState.party.id = party.id;
|
||||
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) =>
|
||||
storeGridWeapon(response.data.grid_weapon)
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
} else {
|
||||
saveWeapon(party.id, weapon, position).then((response) =>
|
||||
storeGridWeapon(response.data.grid_weapon)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveWeapon(partyId: string, weapon: Weapon, position: number) {
|
||||
let uncapLevel = 3
|
||||
if (weapon.uncap.ulb) uncapLevel = 5
|
||||
else if (weapon.uncap.flb) uncapLevel = 4
|
||||
let uncapLevel = 3;
|
||||
if (weapon.uncap.ulb) uncapLevel = 5;
|
||||
else if (weapon.uncap.flb) uncapLevel = 4;
|
||||
|
||||
return await api.endpoints.weapons.create(
|
||||
{
|
||||
|
|
@ -112,39 +112,39 @@ const WeaponGrid = (props: Props) => {
|
|||
},
|
||||
},
|
||||
headers
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function storeGridWeapon(gridWeapon: GridWeapon) {
|
||||
if (gridWeapon.position == -1) {
|
||||
appState.grid.weapons.mainWeapon = gridWeapon
|
||||
appState.party.element = gridWeapon.object.element
|
||||
appState.grid.weapons.mainWeapon = gridWeapon;
|
||||
appState.party.element = gridWeapon.object.element;
|
||||
} else {
|
||||
// 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
|
||||
// Note: Saves, but debouncing is not working properly
|
||||
async function saveUncap(id: string, position: number, uncapLevel: number) {
|
||||
storePreviousUncapValue(position)
|
||||
storePreviousUncapValue(position);
|
||||
|
||||
try {
|
||||
if (uncapLevel != previousUncapValues[position])
|
||||
await api.updateUncap("weapon", id, uncapLevel).then((response) => {
|
||||
storeGridWeapon(response.data.grid_weapon)
|
||||
})
|
||||
storeGridWeapon(response.data.grid_weapon);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
console.error(error);
|
||||
|
||||
// Revert optimistic UI
|
||||
updateUncapLevel(position, previousUncapValues[position])
|
||||
updateUncapLevel(position, previousUncapValues[position]);
|
||||
|
||||
// Remove optimistic key
|
||||
let newPreviousValues = { ...previousUncapValues }
|
||||
delete newPreviousValues[position]
|
||||
setPreviousUncapValues(newPreviousValues)
|
||||
let newPreviousValues = { ...previousUncapValues };
|
||||
delete newPreviousValues[position];
|
||||
setPreviousUncapValues(newPreviousValues);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -153,55 +153,56 @@ const WeaponGrid = (props: Props) => {
|
|||
position: number,
|
||||
uncapLevel: number
|
||||
) {
|
||||
memoizeAction(id, position, uncapLevel)
|
||||
memoizeAction(id, position, uncapLevel);
|
||||
|
||||
// Optimistically update UI
|
||||
updateUncapLevel(position, uncapLevel)
|
||||
updateUncapLevel(position, uncapLevel);
|
||||
}
|
||||
|
||||
const memoizeAction = useCallback(
|
||||
(id: string, position: number, uncapLevel: number) => {
|
||||
debouncedAction(id, position, uncapLevel)
|
||||
debouncedAction(id, position, uncapLevel);
|
||||
},
|
||||
[props, previousUncapValues]
|
||||
)
|
||||
);
|
||||
|
||||
const debouncedAction = useMemo(
|
||||
() =>
|
||||
debounce((id, position, number) => {
|
||||
saveUncap(id, position, number)
|
||||
saveUncap(id, position, number);
|
||||
}, 500),
|
||||
[props, saveUncap]
|
||||
)
|
||||
);
|
||||
|
||||
const updateUncapLevel = (position: number, uncapLevel: number) => {
|
||||
if (appState.grid.weapons.mainWeapon && position == -1)
|
||||
appState.grid.weapons.mainWeapon.uncap_level = uncapLevel
|
||||
appState.grid.weapons.mainWeapon.uncap_level = uncapLevel;
|
||||
else {
|
||||
const weapon = appState.grid.weapons.allWeapons[position]
|
||||
const weapon = appState.grid.weapons.allWeapons[position];
|
||||
if (weapon) {
|
||||
weapon.uncap_level = uncapLevel
|
||||
appState.grid.weapons.allWeapons[position] = weapon
|
||||
}
|
||||
weapon.uncap_level = uncapLevel;
|
||||
appState.grid.weapons.allWeapons[position] = weapon;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function storePreviousUncapValue(position: number) {
|
||||
// Save the current value in case of an unexpected result
|
||||
let newPreviousValues = { ...previousUncapValues }
|
||||
let newPreviousValues = { ...previousUncapValues };
|
||||
|
||||
if (appState.grid.weapons.mainWeapon && position == -1) {
|
||||
newPreviousValues[position] = appState.grid.weapons.mainWeapon.uncap_level
|
||||
newPreviousValues[position] =
|
||||
appState.grid.weapons.mainWeapon.uncap_level;
|
||||
} else {
|
||||
const weapon = appState.grid.weapons.allWeapons[position]
|
||||
const weapon = appState.grid.weapons.allWeapons[position];
|
||||
if (weapon) {
|
||||
newPreviousValues[position] = weapon.uncap_level
|
||||
newPreviousValues[position] = weapon.uncap_level;
|
||||
} else {
|
||||
newPreviousValues[position] = 0
|
||||
newPreviousValues[position] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
setPreviousUncapValues(newPreviousValues)
|
||||
setPreviousUncapValues(newPreviousValues);
|
||||
}
|
||||
|
||||
// Render: JSX components
|
||||
|
|
@ -215,7 +216,7 @@ const WeaponGrid = (props: Props) => {
|
|||
updateObject={receiveWeaponFromSearch}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
const weaponGridElement = Array.from(Array(numWeapons)).map((x, i) => {
|
||||
return (
|
||||
|
|
@ -229,8 +230,8 @@ const WeaponGrid = (props: Props) => {
|
|||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const extraGridElement = (
|
||||
<ExtraWeapons
|
||||
|
|
@ -240,7 +241,7 @@ const WeaponGrid = (props: Props) => {
|
|||
updateObject={receiveWeaponFromSearch}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<div id="WeaponGrid">
|
||||
|
|
@ -250,10 +251,10 @@ const WeaponGrid = (props: Props) => {
|
|||
</div>
|
||||
|
||||
{(() => {
|
||||
return party.extra ? extraGridElement : ""
|
||||
return party.extra ? extraGridElement : "";
|
||||
})()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default WeaponGrid
|
||||
export default WeaponGrid;
|
||||
|
|
|
|||
|
|
@ -1,159 +1,226 @@
|
|||
import React from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
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 UncapIndicator from '~components/UncapIndicator'
|
||||
import WeaponLabelIcon from "~components/WeaponLabelIcon";
|
||||
import UncapIndicator from "~components/UncapIndicator";
|
||||
|
||||
import { axData } from '~utils/axData'
|
||||
import { axData } from "~utils/axData";
|
||||
|
||||
import './index.scss'
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
gridWeapon: GridWeapon
|
||||
children: React.ReactNode
|
||||
gridWeapon: GridWeapon;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface KeyNames {
|
||||
[key: string]: {
|
||||
[key: string]: string
|
||||
en: string,
|
||||
ja: string
|
||||
}
|
||||
[key: string]: string;
|
||||
en: string;
|
||||
ja: string;
|
||||
};
|
||||
}
|
||||
|
||||
const WeaponHovercard = (props: Props) => {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation('common')
|
||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation("common");
|
||||
const locale =
|
||||
router.locale && ["en", "ja"].includes(router.locale)
|
||||
? router.locale
|
||||
: "en";
|
||||
|
||||
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
|
||||
const Proficiency = ['none', 'sword', 'dagger', 'axe', 'spear', 'bow', 'staff', 'fist', 'harp', 'gun', 'katana']
|
||||
const Element = ["null", "wind", "fire", "water", "earth", "dark", "light"];
|
||||
const Proficiency = [
|
||||
"none",
|
||||
"sword",
|
||||
"dagger",
|
||||
"axe",
|
||||
"spear",
|
||||
"bow",
|
||||
"staff",
|
||||
"fist",
|
||||
"harp",
|
||||
"gun",
|
||||
"katana",
|
||||
];
|
||||
const WeaponKeyNames: KeyNames = {
|
||||
'2': {
|
||||
en: 'Pendulum',
|
||||
ja: 'ペンデュラム'
|
||||
"2": {
|
||||
en: "Pendulum",
|
||||
ja: "ペンデュラム",
|
||||
},
|
||||
'3': {
|
||||
en: 'Teluma',
|
||||
ja: 'テルマ'
|
||||
"3": {
|
||||
en: "Teluma",
|
||||
ja: "テルマ",
|
||||
},
|
||||
'17': {
|
||||
en: 'Gauph Key',
|
||||
ja: 'ガフスキー'
|
||||
"17": {
|
||||
en: "Gauph Key",
|
||||
ja: "ガフスキー",
|
||||
},
|
||||
'22': {
|
||||
en: 'Emblem',
|
||||
ja: 'エンブレム'
|
||||
}
|
||||
}
|
||||
"22": {
|
||||
en: "Emblem",
|
||||
ja: "エンブレム",
|
||||
},
|
||||
};
|
||||
|
||||
const tintElement = (props.gridWeapon.object.element == 0 && props.gridWeapon.element) ? Element[props.gridWeapon.element] : Element[props.gridWeapon.object.element]
|
||||
const wikiUrl = `https://gbf.wiki/${props.gridWeapon.object.name.en.replaceAll(' ', '_')}`
|
||||
const tintElement =
|
||||
props.gridWeapon.object.element == 0 && props.gridWeapon.element
|
||||
? Element[props.gridWeapon.element]
|
||||
: Element[props.gridWeapon.object.element];
|
||||
const wikiUrl = `https://gbf.wiki/${props.gridWeapon.object.name.en.replaceAll(
|
||||
" ",
|
||||
"_"
|
||||
)}`;
|
||||
|
||||
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))
|
||||
return "top"
|
||||
else
|
||||
return "bottom"
|
||||
}
|
||||
return "top";
|
||||
else return "bottom";
|
||||
};
|
||||
|
||||
const createPrimaryAxSkillString = () => {
|
||||
const primaryAxSkills = axData[props.gridWeapon.object.ax - 1]
|
||||
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)
|
||||
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 `${axSkill?.name[locale]} +${simpleAxSkill.strength}${
|
||||
axSkill?.suffix ? axSkill.suffix : ""
|
||||
}`;
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const createSecondaryAxSkillString = () => {
|
||||
const primaryAxSkills = axData[props.gridWeapon.object.ax - 1]
|
||||
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 primarySimpleAxSkill = props.gridWeapon.ax[0];
|
||||
const secondarySimpleAxSkill = props.gridWeapon.ax[1];
|
||||
|
||||
const primaryAxSkill = primaryAxSkills.find(skill => skill.id == primarySimpleAxSkill.modifier)
|
||||
const primaryAxSkill = primaryAxSkills.find(
|
||||
(skill) => skill.id == primarySimpleAxSkill.modifier
|
||||
);
|
||||
|
||||
if (primaryAxSkill && primaryAxSkill.secondary) {
|
||||
const secondaryAxSkill = primaryAxSkill.secondary.find(skill => skill.id == secondarySimpleAxSkill.modifier)
|
||||
return `${secondaryAxSkill?.name[locale]} +${secondarySimpleAxSkill.strength}${ (secondaryAxSkill?.suffix) ? secondaryAxSkill.suffix : '' }`
|
||||
const secondaryAxSkill = primaryAxSkill.secondary.find(
|
||||
(skill) => skill.id == secondarySimpleAxSkill.modifier
|
||||
);
|
||||
return `${secondaryAxSkill?.name[locale]} +${
|
||||
secondarySimpleAxSkill.strength
|
||||
}${secondaryAxSkill?.suffix ? secondaryAxSkill.suffix : ""}`;
|
||||
}
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
function weaponImage() {
|
||||
const weapon = props.gridWeapon.object
|
||||
const weapon = props.gridWeapon.object;
|
||||
|
||||
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
|
||||
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 = (
|
||||
<section className="weaponKeys">
|
||||
{ (WeaponKeyNames[props.gridWeapon.object.series]) ?
|
||||
<h5 className={tintElement}>{ WeaponKeyNames[props.gridWeapon.object.series][locale] }{ (locale === 'en') ? 's' : '' }</h5> : ''
|
||||
}
|
||||
{WeaponKeyNames[props.gridWeapon.object.series] ? (
|
||||
<h5 className={tintElement}>
|
||||
{WeaponKeyNames[props.gridWeapon.object.series][locale]}
|
||||
{locale === "en" ? "s" : ""}
|
||||
</h5>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
{ (props.gridWeapon.weapon_keys) ?
|
||||
Array.from(Array(props.gridWeapon.weapon_keys.length)).map((x, i) => {
|
||||
{props.gridWeapon.weapon_keys
|
||||
? Array.from(Array(props.gridWeapon.weapon_keys.length)).map((x, i) => {
|
||||
return (
|
||||
<div className="weaponKey" key={props.gridWeapon.weapon_keys![i].id}>
|
||||
<div
|
||||
className="weaponKey"
|
||||
key={props.gridWeapon.weapon_keys![i].id}
|
||||
>
|
||||
<span>{props.gridWeapon.weapon_keys![i].name[locale]}</span>
|
||||
</div>
|
||||
)
|
||||
}) : '' }
|
||||
);
|
||||
})
|
||||
: ""}
|
||||
</section>
|
||||
)
|
||||
);
|
||||
|
||||
const axSection = (
|
||||
<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="primary axSkill">
|
||||
<img alt="AX1" src={`/icons/ax/primary_${ (props.gridWeapon.ax) ? props.gridWeapon.ax[0].modifier : '' }.png`} />
|
||||
<img
|
||||
alt="AX1"
|
||||
src={`/icons/ax/primary_${
|
||||
props.gridWeapon.ax ? props.gridWeapon.ax[0].modifier : ""
|
||||
}.png`}
|
||||
/>
|
||||
<span>{createPrimaryAxSkillString()}</span>
|
||||
</div>
|
||||
|
||||
{ (props.gridWeapon.ax && props.gridWeapon.ax[1].modifier && props.gridWeapon.ax[1].strength) ?
|
||||
{props.gridWeapon.ax &&
|
||||
props.gridWeapon.ax[1].modifier &&
|
||||
props.gridWeapon.ax[1].strength ? (
|
||||
<div className="secondary axSkill">
|
||||
<img alt="AX2" src={`/icons/ax/secondary_${ (props.gridWeapon.ax) ? props.gridWeapon.ax[1].modifier : '' }.png`} />
|
||||
<img
|
||||
alt="AX2"
|
||||
src={`/icons/ax/secondary_${
|
||||
props.gridWeapon.ax ? props.gridWeapon.ax[1].modifier : ""
|
||||
}.png`}
|
||||
/>
|
||||
<span>{createSecondaryAxSkillString()}</span>
|
||||
</div> : ''}
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<HoverCard.Root>
|
||||
<HoverCard.Trigger>
|
||||
{ props.children }
|
||||
</HoverCard.Trigger>
|
||||
<HoverCard.Trigger>{props.children}</HoverCard.Trigger>
|
||||
<HoverCard.Content className="Weapon Hovercard" side={hovercardSide()}>
|
||||
<div className="top">
|
||||
<div className="title">
|
||||
<h4>{ props.gridWeapon.object.name[locale] }</h4>
|
||||
<img alt={props.gridWeapon.object.name[locale]} src={weaponImage()} />
|
||||
<h4>{props.gridWeapon.object.name[locale]}</h4>
|
||||
<img
|
||||
alt={props.gridWeapon.object.name[locale]}
|
||||
src={weaponImage()}
|
||||
/>
|
||||
</div>
|
||||
<div className="subInfo">
|
||||
<div className="icons">
|
||||
{ (props.gridWeapon.object.element !== 0 || (props.gridWeapon.object.element === 0 && props.gridWeapon.element != null)) ?
|
||||
<WeaponLabelIcon labelType={ (props.gridWeapon.object.element === 0 && props.gridWeapon.element !== 0) ? Element[props.gridWeapon.element] : Element[props.gridWeapon.object.element] } />
|
||||
: '' }
|
||||
<WeaponLabelIcon labelType={ Proficiency[props.gridWeapon.object.proficiency] } />
|
||||
{props.gridWeapon.object.element !== 0 ||
|
||||
(props.gridWeapon.object.element === 0 &&
|
||||
props.gridWeapon.element != null) ? (
|
||||
<WeaponLabelIcon
|
||||
labelType={
|
||||
props.gridWeapon.object.element === 0 &&
|
||||
props.gridWeapon.element !== 0
|
||||
? Element[props.gridWeapon.element]
|
||||
: Element[props.gridWeapon.object.element]
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<WeaponLabelIcon
|
||||
labelType={Proficiency[props.gridWeapon.object.proficiency]}
|
||||
/>
|
||||
</div>
|
||||
<UncapIndicator
|
||||
type="weapon"
|
||||
|
|
@ -164,14 +231,22 @@ const WeaponHovercard = (props: Props) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{ (props.gridWeapon.object.ax > 0 && props.gridWeapon.ax && props.gridWeapon.ax[0].modifier && props.gridWeapon.ax[0].strength ) ? axSection : '' }
|
||||
{ (props.gridWeapon.weapon_keys && props.gridWeapon.weapon_keys.length > 0) ? keysSection : '' }
|
||||
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">{t('buttons.wiki')}</a>
|
||||
{props.gridWeapon.object.ax > 0 &&
|
||||
props.gridWeapon.ax &&
|
||||
props.gridWeapon.ax[0].modifier &&
|
||||
props.gridWeapon.ax[0].strength
|
||||
? axSection
|
||||
: ""}
|
||||
{props.gridWeapon.weapon_keys && props.gridWeapon.weapon_keys.length > 0
|
||||
? keysSection
|
||||
: ""}
|
||||
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">
|
||||
{t("buttons.wiki")}
|
||||
</a>
|
||||
<HoverCard.Arrow />
|
||||
</HoverCard.Content>
|
||||
</HoverCard.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export default WeaponHovercard
|
||||
);
|
||||
};
|
||||
|
||||
export default WeaponHovercard;
|
||||
|
|
|
|||
|
|
@ -1,120 +1,124 @@
|
|||
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
|
||||
interface Props {
|
||||
currentValue?: WeaponKey
|
||||
series: number
|
||||
slot: number
|
||||
onChange?: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
||||
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
||||
currentValue?: WeaponKey;
|
||||
series: number;
|
||||
slot: number;
|
||||
onChange?: (event: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||
}
|
||||
|
||||
const WeaponKeyDropdown = React.forwardRef<HTMLSelectElement, Props>(function useFieldSet(props, ref) {
|
||||
const [keys, setKeys] = useState<WeaponKey[][]>([])
|
||||
const [currentKey, setCurrentKey] = useState('')
|
||||
const WeaponKeyDropdown = React.forwardRef<HTMLSelectElement, Props>(
|
||||
function useFieldSet(props, ref) {
|
||||
const [keys, setKeys] = useState<WeaponKey[][]>([]);
|
||||
const [currentKey, setCurrentKey] = useState("");
|
||||
|
||||
const pendulumNames = [
|
||||
{ en: 'Pendulum', jp: '' },
|
||||
{ en: 'Chain', jp: '' }
|
||||
]
|
||||
{ en: "Pendulum", jp: "" },
|
||||
{ en: "Chain", jp: "" },
|
||||
];
|
||||
|
||||
const telumaNames = [ { en: 'Teluma', jp: '' } ]
|
||||
const emblemNames = [ { en: 'Emblem', jp: '' } ]
|
||||
const telumaNames = [{ en: "Teluma", jp: "" }];
|
||||
const emblemNames = [{ en: "Emblem", jp: "" }];
|
||||
const gauphNames = [
|
||||
{ en: 'Gauph Key', jp: '' },
|
||||
{ en: 'Ultima Key', jp: '' },
|
||||
{ en: 'Gate of Omnipotence', jp: '' }
|
||||
]
|
||||
{ en: "Gauph Key", jp: "" },
|
||||
{ en: "Ultima Key", jp: "" },
|
||||
{ en: "Gate of Omnipotence", jp: "" },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (props.currentValue)
|
||||
setCurrentKey(props.currentValue.id)
|
||||
}, [props.currentValue])
|
||||
if (props.currentValue) setCurrentKey(props.currentValue.id);
|
||||
}, [props.currentValue]);
|
||||
|
||||
useEffect(() => {
|
||||
const filterParams = {
|
||||
params: {
|
||||
series: props.series,
|
||||
slot: props.slot
|
||||
}
|
||||
}
|
||||
slot: props.slot,
|
||||
},
|
||||
};
|
||||
|
||||
function organizeWeaponKeys(weaponKeys: WeaponKey[]) {
|
||||
const numGroups = Math.max.apply(Math, weaponKeys.map(key => key.group))
|
||||
let groupedKeys = []
|
||||
const numGroups = Math.max.apply(
|
||||
Math,
|
||||
weaponKeys.map((key) => key.group)
|
||||
);
|
||||
let groupedKeys = [];
|
||||
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() {
|
||||
api.endpoints.weapon_keys.getAll(filterParams)
|
||||
.then((response) => {
|
||||
const keys = response.data.map((k: any) => k.weapon_key)
|
||||
organizeWeaponKeys(keys)
|
||||
})
|
||||
api.endpoints.weapon_keys.getAll(filterParams).then((response) => {
|
||||
const keys = response.data.map((k: any) => k.weapon_key);
|
||||
organizeWeaponKeys(keys);
|
||||
});
|
||||
}
|
||||
|
||||
fetchWeaponKeys()
|
||||
}, [props.series, props.slot])
|
||||
fetchWeaponKeys();
|
||||
}, [props.series, props.slot]);
|
||||
|
||||
function weaponKeyGroup(index: number) {
|
||||
['α','β','γ','Δ'].sort((a, b) => a.localeCompare(b, 'el'))
|
||||
["α", "β", "γ", "Δ"].sort((a, b) => a.localeCompare(b, "el"));
|
||||
|
||||
const sortByOrder = (a: WeaponKey, b: WeaponKey) => a.order > b.order ? 1 : -1
|
||||
const sortByOrder = (a: WeaponKey, b: WeaponKey) =>
|
||||
a.order > b.order ? 1 : -1;
|
||||
|
||||
const options = keys && keys.length > 0 && keys[index].length > 0 &&
|
||||
const options =
|
||||
keys &&
|
||||
keys.length > 0 &&
|
||||
keys[index].length > 0 &&
|
||||
keys[index].sort(sortByOrder).map((item, i) => {
|
||||
return (
|
||||
<option key={i} value={item.id}>{item.name.en}</option>
|
||||
)
|
||||
})
|
||||
<option key={i} value={item.id}>
|
||||
{item.name.en}
|
||||
</option>
|
||||
);
|
||||
});
|
||||
|
||||
let name: { [key: string]: string } = {}
|
||||
if (props.series == 2 && index == 0)
|
||||
name = pendulumNames[0]
|
||||
let name: { [key: string]: string } = {};
|
||||
if (props.series == 2 && index == 0) name = pendulumNames[0];
|
||||
else if (props.series == 2 && props.slot == 1 && index == 1)
|
||||
name = pendulumNames[1]
|
||||
else if (props.series == 3)
|
||||
name = telumaNames[index]
|
||||
else if (props.series == 17)
|
||||
name = gauphNames[props.slot]
|
||||
else if (props.series == 22)
|
||||
name = emblemNames[index]
|
||||
name = pendulumNames[1];
|
||||
else if (props.series == 3) name = telumaNames[index];
|
||||
else if (props.series == 17) name = gauphNames[props.slot];
|
||||
else if (props.series == 22) name = emblemNames[index];
|
||||
|
||||
return (
|
||||
<optgroup key={index} label={ (props.series == 17 && props.slot == 2) ? name.en : `${name.en}s`}>
|
||||
<optgroup
|
||||
key={index}
|
||||
label={
|
||||
props.series == 17 && props.slot == 2 ? name.en : `${name.en}s`
|
||||
}
|
||||
>
|
||||
{options}
|
||||
</optgroup>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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 = () => {
|
||||
let name = ''
|
||||
if (props.series == 2)
|
||||
name = pendulumNames[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 == 22)
|
||||
name = emblemNames[0].en
|
||||
let name = "";
|
||||
if (props.series == 2) name = pendulumNames[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 == 22) name = emblemNames[0].en;
|
||||
|
||||
return `No ${name}`
|
||||
}
|
||||
return `No ${name}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<select
|
||||
|
|
@ -122,13 +126,17 @@ const WeaponKeyDropdown = React.forwardRef<HTMLSelectElement, Props>(function us
|
|||
value={currentKey}
|
||||
onBlur={props.onBlur}
|
||||
onChange={handleChange}
|
||||
ref={ref}>
|
||||
<option key="-1" value="-1">{ emptyOption() }</option>
|
||||
{ Array.from(Array(keys?.length)).map((x, i) => {
|
||||
return weaponKeyGroup(i)
|
||||
ref={ref}
|
||||
>
|
||||
<option key="-1" value="-1">
|
||||
{emptyOption()}
|
||||
</option>
|
||||
{Array.from(Array(keys?.length)).map((x, i) => {
|
||||
return weaponKeyGroup(i);
|
||||
})}
|
||||
</select>
|
||||
)
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default WeaponKeyDropdown
|
||||
export default WeaponKeyDropdown;
|
||||
|
|
|
|||
|
|
@ -7,141 +7,140 @@
|
|||
/* Elements */
|
||||
|
||||
&.fire.en {
|
||||
background-image: url('/labels/element/fire_en.png')
|
||||
background-image: url("/labels/element/fire_en.png");
|
||||
}
|
||||
|
||||
&.fire.ja {
|
||||
background-image: url('/labels/element/fire_ja.png')
|
||||
background-image: url("/labels/element/fire_ja.png");
|
||||
}
|
||||
|
||||
&.water.en {
|
||||
background-image: url('/labels/element/water_en.png')
|
||||
background-image: url("/labels/element/water_en.png");
|
||||
}
|
||||
|
||||
&.water.ja {
|
||||
background-image: url('/labels/element/water_ja.png')
|
||||
background-image: url("/labels/element/water_ja.png");
|
||||
}
|
||||
|
||||
&.earth.en {
|
||||
background-image: url('/labels/element/earth_en.png')
|
||||
background-image: url("/labels/element/earth_en.png");
|
||||
}
|
||||
|
||||
&.earth.ja {
|
||||
background-image: url('/labels/element/earth_ja.png')
|
||||
background-image: url("/labels/element/earth_ja.png");
|
||||
}
|
||||
|
||||
&.wind.en {
|
||||
background-image: url('/labels/element/wind_en.png')
|
||||
background-image: url("/labels/element/wind_en.png");
|
||||
}
|
||||
|
||||
&.wind.ja {
|
||||
background-image: url('/labels/element/wind_ja.png')
|
||||
background-image: url("/labels/element/wind_ja.png");
|
||||
}
|
||||
|
||||
&.dark.en {
|
||||
background-image: url('/labels/element/dark_en.png')
|
||||
background-image: url("/labels/element/dark_en.png");
|
||||
}
|
||||
|
||||
&.dark.ja {
|
||||
background-image: url('/labels/element/dark_ja.png')
|
||||
background-image: url("/labels/element/dark_ja.png");
|
||||
}
|
||||
|
||||
&.light.en {
|
||||
background-image: url('/labels/element/light_en.png')
|
||||
background-image: url("/labels/element/light_en.png");
|
||||
}
|
||||
|
||||
&.light.ja {
|
||||
background-image: url('/labels/element/light_ja.png')
|
||||
background-image: url("/labels/element/light_ja.png");
|
||||
}
|
||||
|
||||
&.null.en {
|
||||
background-image: url('/labels/element/any_en.png')
|
||||
background-image: url("/labels/element/any_en.png");
|
||||
}
|
||||
|
||||
&.null.ja {
|
||||
background-image: url('/labels/element/any_ja.png')
|
||||
background-image: url("/labels/element/any_ja.png");
|
||||
}
|
||||
|
||||
/* Proficiencies */
|
||||
|
||||
&.sword.en {
|
||||
background-image: url('/labels/proficiency/sabre_en.png')
|
||||
background-image: url("/labels/proficiency/sabre_en.png");
|
||||
}
|
||||
|
||||
&.sword.ja {
|
||||
background-image: url('/labels/proficiency/sabre_ja.png')
|
||||
background-image: url("/labels/proficiency/sabre_ja.png");
|
||||
}
|
||||
|
||||
&.dagger.en {
|
||||
background-image: url('/labels/proficiency/dagger_en.png')
|
||||
background-image: url("/labels/proficiency/dagger_en.png");
|
||||
}
|
||||
|
||||
&.dagger.ja {
|
||||
background-image: url('/labels/proficiency/dagger_ja.png')
|
||||
background-image: url("/labels/proficiency/dagger_ja.png");
|
||||
}
|
||||
|
||||
&.axe.en {
|
||||
background-image: url('/labels/proficiency/axe_en.png')
|
||||
background-image: url("/labels/proficiency/axe_en.png");
|
||||
}
|
||||
|
||||
&.axe.ja {
|
||||
background-image: url('/labels/proficiency/axe_ja.png')
|
||||
background-image: url("/labels/proficiency/axe_ja.png");
|
||||
}
|
||||
|
||||
&.spear.en {
|
||||
background-image: url('/labels/proficiency/spear_en.png')
|
||||
background-image: url("/labels/proficiency/spear_en.png");
|
||||
}
|
||||
|
||||
&.spear.ja {
|
||||
background-image: url('/labels/proficiency/spear_ja.png')
|
||||
background-image: url("/labels/proficiency/spear_ja.png");
|
||||
}
|
||||
|
||||
&.staff.en {
|
||||
background-image: url('/labels/proficiency/staff_en.png')
|
||||
background-image: url("/labels/proficiency/staff_en.png");
|
||||
}
|
||||
|
||||
&.staff.ja {
|
||||
background-image: url('/labels/proficiency/staff_ja.png')
|
||||
background-image: url("/labels/proficiency/staff_ja.png");
|
||||
}
|
||||
|
||||
&.fist.en {
|
||||
background-image: url('/labels/proficiency/melee_en.png')
|
||||
background-image: url("/labels/proficiency/melee_en.png");
|
||||
}
|
||||
|
||||
&.fist.ja {
|
||||
background-image: url('/labels/proficiency/melee_ja.png')
|
||||
background-image: url("/labels/proficiency/melee_ja.png");
|
||||
}
|
||||
|
||||
&.harp.en {
|
||||
background-image: url('/labels/proficiency/harp_en.png')
|
||||
background-image: url("/labels/proficiency/harp_en.png");
|
||||
}
|
||||
|
||||
&.harp.ja {
|
||||
background-image: url('/labels/proficiency/harp_ja.png')
|
||||
background-image: url("/labels/proficiency/harp_ja.png");
|
||||
}
|
||||
|
||||
&.gun.en {
|
||||
background-image: url('/labels/proficiency/gun_en.png')
|
||||
background-image: url("/labels/proficiency/gun_en.png");
|
||||
}
|
||||
|
||||
&.gun.ja {
|
||||
background-image: url('/labels/proficiency/gun_ja.png')
|
||||
background-image: url("/labels/proficiency/gun_ja.png");
|
||||
}
|
||||
|
||||
&.bow.en {
|
||||
background-image: url('/labels/proficiency/bow_en.png')
|
||||
background-image: url("/labels/proficiency/bow_en.png");
|
||||
}
|
||||
|
||||
&.bow.ja {
|
||||
background-image: url('/labels/proficiency/bow_ja.png')
|
||||
background-image: url("/labels/proficiency/bow_ja.png");
|
||||
}
|
||||
|
||||
&.katana.en {
|
||||
background-image: url('/labels/proficiency/katana_en.png')
|
||||
background-image: url("/labels/proficiency/katana_en.png");
|
||||
}
|
||||
|
||||
&.katana.ja {
|
||||
background-image: url('/labels/proficiency/katana_ja.png')
|
||||
background-image: url("/labels/proficiency/katana_ja.png");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import React from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import './index.scss'
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
labelType: string
|
||||
labelType: string;
|
||||
}
|
||||
|
||||
const WeaponLabelIcon = (props: Props) => {
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<i className={`WeaponLabelIcon ${props.labelType} ${router.locale}`} />
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default WeaponLabelIcon
|
||||
export default WeaponLabelIcon;
|
||||
|
|
|
|||
|
|
@ -1,69 +1,71 @@
|
|||
import React, { useState } from "react"
|
||||
import { getCookie } from "cookies-next"
|
||||
import { useRouter } from "next/router"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import { AxiosResponse } from "axios"
|
||||
import React, { useState } from "react";
|
||||
import { getCookie } from "cookies-next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
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 ElementToggle from "~components/ElementToggle"
|
||||
import WeaponKeyDropdown from "~components/WeaponKeyDropdown"
|
||||
import Button from "~components/Button"
|
||||
import AXSelect from "~components/AxSelect";
|
||||
import ElementToggle from "~components/ElementToggle";
|
||||
import WeaponKeyDropdown from "~components/WeaponKeyDropdown";
|
||||
import Button from "~components/Button";
|
||||
|
||||
import api from "~utils/api"
|
||||
import { appState } from "~utils/appState"
|
||||
import api from "~utils/api";
|
||||
import { appState } from "~utils/appState";
|
||||
|
||||
import CrossIcon from "~public/icons/Cross.svg"
|
||||
import "./index.scss"
|
||||
import CrossIcon from "~public/icons/Cross.svg";
|
||||
import "./index.scss";
|
||||
|
||||
interface GridWeaponObject {
|
||||
weapon: {
|
||||
element?: number
|
||||
weapon_key1_id?: string
|
||||
weapon_key2_id?: string
|
||||
weapon_key3_id?: string
|
||||
ax_modifier1?: number
|
||||
ax_modifier2?: number
|
||||
ax_strength1?: number
|
||||
ax_strength2?: number
|
||||
}
|
||||
element?: number;
|
||||
weapon_key1_id?: string;
|
||||
weapon_key2_id?: string;
|
||||
weapon_key3_id?: string;
|
||||
ax_modifier1?: number;
|
||||
ax_modifier2?: number;
|
||||
ax_strength1?: number;
|
||||
ax_strength2?: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface Props {
|
||||
gridWeapon: GridWeapon
|
||||
children: React.ReactNode
|
||||
gridWeapon: GridWeapon;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const WeaponModal = (props: Props) => {
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
const locale =
|
||||
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
||||
const { t } = useTranslation("common")
|
||||
router.locale && ["en", "ja"].includes(router.locale)
|
||||
? router.locale
|
||||
: "en";
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
// Cookies
|
||||
const cookie = getCookie("account")
|
||||
const cookie = getCookie("account");
|
||||
const accountData: AccountCookie = cookie
|
||||
? JSON.parse(cookie as string)
|
||||
: null
|
||||
: null;
|
||||
const headers = accountData
|
||||
? { Authorization: `Bearer ${accountData.token}` }
|
||||
: {}
|
||||
: {};
|
||||
|
||||
// Refs
|
||||
const weaponKey1Select = React.createRef<HTMLSelectElement>()
|
||||
const weaponKey2Select = React.createRef<HTMLSelectElement>()
|
||||
const weaponKey3Select = React.createRef<HTMLSelectElement>()
|
||||
const weaponKey1Select = React.createRef<HTMLSelectElement>();
|
||||
const weaponKey2Select = React.createRef<HTMLSelectElement>();
|
||||
const weaponKey3Select = React.createRef<HTMLSelectElement>();
|
||||
|
||||
// State
|
||||
const [open, setOpen] = useState(false)
|
||||
const [formValid, setFormValid] = useState(false)
|
||||
const [open, setOpen] = useState(false);
|
||||
const [formValid, setFormValid] = useState(false);
|
||||
|
||||
const [element, setElement] = useState(-1)
|
||||
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
|
||||
const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1)
|
||||
const [primaryAxValue, setPrimaryAxValue] = useState(0.0)
|
||||
const [secondaryAxValue, setSecondaryAxValue] = useState(0.0)
|
||||
const [element, setElement] = useState(-1);
|
||||
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1);
|
||||
const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1);
|
||||
const [primaryAxValue, setPrimaryAxValue] = useState(0.0);
|
||||
const [secondaryAxValue, setSecondaryAxValue] = useState(0.0);
|
||||
|
||||
function receiveAxValues(
|
||||
primaryAxModifier: number,
|
||||
|
|
@ -71,64 +73,64 @@ const WeaponModal = (props: Props) => {
|
|||
secondaryAxModifier: number,
|
||||
secondaryAxValue: number
|
||||
) {
|
||||
setPrimaryAxModifier(primaryAxModifier)
|
||||
setSecondaryAxModifier(secondaryAxModifier)
|
||||
setPrimaryAxModifier(primaryAxModifier);
|
||||
setSecondaryAxModifier(secondaryAxModifier);
|
||||
|
||||
setPrimaryAxValue(primaryAxValue)
|
||||
setSecondaryAxValue(secondaryAxValue)
|
||||
setPrimaryAxValue(primaryAxValue);
|
||||
setSecondaryAxValue(secondaryAxValue);
|
||||
}
|
||||
|
||||
function receiveAxValidity(isValid: boolean) {
|
||||
setFormValid(isValid)
|
||||
setFormValid(isValid);
|
||||
}
|
||||
|
||||
function receiveElementValue(element: string) {
|
||||
setElement(parseInt(element))
|
||||
setElement(parseInt(element));
|
||||
}
|
||||
|
||||
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))
|
||||
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))
|
||||
object.weapon.weapon_key2_id = weaponKey2Select.current?.value
|
||||
object.weapon.weapon_key2_id = weaponKey2Select.current?.value;
|
||||
|
||||
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) {
|
||||
object.weapon.ax_modifier1 = primaryAxModifier
|
||||
object.weapon.ax_modifier2 = secondaryAxModifier
|
||||
object.weapon.ax_strength1 = primaryAxValue
|
||||
object.weapon.ax_strength2 = secondaryAxValue
|
||||
object.weapon.ax_modifier1 = primaryAxModifier;
|
||||
object.weapon.ax_modifier2 = secondaryAxModifier;
|
||||
object.weapon.ax_strength1 = primaryAxValue;
|
||||
object.weapon.ax_strength2 = secondaryAxValue;
|
||||
}
|
||||
|
||||
return object
|
||||
return object;
|
||||
}
|
||||
|
||||
async function updateWeapon() {
|
||||
const updateObject = prepareObject()
|
||||
const updateObject = prepareObject();
|
||||
return await api.endpoints.grid_weapons
|
||||
.update(props.gridWeapon.id, updateObject, headers)
|
||||
.then((response) => processResult(response))
|
||||
.catch((error) => processError(error))
|
||||
.catch((error) => processError(error));
|
||||
}
|
||||
|
||||
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
|
||||
else appState.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon
|
||||
if (gridWeapon.mainhand) appState.grid.weapons.mainWeapon = gridWeapon;
|
||||
else appState.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon;
|
||||
|
||||
setOpen(false)
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
function processError(error: any) {
|
||||
console.error(error)
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
const elementSelect = () => {
|
||||
|
|
@ -140,8 +142,8 @@ const WeaponModal = (props: Props) => {
|
|||
sendValue={receiveElementValue}
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const keySelect = () => {
|
||||
return (
|
||||
|
|
@ -192,8 +194,8 @@ const WeaponModal = (props: Props) => {
|
|||
""
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const axSelect = () => {
|
||||
return (
|
||||
|
|
@ -206,12 +208,12 @@ const WeaponModal = (props: Props) => {
|
|||
sendValues={receiveAxValues}
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function openChange(open: boolean) {
|
||||
setFormValid(false)
|
||||
setOpen(open)
|
||||
setFormValid(false);
|
||||
setOpen(open);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -255,7 +257,7 @@ const WeaponModal = (props: Props) => {
|
|||
<Dialog.Overlay className="Overlay" />
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default WeaponModal
|
||||
export default WeaponModal;
|
||||
|
|
|
|||
|
|
@ -37,11 +37,11 @@
|
|||
|
||||
.stars {
|
||||
display: inline-block;
|
||||
color: #FFA15E;
|
||||
color: #ffa15e;
|
||||
font-size: $font-xlarge;
|
||||
|
||||
& > span {
|
||||
color: #65DAFF;
|
||||
color: #65daff;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +1,72 @@
|
|||
import React from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import UncapIndicator from '~components/UncapIndicator'
|
||||
import WeaponLabelIcon from '~components/WeaponLabelIcon'
|
||||
import UncapIndicator from "~components/UncapIndicator";
|
||||
import WeaponLabelIcon from "~components/WeaponLabelIcon";
|
||||
|
||||
import './index.scss'
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
data: Weapon
|
||||
onClick: () => void
|
||||
data: Weapon;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
|
||||
const Proficiency = ['none', 'sword', 'dagger', 'axe', 'spear', 'bow', 'staff', 'fist', 'harp', 'gun', 'katana']
|
||||
const Series = ['seraphic', 'grand', 'opus', 'draconic', 'revenant', 'primal', 'beast','regalia', 'omega', 'olden_primal', 'hollowsky', 'xeno', 'astral', 'rose', 'ultima', 'bahamut', 'epic', 'ennead', 'cosmos', 'ancestral', 'superlative', 'vintage', 'class_champion', 'sephira', 'new_world_foundation']
|
||||
const Element = ["null", "wind", "fire", "water", "earth", "dark", "light"];
|
||||
const Proficiency = [
|
||||
"none",
|
||||
"sword",
|
||||
"dagger",
|
||||
"axe",
|
||||
"spear",
|
||||
"bow",
|
||||
"staff",
|
||||
"fist",
|
||||
"harp",
|
||||
"gun",
|
||||
"katana",
|
||||
];
|
||||
const Series = [
|
||||
"seraphic",
|
||||
"grand",
|
||||
"opus",
|
||||
"draconic",
|
||||
"revenant",
|
||||
"primal",
|
||||
"beast",
|
||||
"regalia",
|
||||
"omega",
|
||||
"olden_primal",
|
||||
"hollowsky",
|
||||
"xeno",
|
||||
"astral",
|
||||
"rose",
|
||||
"ultima",
|
||||
"bahamut",
|
||||
"epic",
|
||||
"ennead",
|
||||
"cosmos",
|
||||
"ancestral",
|
||||
"superlative",
|
||||
"vintage",
|
||||
"class_champion",
|
||||
"sephira",
|
||||
"new_world_foundation",
|
||||
];
|
||||
|
||||
const WeaponResult = (props: Props) => {
|
||||
const router = useRouter()
|
||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
||||
const weapon = props.data
|
||||
const router = useRouter();
|
||||
const locale =
|
||||
router.locale && ["en", "ja"].includes(router.locale)
|
||||
? router.locale
|
||||
: "en";
|
||||
const weapon = props.data;
|
||||
|
||||
return (
|
||||
<li className="WeaponResult" onClick={props.onClick}>
|
||||
<img alt={weapon.name[locale]} src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`} />
|
||||
<img
|
||||
alt={weapon.name[locale]}
|
||||
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`}
|
||||
/>
|
||||
<div className="Info">
|
||||
<h5>{weapon.name[locale]}</h5>
|
||||
<UncapIndicator
|
||||
|
|
@ -37,7 +81,7 @@ const WeaponResult = (props: Props) => {
|
|||
</div>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default WeaponResult
|
||||
export default WeaponResult;
|
||||
|
|
|
|||
|
|
@ -1,224 +1,314 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import React, { useEffect, useState } from "react";
|
||||
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 SearchFilterCheckboxItem from '~components/SearchFilterCheckboxItem'
|
||||
import SearchFilter from "~components/SearchFilter";
|
||||
import SearchFilterCheckboxItem from "~components/SearchFilterCheckboxItem";
|
||||
|
||||
import './index.scss'
|
||||
import { emptyElementState, emptyProficiencyState, emptyRarityState, emptyWeaponSeriesState } from '~utils/emptyStates'
|
||||
import { elements, proficiencies, rarities, weaponSeries } from '~utils/stateValues'
|
||||
import "./index.scss";
|
||||
import {
|
||||
emptyElementState,
|
||||
emptyProficiencyState,
|
||||
emptyRarityState,
|
||||
emptyWeaponSeriesState,
|
||||
} from "~utils/emptyStates";
|
||||
import {
|
||||
elements,
|
||||
proficiencies,
|
||||
rarities,
|
||||
weaponSeries,
|
||||
} from "~utils/stateValues";
|
||||
|
||||
interface Props {
|
||||
sendFilters: (filters: { [key: string]: number[] }) => void
|
||||
sendFilters: (filters: { [key: string]: number[] }) => void;
|
||||
}
|
||||
|
||||
const WeaponSearchFilterBar = (props: Props) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
const [rarityMenu, setRarityMenu] = useState(false)
|
||||
const [elementMenu, setElementMenu] = useState(false)
|
||||
const [proficiencyMenu, setProficiencyMenu] = useState(false)
|
||||
const [seriesMenu, setSeriesMenu] = useState(false)
|
||||
const [rarityMenu, setRarityMenu] = useState(false);
|
||||
const [elementMenu, setElementMenu] = useState(false);
|
||||
const [proficiencyMenu, setProficiencyMenu] = useState(false);
|
||||
const [seriesMenu, setSeriesMenu] = useState(false);
|
||||
|
||||
const [rarityState, setRarityState] = useState<RarityState>(emptyRarityState)
|
||||
const [elementState, setElementState] = useState<ElementState>(emptyElementState)
|
||||
const [proficiencyState, setProficiencyState] = useState<ProficiencyState>(emptyProficiencyState)
|
||||
const [seriesState, setSeriesState] = useState<WeaponSeriesState>(emptyWeaponSeriesState)
|
||||
const [rarityState, setRarityState] = useState<RarityState>(emptyRarityState);
|
||||
const [elementState, setElementState] =
|
||||
useState<ElementState>(emptyElementState);
|
||||
const [proficiencyState, setProficiencyState] = useState<ProficiencyState>(
|
||||
emptyProficiencyState
|
||||
);
|
||||
const [seriesState, setSeriesState] = useState<WeaponSeriesState>(
|
||||
emptyWeaponSeriesState
|
||||
);
|
||||
|
||||
function rarityMenuOpened(open: boolean) {
|
||||
if (open) {
|
||||
setRarityMenu(true)
|
||||
setElementMenu(false)
|
||||
setProficiencyMenu(false)
|
||||
setSeriesMenu(false)
|
||||
} else setRarityMenu(false)
|
||||
setRarityMenu(true);
|
||||
setElementMenu(false);
|
||||
setProficiencyMenu(false);
|
||||
setSeriesMenu(false);
|
||||
} else setRarityMenu(false);
|
||||
}
|
||||
|
||||
function elementMenuOpened(open: boolean) {
|
||||
if (open) {
|
||||
setRarityMenu(false)
|
||||
setElementMenu(true)
|
||||
setProficiencyMenu(false)
|
||||
setSeriesMenu(false)
|
||||
} else setElementMenu(false)
|
||||
setRarityMenu(false);
|
||||
setElementMenu(true);
|
||||
setProficiencyMenu(false);
|
||||
setSeriesMenu(false);
|
||||
} else setElementMenu(false);
|
||||
}
|
||||
|
||||
function proficiencyMenuOpened(open: boolean) {
|
||||
if (open) {
|
||||
setRarityMenu(false)
|
||||
setElementMenu(false)
|
||||
setProficiencyMenu(true)
|
||||
setSeriesMenu(false)
|
||||
} else setProficiencyMenu(false)
|
||||
setRarityMenu(false);
|
||||
setElementMenu(false);
|
||||
setProficiencyMenu(true);
|
||||
setSeriesMenu(false);
|
||||
} else setProficiencyMenu(false);
|
||||
}
|
||||
|
||||
function seriesMenuOpened(open: boolean) {
|
||||
if (open) {
|
||||
setRarityMenu(false)
|
||||
setElementMenu(false)
|
||||
setProficiencyMenu(false)
|
||||
setSeriesMenu(true)
|
||||
} else setSeriesMenu(false)
|
||||
setRarityMenu(false);
|
||||
setElementMenu(false);
|
||||
setProficiencyMenu(false);
|
||||
setSeriesMenu(true);
|
||||
} else setSeriesMenu(false);
|
||||
}
|
||||
|
||||
function handleRarityChange(checked: boolean, key: string) {
|
||||
let newRarityState = cloneDeep(rarityState)
|
||||
newRarityState[key].checked = checked
|
||||
setRarityState(newRarityState)
|
||||
let newRarityState = cloneDeep(rarityState);
|
||||
newRarityState[key].checked = checked;
|
||||
setRarityState(newRarityState);
|
||||
}
|
||||
|
||||
function handleElementChange(checked: boolean, key: string) {
|
||||
let newElementState = cloneDeep(elementState)
|
||||
newElementState[key].checked = checked
|
||||
setElementState(newElementState)
|
||||
let newElementState = cloneDeep(elementState);
|
||||
newElementState[key].checked = checked;
|
||||
setElementState(newElementState);
|
||||
}
|
||||
|
||||
function handleProficiencyChange(checked: boolean, key: string) {
|
||||
let newProficiencyState = cloneDeep(proficiencyState)
|
||||
newProficiencyState[key].checked = checked
|
||||
setProficiencyState(newProficiencyState)
|
||||
let newProficiencyState = cloneDeep(proficiencyState);
|
||||
newProficiencyState[key].checked = checked;
|
||||
setProficiencyState(newProficiencyState);
|
||||
}
|
||||
|
||||
function handleSeriesChange(checked: boolean, key: string) {
|
||||
let newSeriesState = cloneDeep(seriesState)
|
||||
newSeriesState[key].checked = checked
|
||||
setSeriesState(newSeriesState)
|
||||
let newSeriesState = cloneDeep(seriesState);
|
||||
newSeriesState[key].checked = checked;
|
||||
setSeriesState(newSeriesState);
|
||||
}
|
||||
|
||||
function sendFilters() {
|
||||
const checkedRarityFilters = Object.values(rarityState).filter(x => x.checked).map((x, i) => x.id)
|
||||
const checkedElementFilters = Object.values(elementState).filter(x => x.checked).map((x, i) => x.id)
|
||||
const checkedProficiencyFilters = Object.values(proficiencyState).filter(x => x.checked).map((x, i) => x.id)
|
||||
const checkedSeriesFilters = Object.values(seriesState).filter(x => x.checked).map((x, i) => x.id)
|
||||
const checkedRarityFilters = Object.values(rarityState)
|
||||
.filter((x) => x.checked)
|
||||
.map((x, i) => x.id);
|
||||
const checkedElementFilters = Object.values(elementState)
|
||||
.filter((x) => x.checked)
|
||||
.map((x, i) => x.id);
|
||||
const checkedProficiencyFilters = Object.values(proficiencyState)
|
||||
.filter((x) => x.checked)
|
||||
.map((x, i) => x.id);
|
||||
const checkedSeriesFilters = Object.values(seriesState)
|
||||
.filter((x) => x.checked)
|
||||
.map((x, i) => x.id);
|
||||
|
||||
const filters = {
|
||||
rarity: checkedRarityFilters,
|
||||
element: checkedElementFilters,
|
||||
proficiency1: checkedProficiencyFilters,
|
||||
series: checkedSeriesFilters
|
||||
}
|
||||
series: checkedSeriesFilters,
|
||||
};
|
||||
|
||||
props.sendFilters(filters)
|
||||
props.sendFilters(filters);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
sendFilters()
|
||||
}, [rarityState, elementState, proficiencyState, seriesState])
|
||||
sendFilters();
|
||||
}, [rarityState, elementState, proficiencyState, seriesState]);
|
||||
|
||||
return (
|
||||
<div className="SearchFilterBar">
|
||||
<SearchFilter label={t('filters.labels.rarity')} numSelected={Object.values(rarityState).map(x => x.checked).filter(Boolean).length} open={rarityMenu} onOpenChange={rarityMenuOpened}>
|
||||
<DropdownMenu.Label className="Label">{t('filters.labels.rarity')}</DropdownMenu.Label>
|
||||
{ Array.from(Array(rarities.length)).map((x, i) => {
|
||||
<SearchFilter
|
||||
label={t("filters.labels.rarity")}
|
||||
numSelected={
|
||||
Object.values(rarityState)
|
||||
.map((x) => x.checked)
|
||||
.filter(Boolean).length
|
||||
}
|
||||
open={rarityMenu}
|
||||
onOpenChange={rarityMenuOpened}
|
||||
>
|
||||
<DropdownMenu.Label className="Label">
|
||||
{t("filters.labels.rarity")}
|
||||
</DropdownMenu.Label>
|
||||
{Array.from(Array(rarities.length)).map((x, i) => {
|
||||
return (
|
||||
<SearchFilterCheckboxItem
|
||||
key={rarities[i]}
|
||||
onCheckedChange={handleRarityChange}
|
||||
checked={rarityState[rarities[i]].checked}
|
||||
valueKey={rarities[i]}>
|
||||
valueKey={rarities[i]}
|
||||
>
|
||||
{t(`rarities.${rarities[i]}`)}
|
||||
</SearchFilterCheckboxItem>
|
||||
)}
|
||||
) }
|
||||
);
|
||||
})}
|
||||
</SearchFilter>
|
||||
|
||||
<SearchFilter label={t('filters.labels.element')} numSelected={Object.values(elementState).map(x => x.checked).filter(Boolean).length} open={elementMenu} onOpenChange={elementMenuOpened}>
|
||||
<DropdownMenu.Label className="Label">{t('filters.labels.element')}</DropdownMenu.Label>
|
||||
{ Array.from(Array(elements.length)).map((x, i) => {
|
||||
<SearchFilter
|
||||
label={t("filters.labels.element")}
|
||||
numSelected={
|
||||
Object.values(elementState)
|
||||
.map((x) => x.checked)
|
||||
.filter(Boolean).length
|
||||
}
|
||||
open={elementMenu}
|
||||
onOpenChange={elementMenuOpened}
|
||||
>
|
||||
<DropdownMenu.Label className="Label">
|
||||
{t("filters.labels.element")}
|
||||
</DropdownMenu.Label>
|
||||
{Array.from(Array(elements.length)).map((x, i) => {
|
||||
return (
|
||||
<SearchFilterCheckboxItem
|
||||
key={elements[i]}
|
||||
onCheckedChange={handleElementChange}
|
||||
checked={elementState[elements[i]].checked}
|
||||
valueKey={elements[i]}>
|
||||
valueKey={elements[i]}
|
||||
>
|
||||
{t(`elements.${elements[i]}`)}
|
||||
</SearchFilterCheckboxItem>
|
||||
)}
|
||||
) }
|
||||
);
|
||||
})}
|
||||
</SearchFilter>
|
||||
|
||||
<SearchFilter label={t('filters.labels.proficiency')} numSelected={Object.values(proficiencyState).map(x => x.checked).filter(Boolean).length} open={proficiencyMenu} onOpenChange={proficiencyMenuOpened}>
|
||||
<DropdownMenu.Label className="Label">{t('filters.labels.proficiency')}</DropdownMenu.Label>
|
||||
<SearchFilter
|
||||
label={t("filters.labels.proficiency")}
|
||||
numSelected={
|
||||
Object.values(proficiencyState)
|
||||
.map((x) => x.checked)
|
||||
.filter(Boolean).length
|
||||
}
|
||||
open={proficiencyMenu}
|
||||
onOpenChange={proficiencyMenuOpened}
|
||||
>
|
||||
<DropdownMenu.Label className="Label">
|
||||
{t("filters.labels.proficiency")}
|
||||
</DropdownMenu.Label>
|
||||
<section>
|
||||
<DropdownMenu.Group className="Group">
|
||||
{ Array.from(Array(proficiencies.length / 2)).map((x, i) => {
|
||||
{Array.from(Array(proficiencies.length / 2)).map((x, i) => {
|
||||
return (
|
||||
<SearchFilterCheckboxItem
|
||||
key={proficiencies[i]}
|
||||
onCheckedChange={handleProficiencyChange}
|
||||
checked={proficiencyState[proficiencies[i]].checked}
|
||||
valueKey={proficiencies[i]}>
|
||||
valueKey={proficiencies[i]}
|
||||
>
|
||||
{t(`proficiencies.${proficiencies[i]}`)}
|
||||
</SearchFilterCheckboxItem>
|
||||
)}
|
||||
) }
|
||||
);
|
||||
})}
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Group className="Group">
|
||||
{ Array.from(Array(proficiencies.length / 2)).map((x, i) => {
|
||||
{Array.from(Array(proficiencies.length / 2)).map((x, i) => {
|
||||
return (
|
||||
<SearchFilterCheckboxItem
|
||||
key={proficiencies[i + (proficiencies.length / 2)]}
|
||||
key={proficiencies[i + proficiencies.length / 2]}
|
||||
onCheckedChange={handleProficiencyChange}
|
||||
checked={proficiencyState[proficiencies[i + (proficiencies.length / 2)]].checked}
|
||||
valueKey={proficiencies[i + (proficiencies.length / 2)]}>
|
||||
{t(`proficiencies.${proficiencies[i + (proficiencies.length / 2)]}`)}
|
||||
</SearchFilterCheckboxItem>
|
||||
checked={
|
||||
proficiencyState[
|
||||
proficiencies[i + proficiencies.length / 2]
|
||||
].checked
|
||||
}
|
||||
valueKey={proficiencies[i + proficiencies.length / 2]}
|
||||
>
|
||||
{t(
|
||||
`proficiencies.${
|
||||
proficiencies[i + proficiencies.length / 2]
|
||||
}`
|
||||
)}
|
||||
) }
|
||||
</SearchFilterCheckboxItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenu.Group>
|
||||
</section>
|
||||
</SearchFilter>
|
||||
|
||||
<SearchFilter label={t('filters.labels.series')} numSelected={Object.values(seriesState).map(x => x.checked).filter(Boolean).length} open={seriesMenu} onOpenChange={seriesMenuOpened}>
|
||||
<DropdownMenu.Label className="Label">{t('filters.labels.series')}</DropdownMenu.Label>
|
||||
<SearchFilter
|
||||
label={t("filters.labels.series")}
|
||||
numSelected={
|
||||
Object.values(seriesState)
|
||||
.map((x) => x.checked)
|
||||
.filter(Boolean).length
|
||||
}
|
||||
open={seriesMenu}
|
||||
onOpenChange={seriesMenuOpened}
|
||||
>
|
||||
<DropdownMenu.Label className="Label">
|
||||
{t("filters.labels.series")}
|
||||
</DropdownMenu.Label>
|
||||
<section>
|
||||
<DropdownMenu.Group className="Group">
|
||||
{ Array.from(Array(weaponSeries.length / 3)).map((x, i) => {
|
||||
{Array.from(Array(weaponSeries.length / 3)).map((x, i) => {
|
||||
return (
|
||||
<SearchFilterCheckboxItem
|
||||
key={weaponSeries[i]}
|
||||
onCheckedChange={handleSeriesChange}
|
||||
checked={seriesState[weaponSeries[i]].checked}
|
||||
valueKey={weaponSeries[i]}>
|
||||
valueKey={weaponSeries[i]}
|
||||
>
|
||||
{t(`series.${weaponSeries[i]}`)}
|
||||
</SearchFilterCheckboxItem>
|
||||
)}
|
||||
) }
|
||||
);
|
||||
})}
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Group className="Group">
|
||||
{ Array.from(Array(weaponSeries.length / 3)).map((x, i) => {
|
||||
{Array.from(Array(weaponSeries.length / 3)).map((x, i) => {
|
||||
return (
|
||||
<SearchFilterCheckboxItem
|
||||
key={weaponSeries[i + (weaponSeries.length / 3)]}
|
||||
key={weaponSeries[i + weaponSeries.length / 3]}
|
||||
onCheckedChange={handleSeriesChange}
|
||||
checked={seriesState[weaponSeries[i + (weaponSeries.length / 3)]].checked}
|
||||
valueKey={weaponSeries[i + (weaponSeries.length / 3)]}>
|
||||
{t(`series.${weaponSeries[i + (weaponSeries.length / 3)]}`)}
|
||||
checked={
|
||||
seriesState[weaponSeries[i + weaponSeries.length / 3]]
|
||||
.checked
|
||||
}
|
||||
valueKey={weaponSeries[i + weaponSeries.length / 3]}
|
||||
>
|
||||
{t(`series.${weaponSeries[i + weaponSeries.length / 3]}`)}
|
||||
</SearchFilterCheckboxItem>
|
||||
)}
|
||||
) }
|
||||
);
|
||||
})}
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Group className="Group">
|
||||
{ Array.from(Array(weaponSeries.length / 3)).map((x, i) => {
|
||||
{Array.from(Array(weaponSeries.length / 3)).map((x, i) => {
|
||||
return (
|
||||
<SearchFilterCheckboxItem
|
||||
key={weaponSeries[i + (2 * (weaponSeries.length / 3))]}
|
||||
key={weaponSeries[i + 2 * (weaponSeries.length / 3)]}
|
||||
onCheckedChange={handleSeriesChange}
|
||||
checked={seriesState[weaponSeries[i + (2 * (weaponSeries.length / 3))]].checked}
|
||||
valueKey={weaponSeries[i + (2 * (weaponSeries.length / 3))]}>
|
||||
{t(`series.${weaponSeries[i + (2 * (weaponSeries.length / 3))]}`)}
|
||||
</SearchFilterCheckboxItem>
|
||||
checked={
|
||||
seriesState[weaponSeries[i + 2 * (weaponSeries.length / 3)]]
|
||||
.checked
|
||||
}
|
||||
valueKey={weaponSeries[i + 2 * (weaponSeries.length / 3)]}
|
||||
>
|
||||
{t(
|
||||
`series.${weaponSeries[i + 2 * (weaponSeries.length / 3)]}`
|
||||
)}
|
||||
) }
|
||||
</SearchFilterCheckboxItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenu.Group>
|
||||
</section>
|
||||
</SearchFilter>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default WeaponSearchFilterBar
|
||||
export default WeaponSearchFilterBar;
|
||||
|
|
|
|||
|
|
@ -1,37 +1,39 @@
|
|||
import React, { useEffect, useState } from "react"
|
||||
import { useRouter } from "next/router"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import classnames from "classnames"
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import classnames from "classnames";
|
||||
|
||||
import SearchModal from "~components/SearchModal"
|
||||
import WeaponModal from "~components/WeaponModal"
|
||||
import WeaponHovercard from "~components/WeaponHovercard"
|
||||
import UncapIndicator from "~components/UncapIndicator"
|
||||
import Button from "~components/Button"
|
||||
import SearchModal from "~components/SearchModal";
|
||||
import WeaponModal from "~components/WeaponModal";
|
||||
import WeaponHovercard from "~components/WeaponHovercard";
|
||||
import UncapIndicator from "~components/UncapIndicator";
|
||||
import Button from "~components/Button";
|
||||
|
||||
import { ButtonType } from "~utils/enums"
|
||||
import type { SearchableObject } from "~types"
|
||||
import { ButtonType } from "~utils/enums";
|
||||
import type { SearchableObject } from "~types";
|
||||
|
||||
import PlusIcon from "~public/icons/Add.svg"
|
||||
import "./index.scss"
|
||||
import PlusIcon from "~public/icons/Add.svg";
|
||||
import "./index.scss";
|
||||
|
||||
interface Props {
|
||||
gridWeapon: GridWeapon | undefined
|
||||
unitType: 0 | 1
|
||||
position: number
|
||||
editable: boolean
|
||||
updateObject: (object: SearchableObject, position: number) => void
|
||||
updateUncap: (id: string, position: number, uncap: number) => void
|
||||
gridWeapon: GridWeapon | undefined;
|
||||
unitType: 0 | 1;
|
||||
position: number;
|
||||
editable: boolean;
|
||||
updateObject: (object: SearchableObject, position: number) => void;
|
||||
updateUncap: (id: string, position: number, uncap: number) => void;
|
||||
}
|
||||
|
||||
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 =
|
||||
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
||||
router.locale && ["en", "ja"].includes(router.locale)
|
||||
? router.locale
|
||||
: "en";
|
||||
|
||||
const classes = classnames({
|
||||
WeaponUnit: true,
|
||||
|
|
@ -39,48 +41,48 @@ const WeaponUnit = (props: Props) => {
|
|||
grid: props.unitType == 1,
|
||||
editable: props.editable,
|
||||
filled: props.gridWeapon !== undefined,
|
||||
})
|
||||
});
|
||||
|
||||
const gridWeapon = props.gridWeapon
|
||||
const weapon = gridWeapon?.object
|
||||
const gridWeapon = props.gridWeapon;
|
||||
const weapon = gridWeapon?.object;
|
||||
|
||||
useEffect(() => {
|
||||
generateImageUrl()
|
||||
})
|
||||
generateImageUrl();
|
||||
});
|
||||
|
||||
function generateImageUrl() {
|
||||
let imgSrc = ""
|
||||
let imgSrc = "";
|
||||
if (props.gridWeapon) {
|
||||
const weapon = props.gridWeapon.object!
|
||||
const weapon = props.gridWeapon.object!;
|
||||
|
||||
if (props.unitType == 0) {
|
||||
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
|
||||
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 {
|
||||
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
|
||||
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) {
|
||||
if (props.gridWeapon)
|
||||
props.updateUncap(props.gridWeapon.id, props.position, uncap)
|
||||
props.updateUncap(props.gridWeapon.id, props.position, uncap);
|
||||
}
|
||||
|
||||
function canBeModified(gridWeapon: GridWeapon) {
|
||||
const weapon = gridWeapon.object
|
||||
const weapon = gridWeapon.object;
|
||||
|
||||
return (
|
||||
weapon.ax > 0 ||
|
||||
(weapon.series && [2, 3, 17, 22, 24].includes(weapon.series))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const image = (
|
||||
|
|
@ -94,7 +96,7 @@ const WeaponUnit = (props: Props) => {
|
|||
""
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
const editableImage = (
|
||||
<SearchModal
|
||||
|
|
@ -105,7 +107,7 @@ const WeaponUnit = (props: Props) => {
|
|||
>
|
||||
{image}
|
||||
</SearchModal>
|
||||
)
|
||||
);
|
||||
|
||||
const unitContent = (
|
||||
<div className={classes}>
|
||||
|
|
@ -133,13 +135,13 @@ const WeaponUnit = (props: Props) => {
|
|||
)}
|
||||
<h3 className="WeaponName">{weapon?.name[locale]}</h3>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
const withHovercard = (
|
||||
<WeaponHovercard gridWeapon={gridWeapon!}>{unitContent}</WeaponHovercard>
|
||||
)
|
||||
);
|
||||
|
||||
return gridWeapon && !props.editable ? withHovercard : unitContent
|
||||
}
|
||||
return gridWeapon && !props.editable ? withHovercard : unitContent;
|
||||
};
|
||||
|
||||
export default WeaponUnit
|
||||
export default WeaponUnit;
|
||||
|
|
|
|||
|
|
@ -1,61 +1,61 @@
|
|||
import React, { useCallback, useEffect, useState } from "react"
|
||||
import Head from "next/head"
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
|
||||
import { getCookie } from "cookies-next"
|
||||
import { queryTypes, useQueryState } from "next-usequerystate"
|
||||
import { useRouter } from "next/router"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import InfiniteScroll from "react-infinite-scroll-component"
|
||||
import { getCookie } from "cookies-next";
|
||||
import { queryTypes, useQueryState } from "next-usequerystate";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
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 useDidMountEffect from "~utils/useDidMountEffect"
|
||||
import { elements, allElement } from "~utils/Element"
|
||||
import api from "~utils/api";
|
||||
import useDidMountEffect from "~utils/useDidMountEffect";
|
||||
import { elements, allElement } from "~utils/Element";
|
||||
|
||||
import GridRep from "~components/GridRep"
|
||||
import GridRepCollection from "~components/GridRepCollection"
|
||||
import FilterBar from "~components/FilterBar"
|
||||
import GridRep from "~components/GridRep";
|
||||
import GridRepCollection from "~components/GridRepCollection";
|
||||
import FilterBar from "~components/FilterBar";
|
||||
|
||||
import type { NextApiRequest, NextApiResponse } from "next"
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
interface Props {
|
||||
user?: User
|
||||
teams?: { count: number; total_pages: number; results: Party[] }
|
||||
raids: Raid[]
|
||||
sortedRaids: Raid[][]
|
||||
user?: User;
|
||||
teams?: { count: number; total_pages: number; results: Party[] };
|
||||
raids: Raid[];
|
||||
sortedRaids: Raid[][];
|
||||
}
|
||||
|
||||
const ProfileRoute: React.FC<Props> = (props: Props) => {
|
||||
// Set up cookies
|
||||
const cookie = getCookie("account")
|
||||
const cookie = getCookie("account");
|
||||
const accountData: AccountCookie = cookie
|
||||
? JSON.parse(cookie as string)
|
||||
: null
|
||||
: null;
|
||||
const headers = accountData
|
||||
? { Authorization: `Bearer ${accountData.token}` }
|
||||
: {}
|
||||
: {};
|
||||
|
||||
// Set up router
|
||||
const router = useRouter()
|
||||
const { username } = router.query
|
||||
const router = useRouter();
|
||||
const { username } = router.query;
|
||||
|
||||
// Import translations
|
||||
const { t } = useTranslation("common")
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
// Set up app-specific states
|
||||
const [raidsLoading, setRaidsLoading] = useState(true)
|
||||
const [scrolled, setScrolled] = useState(false)
|
||||
const [raidsLoading, setRaidsLoading] = useState(true);
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
|
||||
// Set up page-specific states
|
||||
const [parties, setParties] = useState<Party[]>([])
|
||||
const [raids, setRaids] = useState<Raid[]>()
|
||||
const [raid, setRaid] = useState<Raid>()
|
||||
const [parties, setParties] = useState<Party[]>([]);
|
||||
const [raids, setRaids] = useState<Raid[]>();
|
||||
const [raid, setRaid] = useState<Raid>();
|
||||
|
||||
// Set up infinite scrolling-related states
|
||||
const [recordCount, setRecordCount] = useState(0)
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [totalPages, setTotalPages] = useState(1)
|
||||
const [recordCount, setRecordCount] = useState(0);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
|
||||
// Set up filter-specific query states
|
||||
// Recency is in seconds
|
||||
|
|
@ -63,57 +63,59 @@ const ProfileRoute: React.FC<Props> = (props: Props) => {
|
|||
defaultValue: -1,
|
||||
parse: (query: string) => parseElement(query),
|
||||
serialize: (value) => serializeElement(value),
|
||||
})
|
||||
const [raidSlug, setRaidSlug] = useQueryState("raid", { defaultValue: "all" })
|
||||
});
|
||||
const [raidSlug, setRaidSlug] = useQueryState("raid", {
|
||||
defaultValue: "all",
|
||||
});
|
||||
const [recency, setRecency] = useQueryState(
|
||||
"recency",
|
||||
queryTypes.integer.withDefault(-1)
|
||||
)
|
||||
);
|
||||
|
||||
// Define transformers for element
|
||||
function parseElement(query: string) {
|
||||
let element: TeamElement | undefined =
|
||||
query === "all"
|
||||
? allElement
|
||||
: elements.find((element) => element.name.en.toLowerCase() === query)
|
||||
return element ? element.id : -1
|
||||
: elements.find((element) => element.name.en.toLowerCase() === query);
|
||||
return element ? element.id : -1;
|
||||
}
|
||||
|
||||
function serializeElement(value: number | undefined) {
|
||||
let name = ""
|
||||
let name = "";
|
||||
|
||||
if (value != undefined) {
|
||||
if (value == -1) name = allElement.name.en.toLowerCase()
|
||||
else name = elements[value].name.en.toLowerCase()
|
||||
if (value == -1) name = allElement.name.en.toLowerCase();
|
||||
else name = elements[value].name.en.toLowerCase();
|
||||
}
|
||||
|
||||
return name
|
||||
return name;
|
||||
}
|
||||
|
||||
// Set the initial parties from props
|
||||
useEffect(() => {
|
||||
if (props.teams) {
|
||||
setTotalPages(props.teams.total_pages)
|
||||
setRecordCount(props.teams.count)
|
||||
replaceResults(props.teams.count, props.teams.results)
|
||||
setTotalPages(props.teams.total_pages);
|
||||
setRecordCount(props.teams.count);
|
||||
replaceResults(props.teams.count, props.teams.results);
|
||||
}
|
||||
setCurrentPage(1)
|
||||
}, [])
|
||||
setCurrentPage(1);
|
||||
}, []);
|
||||
|
||||
// Add scroll event listener for shadow on FilterBar on mount
|
||||
useEffect(() => {
|
||||
window.addEventListener("scroll", handleScroll)
|
||||
return () => window.removeEventListener("scroll", handleScroll)
|
||||
}, [])
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
// Handle errors
|
||||
const handleError = useCallback((error: any) => {
|
||||
if (error.response != null) {
|
||||
console.error(error)
|
||||
console.error(error);
|
||||
} else {
|
||||
console.error("There was an error.")
|
||||
console.error("There was an error.");
|
||||
}
|
||||
}, [])
|
||||
}, []);
|
||||
|
||||
const fetchProfile = useCallback(
|
||||
({ replace }: { replace: boolean }) => {
|
||||
|
|
@ -124,7 +126,7 @@ const ProfileRoute: React.FC<Props> = (props: Props) => {
|
|||
recency: recency != -1 ? recency : undefined,
|
||||
page: currentPage,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if (username && !Array.isArray(username)) {
|
||||
api.endpoints.users
|
||||
|
|
@ -133,62 +135,62 @@ const ProfileRoute: React.FC<Props> = (props: Props) => {
|
|||
params: { ...filters, ...{ headers: headers } },
|
||||
})
|
||||
.then((response) => {
|
||||
setTotalPages(response.data.parties.total_pages)
|
||||
setRecordCount(response.data.parties.count)
|
||||
setTotalPages(response.data.parties.total_pages);
|
||||
setRecordCount(response.data.parties.count);
|
||||
|
||||
if (replace)
|
||||
replaceResults(
|
||||
response.data.parties.count,
|
||||
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]
|
||||
)
|
||||
);
|
||||
|
||||
function replaceResults(count: number, list: Party[]) {
|
||||
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 {
|
||||
setParties([])
|
||||
setParties([]);
|
||||
}
|
||||
}
|
||||
|
||||
function appendResults(list: Party[]) {
|
||||
setParties([...parties, ...list])
|
||||
setParties([...parties, ...list]);
|
||||
}
|
||||
|
||||
// Fetch all raids on mount, then find the raid in the URL if present
|
||||
useEffect(() => {
|
||||
api.endpoints.raids.getAll().then((response) => {
|
||||
const cleanRaids: Raid[] = response.data.map((r: any) => r.raid)
|
||||
setRaids(cleanRaids)
|
||||
const cleanRaids: Raid[] = response.data.map((r: any) => r.raid);
|
||||
setRaids(cleanRaids);
|
||||
|
||||
setRaidsLoading(false)
|
||||
setRaidsLoading(false);
|
||||
|
||||
const raid = cleanRaids.find((r) => r.slug === raidSlug)
|
||||
setRaid(raid)
|
||||
const raid = cleanRaids.find((r) => r.slug === raidSlug);
|
||||
setRaid(raid);
|
||||
|
||||
return raid
|
||||
})
|
||||
}, [setRaids])
|
||||
return raid;
|
||||
});
|
||||
}, [setRaids]);
|
||||
|
||||
// When the element, raid or recency filter changes,
|
||||
// fetch all teams again.
|
||||
useDidMountEffect(() => {
|
||||
setCurrentPage(1)
|
||||
fetchProfile({ replace: true })
|
||||
}, [element, raid, recency])
|
||||
setCurrentPage(1);
|
||||
fetchProfile({ replace: true });
|
||||
}, [element, raid, recency]);
|
||||
|
||||
// When the page changes, fetch all teams again.
|
||||
useDidMountEffect(() => {
|
||||
// Current page changed
|
||||
if (currentPage > 1) fetchProfile({ replace: false })
|
||||
else if (currentPage == 1) fetchProfile({ replace: true })
|
||||
}, [currentPage])
|
||||
if (currentPage > 1) fetchProfile({ replace: false });
|
||||
else if (currentPage == 1) fetchProfile({ replace: true });
|
||||
}, [currentPage]);
|
||||
|
||||
// Receive filters from the filter bar
|
||||
function receiveFilters({
|
||||
|
|
@ -196,30 +198,30 @@ const ProfileRoute: React.FC<Props> = (props: Props) => {
|
|||
raidSlug,
|
||||
recency,
|
||||
}: {
|
||||
element?: number
|
||||
raidSlug?: string
|
||||
recency?: number
|
||||
element?: number;
|
||||
raidSlug?: string;
|
||||
recency?: number;
|
||||
}) {
|
||||
if (element == 0) setElement(0)
|
||||
else if (element) setElement(element)
|
||||
if (element == 0) setElement(0);
|
||||
else if (element) setElement(element);
|
||||
|
||||
if (raids && raidSlug) {
|
||||
const raid = raids.find((raid) => raid.slug === raidSlug)
|
||||
setRaid(raid)
|
||||
setRaidSlug(raidSlug)
|
||||
const raid = raids.find((raid) => raid.slug === raidSlug);
|
||||
setRaid(raid);
|
||||
setRaidSlug(raidSlug);
|
||||
}
|
||||
|
||||
if (recency) setRecency(recency)
|
||||
if (recency) setRecency(recency);
|
||||
}
|
||||
|
||||
// Methods: Navigation
|
||||
function handleScroll() {
|
||||
if (window.pageYOffset > 90) setScrolled(true)
|
||||
else setScrolled(false)
|
||||
if (window.pageYOffset > 90) setScrolled(true);
|
||||
else setScrolled(false);
|
||||
}
|
||||
|
||||
function goTo(shortcode: string) {
|
||||
router.push(`/p/${shortcode}`)
|
||||
router.push(`/p/${shortcode}`);
|
||||
}
|
||||
|
||||
// TODO: Add save functions
|
||||
|
|
@ -238,8 +240,8 @@ const ProfileRoute: React.FC<Props> = (props: Props) => {
|
|||
key={`party-${i}`}
|
||||
onClick={goTo}
|
||||
/>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -314,8 +316,8 @@ const ProfileRoute: React.FC<Props> = (props: Props) => {
|
|||
)}
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const getServerSidePaths = async () => {
|
||||
return {
|
||||
|
|
@ -324,8 +326,8 @@ export const getServerSidePaths = async () => {
|
|||
{ params: { party: "string" } },
|
||||
],
|
||||
fallback: true,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
export const getServerSideProps = async ({ req, res, locale, query }: { req: NextApiRequest, res: NextApiResponse, locale: string, query: { [index: string]: string } }) => {
|
||||
|
|
@ -410,22 +412,22 @@ const organizeRaids = (raids: Raid[]) => {
|
|||
level: 0,
|
||||
group: 0,
|
||||
element: 0,
|
||||
}
|
||||
};
|
||||
|
||||
const numGroups = Math.max.apply(
|
||||
Math,
|
||||
raids.map((raid) => raid.group)
|
||||
)
|
||||
let groupedRaids = []
|
||||
);
|
||||
let groupedRaids = [];
|
||||
|
||||
for (let i = 0; i <= numGroups; i++) {
|
||||
groupedRaids[i] = raids.filter((raid) => raid.group == i)
|
||||
groupedRaids[i] = raids.filter((raid) => raid.group == i);
|
||||
}
|
||||
|
||||
return {
|
||||
raids: raids,
|
||||
sortedRaids: groupedRaids,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default ProfileRoute
|
||||
export default ProfileRoute;
|
||||
|
|
|
|||
|
|
@ -1,40 +1,42 @@
|
|||
import { useEffect } from "react"
|
||||
import { getCookie } from "cookies-next"
|
||||
import { appWithTranslation } from "next-i18next"
|
||||
import { useEffect } from "react";
|
||||
import { getCookie } from "cookies-next";
|
||||
import { appWithTranslation } from "next-i18next";
|
||||
|
||||
import type { AppProps } from "next/app"
|
||||
import Layout from "~components/Layout"
|
||||
import type { AppProps } from "next/app";
|
||||
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) {
|
||||
const cookie = getCookie("account")
|
||||
const cookieData: AccountCookie = cookie ? JSON.parse(cookie as string) : null
|
||||
const cookie = getCookie("account");
|
||||
const cookieData: AccountCookie = cookie
|
||||
? JSON.parse(cookie as string)
|
||||
: null;
|
||||
|
||||
useEffect(() => {
|
||||
if (cookie) {
|
||||
console.log(`Logged in as user "${cookieData.username}"`)
|
||||
console.log(`Logged in as user "${cookieData.username}"`);
|
||||
|
||||
accountState.account.authorized = true
|
||||
accountState.account.authorized = true;
|
||||
accountState.account.user = {
|
||||
id: cookieData.userId,
|
||||
username: cookieData.username,
|
||||
picture: "",
|
||||
element: "",
|
||||
gender: 0,
|
||||
}
|
||||
};
|
||||
} else {
|
||||
console.log(`You are not currently logged in.`)
|
||||
console.log(`You are not currently logged in.`);
|
||||
}
|
||||
}, [cookie, cookieData])
|
||||
}, [cookie, cookieData]);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default appWithTranslation(MyApp)
|
||||
export default appWithTranslation(MyApp);
|
||||
|
|
|
|||
|
|
@ -1,43 +1,43 @@
|
|||
import React, { useEffect } from "react"
|
||||
import { getCookie } from "cookies-next"
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations"
|
||||
import React, { useEffect } from "react";
|
||||
import { getCookie } from "cookies-next";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
|
||||
import Party from "~components/Party"
|
||||
import Party from "~components/Party";
|
||||
|
||||
import { appState } from "~utils/appState"
|
||||
import api from "~utils/api"
|
||||
import { appState } from "~utils/appState";
|
||||
import api from "~utils/api";
|
||||
|
||||
import type { NextApiRequest, NextApiResponse } from "next"
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
interface Props {
|
||||
jobs: Job[]
|
||||
jobSkills: JobSkill[]
|
||||
raids: Raid[]
|
||||
sortedRaids: Raid[][]
|
||||
jobs: Job[];
|
||||
jobSkills: JobSkill[];
|
||||
raids: Raid[];
|
||||
sortedRaids: Raid[][];
|
||||
}
|
||||
|
||||
const NewRoute: React.FC<Props> = (props: Props) => {
|
||||
function callback(path: string) {
|
||||
// This is scuffed, how do we do this natively?
|
||||
window.history.replaceState(null, `Grid Tool`, `${path}`)
|
||||
window.history.replaceState(null, `Grid Tool`, `${path}`);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
persistStaticData()
|
||||
}, [persistStaticData])
|
||||
persistStaticData();
|
||||
}, [persistStaticData]);
|
||||
|
||||
function persistStaticData() {
|
||||
appState.raids = props.raids
|
||||
appState.jobs = props.jobs
|
||||
appState.jobSkills = props.jobSkills
|
||||
appState.raids = props.raids;
|
||||
appState.jobs = props.jobs;
|
||||
appState.jobSkills = props.jobSkills;
|
||||
}
|
||||
|
||||
return (
|
||||
<div id="Content">
|
||||
<Party new={true} raids={props.sortedRaids} pushHistory={callback} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const getServerSidePaths = async () => {
|
||||
return {
|
||||
|
|
@ -46,8 +46,8 @@ export const getServerSidePaths = async () => {
|
|||
{ params: { party: "string" } },
|
||||
],
|
||||
fallback: true,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
export const getServerSideProps = async ({ req, res, locale, query }: { req: NextApiRequest, res: NextApiResponse, locale: string, query: { [index: string]: string } }) => {
|
||||
|
|
@ -96,22 +96,22 @@ const organizeRaids = (raids: Raid[]) => {
|
|||
level: 0,
|
||||
group: 0,
|
||||
element: 0,
|
||||
}
|
||||
};
|
||||
|
||||
const numGroups = Math.max.apply(
|
||||
Math,
|
||||
raids.map((raid) => raid.group)
|
||||
)
|
||||
let groupedRaids = []
|
||||
);
|
||||
let groupedRaids = [];
|
||||
|
||||
for (let i = 0; i <= numGroups; i++) {
|
||||
groupedRaids[i] = raids.filter((raid) => raid.group == i)
|
||||
groupedRaids[i] = raids.filter((raid) => raid.group == i);
|
||||
}
|
||||
|
||||
return {
|
||||
raids: raids,
|
||||
sortedRaids: groupedRaids,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default NewRoute
|
||||
export default NewRoute;
|
||||
|
|
|
|||
|
|
@ -1,39 +1,39 @@
|
|||
import React, { useEffect } from "react"
|
||||
import { getCookie } from "cookies-next"
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations"
|
||||
import React, { useEffect } from "react";
|
||||
import { getCookie } from "cookies-next";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
|
||||
import Party from "~components/Party"
|
||||
import Party from "~components/Party";
|
||||
|
||||
import { appState } from "~utils/appState"
|
||||
import api from "~utils/api"
|
||||
import { appState } from "~utils/appState";
|
||||
import api from "~utils/api";
|
||||
|
||||
import type { NextApiRequest, NextApiResponse } from "next"
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
interface Props {
|
||||
party: Party
|
||||
jobs: Job[]
|
||||
jobSkills: JobSkill[]
|
||||
raids: Raid[]
|
||||
sortedRaids: Raid[][]
|
||||
party: Party;
|
||||
jobs: Job[];
|
||||
jobSkills: JobSkill[];
|
||||
raids: Raid[];
|
||||
sortedRaids: Raid[][];
|
||||
}
|
||||
|
||||
const PartyRoute: React.FC<Props> = (props: Props) => {
|
||||
useEffect(() => {
|
||||
persistStaticData()
|
||||
}, [persistStaticData])
|
||||
persistStaticData();
|
||||
}, [persistStaticData]);
|
||||
|
||||
function persistStaticData() {
|
||||
appState.raids = props.raids
|
||||
appState.jobs = props.jobs
|
||||
appState.jobSkills = props.jobSkills
|
||||
appState.raids = props.raids;
|
||||
appState.jobs = props.jobs;
|
||||
appState.jobSkills = props.jobSkills;
|
||||
}
|
||||
|
||||
return (
|
||||
<div id="Content">
|
||||
<Party team={props.party} raids={props.sortedRaids} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const getServerSidePaths = async () => {
|
||||
return {
|
||||
|
|
@ -42,8 +42,8 @@ export const getServerSidePaths = async () => {
|
|||
{ params: { party: "string" } },
|
||||
],
|
||||
fallback: true,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
export const getServerSideProps = async ({ req, res, locale, query }: { req: NextApiRequest, res: NextApiResponse, locale: string, query: { [index: string]: string } }) => {
|
||||
|
|
@ -104,22 +104,22 @@ const organizeRaids = (raids: Raid[]) => {
|
|||
level: 0,
|
||||
group: 0,
|
||||
element: 0,
|
||||
}
|
||||
};
|
||||
|
||||
const numGroups = Math.max.apply(
|
||||
Math,
|
||||
raids.map((raid) => raid.group)
|
||||
)
|
||||
let groupedRaids = []
|
||||
);
|
||||
let groupedRaids = [];
|
||||
|
||||
for (let i = 0; i <= numGroups; i++) {
|
||||
groupedRaids[i] = raids.filter((raid) => raid.group == i)
|
||||
groupedRaids[i] = raids.filter((raid) => raid.group == i);
|
||||
}
|
||||
|
||||
return {
|
||||
raids: raids,
|
||||
sortedRaids: groupedRaids,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default PartyRoute
|
||||
export default PartyRoute;
|
||||
|
|
|
|||
236
pages/saved.tsx
236
pages/saved.tsx
|
|
@ -1,60 +1,60 @@
|
|||
import React, { useCallback, useEffect, useState } from "react"
|
||||
import Head from "next/head"
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
|
||||
import { getCookie } from "cookies-next"
|
||||
import { queryTypes, useQueryState } from "next-usequerystate"
|
||||
import { useRouter } from "next/router"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import InfiniteScroll from "react-infinite-scroll-component"
|
||||
import { getCookie } from "cookies-next";
|
||||
import { queryTypes, useQueryState } from "next-usequerystate";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import InfiniteScroll from "react-infinite-scroll-component";
|
||||
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations"
|
||||
import clonedeep from "lodash.clonedeep"
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import clonedeep from "lodash.clonedeep";
|
||||
|
||||
import api from "~utils/api"
|
||||
import useDidMountEffect from "~utils/useDidMountEffect"
|
||||
import { elements, allElement } from "~utils/Element"
|
||||
import api from "~utils/api";
|
||||
import useDidMountEffect from "~utils/useDidMountEffect";
|
||||
import { elements, allElement } from "~utils/Element";
|
||||
|
||||
import GridRep from "~components/GridRep"
|
||||
import GridRepCollection from "~components/GridRepCollection"
|
||||
import FilterBar from "~components/FilterBar"
|
||||
import GridRep from "~components/GridRep";
|
||||
import GridRepCollection from "~components/GridRepCollection";
|
||||
import FilterBar from "~components/FilterBar";
|
||||
|
||||
import type { NextApiRequest, NextApiResponse } from "next"
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
interface Props {
|
||||
teams?: { count: number; total_pages: number; results: Party[] }
|
||||
raids: Raid[]
|
||||
sortedRaids: Raid[][]
|
||||
teams?: { count: number; total_pages: number; results: Party[] };
|
||||
raids: Raid[];
|
||||
sortedRaids: Raid[][];
|
||||
}
|
||||
|
||||
const SavedRoute: React.FC<Props> = (props: Props) => {
|
||||
// Set up cookies
|
||||
const cookie = getCookie("account")
|
||||
const cookie = getCookie("account");
|
||||
const accountData: AccountCookie = cookie
|
||||
? JSON.parse(cookie as string)
|
||||
: null
|
||||
: null;
|
||||
const headers = accountData
|
||||
? { Authorization: `Bearer ${accountData.token}` }
|
||||
: {}
|
||||
: {};
|
||||
|
||||
// Set up router
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
|
||||
// Import translations
|
||||
const { t } = useTranslation("common")
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
// Set up app-specific states
|
||||
const [raidsLoading, setRaidsLoading] = useState(true)
|
||||
const [scrolled, setScrolled] = useState(false)
|
||||
const [raidsLoading, setRaidsLoading] = useState(true);
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
|
||||
// Set up page-specific states
|
||||
const [parties, setParties] = useState<Party[]>([])
|
||||
const [raids, setRaids] = useState<Raid[]>()
|
||||
const [raid, setRaid] = useState<Raid>()
|
||||
const [parties, setParties] = useState<Party[]>([]);
|
||||
const [raids, setRaids] = useState<Raid[]>();
|
||||
const [raid, setRaid] = useState<Raid>();
|
||||
|
||||
// Set up infinite scrolling-related states
|
||||
const [recordCount, setRecordCount] = useState(0)
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [totalPages, setTotalPages] = useState(1)
|
||||
const [recordCount, setRecordCount] = useState(0);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
|
||||
// Set up filter-specific query states
|
||||
// Recency is in seconds
|
||||
|
|
@ -62,57 +62,59 @@ const SavedRoute: React.FC<Props> = (props: Props) => {
|
|||
defaultValue: -1,
|
||||
parse: (query: string) => parseElement(query),
|
||||
serialize: (value) => serializeElement(value),
|
||||
})
|
||||
const [raidSlug, setRaidSlug] = useQueryState("raid", { defaultValue: "all" })
|
||||
});
|
||||
const [raidSlug, setRaidSlug] = useQueryState("raid", {
|
||||
defaultValue: "all",
|
||||
});
|
||||
const [recency, setRecency] = useQueryState(
|
||||
"recency",
|
||||
queryTypes.integer.withDefault(-1)
|
||||
)
|
||||
);
|
||||
|
||||
// Define transformers for element
|
||||
function parseElement(query: string) {
|
||||
let element: TeamElement | undefined =
|
||||
query === "all"
|
||||
? allElement
|
||||
: elements.find((element) => element.name.en.toLowerCase() === query)
|
||||
return element ? element.id : -1
|
||||
: elements.find((element) => element.name.en.toLowerCase() === query);
|
||||
return element ? element.id : -1;
|
||||
}
|
||||
|
||||
function serializeElement(value: number | undefined) {
|
||||
let name = ""
|
||||
let name = "";
|
||||
|
||||
if (value != undefined) {
|
||||
if (value == -1) name = allElement.name.en.toLowerCase()
|
||||
else name = elements[value].name.en.toLowerCase()
|
||||
if (value == -1) name = allElement.name.en.toLowerCase();
|
||||
else name = elements[value].name.en.toLowerCase();
|
||||
}
|
||||
|
||||
return name
|
||||
return name;
|
||||
}
|
||||
|
||||
// Set the initial parties from props
|
||||
useEffect(() => {
|
||||
if (props.teams) {
|
||||
setTotalPages(props.teams.total_pages)
|
||||
setRecordCount(props.teams.count)
|
||||
replaceResults(props.teams.count, props.teams.results)
|
||||
setTotalPages(props.teams.total_pages);
|
||||
setRecordCount(props.teams.count);
|
||||
replaceResults(props.teams.count, props.teams.results);
|
||||
}
|
||||
setCurrentPage(1)
|
||||
}, [])
|
||||
setCurrentPage(1);
|
||||
}, []);
|
||||
|
||||
// Add scroll event listener for shadow on FilterBar on mount
|
||||
useEffect(() => {
|
||||
window.addEventListener("scroll", handleScroll)
|
||||
return () => window.removeEventListener("scroll", handleScroll)
|
||||
}, [])
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
// Handle errors
|
||||
const handleError = useCallback((error: any) => {
|
||||
if (error.response != null) {
|
||||
console.error(error)
|
||||
console.error(error);
|
||||
} else {
|
||||
console.error("There was an error.")
|
||||
console.error("There was an error.");
|
||||
}
|
||||
}, [])
|
||||
}, []);
|
||||
|
||||
const fetchTeams = useCallback(
|
||||
({ replace }: { replace: boolean }) => {
|
||||
|
|
@ -123,63 +125,63 @@ const SavedRoute: React.FC<Props> = (props: Props) => {
|
|||
recency: recency != -1 ? recency : undefined,
|
||||
page: currentPage,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
api
|
||||
.savedTeams({ ...filters, ...{ headers: headers } })
|
||||
.then((response) => {
|
||||
setTotalPages(response.data.total_pages)
|
||||
setRecordCount(response.data.count)
|
||||
setTotalPages(response.data.total_pages);
|
||||
setRecordCount(response.data.count);
|
||||
|
||||
if (replace)
|
||||
replaceResults(response.data.count, response.data.results)
|
||||
else appendResults(response.data.results)
|
||||
replaceResults(response.data.count, response.data.results);
|
||||
else appendResults(response.data.results);
|
||||
})
|
||||
.catch((error) => handleError(error))
|
||||
.catch((error) => handleError(error));
|
||||
},
|
||||
[currentPage, parties, element, raid, recency]
|
||||
)
|
||||
);
|
||||
|
||||
function replaceResults(count: number, list: Party[]) {
|
||||
if (count > 0) {
|
||||
setParties(list)
|
||||
setParties(list);
|
||||
} else {
|
||||
setParties([])
|
||||
setParties([]);
|
||||
}
|
||||
}
|
||||
|
||||
function appendResults(list: Party[]) {
|
||||
setParties([...parties, ...list])
|
||||
setParties([...parties, ...list]);
|
||||
}
|
||||
|
||||
// Fetch all raids on mount, then find the raid in the URL if present
|
||||
useEffect(() => {
|
||||
api.endpoints.raids.getAll().then((response) => {
|
||||
const cleanRaids: Raid[] = response.data.map((r: any) => r.raid)
|
||||
setRaids(cleanRaids)
|
||||
const cleanRaids: Raid[] = response.data.map((r: any) => r.raid);
|
||||
setRaids(cleanRaids);
|
||||
|
||||
setRaidsLoading(false)
|
||||
setRaidsLoading(false);
|
||||
|
||||
const raid = cleanRaids.find((r) => r.slug === raidSlug)
|
||||
setRaid(raid)
|
||||
const raid = cleanRaids.find((r) => r.slug === raidSlug);
|
||||
setRaid(raid);
|
||||
|
||||
return raid
|
||||
})
|
||||
}, [setRaids])
|
||||
return raid;
|
||||
});
|
||||
}, [setRaids]);
|
||||
|
||||
// When the element, raid or recency filter changes,
|
||||
// fetch all teams again.
|
||||
useDidMountEffect(() => {
|
||||
setCurrentPage(1)
|
||||
fetchTeams({ replace: true })
|
||||
}, [element, raid, recency])
|
||||
setCurrentPage(1);
|
||||
fetchTeams({ replace: true });
|
||||
}, [element, raid, recency]);
|
||||
|
||||
// When the page changes, fetch all teams again.
|
||||
useDidMountEffect(() => {
|
||||
// Current page changed
|
||||
if (currentPage > 1) fetchTeams({ replace: false })
|
||||
else if (currentPage == 1) fetchTeams({ replace: true })
|
||||
}, [currentPage])
|
||||
if (currentPage > 1) fetchTeams({ replace: false });
|
||||
else if (currentPage == 1) fetchTeams({ replace: true });
|
||||
}, [currentPage]);
|
||||
|
||||
// Receive filters from the filter bar
|
||||
function receiveFilters({
|
||||
|
|
@ -187,68 +189,68 @@ const SavedRoute: React.FC<Props> = (props: Props) => {
|
|||
raidSlug,
|
||||
recency,
|
||||
}: {
|
||||
element?: number
|
||||
raidSlug?: string
|
||||
recency?: number
|
||||
element?: number;
|
||||
raidSlug?: string;
|
||||
recency?: number;
|
||||
}) {
|
||||
if (element == 0) setElement(0)
|
||||
else if (element) setElement(element)
|
||||
if (element == 0) setElement(0);
|
||||
else if (element) setElement(element);
|
||||
|
||||
if (raids && raidSlug) {
|
||||
const raid = raids.find((raid) => raid.slug === raidSlug)
|
||||
setRaid(raid)
|
||||
setRaidSlug(raidSlug)
|
||||
const raid = raids.find((raid) => raid.slug === raidSlug);
|
||||
setRaid(raid);
|
||||
setRaidSlug(raidSlug);
|
||||
}
|
||||
|
||||
if (recency) setRecency(recency)
|
||||
if (recency) setRecency(recency);
|
||||
}
|
||||
|
||||
// Methods: Favorites
|
||||
function toggleFavorite(teamId: string, favorited: boolean) {
|
||||
if (favorited) unsaveFavorite(teamId)
|
||||
else saveFavorite(teamId)
|
||||
if (favorited) unsaveFavorite(teamId);
|
||||
else saveFavorite(teamId);
|
||||
}
|
||||
|
||||
function saveFavorite(teamId: string) {
|
||||
api.saveTeam({ id: teamId, params: headers }).then((response) => {
|
||||
if (response.status == 201) {
|
||||
const index = parties.findIndex((p) => p.id === teamId)
|
||||
const party = parties[index]
|
||||
const index = parties.findIndex((p) => p.id === teamId);
|
||||
const party = parties[index];
|
||||
|
||||
party.favorited = true
|
||||
party.favorited = true;
|
||||
|
||||
let clonedParties = clonedeep(parties)
|
||||
clonedParties[index] = party
|
||||
let clonedParties = clonedeep(parties);
|
||||
clonedParties[index] = party;
|
||||
|
||||
setParties(clonedParties)
|
||||
setParties(clonedParties);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function unsaveFavorite(teamId: string) {
|
||||
api.unsaveTeam({ id: teamId, params: headers }).then((response) => {
|
||||
if (response.status == 200) {
|
||||
const index = parties.findIndex((p) => p.id === teamId)
|
||||
const party = parties[index]
|
||||
const index = parties.findIndex((p) => p.id === teamId);
|
||||
const party = parties[index];
|
||||
|
||||
party.favorited = false
|
||||
party.favorited = false;
|
||||
|
||||
let clonedParties = clonedeep(parties)
|
||||
clonedParties.splice(index, 1)
|
||||
let clonedParties = clonedeep(parties);
|
||||
clonedParties.splice(index, 1);
|
||||
|
||||
setParties(clonedParties)
|
||||
setParties(clonedParties);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Methods: Navigation
|
||||
function handleScroll() {
|
||||
if (window.pageYOffset > 90) setScrolled(true)
|
||||
else setScrolled(false)
|
||||
if (window.pageYOffset > 90) setScrolled(true);
|
||||
else setScrolled(false);
|
||||
}
|
||||
|
||||
function goTo(shortcode: string) {
|
||||
router.push(`/p/${shortcode}`)
|
||||
router.push(`/p/${shortcode}`);
|
||||
}
|
||||
|
||||
function renderParties() {
|
||||
|
|
@ -268,8 +270,8 @@ const SavedRoute: React.FC<Props> = (props: Props) => {
|
|||
onClick={goTo}
|
||||
onSave={toggleFavorite}
|
||||
/>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -319,8 +321,8 @@ const SavedRoute: React.FC<Props> = (props: Props) => {
|
|||
)}
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const getServerSidePaths = async () => {
|
||||
return {
|
||||
|
|
@ -329,8 +331,8 @@ export const getServerSidePaths = async () => {
|
|||
{ params: { party: "string" } },
|
||||
],
|
||||
fallback: true,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
export const getServerSideProps = async ({ req, res, locale, query }: { req: NextApiRequest, res: NextApiResponse, locale: string, query: { [index: string]: string } }) => {
|
||||
|
|
@ -406,22 +408,22 @@ const organizeRaids = (raids: Raid[]) => {
|
|||
level: 0,
|
||||
group: 0,
|
||||
element: 0,
|
||||
}
|
||||
};
|
||||
|
||||
const numGroups = Math.max.apply(
|
||||
Math,
|
||||
raids.map((raid) => raid.group)
|
||||
)
|
||||
let groupedRaids = []
|
||||
);
|
||||
let groupedRaids = [];
|
||||
|
||||
for (let i = 0; i <= numGroups; i++) {
|
||||
groupedRaids[i] = raids.filter((raid) => raid.group == i)
|
||||
groupedRaids[i] = raids.filter((raid) => raid.group == i);
|
||||
}
|
||||
|
||||
return {
|
||||
raids: raids,
|
||||
sortedRaids: groupedRaids,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default SavedRoute
|
||||
export default SavedRoute;
|
||||
|
|
|
|||
236
pages/teams.tsx
236
pages/teams.tsx
|
|
@ -1,60 +1,60 @@
|
|||
import React, { useCallback, useEffect, useState } from "react"
|
||||
import Head from "next/head"
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
|
||||
import { getCookie } from "cookies-next"
|
||||
import { queryTypes, useQueryState } from "next-usequerystate"
|
||||
import { useRouter } from "next/router"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import InfiniteScroll from "react-infinite-scroll-component"
|
||||
import { getCookie } from "cookies-next";
|
||||
import { queryTypes, useQueryState } from "next-usequerystate";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import InfiniteScroll from "react-infinite-scroll-component";
|
||||
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations"
|
||||
import clonedeep from "lodash.clonedeep"
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import clonedeep from "lodash.clonedeep";
|
||||
|
||||
import api from "~utils/api"
|
||||
import useDidMountEffect from "~utils/useDidMountEffect"
|
||||
import { elements, allElement } from "~utils/Element"
|
||||
import api from "~utils/api";
|
||||
import useDidMountEffect from "~utils/useDidMountEffect";
|
||||
import { elements, allElement } from "~utils/Element";
|
||||
|
||||
import GridRep from "~components/GridRep"
|
||||
import GridRepCollection from "~components/GridRepCollection"
|
||||
import FilterBar from "~components/FilterBar"
|
||||
import GridRep from "~components/GridRep";
|
||||
import GridRepCollection from "~components/GridRepCollection";
|
||||
import FilterBar from "~components/FilterBar";
|
||||
|
||||
import type { NextApiRequest, NextApiResponse } from "next"
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
interface Props {
|
||||
teams?: { count: number; total_pages: number; results: Party[] }
|
||||
raids: Raid[]
|
||||
sortedRaids: Raid[][]
|
||||
teams?: { count: number; total_pages: number; results: Party[] };
|
||||
raids: Raid[];
|
||||
sortedRaids: Raid[][];
|
||||
}
|
||||
|
||||
const TeamsRoute: React.FC<Props> = (props: Props) => {
|
||||
// Set up cookies
|
||||
const cookie = getCookie("account")
|
||||
const cookie = getCookie("account");
|
||||
const accountData: AccountCookie = cookie
|
||||
? JSON.parse(cookie as string)
|
||||
: null
|
||||
: null;
|
||||
const headers = accountData
|
||||
? { Authorization: `Bearer ${accountData.token}` }
|
||||
: {}
|
||||
: {};
|
||||
|
||||
// Set up router
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
|
||||
// Import translations
|
||||
const { t } = useTranslation("common")
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
// Set up app-specific states
|
||||
const [raidsLoading, setRaidsLoading] = useState(true)
|
||||
const [scrolled, setScrolled] = useState(false)
|
||||
const [raidsLoading, setRaidsLoading] = useState(true);
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
|
||||
// Set up page-specific states
|
||||
const [parties, setParties] = useState<Party[]>([])
|
||||
const [raids, setRaids] = useState<Raid[]>()
|
||||
const [raid, setRaid] = useState<Raid>()
|
||||
const [parties, setParties] = useState<Party[]>([]);
|
||||
const [raids, setRaids] = useState<Raid[]>();
|
||||
const [raid, setRaid] = useState<Raid>();
|
||||
|
||||
// Set up infinite scrolling-related states
|
||||
const [recordCount, setRecordCount] = useState(0)
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [totalPages, setTotalPages] = useState(1)
|
||||
const [recordCount, setRecordCount] = useState(0);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
|
||||
// Set up filter-specific query states
|
||||
// Recency is in seconds
|
||||
|
|
@ -62,57 +62,59 @@ const TeamsRoute: React.FC<Props> = (props: Props) => {
|
|||
defaultValue: -1,
|
||||
parse: (query: string) => parseElement(query),
|
||||
serialize: (value) => serializeElement(value),
|
||||
})
|
||||
const [raidSlug, setRaidSlug] = useQueryState("raid", { defaultValue: "all" })
|
||||
});
|
||||
const [raidSlug, setRaidSlug] = useQueryState("raid", {
|
||||
defaultValue: "all",
|
||||
});
|
||||
const [recency, setRecency] = useQueryState(
|
||||
"recency",
|
||||
queryTypes.integer.withDefault(-1)
|
||||
)
|
||||
);
|
||||
|
||||
// Define transformers for element
|
||||
function parseElement(query: string) {
|
||||
let element: TeamElement | undefined =
|
||||
query === "all"
|
||||
? allElement
|
||||
: elements.find((element) => element.name.en.toLowerCase() === query)
|
||||
return element ? element.id : -1
|
||||
: elements.find((element) => element.name.en.toLowerCase() === query);
|
||||
return element ? element.id : -1;
|
||||
}
|
||||
|
||||
function serializeElement(value: number | undefined) {
|
||||
let name = ""
|
||||
let name = "";
|
||||
|
||||
if (value != undefined) {
|
||||
if (value == -1) name = allElement.name.en.toLowerCase()
|
||||
else name = elements[value].name.en.toLowerCase()
|
||||
if (value == -1) name = allElement.name.en.toLowerCase();
|
||||
else name = elements[value].name.en.toLowerCase();
|
||||
}
|
||||
|
||||
return name
|
||||
return name;
|
||||
}
|
||||
|
||||
// Set the initial parties from props
|
||||
useEffect(() => {
|
||||
if (props.teams) {
|
||||
setTotalPages(props.teams.total_pages)
|
||||
setRecordCount(props.teams.count)
|
||||
replaceResults(props.teams.count, props.teams.results)
|
||||
setTotalPages(props.teams.total_pages);
|
||||
setRecordCount(props.teams.count);
|
||||
replaceResults(props.teams.count, props.teams.results);
|
||||
}
|
||||
setCurrentPage(1)
|
||||
}, [])
|
||||
setCurrentPage(1);
|
||||
}, []);
|
||||
|
||||
// Add scroll event listener for shadow on FilterBar on mount
|
||||
useEffect(() => {
|
||||
window.addEventListener("scroll", handleScroll)
|
||||
return () => window.removeEventListener("scroll", handleScroll)
|
||||
}, [])
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
// Handle errors
|
||||
const handleError = useCallback((error: any) => {
|
||||
if (error.response != null) {
|
||||
console.error(error)
|
||||
console.error(error);
|
||||
} else {
|
||||
console.error("There was an error.")
|
||||
console.error("There was an error.");
|
||||
}
|
||||
}, [])
|
||||
}, []);
|
||||
|
||||
const fetchTeams = useCallback(
|
||||
({ replace }: { replace: boolean }) => {
|
||||
|
|
@ -123,63 +125,63 @@ const TeamsRoute: React.FC<Props> = (props: Props) => {
|
|||
recency: recency != -1 ? recency : undefined,
|
||||
page: currentPage,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
api.endpoints.parties
|
||||
.getAll({ ...filters, ...{ headers: headers } })
|
||||
.then((response) => {
|
||||
setTotalPages(response.data.total_pages)
|
||||
setRecordCount(response.data.count)
|
||||
setTotalPages(response.data.total_pages);
|
||||
setRecordCount(response.data.count);
|
||||
|
||||
if (replace)
|
||||
replaceResults(response.data.count, response.data.results)
|
||||
else appendResults(response.data.results)
|
||||
replaceResults(response.data.count, response.data.results);
|
||||
else appendResults(response.data.results);
|
||||
})
|
||||
.catch((error) => handleError(error))
|
||||
.catch((error) => handleError(error));
|
||||
},
|
||||
[currentPage, parties, element, raid, recency]
|
||||
)
|
||||
);
|
||||
|
||||
function replaceResults(count: number, list: Party[]) {
|
||||
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 {
|
||||
setParties([])
|
||||
setParties([]);
|
||||
}
|
||||
}
|
||||
|
||||
function appendResults(list: Party[]) {
|
||||
setParties([...parties, ...list])
|
||||
setParties([...parties, ...list]);
|
||||
}
|
||||
|
||||
// Fetch all raids on mount, then find the raid in the URL if present
|
||||
useEffect(() => {
|
||||
api.endpoints.raids.getAll().then((response) => {
|
||||
const cleanRaids: Raid[] = response.data.map((r: any) => r.raid)
|
||||
setRaids(cleanRaids)
|
||||
const cleanRaids: Raid[] = response.data.map((r: any) => r.raid);
|
||||
setRaids(cleanRaids);
|
||||
|
||||
setRaidsLoading(false)
|
||||
setRaidsLoading(false);
|
||||
|
||||
const raid = cleanRaids.find((r) => r.slug === raidSlug)
|
||||
setRaid(raid)
|
||||
const raid = cleanRaids.find((r) => r.slug === raidSlug);
|
||||
setRaid(raid);
|
||||
|
||||
return raid
|
||||
})
|
||||
}, [setRaids])
|
||||
return raid;
|
||||
});
|
||||
}, [setRaids]);
|
||||
|
||||
// When the element, raid or recency filter changes,
|
||||
// fetch all teams again.
|
||||
useDidMountEffect(() => {
|
||||
setCurrentPage(1)
|
||||
fetchTeams({ replace: true })
|
||||
}, [element, raid, recency])
|
||||
setCurrentPage(1);
|
||||
fetchTeams({ replace: true });
|
||||
}, [element, raid, recency]);
|
||||
|
||||
// When the page changes, fetch all teams again.
|
||||
useDidMountEffect(() => {
|
||||
// Current page changed
|
||||
if (currentPage > 1) fetchTeams({ replace: false })
|
||||
else if (currentPage == 1) fetchTeams({ replace: true })
|
||||
}, [currentPage])
|
||||
if (currentPage > 1) fetchTeams({ replace: false });
|
||||
else if (currentPage == 1) fetchTeams({ replace: true });
|
||||
}, [currentPage]);
|
||||
|
||||
// Receive filters from the filter bar
|
||||
function receiveFilters({
|
||||
|
|
@ -187,68 +189,68 @@ const TeamsRoute: React.FC<Props> = (props: Props) => {
|
|||
raidSlug,
|
||||
recency,
|
||||
}: {
|
||||
element?: number
|
||||
raidSlug?: string
|
||||
recency?: number
|
||||
element?: number;
|
||||
raidSlug?: string;
|
||||
recency?: number;
|
||||
}) {
|
||||
if (element == 0) setElement(0)
|
||||
else if (element) setElement(element)
|
||||
if (element == 0) setElement(0);
|
||||
else if (element) setElement(element);
|
||||
|
||||
if (raids && raidSlug) {
|
||||
const raid = raids.find((raid) => raid.slug === raidSlug)
|
||||
setRaid(raid)
|
||||
setRaidSlug(raidSlug)
|
||||
const raid = raids.find((raid) => raid.slug === raidSlug);
|
||||
setRaid(raid);
|
||||
setRaidSlug(raidSlug);
|
||||
}
|
||||
|
||||
if (recency) setRecency(recency)
|
||||
if (recency) setRecency(recency);
|
||||
}
|
||||
|
||||
// Methods: Favorites
|
||||
function toggleFavorite(teamId: string, favorited: boolean) {
|
||||
if (favorited) unsaveFavorite(teamId)
|
||||
else saveFavorite(teamId)
|
||||
if (favorited) unsaveFavorite(teamId);
|
||||
else saveFavorite(teamId);
|
||||
}
|
||||
|
||||
function saveFavorite(teamId: string) {
|
||||
api.saveTeam({ id: teamId, params: headers }).then((response) => {
|
||||
if (response.status == 201) {
|
||||
const index = parties.findIndex((p) => p.id === teamId)
|
||||
const party = parties[index]
|
||||
const index = parties.findIndex((p) => p.id === teamId);
|
||||
const party = parties[index];
|
||||
|
||||
party.favorited = true
|
||||
party.favorited = true;
|
||||
|
||||
let clonedParties = clonedeep(parties)
|
||||
clonedParties[index] = party
|
||||
let clonedParties = clonedeep(parties);
|
||||
clonedParties[index] = party;
|
||||
|
||||
setParties(clonedParties)
|
||||
setParties(clonedParties);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function unsaveFavorite(teamId: string) {
|
||||
api.unsaveTeam({ id: teamId, params: headers }).then((response) => {
|
||||
if (response.status == 200) {
|
||||
const index = parties.findIndex((p) => p.id === teamId)
|
||||
const party = parties[index]
|
||||
const index = parties.findIndex((p) => p.id === teamId);
|
||||
const party = parties[index];
|
||||
|
||||
party.favorited = false
|
||||
party.favorited = false;
|
||||
|
||||
let clonedParties = clonedeep(parties)
|
||||
clonedParties[index] = party
|
||||
let clonedParties = clonedeep(parties);
|
||||
clonedParties[index] = party;
|
||||
|
||||
setParties(clonedParties)
|
||||
setParties(clonedParties);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Methods: Navigation
|
||||
function handleScroll() {
|
||||
if (window.pageYOffset > 90) setScrolled(true)
|
||||
else setScrolled(false)
|
||||
if (window.pageYOffset > 90) setScrolled(true);
|
||||
else setScrolled(false);
|
||||
}
|
||||
|
||||
function goTo(shortcode: string) {
|
||||
router.push(`/p/${shortcode}`)
|
||||
router.push(`/p/${shortcode}`);
|
||||
}
|
||||
|
||||
function renderParties() {
|
||||
|
|
@ -268,8 +270,8 @@ const TeamsRoute: React.FC<Props> = (props: Props) => {
|
|||
onClick={goTo}
|
||||
onSave={toggleFavorite}
|
||||
/>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -327,8 +329,8 @@ const TeamsRoute: React.FC<Props> = (props: Props) => {
|
|||
)}
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const getServerSidePaths = async () => {
|
||||
return {
|
||||
|
|
@ -337,8 +339,8 @@ export const getServerSidePaths = async () => {
|
|||
{ params: { party: "string" } },
|
||||
],
|
||||
fallback: true,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
export const getServerSideProps = async ({ req, res, locale, query }: { req: NextApiRequest, res: NextApiResponse, locale: string, query: { [index: string]: string } }) => {
|
||||
|
|
@ -414,22 +416,22 @@ const organizeRaids = (raids: Raid[]) => {
|
|||
level: 0,
|
||||
group: 0,
|
||||
element: 0,
|
||||
}
|
||||
};
|
||||
|
||||
const numGroups = Math.max.apply(
|
||||
Math,
|
||||
raids.map((raid) => raid.group)
|
||||
)
|
||||
let groupedRaids = []
|
||||
);
|
||||
let groupedRaids = [];
|
||||
|
||||
for (let i = 0; i <= numGroups; i++) {
|
||||
groupedRaids[i] = raids.filter((raid) => raid.group == i)
|
||||
groupedRaids[i] = raids.filter((raid) => raid.group == i);
|
||||
}
|
||||
|
||||
return {
|
||||
raids: raids,
|
||||
sortedRaids: groupedRaids,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default TeamsRoute
|
||||
export default TeamsRoute;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
@import '~meyer-reset-scss';
|
||||
@import "~meyer-reset-scss";
|
||||
|
||||
html {
|
||||
@include themed() {
|
||||
background-color: t($background);
|
||||
}
|
||||
|
||||
background: $background-color;
|
||||
font-size: 62.5%;
|
||||
height: 100%;
|
||||
|
|
@ -9,7 +13,8 @@ html {
|
|||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
box-sizing: border-box;
|
||||
font-family: system-ui, -apple-system, 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-family: system-ui, -apple-system, "Helvetica Neue", Helvetica, Arial,
|
||||
sans-serif;
|
||||
font-size: 1.4rem;
|
||||
height: 100%;
|
||||
padding: $unit * 2 !important;
|
||||
|
|
@ -55,11 +60,16 @@ a {
|
|||
}
|
||||
}
|
||||
|
||||
button, input {
|
||||
font-family: system-ui, -apple-system, 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
button,
|
||||
input {
|
||||
font-family: system-ui, -apple-system, "Helvetica Neue", Helvetica, Arial,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
h1, h2, h3, p {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
p {
|
||||
color: $grey-00;
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +81,7 @@ h1 {
|
|||
|
||||
select {
|
||||
appearance: none;
|
||||
background-image: url('/icons/Arrow.svg');
|
||||
background-image: url("/icons/Arrow.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position-y: center;
|
||||
background-position-x: 97%;
|
||||
|
|
@ -101,7 +111,6 @@ select {
|
|||
min-width: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.Overlay {
|
||||
|
|
@ -117,7 +126,8 @@ select {
|
|||
.Dialog {
|
||||
$multiplier: 4;
|
||||
|
||||
animation: 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0s 1 normal none running openModal;
|
||||
animation: 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0s 1 normal none running
|
||||
openModal;
|
||||
background: white;
|
||||
border-radius: $unit;
|
||||
display: flex;
|
||||
|
|
@ -279,7 +289,6 @@ select {
|
|||
color: $dark-bg-light;
|
||||
}
|
||||
|
||||
|
||||
&.light {
|
||||
color: $light-bg-light;
|
||||
}
|
||||
|
|
@ -293,7 +302,8 @@ select {
|
|||
}
|
||||
}
|
||||
|
||||
#Teams, #Profile {
|
||||
#Teams,
|
||||
#Profile {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
|
|
@ -306,7 +316,7 @@ select {
|
|||
margin: auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;;
|
||||
align-items: center;
|
||||
|
||||
h2 {
|
||||
color: $grey-60;
|
||||
|
|
@ -373,4 +383,3 @@ i.tag {
|
|||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,60 +1,68 @@
|
|||
// @import 'include-media/dist/_include-media';
|
||||
|
||||
// Breakpoints
|
||||
$breakpoints: (small: 320px, medium: 800px, large: 1024px);
|
||||
$breakpoints: (
|
||||
small: 320px,
|
||||
medium: 800px,
|
||||
large: 1024px,
|
||||
);
|
||||
$medium-screen: 800px;
|
||||
|
||||
// Sizing
|
||||
$unit: 8px;
|
||||
|
||||
// Colors
|
||||
$grey-00: #444;
|
||||
$grey-00: #222;
|
||||
$grey-10: #444;
|
||||
$grey-20: #555;
|
||||
$grey-30: #666;
|
||||
$grey-40: #777;
|
||||
$grey-50: #888;
|
||||
$grey-60: #A9A9A9;
|
||||
$grey-70: #C6C6C6;
|
||||
$grey-80: #E9E9E9;
|
||||
$grey-90: #F5F5F5;
|
||||
$grey-100:#FAFAFA;
|
||||
$grey-60: #a9a9a9;
|
||||
$grey-70: #c6c6c6;
|
||||
$grey-80: #e9e9e9;
|
||||
$grey-90: #f5f5f5;
|
||||
$grey-100: #fafafa;
|
||||
$background-color: $grey-90;
|
||||
|
||||
$blue: #275DC5;
|
||||
$red: #FF6161;
|
||||
$error: #D13A3A;
|
||||
$page--bg--light: $grey-90;
|
||||
$page--bg--dark: $grey-00;
|
||||
|
||||
$blue: #275dc5;
|
||||
$red: #ff6161;
|
||||
$error: #d13a3a;
|
||||
|
||||
// Colors: Elements
|
||||
$wind-text-dark: #009961;
|
||||
$wind-text-light: #1DC688;
|
||||
$wind-bg-dark: #4CFFBF;
|
||||
$wind-bg-light: #CDFFED;
|
||||
$wind-text-light: #1dc688;
|
||||
$wind-bg-dark: #4cffbf;
|
||||
$wind-bg-light: #cdffed;
|
||||
|
||||
$fire-text-dark: #990000;
|
||||
$fire-text-light: #EC5C5C;
|
||||
$fire-bg-dark: #FF4D4D;
|
||||
$fire-bg-light: #FFCDCD;
|
||||
$fire-text-light: #ec5c5c;
|
||||
$fire-bg-dark: #ff4d4d;
|
||||
$fire-bg-light: #ffcdcd;
|
||||
|
||||
$water-text-dark: #006199;
|
||||
$water-text-light: #5CB7EC;
|
||||
$water-bg-dark: #4DBFFF;
|
||||
$water-bg-light: #CDEDFF;
|
||||
$water-text-light: #5cb7ec;
|
||||
$water-bg-dark: #4dbfff;
|
||||
$water-bg-light: #cdedff;
|
||||
|
||||
$earth-text-dark: #994000;
|
||||
$earth-text-light: #EC985C;
|
||||
$earth-bg-dark: #FF974C;
|
||||
$earth-bg-light: #FFE2CD;
|
||||
$earth-text-light: #ec985c;
|
||||
$earth-bg-dark: #ff974c;
|
||||
$earth-bg-light: #ffe2cd;
|
||||
|
||||
$light-text-dark: #998A00;
|
||||
$light-text-light: #C5B20C;
|
||||
$light-bg-dark: #FFED4C;
|
||||
$light-bg-light: #FFFACD;
|
||||
$light-text-dark: #998a00;
|
||||
$light-text-light: #c5b20c;
|
||||
$light-bg-dark: #ffed4c;
|
||||
$light-bg-light: #fffacd;
|
||||
|
||||
$dark-text-dark: #8806B7;
|
||||
$dark-text-light: #C65CEC;
|
||||
$dark-bg-dark: #D14CFF;
|
||||
$dark-bg-light: #F2CDFF;
|
||||
$dark-text-dark: #8806b7;
|
||||
$dark-text-light: #c65cec;
|
||||
$dark-bg-dark: #d14cff;
|
||||
$dark-bg-light: #f2cdff;
|
||||
|
||||
// Font weight
|
||||
$normal: 400;
|
||||
$medium: 500;
|
||||
$bold: 600;
|
||||
|
|
|
|||
6
types/AccountCookie.d.ts
vendored
6
types/AccountCookie.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
|||
interface AccountCookie {
|
||||
userId: string
|
||||
username: string
|
||||
token: string
|
||||
userId: string;
|
||||
username: string;
|
||||
token: string;
|
||||
}
|
||||
|
|
|
|||
18
types/AxSkill.d.ts
vendored
18
types/AxSkill.d.ts
vendored
|
|
@ -1,12 +1,12 @@
|
|||
interface AxSkill {
|
||||
name: {
|
||||
[key: string]: string
|
||||
en: string,
|
||||
ja: string
|
||||
},
|
||||
id: number,
|
||||
minValue: number,
|
||||
maxValue: number,
|
||||
suffix?: string,
|
||||
secondary?: AxSkill[]
|
||||
[key: string]: string;
|
||||
en: string;
|
||||
ja: string;
|
||||
};
|
||||
id: number;
|
||||
minValue: number;
|
||||
maxValue: number;
|
||||
suffix?: string;
|
||||
secondary?: AxSkill[];
|
||||
}
|
||||
62
types/Character.d.ts
vendored
62
types/Character.d.ts
vendored
|
|
@ -1,40 +1,40 @@
|
|||
interface Character {
|
||||
type: "character"
|
||||
type: "character";
|
||||
|
||||
id: string
|
||||
granblue_id: string
|
||||
character_id: readonly number[]
|
||||
element: number
|
||||
rarity: number
|
||||
gender: number
|
||||
max_level: number
|
||||
id: string;
|
||||
granblue_id: string;
|
||||
character_id: readonly number[];
|
||||
element: number;
|
||||
rarity: number;
|
||||
gender: number;
|
||||
max_level: number;
|
||||
name: {
|
||||
[key: string]: string
|
||||
en: string
|
||||
ja: string
|
||||
}
|
||||
[key: string]: string;
|
||||
en: string;
|
||||
ja: string;
|
||||
};
|
||||
hp: {
|
||||
min_hp: number
|
||||
max_hp: number
|
||||
max_hp_flb: number
|
||||
}
|
||||
min_hp: number;
|
||||
max_hp: number;
|
||||
max_hp_flb: number;
|
||||
};
|
||||
atk: {
|
||||
min_atk: number
|
||||
max_atk: number
|
||||
max_atk_flb: number
|
||||
}
|
||||
min_atk: number;
|
||||
max_atk: number;
|
||||
max_atk_flb: number;
|
||||
};
|
||||
uncap: {
|
||||
flb: boolean
|
||||
ulb: boolean
|
||||
}
|
||||
flb: boolean;
|
||||
ulb: boolean;
|
||||
};
|
||||
race: {
|
||||
race1: number
|
||||
race2: number
|
||||
}
|
||||
race1: number;
|
||||
race2: number;
|
||||
};
|
||||
proficiency: {
|
||||
proficiency1: number
|
||||
proficiency2: number
|
||||
}
|
||||
position?: number
|
||||
special: boolean
|
||||
proficiency1: number;
|
||||
proficiency2: number;
|
||||
};
|
||||
position?: number;
|
||||
special: boolean;
|
||||
}
|
||||
|
|
|
|||
4
types/CheckedState.d.ts
vendored
4
types/CheckedState.d.ts
vendored
|
|
@ -1,4 +1,4 @@
|
|||
interface CheckedState {
|
||||
id: number
|
||||
checked: boolean
|
||||
id: number;
|
||||
checked: boolean;
|
||||
}
|
||||
16
types/ElementState.d.ts
vendored
16
types/ElementState.d.ts
vendored
|
|
@ -1,10 +1,10 @@
|
|||
interface ElementState {
|
||||
[key: string]: CheckedState
|
||||
null: CheckedState
|
||||
wind: CheckedState
|
||||
fire: CheckedState
|
||||
water: CheckedState
|
||||
earth: CheckedState
|
||||
dark: CheckedState
|
||||
light: CheckedState
|
||||
[key: string]: CheckedState;
|
||||
null: CheckedState;
|
||||
wind: CheckedState;
|
||||
fire: CheckedState;
|
||||
water: CheckedState;
|
||||
earth: CheckedState;
|
||||
dark: 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 {
|
||||
id: string
|
||||
position: number
|
||||
object: Character
|
||||
uncap_level: number
|
||||
id: string;
|
||||
position: number;
|
||||
object: Character;
|
||||
uncap_level: number;
|
||||
}
|
||||
12
types/GridSummon.d.ts
vendored
12
types/GridSummon.d.ts
vendored
|
|
@ -1,8 +1,8 @@
|
|||
interface GridSummon {
|
||||
id: string
|
||||
main: boolean
|
||||
friend: boolean
|
||||
position: number
|
||||
object: Summon
|
||||
uncap_level: number
|
||||
id: string;
|
||||
main: boolean;
|
||||
friend: boolean;
|
||||
position: number;
|
||||
object: Summon;
|
||||
uncap_level: number;
|
||||
}
|
||||
16
types/GridWeapon.d.ts
vendored
16
types/GridWeapon.d.ts
vendored
|
|
@ -1,10 +1,10 @@
|
|||
interface GridWeapon {
|
||||
id: string
|
||||
mainhand: boolean
|
||||
position: number
|
||||
object: Weapon
|
||||
uncap_level: number
|
||||
element: number
|
||||
weapon_keys?: Array<WeaponKey>
|
||||
ax?: Array<SimpleAxSkill>
|
||||
id: string;
|
||||
mainhand: boolean;
|
||||
position: number;
|
||||
object: Weapon;
|
||||
uncap_level: number;
|
||||
element: number;
|
||||
weapon_keys?: Array<WeaponKey>;
|
||||
ax?: Array<SimpleAxSkill>;
|
||||
}
|
||||
24
types/Job.d.ts
vendored
24
types/Job.d.ts
vendored
|
|
@ -1,16 +1,16 @@
|
|||
interface Job {
|
||||
id: string
|
||||
row: string
|
||||
ml: boolean
|
||||
order: number
|
||||
id: string;
|
||||
row: string;
|
||||
ml: boolean;
|
||||
order: number;
|
||||
name: {
|
||||
[key: string]: string
|
||||
en: string
|
||||
ja: string
|
||||
}
|
||||
[key: string]: string;
|
||||
en: string;
|
||||
ja: string;
|
||||
};
|
||||
proficiency: {
|
||||
proficiency1: number
|
||||
proficiency2: number
|
||||
}
|
||||
base_job?: Job
|
||||
proficiency1: number;
|
||||
proficiency2: number;
|
||||
};
|
||||
base_job?: Job;
|
||||
}
|
||||
|
|
|
|||
26
types/JobSkill.d.ts
vendored
26
types/JobSkill.d.ts
vendored
|
|
@ -1,16 +1,16 @@
|
|||
interface JobSkill {
|
||||
id: string
|
||||
job: Job
|
||||
id: string;
|
||||
job: Job;
|
||||
name: {
|
||||
[key: string]: string
|
||||
en: string
|
||||
ja: string
|
||||
}
|
||||
slug: string
|
||||
color: number
|
||||
main: boolean
|
||||
base: boolean
|
||||
sub: boolean
|
||||
emp: boolean
|
||||
order: number
|
||||
[key: string]: string;
|
||||
en: string;
|
||||
ja: string;
|
||||
};
|
||||
slug: string;
|
||||
color: number;
|
||||
main: boolean;
|
||||
base: boolean;
|
||||
sub: boolean;
|
||||
emp: boolean;
|
||||
order: number;
|
||||
}
|
||||
|
|
|
|||
4
types/OnClickEvent.d.ts
vendored
4
types/OnClickEvent.d.ts
vendored
|
|
@ -1 +1,3 @@
|
|||
type OnClickEvent = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
|
||||
type OnClickEvent = (
|
||||
event: React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
) => void;
|
||||
|
|
|
|||
42
types/Party.d.ts
vendored
42
types/Party.d.ts
vendored
|
|
@ -1,25 +1,25 @@
|
|||
type JobSkillObject = {
|
||||
[key: number]: JobSkill | undefined
|
||||
0: JobSkill | undefined
|
||||
1: JobSkill | undefined
|
||||
2: JobSkill | undefined
|
||||
3: JobSkill | undefined
|
||||
}
|
||||
[key: number]: JobSkill | undefined;
|
||||
0: JobSkill | undefined;
|
||||
1: JobSkill | undefined;
|
||||
2: JobSkill | undefined;
|
||||
3: JobSkill | undefined;
|
||||
};
|
||||
|
||||
interface Party {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
raid: Raid
|
||||
job: Job
|
||||
job_skills: JobSkillObject
|
||||
shortcode: string
|
||||
extra: boolean
|
||||
favorited: boolean
|
||||
characters: Array<GridCharacter>
|
||||
weapons: Array<GridWeapon>
|
||||
summons: Array<GridSummon>
|
||||
user: User
|
||||
created_at: string
|
||||
updated_at: string
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
raid: Raid;
|
||||
job: Job;
|
||||
job_skills: JobSkillObject;
|
||||
shortcode: string;
|
||||
extra: boolean;
|
||||
favorited: boolean;
|
||||
characters: Array<GridCharacter>;
|
||||
weapons: Array<GridWeapon>;
|
||||
summons: Array<GridSummon>;
|
||||
user: User;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
|
|
|||
22
types/ProficiencyState.d.ts
vendored
22
types/ProficiencyState.d.ts
vendored
|
|
@ -1,13 +1,13 @@
|
|||
interface ProficiencyState {
|
||||
[key: string]: CheckedState
|
||||
sabre: CheckedState
|
||||
dagger: CheckedState
|
||||
spear: CheckedState
|
||||
axe: CheckedState
|
||||
staff: CheckedState
|
||||
melee: CheckedState
|
||||
gun: CheckedState
|
||||
bow: CheckedState
|
||||
harp: CheckedState
|
||||
katana: CheckedState
|
||||
[key: string]: CheckedState;
|
||||
sabre: CheckedState;
|
||||
dagger: CheckedState;
|
||||
spear: CheckedState;
|
||||
axe: CheckedState;
|
||||
staff: CheckedState;
|
||||
melee: CheckedState;
|
||||
gun: CheckedState;
|
||||
bow: CheckedState;
|
||||
harp: CheckedState;
|
||||
katana: CheckedState;
|
||||
}
|
||||
18
types/Raid.d.ts
vendored
18
types/Raid.d.ts
vendored
|
|
@ -1,12 +1,12 @@
|
|||
interface Raid {
|
||||
id: string
|
||||
id: string;
|
||||
name: {
|
||||
[key: string]: string
|
||||
en: string
|
||||
ja: string
|
||||
}
|
||||
slug: string
|
||||
level: number
|
||||
group: number
|
||||
element: number
|
||||
[key: string]: string;
|
||||
en: string;
|
||||
ja: string;
|
||||
};
|
||||
slug: string;
|
||||
level: number;
|
||||
group: number;
|
||||
element: number;
|
||||
}
|
||||
6
types/RarityState.d.ts
vendored
6
types/RarityState.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
|||
interface RarityState {
|
||||
[key: string]: CheckedState
|
||||
sr: CheckedState
|
||||
ssr: CheckedState
|
||||
[key: string]: CheckedState;
|
||||
sr: CheckedState;
|
||||
ssr: CheckedState;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue