diff --git a/components/AboutModal/index.scss b/components/AboutModal/index.scss index 32f12aad..abab57b5 100644 --- a/components/AboutModal/index.scss +++ b/components/AboutModal/index.scss @@ -1,21 +1,21 @@ .About.Dialog { - width: $unit * 60; + width: $unit * 60; - section { - margin-bottom: $unit; + section { + margin-bottom: $unit; - h2 { - margin-bottom: $unit * 3; - } + h2 { + margin-bottom: $unit * 3; } + } - .DialogDescription { - font-size: $font-regular; - line-height: 1.24; - margin-bottom: $unit; + .DialogDescription { + font-size: $font-regular; + line-height: 1.24; + margin-bottom: $unit; - &:last-of-type { - margin-bottom: 0; - } + &:last-of-type { + margin-bottom: 0; } + } } diff --git a/components/AboutModal/index.tsx b/components/AboutModal/index.tsx index 501f0122..d6025c73 100644 --- a/components/AboutModal/index.tsx +++ b/components/AboutModal/index.tsx @@ -1,61 +1,73 @@ -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 ( - - -
  • - {t('modals.about.title')} -
  • -
    - - event.preventDefault() }> -
    - {t('menu.about')} - - - - - -
    + return ( + + +
  • + {t("modals.about.title")} +
  • +
    + + event.preventDefault()} + > +
    + + {t("menu.about")} + + + + + + +
    -
    - - Granblue.team is a tool to save and share team compositions for Granblue Fantasy. - - - Start adding things to a team and a URL will be created for you to share it wherever you like, no account needed. - - - 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. - -
    +
    + + Granblue.team is a tool to save and share team compositions for{" "} + Granblue Fantasy. + + + Start adding things to a team and a URL will be created for you to + share it wherever you like, no account needed. + + + 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. + +
    -
    - Credits - - Granblue.team was built by @jedmund with a lot of help from @lalalalinna and @tarngerine. - -
    +
    + Credits + + Granblue.team was built by{" "} + @jedmund with a lot of + help from{" "} + @lalalalinna and{" "} + @tarngerine. + +
    -
    - Open Source - - This app is open source. You can contribute on Github. - -
    -
    - -
    -
    - ) -} +
    + Open Source + + This app is open source. You can contribute on Github. + +
    +
    + +
    +
    + ); +}; -export default AboutModal \ No newline at end of file +export default AboutModal; diff --git a/components/AccountModal/index.scss b/components/AccountModal/index.scss index 497e316e..63553fc4 100644 --- a/components/AccountModal/index.scss +++ b/components/AccountModal/index.scss @@ -1,164 +1,164 @@ .Account.Dialog { + display: flex; + flex-direction: column; + gap: $unit * 2; + width: $unit * 60; + + form { display: flex; flex-direction: column; gap: $unit * 2; - width: $unit * 60; - form { + .Switch { + $height: 34px; + background: $grey-70; + border-radius: calc($height / 2); + border: none; + position: relative; + width: 58px; + height: $height; + + &:focus { + box-shadow: 0 0 0 2px $grey-00; + } + + &[data-state="checked"] { + background: $grey-00; + } + } + + .Thumb { + background: white; + border-radius: 13px; + display: block; + height: 26px; + width: 26px; + transition: transform 100ms; + transform: translateX(-1px); + + &:hover { + cursor: pointer; + } + + &[data-state="checked"] { + background: white; + transform: translateX(21px); + } + } + + .Button { + font-size: $font-regular; + padding: ($unit * 1.5) ($unit * 2); + margin-top: $unit * 2; + width: 100%; + + &.btn-disabled { + background: $grey-90; + color: $grey-70; + cursor: not-allowed; + } + + &:not(.btn-disabled) { + background: $grey-90; + color: $grey-40; + + &:hover { + background: $grey-80; + } + } + } + + .field { + align-items: center; + display: flex; + flex-direction: row; + gap: $unit * 2; + + select { + background: no-repeat url("/icons/ArrowDark.svg"), $grey-90; + background-position-y: center; + background-position-x: 95%; + margin: 0; + width: 240px; + } + + .left { display: flex; flex-direction: column; - gap: $unit * 2; + flex-grow: 1; + gap: calc($unit / 2); - .Switch { - $height: 34px; - background: $grey-70; - border-radius: calc($height / 2); - border: none; - position: relative; - width: 58px; - height: $height; - - &:focus { - box-shadow: 0 0 0 2px $grey-00; - } - - &[data-state="checked"] { - background: $grey-00; - } + label { + color: $grey-00; + font-size: $font-regular; } - .Thumb { - background: white; - border-radius: 13px; - display: block; - height: 26px; - width: 26px; - transition: transform 100ms; - transform: translateX(-1px); + p { + color: $grey-60; + font-size: $font-small; + line-height: 1.1; + max-width: 300px; - &:hover { - cursor: pointer; - } + &.jp { + max-width: 270px; + } + } + } - &[data-state="checked"] { - background: white; - transform: translateX(21px); - } + .preview { + $diameter: 48px; + background-color: $grey-90; + border-radius: 999px; + height: $diameter; + width: $diameter; + + img { + height: $diameter; + width: $diameter; } - .Button { - font-size: $font-regular; - padding: ($unit * 1.5) ($unit * 2); - margin-top: $unit * 2; - width: 100%; - - &.btn-disabled { - background: $grey-90; - color: $grey-70; - cursor: not-allowed; - } - - &:not(.btn-disabled) { - background: $grey-90; - color: $grey-40; - - &:hover { - background: $grey-80; - } - } + &.fire { + background: $fire-bg-light; } - .field { - align-items: center; - display: flex; - flex-direction: row; - gap: $unit * 2; - - select { - background: no-repeat url('/icons/ArrowDark.svg'), $grey-90; - background-position-y: center; - background-position-x: 95%; - margin: 0; - width: 240px; - } - - .left { - display: flex; - flex-direction: column; - flex-grow: 1; - gap: calc($unit / 2); - - label { - color: $grey-00; - font-size: $font-regular; - } - - p { - color: $grey-60; - font-size: $font-small; - line-height: 1.1; - max-width: 300px; - - &.jp { - max-width: 270px; - } - } - } - - .preview { - $diameter: 48px; - background-color: $grey-90; - border-radius: 999px; - height: $diameter; - width: $diameter; - - img { - height: $diameter; - width: $diameter; - } - - &.fire { - background: $fire-bg-light; - } - - &.water { - background: $water-bg-light; - } - - &.wind { - background: $wind-bg-light; - } - - &.earth { - background: $earth-bg-light; - } - - &.dark { - background: $dark-bg-light; - } - - &.light { - background: $light-bg-light; - } - } + &.water { + background: $water-bg-light; } - section { - margin-bottom: $unit; - - h2 { - margin-bottom: $unit * 3; - } + &.wind { + background: $wind-bg-light; } + + &.earth { + background: $earth-bg-light; + } + + &.dark { + background: $dark-bg-light; + } + + &.light { + background: $light-bg-light; + } + } } - .DialogDescription { - font-size: $font-regular; - line-height: 1.24; - margin-bottom: $unit; + section { + margin-bottom: $unit; - &:last-of-type { - margin-bottom: 0; - } + h2 { + margin-bottom: $unit * 3; + } } + } + + .DialogDescription { + font-size: $font-regular; + line-height: 1.24; + margin-bottom: $unit; + + &:last-of-type { + margin-bottom: 0; + } + } } diff --git a/components/AccountModal/index.tsx b/components/AccountModal/index.tsx index 6b081b20..2c509398 100644 --- a/components/AccountModal/index.tsx +++ b/components/AccountModal/index.tsx @@ -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() - const languageSelect = React.createRef() - const genderSelect = React.createRef() - const privateSelect = React.createRef() + const pictureSelect = React.createRef(); + const languageSelect = React.createRef(); + const genderSelect = React.createRef(); + const privateSelect = React.createRef(); // useEffect(() => { // if (cookies.user) setPicture(cookies.user.picture) @@ -62,27 +64,27 @@ const AccountModal = () => { - ) - }) + ); + }); function handlePictureChange(event: React.ChangeEvent) { - if (pictureSelect.current) setPicture(pictureSelect.current.value) + if (pictureSelect.current) setPicture(pictureSelect.current.value); } function handleLanguageChange(event: React.ChangeEvent) { - if (languageSelect.current) setLanguage(languageSelect.current.value) + if (languageSelect.current) setLanguage(languageSelect.current.value); } function handleGenderChange(event: React.ChangeEvent) { - 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) { - 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 = () => { - ) -} + ); +}; -export default AccountModal +export default AccountModal; diff --git a/components/Alert/index.tsx b/components/Alert/index.tsx index 8e785c70..8fa96014 100644 --- a/components/Alert/index.tsx +++ b/components/Alert/index.tsx @@ -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) => { - ) -} + ); +}; -export default Alert +export default Alert; diff --git a/components/AxSelect/index.scss b/components/AxSelect/index.scss index 559645d3..1b9c59f4 100644 --- a/components/AxSelect/index.scss +++ b/components/AxSelect/index.scss @@ -1,48 +1,48 @@ .AXSelect { - display: flex; - flex-direction: column; - gap: $unit; + display: flex; + flex-direction: column; + gap: $unit; - .AXSet { - &.hidden { - display: none; - } - - .errors { - color: $error; - display: none; - padding: $unit 0; - - &.visible { - display: block; - } - } - - .fields { - display: flex; - flex-direction: row; - gap: $unit; - - select { - flex-grow: 1; - margin: 0; - } - - .Input { - -webkit-font-smoothing: antialiased; - border: none; - background-color: $grey-90; - border-radius: 6px; - box-sizing: border-box; - color: $grey-00; - height: $unit * 6; - display: block; - font-size: $font-regular; - padding: $unit; - text-align: right; - min-width: 100px; - width: 100px; - } - } + .AXSet { + &.hidden { + display: none; } -} \ No newline at end of file + + .errors { + color: $error; + display: none; + padding: $unit 0; + + &.visible { + display: block; + } + } + + .fields { + display: flex; + flex-direction: row; + gap: $unit; + + select { + flex-grow: 1; + margin: 0; + } + + .Input { + -webkit-font-smoothing: antialiased; + border: none; + background-color: $grey-90; + border-radius: 6px; + box-sizing: border-box; + color: $grey-00; + height: $unit * 6; + display: block; + font-size: $font-regular; + padding: $unit; + text-align: right; + min-width: 100px; + width: 100px; + } + } + } +} diff --git a/components/AxSelect/index.tsx b/components/AxSelect/index.tsx index 510920c9..ff427e55 100644 --- a/components/AxSelect/index.tsx +++ b/components/AxSelect/index.tsx @@ -1,266 +1,351 @@ -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({ - axValue1: '', - axValue2: '' - }) + // Set up form states and error handling + const [errors, setErrors] = useState({ + axValue1: "", + axValue2: "", + }); - const primaryErrorClasses = classNames({ - 'errors': true, - 'visible': errors.axValue1.length > 0 - }) + const primaryErrorClasses = classNames({ + errors: true, + visible: errors.axValue1.length > 0, + }); - const secondaryErrorClasses = classNames({ - 'errors': true, - 'visible': errors.axValue2.length > 0 - }) + const secondaryErrorClasses = classNames({ + errors: true, + visible: errors.axValue2.length > 0, + }); - // Refs - const primaryAxModifierSelect = React.createRef() - const primaryAxValueInput = React.createRef() - const secondaryAxModifierSelect = React.createRef() - const secondaryAxValueInput = React.createRef() + // Refs + const primaryAxModifierSelect = React.createRef(); + const primaryAxValueInput = React.createRef(); + const secondaryAxModifierSelect = React.createRef(); + const secondaryAxValueInput = React.createRef(); - // States - const [primaryAxModifier, setPrimaryAxModifier] = useState(-1) - const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1) - const [primaryAxValue, setPrimaryAxValue] = useState(0.0) - const [secondaryAxValue, setSecondaryAxValue] = useState(0.0) + // States + 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) + useEffect(() => { + if (props.currentSkills && props.currentSkills[0]) { + if (props.currentSkills[0].modifier != null) + 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); + + setSecondaryAxValue(props.currentSkills[1].strength); + } + }, [props.currentSkills]); + + useEffect(() => { + props.sendValues( + primaryAxModifier, + primaryAxValue, + secondaryAxModifier, + secondaryAxValue + ); + }, [ + props, + primaryAxModifier, + primaryAxValue, + secondaryAxModifier, + secondaryAxValue, + ]); + + useEffect(() => { + props.sendValidity( + primaryAxValue > 0 && errors.axValue1 === "" && errors.axValue2 === "" + ); + }, [props, primaryAxValue, errors]); + + // Classes + const secondarySetClasses = classNames({ + AXSet: true, + hidden: primaryAxModifier < 0, + }); + + function generateOptions(modifierSet: number) { + const axOptions = axData[props.axType - 1]; + + let axOptionElements: React.ReactNode[] = []; + if (modifierSet == 0) { + axOptionElements = axOptions.map((ax, i) => { + return ( + + ); + }); + } 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 (modifier >= 0 && axOptions[modifier]) { + const primarySkill = axOptions[modifier]; + + if (primarySkill.secondary) { + const secondaryAxOptions = primarySkill.secondary; + axOptionElements = secondaryAxOptions.map((ax, i) => { + return ( + + ); + }); } + } + } - if (props.currentSkills && props.currentSkills[1]) { - if (props.currentSkills[1].modifier != null) - setSecondaryAxModifier(props.currentSkills[1].modifier) + axOptionElements?.unshift( + + ); + return axOptionElements; + } - setSecondaryAxValue(props.currentSkills[1].strength) - } - }, [props.currentSkills]) + function handleSelectChange(event: React.ChangeEvent) { + const value = parseInt(event.target.value); - useEffect(() => { - props.sendValues(primaryAxModifier, primaryAxValue, secondaryAxModifier, secondaryAxValue) - }, [props, primaryAxModifier, primaryAxValue, secondaryAxModifier, secondaryAxValue]) + if (primaryAxModifierSelect.current == event.target) { + setPrimaryAxModifier(value); - useEffect(() => { - props.sendValidity(primaryAxValue > 0 && errors.axValue1 === '' && errors.axValue2 === '') - }, [props, primaryAxValue, errors]) + if ( + primaryAxValueInput.current && + secondaryAxModifierSelect.current && + secondaryAxValueInput.current + ) { + setupInput( + axData[props.axType - 1][value], + primaryAxValueInput.current + ); - // Classes - const secondarySetClasses = classNames({ - 'AXSet': true, - 'hidden': primaryAxModifier < 0 - }) + secondaryAxModifierSelect.current.value = "-1"; + secondaryAxValueInput.current.value = ""; + } + } else { + setSecondaryAxModifier(value); - function generateOptions(modifierSet: number) { - const axOptions = axData[props.axType - 1] + const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]; + const currentAxSkill = primaryAxSkill.secondary + ? primaryAxSkill.secondary.find((skill) => skill.id == value) + : undefined; - let axOptionElements: React.ReactNode[] = [] - if (modifierSet == 0) { - axOptionElements = axOptions.map((ax, i) => { - return ( - - ) - }) + if (secondaryAxValueInput.current) + setupInput(currentAxSkill, secondaryAxValueInput.current); + } + } + + function handleInputChange(event: React.ChangeEvent) { + const value = parseFloat(event.target.value); + let newErrors = { ...errors }; + + if (primaryAxValueInput.current == event.target) { + if (handlePrimaryErrors(value)) setPrimaryAxValue(value); + } else { + if (handleSecondaryErrors(value)) setSecondaryAxValue(value); + } + } + + function handlePrimaryErrors(value: number) { + const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]; + let newErrors = { ...errors }; + + if (value < primaryAxSkill.minValue) { + newErrors.axValue1 = t("ax.errors.value_too_low", { + name: primaryAxSkill.name[locale], + minValue: primaryAxSkill.minValue, + suffix: primaryAxSkill.suffix ? primaryAxSkill.suffix : "", + }); + } else if (value > primaryAxSkill.maxValue) { + newErrors.axValue1 = t("ax.errors.value_too_high", { + name: primaryAxSkill.name[locale], + maxValue: primaryAxSkill.minValue, + suffix: primaryAxSkill.suffix ? primaryAxSkill.suffix : "", + }); + } else if (!value || value <= 0) { + newErrors.axValue1 = t("ax.errors.value_empty", { + name: primaryAxSkill.name[locale], + }); + } else { + newErrors.axValue1 = ""; + } + + setErrors(newErrors); + + return newErrors.axValue1.length === 0; + } + + function handleSecondaryErrors(value: number) { + const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]; + let newErrors = { ...errors }; + + if (primaryAxSkill.secondary) { + const secondaryAxSkill = primaryAxSkill.secondary.find( + (skill) => skill.id == secondaryAxModifier + ); + + if (secondaryAxSkill) { + if (value < secondaryAxSkill.minValue) { + newErrors.axValue2 = t("ax.errors.value_too_low", { + name: secondaryAxSkill.name[locale], + minValue: secondaryAxSkill.minValue, + suffix: secondaryAxSkill.suffix ? secondaryAxSkill.suffix : "", + }); + } else if (value > secondaryAxSkill.maxValue) { + newErrors.axValue2 = t("ax.errors.value_too_high", { + name: secondaryAxSkill.name[locale], + maxValue: secondaryAxSkill.minValue, + suffix: secondaryAxSkill.suffix ? secondaryAxSkill.suffix : "", + }); + } else if (!secondaryAxSkill.suffix && value % 1 !== 0) { + 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], + }); } 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 + newErrors.axValue2 = ""; + } + } + } - if (modifier >= 0 && axOptions[modifier]) { - const primarySkill = axOptions[modifier] + setErrors(newErrors); - if (primarySkill.secondary) { - const secondaryAxOptions = primarySkill.secondary - axOptionElements = secondaryAxOptions.map((ax, i) => { - return ( - - ) - }) - } + return newErrors.axValue2.length === 0; + } + + function setupInput(ax: AxSkill | undefined, element: HTMLInputElement) { + if (ax) { + 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"; + } else { + if (primaryAxValueInput.current && secondaryAxValueInput.current) { + if (primaryAxValueInput.current == element) { + primaryAxValueInput.current.disabled = true; + primaryAxValueInput.current.placeholder = ""; + } + + secondaryAxValueInput.current.disabled = true; + secondaryAxValueInput.current.placeholder = ""; + } + } + } + + return ( +
    +
    +
    + + skill.id == value) : undefined - - if (secondaryAxValueInput.current) - setupInput(currentAxSkill, secondaryAxValueInput.current) - } - } - - function handleInputChange(event: React.ChangeEvent) { - const value = parseFloat(event.target.value) - let newErrors = {...errors} - - if (primaryAxValueInput.current == event.target) { - if (handlePrimaryErrors(value)) - setPrimaryAxValue(value) - } else { - if (handleSecondaryErrors(value)) - setSecondaryAxValue(value) - } - } - - function handlePrimaryErrors(value: number) { - const primaryAxSkill = axData[props.axType - 1][primaryAxModifier] - let newErrors = {...errors} - - if (value < primaryAxSkill.minValue) { - newErrors.axValue1 = t('ax.errors.value_too_low', { - name: primaryAxSkill.name[locale], - minValue: primaryAxSkill.minValue, - suffix: (primaryAxSkill.suffix) ? primaryAxSkill.suffix : '' - }) - } else if (value > primaryAxSkill.maxValue) { - newErrors.axValue1 = t('ax.errors.value_too_high', { - name: primaryAxSkill.name[locale], - maxValue: primaryAxSkill.minValue, - suffix: (primaryAxSkill.suffix) ? primaryAxSkill.suffix : '' - }) - } else if (!value || value <= 0) { - newErrors.axValue1 = t('ax.errors.value_empty', { name: primaryAxSkill.name[locale] }) - } else { - newErrors.axValue1 = '' - } - - setErrors(newErrors) - - return newErrors.axValue1.length === 0 - } - - function handleSecondaryErrors(value: number) { - const primaryAxSkill = axData[props.axType - 1][primaryAxModifier] - let newErrors = {...errors} - - if (primaryAxSkill.secondary) { - const secondaryAxSkill = primaryAxSkill.secondary.find(skill => skill.id == secondaryAxModifier) - - if (secondaryAxSkill) { - if (value < secondaryAxSkill.minValue) { - newErrors.axValue2 = t('ax.errors.value_too_low', { - name: secondaryAxSkill.name[locale], - minValue: secondaryAxSkill.minValue, - suffix: (secondaryAxSkill.suffix) ? secondaryAxSkill.suffix : '' - }) - } else if (value > secondaryAxSkill.maxValue) { - newErrors.axValue2 = t('ax.errors.value_too_high', { - name: secondaryAxSkill.name[locale], - maxValue: secondaryAxSkill.minValue, - suffix: (secondaryAxSkill.suffix) ? secondaryAxSkill.suffix : '' - }) - } else if (!secondaryAxSkill.suffix && value % 1 !== 0) { - 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] }) - } else { - newErrors.axValue2 = '' - } - } - } - - setErrors(newErrors) - - return newErrors.axValue2.length === 0 - } - - function setupInput(ax: AxSkill | undefined, element: HTMLInputElement) { - if (ax) { - 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" - } else { - if (primaryAxValueInput.current && secondaryAxValueInput.current) { - if (primaryAxValueInput.current == element) { - primaryAxValueInput.current.disabled = true - primaryAxValueInput.current.placeholder = '' - } - - secondaryAxValueInput.current.disabled = true - secondaryAxValueInput.current.placeholder = '' - } - } - } - - return ( -
    -
    -
    - - -
    -

    {errors.axValue1}

    -
    - -
    -
    - - -
    -

    {errors.axValue2}

    -
    + className="Input" + type="number" + onChange={handleInputChange} + ref={primaryAxValueInput} + disabled={primaryAxValue != 0} + />
    - ) -} +

    {errors.axValue1}

    +
    -export default AXSelect \ No newline at end of file +
    +
    + + +
    +

    {errors.axValue2}

    +
    +
    + ); +}; + +export default AXSelect; diff --git a/components/Button/index.scss b/components/Button/index.scss index f9053487..78769aee 100644 --- a/components/Button/index.scss +++ b/components/Button/index.scss @@ -1,214 +1,213 @@ .Button { - align-items: center; - background: transparent; - border: none; - border-radius: 6px; - color: $grey-50; - display: inline-flex; - font-size: $font-button; - font-weight: $normal; - gap: 6px; - padding: 8px 12px; + align-items: center; + background: transparent; + border: none; + border-radius: 6px; + color: $grey-50; + display: inline-flex; + font-size: $font-button; + font-weight: $normal; + gap: 6px; + padding: 8px 12px; + + &:hover { + background: white; + cursor: pointer; + color: $grey-00; + + .icon svg { + fill: $grey-00; + } + + .icon.stroke svg { + fill: none; + stroke: $grey-00; + } + } + + &.destructive:hover { + background: $error; + color: white; + + .icon svg { + fill: white; + } + } + + &.save:hover { + color: #ff4d4d; + + .icon svg { + fill: #ff4d4d; + stroke: #ff4d4d; + } + } + + &.save.Active { + color: #ff4d4d; + + .icon svg { + fill: #ff4d4d; + stroke: #ff4d4d; + } &:hover { - background: white; - cursor: pointer; - color: $grey-00; + color: darken(#ff4d4d, 30); - .icon svg { - fill: $grey-00; - } + .icon svg { + fill: darken(#ff4d4d, 30); + stroke: darken(#ff4d4d, 30); + } + } + } - .icon.stroke svg { - fill: none; - stroke: $grey-00; - } + &.modal:hover { + background: $grey-90; + } + + &.modal.destructive { + color: $error; + + &:hover { + color: darken($error, 10); + } + } + + .icon { + margin-top: 2px; + + svg { + fill: $grey-50; + height: 12px; + width: 12px; } - &.destructive:hover { - background: $error; - color: white; - - .icon svg { - fill: white; - } + &.check svg { + margin-top: 1px; + height: 14px; + width: auto; } - &.save:hover { - color: #FF4D4D; - - .icon svg { - fill: #FF4D4D; - stroke: #FF4D4D; - } + &.stroke svg { + fill: none; + stroke: $grey-50; } - &.save.Active { - color: #FF4D4D; - - .icon svg { - fill: #FF4D4D; - stroke: #FF4D4D; - } - - &:hover { - color: darken(#FF4D4D, 30); - - .icon svg { - fill: darken(#FF4D4D, 30); - stroke: darken(#FF4D4D, 30); - } - } + &.settings svg { + height: 13px; + width: 13px; } + } - &.modal:hover { - background: $grey-90; + &.Active { + background: white; + } + + &.btn-blue { + background: $blue; + color: #8b8b8b; + + &:hover { + background: #4b9be5; + color: #233e56; } + } - &.modal.destructive { - color: $error; - - &:hover { - color: darken($error, 10) - } + &.btn-red { + background: #fa4242; + color: #860f0f; + + &:hover { + background: #e91a1a; + color: #4e1717; + + .icon { + color: #4e1717; + } } .icon { - margin-top: 2px; - - svg { - fill: $grey-50; - height: 12px; - width: 12px; - } - - &.check svg { - margin-top: 1px; - height: 14px; - width: auto; - } - - &.stroke svg { - fill: none; - stroke: $grey-50; - } - - &.settings svg { - height: 13px; - width: 13px; - } + color: #860f0f; } + } - &.Active { - background: white; + &.btn-disabled { + background: #e0e0e0; + color: #bababa; + + &:hover { + background: #e0e0e0; + color: #bababa; } + } - &.btn-blue { - background: $blue; - color: #8b8b8b; + &.null { + background: $grey-90; + color: $grey-50; - &:hover { - background: #4B9BE5; - color: #233E56; - } + &:hover { + background: $grey-70; + color: $grey-00; } + } - &.btn-red { - background: #fa4242; - color: #860f0f; + &.wind { + background: $wind-bg-light; + color: $wind-text-dark; - &:hover { - background: #e91a1a; - color: #4e1717; - - .icon { - color: #4e1717; - } - } - - .icon { - color: #860f0f; - } + &:hover { + background: darken($wind-bg-light, 10); } + } - &.btn-disabled { - background: #e0e0e0; - color: #bababa; + &.fire { + background: $fire-bg-light; + color: $fire-text-dark; - &:hover { - background: #e0e0e0; - color: #bababa; - } + &:hover { + background: darken($fire-bg-light, 10); } + } - &.null { - background: $grey-90; - color: $grey-50; + &.water { + background: $water-bg-light; + color: $water-text-dark; - &:hover { - background: $grey-70; - color: $grey-00; - } + &:hover { + background: darken($water-bg-light, 10); } + } - &.wind { - background: $wind-bg-light; - color: $wind-text-dark; + &.earth { + background: $earth-bg-light; + color: $earth-text-dark; - &:hover { - background: darken($wind-bg-light, 10); - } + &:hover { + background: darken($earth-bg-light, 10); } + } - &.fire { - background: $fire-bg-light; - color: $fire-text-dark; + &.dark { + background: $dark-bg-light; + color: $dark-text-dark; - &:hover { - background: darken($fire-bg-light, 10); - } + &:hover { + background: darken($dark-bg-light, 10); } + } - &.water { - background: $water-bg-light; - color: $water-text-dark; + &.light { + background: $light-bg-light; + color: $light-text-dark; - &:hover { - background: darken($water-bg-light, 10); - } + &:hover { + background: darken($light-bg-light, 10); } + } - &.earth { - background: $earth-bg-light; - color: $earth-text-dark; - - &:hover { - background: darken($earth-bg-light, 10); - } - } - - &.dark { - background: $dark-bg-light; - color: $dark-text-dark; - - &:hover { - background: darken($dark-bg-light, 10); - } - } - - - &.light { - background: $light-bg-light; - color: $light-text-dark; - - &:hover { - background: darken($light-bg-light, 10); - } - } - - .text { - color: inherit; - display: block; - width: 100%; - } + .text { + color: inherit; + display: block; + width: 100%; + } } diff --git a/components/Button/index.tsx b/components/Button/index.tsx index f83f5e76..b66129b6 100644 --- a/components/Button/index.tsx +++ b/components/Button/index.tsx @@ -1,141 +1,161 @@ -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) => void + active?: boolean; + disabled?: boolean; + classes?: string[]; + icon?: string; + type?: ButtonType; + children?: React.ReactNode; + onClick?: (event: React.MouseEvent) => 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) + // States + const [active, setActive] = useState(false); + const [disabled, setDisabled] = useState(false); + const [pressed, setPressed] = useState(false); + const [buttonType, setButtonType] = useState(ButtonType.Base); - const classes = classNames({ - Button: true, - 'Active': active, - 'btn-pressed': pressed, - 'btn-disabled': disabled, - 'save': props.icon === 'save', - 'destructive': props.type == ButtonType.Destructive - }, props.classes) + const classes = classNames( + { + Button: true, + 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]) + 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]); - const addIcon = ( - - - - ) + const addIcon = ( + + + + ); - const menuIcon = ( - - - - ) + const menuIcon = ( + + + + ); - const linkIcon = ( - - - - ) + const linkIcon = ( + + + + ); - const checkIcon = ( - - - - ) + const checkIcon = ( + + + + ); - const crossIcon = ( - - - - ) + const crossIcon = ( + + + + ); - const editIcon = ( - - - - ) + const editIcon = ( + + + + ); - const saveIcon = ( - - - - ) + const saveIcon = ( + + + + ); - const settingsIcon = ( - - - - ) + const settingsIcon = ( + + + + ); - function getIcon() { - 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 - } + function getIcon() { + let icon: React.ReactNode; - return icon + 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; } - function handleMouseDown() { - setPressed(true) - } + return icon; + } - function handleMouseUp() { - setPressed(false) - } - return ( - - ) -} + function handleMouseUp() { + setPressed(false); + } + return ( + + ); +}; + +export default Button; diff --git a/components/CharLimitedFieldset/index.scss b/components/CharLimitedFieldset/index.scss index 8f81bf3b..c23c84df 100644 --- a/components/CharLimitedFieldset/index.scss +++ b/components/CharLimitedFieldset/index.scss @@ -1,29 +1,29 @@ .Limited { - background: white; - border-radius: 6px; - border: 2px solid transparent; - box-sizing: border-box; - display: flex; - gap: $unit; - padding-right: $unit * 2; + background: white; + border-radius: 6px; + border: 2px solid transparent; + box-sizing: border-box; + display: flex; + gap: $unit; + padding-right: $unit * 2; - &:focus-within { - border: 2px solid $blue; - box-shadow: 0 2px rgba(255, 255, 255, 1); + &:focus-within { + border: 2px solid $blue; + box-shadow: 0 2px rgba(255, 255, 255, 1); + } + + .Counter { + color: $grey-50; + font-weight: $bold; + line-height: 42px; + } + + .Input { + background: transparent; + border-radius: 0; + + &:focus { + outline: none; } - - .Counter { - color: $grey-50; - font-weight: $bold; - line-height: 42px; - } - - .Input { - background: transparent; - border-radius: 0; - - &:focus { - outline: none; - } - } -} \ No newline at end of file + } +} diff --git a/components/CharLimitedFieldset/index.tsx b/components/CharLimitedFieldset/index.tsx index ff741c59..2c4ba006 100644 --- a/components/CharLimitedFieldset/index.tsx +++ b/components/CharLimitedFieldset/index.tsx @@ -1,54 +1,57 @@ -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) => void - onChange?: (event: React.ChangeEvent) => void + fieldName: string; + placeholder: string; + value?: string; + limit: number; + error: string; + onBlur?: (event: React.ChangeEvent) => void; + onChange?: (event: React.ChangeEvent) => void; } -const CharLimitedFieldset = React.forwardRef(function useFieldSet(props, ref) { - const fieldType = (['password', 'confirm_password'].includes(props.fieldName)) ? 'password' : 'text' +const CharLimitedFieldset = React.forwardRef( + 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) { - 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 ( -
    -
    - - {currentCount} -
    - { - props.error.length > 0 && -

    {props.error}

    - } -
    - ) -}) +
    +
    + + {currentCount} +
    + {props.error.length > 0 &&

    {props.error}

    } +
    + ); + } +); -export default CharLimitedFieldset \ No newline at end of file +export default CharLimitedFieldset; diff --git a/components/CharacterConflictModal/index.tsx b/components/CharacterConflictModal/index.tsx index 1f1ff6c0..fe797a79 100644 --- a/components/CharacterConflictModal/index.tsx +++ b/components/CharacterConflictModal/index.tsx @@ -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) => { - ) -} + ); +}; -export default CharacterConflictModal +export default CharacterConflictModal; diff --git a/components/CharacterGrid/index.scss b/components/CharacterGrid/index.scss index 01e6ada2..4a45134f 100644 --- a/components/CharacterGrid/index.scss +++ b/components/CharacterGrid/index.scss @@ -1,31 +1,31 @@ #CharacterGrid { - display: flex; - flex-direction: column; - justify-content: center; - margin: auto; - max-width: 761px; + display: flex; + flex-direction: column; + justify-content: center; + margin: auto; + max-width: 761px; } #grid_characters { - display: flex; - margin: 0; - padding: 0; - max-width: 761px; + display: flex; + margin: 0; + padding: 0; + max-width: 761px; + + @media (max-width: $medium-screen) { + justify-content: space-between; + width: 100%; + } + + & > * { + margin-right: $unit * 3; @media (max-width: $medium-screen) { - justify-content: space-between; - width: 100%; + margin-right: inherit; } + } - & > * { - margin-right: $unit * 3; - - @media (max-width: $medium-screen) { - margin-right: inherit; - } - } - - & > li:last-child { - margin: 0; - } -} \ No newline at end of file + & > li:last-child { + margin: 0; + } +} diff --git a/components/CharacterGrid/index.tsx b/components/CharacterGrid/index.tsx index 521c54c2..59923024 100644 --- a/components/CharacterGrid/index.tsx +++ b/components/CharacterGrid/index.tsx @@ -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> - pushHistory?: (path: string) => void + new: boolean; + characters?: GridCharacter[]; + createParty: () => Promise>; + 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() - const [conflicts, setConflicts] = useState([]) - const [position, setPosition] = useState(0) + const [incoming, setIncoming] = useState(); + const [conflicts, setConflicts] = useState([]); + const [position, setPosition] = useState(0); // Set up state for data - const [job, setJob] = useState() + const [job, setJob] = useState(); const [jobSkills, setJobSkills] = useState({ 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) + } skills to your party at once.`; + setErrorMessage(message); } - console.log(error.response.data) - }) + 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} /> - ) + ); })}
    - ) -} + ); +}; -export default CharacterGrid +export default CharacterGrid; diff --git a/components/CharacterHovercard/index.tsx b/components/CharacterHovercard/index.tsx index 9ae4c78d..5007e2ae 100644 --- a/components/CharacterHovercard/index.tsx +++ b/components/CharacterHovercard/index.tsx @@ -1,92 +1,125 @@ -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 - } + [key: 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 = "" - - if (props.gridCharacter) { - const character = props.gridCharacter.object + function characterImage() { + let imgSrc = ""; - // 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' + if (props.gridCharacter) { + const character = props.gridCharacter.object; - imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_${suffix}.jpg` - } + // 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"; - return imgSrc + imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_${suffix}.jpg`; } - return ( - - - { props.children } - - -
    -
    -

    { props.gridCharacter.object.name[locale] }

    - {props.gridCharacter.object.name[locale]} -
    -
    -
    - - - { (props.gridCharacter.object.proficiency.proficiency2) ? - - : ''} -
    - -
    -
    + return imgSrc; + } - {t('buttons.wiki')} - -
    -
    - ) -} + return ( + + {props.children} + +
    +
    +

    {props.gridCharacter.object.name[locale]}

    + {props.gridCharacter.object.name[locale]} +
    +
    +
    + + + {props.gridCharacter.object.proficiency.proficiency2 ? ( + + ) : ( + "" + )} +
    + +
    +
    -export default CharacterHovercard + + {t("buttons.wiki")} + + +
    +
    + ); +}; +export default CharacterHovercard; diff --git a/components/CharacterResult/index.scss b/components/CharacterResult/index.scss index 7c19ab70..77a7893a 100644 --- a/components/CharacterResult/index.scss +++ b/components/CharacterResult/index.scss @@ -1,63 +1,63 @@ .CharacterResult { + border-radius: 6px; + display: flex; + gap: $unit; + padding: $unit * 1.5; + + &:hover { + background: $grey-90; + cursor: pointer; + } + + img { + background: $grey-80; border-radius: 6px; + display: inline-block; + height: 72px; + width: 120px; + } + + .Info { display: flex; - gap: $unit; - padding: $unit * 1.5; + flex-direction: column; + flex-grow: 1; + gap: calc($unit / 2); - &:hover { - background: $grey-90; - cursor: pointer; + h5 { + color: #555; + display: inline-block; + font-size: $font-medium; + font-weight: $medium; } - img { - background: $grey-80; - border-radius: 6px; - display: inline-block; - height: 72px; - width: 120px; + .UncapIndicator { + justify-content: left; + pointer-events: none; } - .Info { - display: flex; - flex-direction: column; - flex-grow: 1; - gap: calc($unit / 2); + .stars { + display: inline-block; + color: #ffa15e; + font-size: $font-xlarge; - h5 { - color: #555; - display: inline-block; - font-size: $font-medium; - font-weight: $medium; - } - - .UncapIndicator { - justify-content: left; - pointer-events: none; - } - - .stars { - display: inline-block; - color: #FFA15E; - font-size: $font-xlarge; - - & > span { - color: #65DAFF; - } - } - - .tags { - display: flex; - flex-direction: row; - gap: calc($unit / 2); - - .WeaponLabelIcon { - $aspect-ratio: calc(25 / 60); - $height: 22px; - background-size: calc($height / $aspect-ratio) $height; - background-repeat: no-repeat; - height: $height; - width: calc($height/ $aspect-ratio); - } - } + & > span { + color: #65daff; + } } -} \ No newline at end of file + + .tags { + display: flex; + flex-direction: row; + gap: calc($unit / 2); + + .WeaponLabelIcon { + $aspect-ratio: calc(25 / 60); + $height: 22px; + background-size: calc($height / $aspect-ratio) $height; + background-repeat: no-repeat; + height: $height; + width: calc($height/ $aspect-ratio); + } + } + } +} diff --git a/components/CharacterResult/index.tsx b/components/CharacterResult/index.tsx index cb6ad816..0cf6190e 100644 --- a/components/CharacterResult/index.tsx +++ b/components/CharacterResult/index.tsx @@ -1,51 +1,54 @@ -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` + const characterUrl = () => { + 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` - } - - return url + if (character.granblue_id === "3030182000") { + url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_01_01.jpg`; } - return ( -
  • - {character.name[locale]} -
    -
    {character.name[locale]}
    - -
    - -
    -
    -
  • - ) -} + return url; + }; -export default CharacterResult \ No newline at end of file + return ( +
  • + {character.name[locale]} +
    +
    {character.name[locale]}
    + +
    + +
    +
    +
  • + ); +}; + +export default CharacterResult; diff --git a/components/CharacterSearchFilterBar/index.tsx b/components/CharacterSearchFilterBar/index.tsx index df451ec5..5f795b5f 100644 --- a/components/CharacterSearchFilterBar/index.tsx +++ b/components/CharacterSearchFilterBar/index.tsx @@ -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(emptyRarityState) - const [elementState, setElementState] = useState(emptyElementState) - const [proficiency1State, setProficiency1State] = useState(emptyProficiencyState) - const [proficiency2State, setProficiency2State] = useState(emptyProficiencyState) + const [rarityState, setRarityState] = useState(emptyRarityState); + const [elementState, setElementState] = + useState(emptyElementState); + const [proficiency1State, setProficiency1State] = useState( + emptyProficiencyState + ); + const [proficiency2State, setProficiency2State] = useState( + emptyProficiencyState + ); - function rarityMenuOpened(open: boolean) { - if (open) { - setRarityMenu(true) - setElementMenu(false) - setProficiency1Menu(false) - setProficiency2Menu(false) - } else setRarityMenu(false) - } + function rarityMenuOpened(open: boolean) { + if (open) { + 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) - } + function elementMenuOpened(open: boolean) { + if (open) { + 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) - } + function proficiency1MenuOpened(open: boolean) { + if (open) { + 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) - } + function proficiency2MenuOpened(open: boolean) { + if (open) { + 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) - } + function handleRarityChange(checked: boolean, key: string) { + 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) - } + function handleElementChange(checked: boolean, key: string) { + 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) - } + function handleProficiency1Change(checked: boolean, key: string) { + 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) - } + function handleProficiency2Change(checked: boolean, key: string) { + 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) + 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 filters = { - rarity: checkedRarityFilters, - element: checkedElementFilters, - proficiency1: checkedProficiency1Filters, - proficiency2: checkedProficiency2Filters - } + const filters = { + rarity: checkedRarityFilters, + element: checkedElementFilters, + proficiency1: checkedProficiency1Filters, + proficiency2: checkedProficiency2Filters, + }; - props.sendFilters(filters) - } + props.sendFilters(filters); + } - useEffect(() => { - sendFilters() - }, [rarityState, elementState, proficiency1State, proficiency2State]) + useEffect(() => { + 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 - - return ( - - {`${t('filters.labels.proficiency')} ${proficiency}`} -
    - - { Array.from(Array(proficiencies.length / 2)).map((x, i) => { - const checked = (proficiency == 1) - ? proficiency1State[proficiencies[i]].checked - : proficiency2State[proficiencies[i]].checked - - return ( - - {t(`proficiencies.${proficiencies[i]}`)} - - )} - ) } - - - { 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 ( - - {t(`proficiencies.${proficiencies[i + (proficiencies.length / 2)]}`)} - - )} - ) } - -
    -
    - ) - } + 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; return ( -
    - x.checked).filter(Boolean).length} open={rarityMenu} onOpenChange={rarityMenuOpened}> - {t('filters.labels.rarity')} - { Array.from(Array(rarities.length)).map((x, i) => { - return ( - - {t(`rarities.${rarities[i]}`)} - - )} - ) } - + + {`${t( + "filters.labels.proficiency" + )} ${proficiency}`} +
    + + {Array.from(Array(proficiencies.length / 2)).map((x, i) => { + const checked = + proficiency == 1 + ? proficiency1State[proficiencies[i]].checked + : proficiency2State[proficiencies[i]].checked; - x.checked).filter(Boolean).length} open={elementMenu} onOpenChange={elementMenuOpened}> - {t('filters.labels.element')} - { Array.from(Array(elements.length)).map((x, i) => { - return ( - - {t(`elements.${elements[i]}`)} - - )} - ) } - + return ( + + {t(`proficiencies.${proficiencies[i]}`)} + + ); + })} + + + {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; - { renderProficiencyFilter(1) } - { renderProficiencyFilter(2) } -
    - ) -} + return ( + + {t( + `proficiencies.${ + proficiencies[i + proficiencies.length / 2] + }` + )} + + ); + })} + + + + ); + } -export default CharacterSearchFilterBar + return ( +
    + x.checked) + .filter(Boolean).length + } + open={rarityMenu} + onOpenChange={rarityMenuOpened} + > + + {t("filters.labels.rarity")} + + {Array.from(Array(rarities.length)).map((x, i) => { + return ( + + {t(`rarities.${rarities[i]}`)} + + ); + })} + + + x.checked) + .filter(Boolean).length + } + open={elementMenu} + onOpenChange={elementMenuOpened} + > + + {t("filters.labels.element")} + + {Array.from(Array(elements.length)).map((x, i) => { + return ( + + {t(`elements.${elements[i]}`)} + + ); + })} + + + {renderProficiencyFilter(1)} + {renderProficiencyFilter(2)} +
    + ); +}; + +export default CharacterSearchFilterBar; diff --git a/components/CharacterUnit/index.scss b/components/CharacterUnit/index.scss index b51bd057..265f0555 100644 --- a/components/CharacterUnit/index.scss +++ b/components/CharacterUnit/index.scss @@ -1,79 +1,78 @@ .CharacterUnit { + display: flex; + flex-direction: column; + gap: calc($unit / 2); + min-height: 320px; + max-width: 200px; + margin-bottom: $unit * 4; + + &.editable .CharacterImage:hover { + border: $hover-stroke; + box-shadow: $hover-shadow; + cursor: pointer; + transform: $scale-tall; + } + + &.filled h3 { + display: block; + } + + &.filled ul { display: flex; - flex-direction: column; - gap: calc($unit / 2); - min-height: 320px; - max-width: 200px; - margin-bottom: $unit * 4; + } - &.editable .CharacterImage:hover { - border: $hover-stroke; - box-shadow: $hover-shadow; - cursor: pointer; - transform: $scale-tall; + h3, + ul { + display: none; + } + + h3 { + color: #333; + font-size: $font-regular; + font-weight: $normal; + line-height: 1.1; + margin: 0; + max-width: 131px; + text-align: center; + word-wrap: normal; + } + + img { + position: relative; + width: 100%; + z-index: 2; + } + + .CharacterImage { + aspect-ratio: 131 / 273; + background: white; + border: 1px solid rgba(0, 0, 0, 0); + border-radius: $unit; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + transition: all 0.18s ease-in-out; + height: auto; + width: 131px; + + @media (max-width: $medium-screen) { + width: 17vw; } - &.filled h3 { - display: block; + &:hover .icon svg { + color: $grey-40; } - &.filled ul { - display: flex; - } - - h3, - ul { - display: none; - } - - h3 { - color: #333; - font-size: $font-regular; - font-weight: $normal; - line-height: 1.1; - margin: 0; - max-width: 131px; - text-align: center; - word-wrap: normal; - } - - img { - position: relative; - width: 100%; - z-index: 2; - } - - - .CharacterImage { - aspect-ratio: 131 / 273; - background: white; - border: 1px solid rgba(0, 0, 0, 0); - border-radius: $unit; - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - transition: all 0.18s ease-in-out; - height: auto; - width: 131px; - - @media (max-width: $medium-screen) { - width: 17vw; - } - - &:hover .icon svg { - color: $grey-40; - } - - .icon { - position: absolute; - height: $unit * 3; - width: $unit * 3; - z-index: 1; - - svg { - fill: $grey-70; - } - } + .icon { + position: absolute; + height: $unit * 3; + width: $unit * 3; + z-index: 1; + + svg { + fill: $grey-70; + } } + } } diff --git a/components/CharacterUnit/index.tsx b/components/CharacterUnit/index.tsx index 13e46635..de02955a 100644 --- a/components/CharacterUnit/index.tsx +++ b/components/CharacterUnit/index.tsx @@ -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) => { "" )} - ) + ); const editableImage = ( { > {image} - ) + ); const unitContent = (
    @@ -123,15 +125,15 @@ const CharacterUnit = (props: Props) => { )}

    {character?.name[locale]}

    - ) + ); const withHovercard = ( {unitContent} - ) + ); - return gridCharacter && !props.editable ? withHovercard : unitContent -} + return gridCharacter && !props.editable ? withHovercard : unitContent; +}; -export default CharacterUnit +export default CharacterUnit; diff --git a/components/ElementToggle/index.scss b/components/ElementToggle/index.scss index f675e16a..6b447f4e 100644 --- a/components/ElementToggle/index.scss +++ b/components/ElementToggle/index.scss @@ -1,64 +1,65 @@ .ToggleGroup { - $height: 36px; + $height: 36px; - border: 1px solid rgba(0, 0, 0, 0.14); - border-radius: $height; - display: flex; - height: $height; - gap: calc($unit / 4); - padding: calc($unit / 2); + border: 1px solid rgba(0, 0, 0, 0.14); + border-radius: $height; + display: flex; + height: $height; + gap: calc($unit / 4); + padding: calc($unit / 2); - .ToggleItem { - background: white; - border: none; - border-radius: 18px; - color: $grey-40; - flex-grow: 1; - font-size: $font-regular; - padding: ($unit) $unit * 2; + .ToggleItem { + background: white; + border: none; + border-radius: 18px; + color: $grey-40; + flex-grow: 1; + font-size: $font-regular; + padding: ($unit) $unit * 2; - &.ja { - padding-top: 6px; - padding-bottom: 10px; - } - - &:hover { - cursor: pointer; - } - - &:hover, &[data-state="on"] { - background:$grey-80; - color: $grey-00; - - &.fire { - background: $fire-bg-light; - color: $fire-text-dark; - } - - &.water { - background: $water-bg-light; - color: $water-text-dark; - } - - &.earth { - background: $earth-bg-light; - color: $earth-text-dark; - } - - &.wind { - background: $wind-bg-light; - color: $wind-text-dark; - } - - &.dark { - background: $dark-bg-light; - color: $dark-text-dark; - } - - &.light { - background: $light-bg-light; - color: $light-text-dark; - } - } + &.ja { + padding-top: 6px; + padding-bottom: 10px; } -} \ No newline at end of file + + &:hover { + cursor: pointer; + } + + &:hover, + &[data-state="on"] { + background: $grey-80; + color: $grey-00; + + &.fire { + background: $fire-bg-light; + color: $fire-text-dark; + } + + &.water { + background: $water-bg-light; + color: $water-text-dark; + } + + &.earth { + background: $earth-bg-light; + color: $earth-text-dark; + } + + &.wind { + background: $wind-bg-light; + color: $wind-text-dark; + } + + &.dark { + background: $dark-bg-light; + color: $dark-text-dark; + } + + &.light { + background: $light-bg-light; + color: $light-text-dark; + } + } + } +} diff --git a/components/ElementToggle/index.tsx b/components/ElementToggle/index.tsx index fe6a9e99..104dd698 100644 --- a/components/ElementToggle/index.tsx +++ b/components/ElementToggle/index.tsx @@ -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 ( - - - {t('elements.null')} - - - {t('elements.wind')} - - - {t('elements.fire')} - - - {t('elements.water')} - - - {t('elements.earth')} - - - {t('elements.dark')} - - - {t('elements.light')} - - - ) -} + return ( + + + {t("elements.null")} + + + {t("elements.wind")} + + + {t("elements.fire")} + + + {t("elements.water")} + + + {t("elements.earth")} + + + {t("elements.dark")} + + + {t("elements.light")} + + + ); +}; -export default ElementToggle \ No newline at end of file +export default ElementToggle; diff --git a/components/ExtraSummons/index.scss b/components/ExtraSummons/index.scss index ac570251..51c0cb02 100644 --- a/components/ExtraSummons/index.scss +++ b/components/ExtraSummons/index.scss @@ -1,55 +1,55 @@ #ExtraSummons { - background: #FFEBD9; - border-radius: 8px; - box-sizing: border-box; + background: #ffebd9; + border-radius: 8px; + box-sizing: border-box; + display: flex; + justify-content: center; + margin: 20px auto; + max-width: 727px; + padding: 16px 16px 16px 0; + position: relative; + left: 9px; + + @media (max-width: $medium-screen) { + left: auto; + max-width: auto; + width: 100%; + } + + & > span { + color: #825b39; display: flex; + align-items: center; justify-content: center; - margin: 20px auto; - max-width: 727px; - padding: 16px 16px 16px 0; - position: relative; - left: 9px; + line-height: 1.2; + font-weight: 500; + margin-right: 16px; + text-align: center; + width: 387px; + } - @media (max-width: $medium-screen) { - left: auto; - max-width: auto; - width: 100%; + #grid_summons { + display: grid; + grid-template-columns: auto auto; + grid-column-gap: $unit * 2; + grid-template-rows: 1fr; + grid-row-gap: $unit * 3; + + & > li { + list-style: none; + min-height: 0; + + .SummonUnit { + min-height: 0; + } } + } - & > span { - color: #825B39; - display: flex; - align-items: center; - justify-content: center; - line-height: 1.2; - font-weight: 500; - margin-right: 16px; - text-align: center; - width: 387px; - } + .SummonUnit .SummonImage { + background: #facea7; + } - #grid_summons { - display: grid; - grid-template-columns: auto auto; - grid-column-gap: $unit * 2; - grid-template-rows: 1fr; - grid-row-gap: $unit * 3; - - & > li { - list-style: none; - min-height: 0; - - .SummonUnit { - min-height: 0; - } - } - } - - .SummonUnit .SummonImage { - background: #facea7; - } - - .SummonUnit .SummonImage .icon svg { - fill: #a8703f; - } -} \ No newline at end of file + .SummonUnit .SummonImage .icon svg { + fill: #a8703f; + } +} diff --git a/components/ExtraSummons/index.tsx b/components/ExtraSummons/index.tsx index c064ba25..4f0d5890 100644 --- a/components/ExtraSummons/index.tsx +++ b/components/ExtraSummons/index.tsx @@ -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 - editable: boolean - exists: boolean - found?: boolean - offset: number - updateObject: (object: SearchableObject, position: number) => void - updateUncap: (id: string, position: number, uncap: number) => void + grid: GridArray; + 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 (
    @@ -36,11 +36,11 @@ const ExtraSummons = (props: Props) => { updateUncap={props.updateUncap} /> - ) + ); })}
    - ) -} + ); +}; -export default ExtraSummons +export default ExtraSummons; diff --git a/components/ExtraWeapons/index.scss b/components/ExtraWeapons/index.scss index eac815e9..524c2a48 100644 --- a/components/ExtraWeapons/index.scss +++ b/components/ExtraWeapons/index.scss @@ -1,47 +1,47 @@ #ExtraGrid { - background: #ECEBFF; - border-radius: 8px; - box-sizing: border-box; + background: #ecebff; + border-radius: 8px; + box-sizing: border-box; + display: flex; + justify-content: center; + margin: 20px auto; + max-width: 766px; + padding: 16px 16px 16px 0; + position: relative; + left: 8px; + + @media (max-width: $medium-screen) { + left: auto; + max-width: auto; + width: 100%; + } + + & > span { + color: #4f3c79; display: flex; + align-items: center; + flex-grow: 1; justify-content: center; - margin: 20px auto; - max-width: 766px; - padding: 16px 16px 16px 0; - position: relative; - left: 8px; + line-height: 1.2; + font-weight: 500; + margin-right: 16px; + text-align: center; + } - @media (max-width: $medium-screen) { - left: auto; - max-width: auto; - width: 100%; - } + .grid_weapons { + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin: 0; + padding: 0; + max-width: 528px; + } - & > span { - color: #4F3C79; - display: flex; - align-items: center; - flex-grow: 1; - justify-content: center; - line-height: 1.2; - font-weight: 500; - margin-right: 16px; - text-align: center; - } + .WeaponUnit .WeaponImage { + background: #d5d3f6; + } - .grid_weapons { - display: flex; - flex-direction: row; - flex-wrap: wrap; - margin: 0; - padding: 0; - max-width: 528px; - } - - .WeaponUnit .WeaponImage { - background: #D5D3F6; - } - - .WeaponUnit .WeaponImage .icon svg { - fill: #8F8AC6; - } -} \ No newline at end of file + .WeaponUnit .WeaponImage .icon svg { + fill: #8f8ac6; + } +} diff --git a/components/ExtraWeapons/index.tsx b/components/ExtraWeapons/index.tsx index f9ed14a0..9ddbb9bd 100644 --- a/components/ExtraWeapons/index.tsx +++ b/components/ExtraWeapons/index.tsx @@ -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 - editable: boolean - found?: boolean - offset: number - updateObject: (object: SearchableObject, position: number) => void - updateUncap: (id: string, position: number, uncap: number) => void + grid: GridArray; + 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 (
    @@ -36,11 +36,11 @@ const ExtraWeapons = (props: Props) => { updateUncap={props.updateUncap} /> - ) + ); })}
    - ) -} + ); +}; -export default ExtraWeapons +export default ExtraWeapons; diff --git a/components/Fieldset/index.scss b/components/Fieldset/index.scss index 99587b40..97cee7ff 100644 --- a/components/Fieldset/index.scss +++ b/components/Fieldset/index.scss @@ -1,33 +1,34 @@ .Fieldset { + border: none; + display: inline-flex; + flex-direction: column; + padding: 0; + margin: 0 0 $unit 0; + + .Input { + -webkit-font-smoothing: antialiased; border: none; - display: inline-flex; - flex-direction: column; - padding: 0; - margin: 0 0 $unit 0; - .Input { - -webkit-font-smoothing: antialiased; - border: none; - - background-color: white; - border-radius: 6px; - box-sizing: border-box; - color: $grey-00; - display: block; - font-size: $font-regular; - padding: 12px 16px; - width: 100%; - } + background-color: white; + border-radius: 6px; + box-sizing: border-box; + color: $grey-00; + display: block; + font-size: $font-regular; + padding: 12px 16px; + width: 100%; + } - .InputError { - color: $error; - font-size: $font-tiny; - margin: $unit 0; - padding: calc($unit / 2) ($unit * 2); - } + .InputError { + color: $error; + font-size: $font-tiny; + margin: $unit 0; + padding: calc($unit / 2) ($unit * 2); + } } -::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */ - color: #a9a9a9 !important; - opacity: 1; /* Firefox */ -} \ No newline at end of file +::placeholder { + /* Chrome, Firefox, Opera, Safari 10.1+ */ + color: #a9a9a9 !important; + opacity: 1; /* Firefox */ +} diff --git a/components/Fieldset/index.tsx b/components/Fieldset/index.tsx index 1eb78e55..25ffeadc 100644 --- a/components/Fieldset/index.tsx +++ b/components/Fieldset/index.tsx @@ -1,38 +1,40 @@ -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) => void - onChange?: (event: React.ChangeEvent) => void + fieldName: string; + placeholder: string; + value?: string; + error: string; + onBlur?: (event: React.ChangeEvent) => void; + onChange?: (event: React.ChangeEvent) => void; } -const Fieldset = React.forwardRef(function fieldSet(props, ref) { - const fieldType = (['password', 'confirm_password'].includes(props.fieldName)) ? 'password' : 'text' +const Fieldset = React.forwardRef(function fieldSet( + props, + ref +) { + const fieldType = ["password", "confirm_password"].includes(props.fieldName) + ? "password" + : "text"; - return ( -
    - - { - props.error.length > 0 && -

    {props.error}

    - } -
    - ) -}) + return ( +
    + + {props.error.length > 0 &&

    {props.error}

    } +
    + ); +}); -export default Fieldset \ No newline at end of file +export default Fieldset; diff --git a/components/FilterBar/index.scss b/components/FilterBar/index.scss index 994a3cf3..38baf662 100644 --- a/components/FilterBar/index.scss +++ b/components/FilterBar/index.scss @@ -1,63 +1,62 @@ .FilterBar { + align-items: center; + background: white; + border-radius: 6px; + display: flex; + flex-direction: row; + gap: $unit * 2; + margin: 0 auto; + margin-top: 7px; // Line up with HeaderMenu + padding: $unit * 2; + position: sticky; + transition: box-shadow 0.24s ease-in-out; + top: $unit * 4; + width: 966px; + + &.shadow { + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.14); + } + + h1 { + color: $grey-20; + font-size: $font-regular; + font-weight: $normal; + flex-grow: 1; + text-align: left; + } + + select { + background: url("/icons/Arrow.svg"), $grey-90; + background-repeat: no-repeat; + background-position-y: center; + background-position-x: 95%; + background-size: $unit * 1.5; + color: $grey-50; + font-size: $font-small; + margin: 0; + max-width: 200px; + } + + .UserInfo { align-items: center; - background: white; - border-radius: 6px; display: flex; flex-direction: row; - gap: $unit * 2; - margin: 0 auto; - margin-top: 7px; // Line up with HeaderMenu - padding: $unit * 2; - position: sticky; - transition: box-shadow 0.24s ease-in-out; - top: $unit * 4; - width: 966px; + flex-grow: 1; + gap: $unit * 1.5; - &.shadow { - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.14); + img { + $diameter: $unit * 6; + border-radius: $diameter / 2; + height: $diameter; + width: $diameter; + + &.gran { + background-color: #cee7fe; + } + + &.djeeta { + background-color: #ffe1fe; + } } - - h1 { - color: $grey-20; - font-size: $font-regular; - font-weight: $normal; - flex-grow: 1; - text-align: left; - } - - select { - background: url('/icons/Arrow.svg'), $grey-90; - background-repeat: no-repeat; - background-position-y: center; - background-position-x: 95%; - background-size: $unit * 1.5; - color: $grey-50; - font-size: $font-small; - margin: 0; - max-width: 200px; - } - - - .UserInfo { - align-items: center; - display: flex; - flex-direction: row; - flex-grow: 1; - gap: $unit * 1.5; - - img { - $diameter: $unit * 6; - border-radius: $diameter / 2; - height: $diameter; - width: $diameter; - - &.gran { - background-color: #CEE7FE; - } - - &.djeeta { - background-color: #FFE1FE; - } - } - } -} \ No newline at end of file + } +} diff --git a/components/FilterBar/index.tsx b/components/FilterBar/index.tsx index f3446142..0642234d 100644 --- a/components/FilterBar/index.tsx +++ b/components/FilterBar/index.tsx @@ -1,79 +1,125 @@ -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 FilterBar = (props: Props) => { + // Set up translation + const { t } = useTranslation("common"); - // Set up refs for filter dropdowns - const elementSelect = React.createRef() - const raidSelect = React.createRef() - const recencySelect = React.createRef() + // Set up refs for filter dropdowns + const elementSelect = React.createRef(); + const raidSelect = React.createRef(); + const recencySelect = React.createRef(); - // Set up classes object for showing shadow on scroll - const classes = classNames({ - 'FilterBar': true, - 'shadow': props.scrolled - }) + // Set up classes object for showing shadow on scroll + const classes = classNames({ + FilterBar: true, + shadow: props.scrolled, + }); - function elementSelectChanged() { - const elementValue = (elementSelect.current) ? parseInt(elementSelect.current.value) : -1 - props.onFilter({ element: elementValue }) - } + function elementSelectChanged() { + 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 }) - } + function recencySelectChanged() { + const recencyValue = recencySelect.current + ? parseInt(recencySelect.current.value) + : -1; + props.onFilter({ recency: recencyValue }); + } - function raidSelectChanged(slug?: string) { - props.onFilter({ raidSlug: slug }) - } + function raidSelectChanged(slug?: string) { + props.onFilter({ raidSlug: slug }); + } - return ( -
    - {props.children} - - - -
    - ) -} + return ( +
    + {props.children} + + + +
    + ); +}; -export default FilterBar +export default FilterBar; diff --git a/components/GridRep/index.scss b/components/GridRep/index.scss index 37b5a10d..88851d77 100644 --- a/components/GridRep/index.scss +++ b/components/GridRep/index.scss @@ -1,148 +1,152 @@ .GridRep { - border-radius: 6px; + border-radius: 6px; + display: flex; + flex-direction: column; + gap: $unit; + padding: $unit * 2; + + &:hover { + background: white; + + h2, + .Grid { + cursor: pointer; + } + + .Grid .weapon { + box-shadow: inset 0 0 0 1px $grey-80; + } + } + + .Grid { + display: flex; + flex-direction: row; + flex-shrink: 0; + + .weapon { + background: white; + border-radius: 4px; + } + + .grid_mainhand { + margin-right: $unit; + height: 139px; + width: 66px; + } + + .grid_weapons { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-template-rows: 1fr 1fr 1fr; + gap: $unit; + margin: 0; + padding: 0; + width: fit-content; + } + + .grid_weapon { + float: left; + height: 40px; + width: 70px; + } + + .grid_mainhand img[src*="jpg"], + .grid_weapon img[src*="jpg"] { + border-radius: 4px; + width: 100%; + height: 100%; + } + } + + .Details { display: flex; flex-direction: column; - gap: $unit; - padding: $unit * 2; + gap: calc($unit / 2); - &:hover { - background: white; + h2 { + color: $grey-00; + font-size: $font-regular; + overflow: hidden; + padding-bottom: 1px; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 258px; // Can we not do this? - h2, .Grid { - cursor: pointer; - } - - .Grid .weapon { - box-shadow: inset 0 0 0 1px $grey-80; - } + &.empty { + color: $grey-50; + } } - .Grid { - display: flex; - flex-direction: row; - flex-shrink: 0; + .top { + display: flex; + flex-direction: row; + gap: calc($unit / 2); + align-items: center; - .weapon { - background: white; - border-radius: 4px; - } - - .grid_mainhand { - margin-right: $unit; - height: 139px; - width: 66px; - } - - .grid_weapons { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - grid-template-rows: 1fr 1fr 1fr; - gap: $unit; - margin: 0; - padding: 0; - width: fit-content; - } - - .grid_weapon { - float: left; - height: 40px; - width: 70px; - } - - .grid_mainhand img[src*="jpg"], - .grid_weapon img[src*="jpg"] { - border-radius: 4px; - width: 100%; - height: 100%; - } - } - - .Details { + .info { display: flex; flex-direction: column; + flex-grow: 1; gap: calc($unit / 2); + } - h2 { - color: $grey-00; - font-size: $font-regular; - overflow: hidden; - padding-bottom: 1px; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 258px; // Can we not do this? + button svg { + width: 14px; + height: 14px; + } - &.empty { - color: $grey-50; - } - } - - .top { - display: flex; - flex-direction: row; - gap: calc($unit / 2); - align-items: center; - - .info { - display: flex; - flex-direction: column; - flex-grow: 1; - gap: calc($unit / 2); - } - - button svg { - width: 14px; - height: 14px; - } - - button:hover, - button.Active { - background: $grey-90; - } - } - - .bottom { - display: flex; - flex-direction: row; - } - - .raid, .user, time { - color: $grey-50; - font-size: $font-small; - } - - .raid, .user { - flex-grow: 1; - } - - .raid { - margin-bottom: calc($unit / 2); - } - - .user { - display: flex; - gap: calc($unit / 2); - align-items: center; - - - img, .no-user { - $diameter: 18px; - - border-radius: calc($diameter / 2); - height: $diameter; - width: $diameter; - } - - img.gran { - background-color: #CEE7FE; - } - - img.djeeta { - background-color: #FFE1FE; - } - - .no-user { - background: $grey-80; - } - } + button:hover, + button.Active { + background: $grey-90; + } } + + .bottom { + display: flex; + flex-direction: row; + } + + .raid, + .user, + time { + color: $grey-50; + font-size: $font-small; + } + + .raid, + .user { + flex-grow: 1; + } + + .raid { + margin-bottom: calc($unit / 2); + } + + .user { + display: flex; + gap: calc($unit / 2); + align-items: center; + + img, + .no-user { + $diameter: 18px; + + border-radius: calc($diameter / 2); + height: $diameter; + width: $diameter; + } + + img.gran { + background-color: #cee7fe; + } + + img.djeeta { + background-color: #ffe1fe; + } + + .no-user { + background: $grey-80; + } + } + } } diff --git a/components/GridRep/index.tsx b/components/GridRep/index.tsx index ff9fe507..2febb9ea 100644 --- a/components/GridRep/index.tsx +++ b/components/GridRep/index.tsx @@ -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() - const [weapons, setWeapons] = useState>({}) - const [grid, setGrid] = useState>({}) + const [mainhand, setMainhand] = useState(); + const [weapons, setWeapons] = useState>({}); + const [grid, setGrid] = useState>({}); 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) => { {mainhand.name[locale]} ) : ( "" - ) + ); } 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) => { {weapons[position]?.name[locale]} ) : ( "" - ) + ); } 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
    - } + ); + } else return
    ; + }; const details = (
    @@ -145,7 +147,7 @@ const GridRep = (props: Props) => {
    - ) + ); const detailsWithUsername = (
    @@ -181,7 +183,7 @@ const GridRep = (props: Props) => {
    - ) + ); return (
    @@ -198,12 +200,12 @@ const GridRep = (props: Props) => { > {generateGridImage(i)} - ) + ); })}
    - ) -} + ); +}; -export default GridRep +export default GridRep; diff --git a/components/GridRepCollection/index.tsx b/components/GridRepCollection/index.tsx index 77592ffd..93332c6a 100644 --- a/components/GridRepCollection/index.tsx +++ b/components/GridRepCollection/index.tsx @@ -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
    {props.children}
    -} + return
    {props.children}
    ; +}; -export default GridRepCollection +export default GridRepCollection; diff --git a/components/Header/index.scss b/components/Header/index.scss index f4b2ff11..d3c1eeff 100644 --- a/components/Header/index.scss +++ b/components/Header/index.scss @@ -1,37 +1,37 @@ .Header { + display: flex; + height: 34px; + width: 100%; + + &.bottom { + position: sticky; + bottom: $unit * 2; + } + + #right > div { display: flex; - height: 34px; - width: 100%; + gap: 8px; + } - &.bottom { - position: sticky; - bottom: $unit * 2; + .dropdown { + display: inline-block; + position: relative; + + &:hover { + padding-right: 50px; + padding-bottom: 16px; + + .Button { + background: white; + } + + .Menu { + display: block; + } } + } - #right > div { - display: flex; - gap: 8px; - } - - .dropdown { - display: inline-block; - position: relative; - - &:hover { - padding-right: 50px; - padding-bottom: 16px; - - .Button { - background: white; - } - - .Menu { - display: block; - } - } - } - - .push { - margin-left: auto; - } + .push { + margin-left: auto; + } } diff --git a/components/Header/index.tsx b/components/Header/index.tsx index d82d83e3..29663b38 100644 --- a/components/Header/index.tsx +++ b/components/Header/index.tsx @@ -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 ( - - ) -} +const Header = (props: Props) => { + return ( + + ); +}; -export default Header \ No newline at end of file +export default Header; diff --git a/components/HeaderMenu/index.scss b/components/HeaderMenu/index.scss index 1d3d501d..8047153e 100644 --- a/components/HeaderMenu/index.scss +++ b/components/HeaderMenu/index.scss @@ -1,147 +1,149 @@ .Menu { - background: white; - border-radius: 6px; - display: none; - min-width: 220px; - position: absolute; - top: $unit * 5; // This shouldn't be hardcoded. How to calculate it? - z-index: 10; + background: white; + border-radius: 6px; + display: none; + min-width: 220px; + position: absolute; + top: $unit * 5; // This shouldn't be hardcoded. How to calculate it? + z-index: 10; } .MenuItem { - color: $grey-40; - font-weight: $normal; + color: $grey-40; + font-weight: $normal; - &:hover:not(.disabled) { - background: $grey-100; - color: $grey-00; - cursor: pointer; - - a { - color: $grey-00; - } - } - - &.profile > div { - padding: 6px 12px; - } - - &.language { - align-items: center; - display: flex; - flex-direction: row; - gap: $unit; - padding-right: $unit; - - span { - flex-grow: 1; - } - - .Switch { - $height: 24px; - - background: $grey-60; - border-radius: calc($height / 2); - border: none; - position: relative; - width: 44px; - height: $height; - - &:hover { - cursor: pointer; - } - - .Thumb { - $diameter: 18px; - - background: white; - border-radius: calc($diameter / 2); - display: block; - height: $diameter; - width: $diameter; - transition: transform 100ms; - transform: translateX(-2px); - z-index: 3; - - &:hover { - cursor: pointer; - } - - &[data-state="checked"] { - background: white; - transform: translateX(17px); - } - } - - .left, .right { - color: white; - font-size: 10px; - font-weight: $bold; - position: absolute; - z-index: 2; - } - - .left { - top: 6px; - left: 6px; - } - - .right { - top: 6px; - right: 5px; - } - } - } + &:hover:not(.disabled) { + background: $grey-100; + color: $grey-00; + cursor: pointer; a { - color: $grey-40; + color: $grey-00; + } + } + + &.profile > div { + padding: 6px 12px; + } + + &.language { + align-items: center; + display: flex; + flex-direction: row; + gap: $unit; + padding-right: $unit; + + span { + flex-grow: 1; } - & > a, & > span { + .Switch { + $height: 24px; + + background: $grey-60; + border-radius: calc($height / 2); + border: none; + position: relative; + width: 44px; + height: $height; + + &:hover { + cursor: pointer; + } + + .Thumb { + $diameter: 18px; + + background: white; + border-radius: calc($diameter / 2); display: block; - padding: 12px 12px; - } - - & > div { - align-items: center; - display: flex; - flex-direction: row; - padding: 10px 12px; + height: $diameter; + width: $diameter; + transition: transform 100ms; + transform: translateX(-2px); + z-index: 3; &:hover { - i.tag { - background: $grey-60; - color: white; - } + cursor: pointer; } - span { - flex-grow: 1; + &[data-state="checked"] { + background: white; + transform: translateX(17px); } + } - img { - $diameter: 32px; - border-radius: calc($diameter / 2); - height: $diameter; - width: $diameter; - } + .left, + .right { + color: white; + font-size: 10px; + font-weight: $bold; + position: absolute; + z-index: 2; + } + + .left { + top: 6px; + left: 6px; + } + + .right { + top: 6px; + right: 5px; + } } + } + + a { + color: $grey-40; + } + + & > a, + & > span { + display: block; + padding: 12px 12px; + } + + & > div { + align-items: center; + display: flex; + flex-direction: row; + padding: 10px 12px; + + &:hover { + i.tag { + background: $grey-60; + color: white; + } + } + + span { + flex-grow: 1; + } + + img { + $diameter: 32px; + border-radius: calc($diameter / 2); + height: $diameter; + width: $diameter; + } + } } .MenuGroup { - border-bottom: 1px solid #f5f5f5; + border-bottom: 1px solid #f5f5f5; - &:first-child .MenuItem:first-child:hover { - border-top-left-radius: 6px; - border-top-right-radius: 6px; - } + &:first-child .MenuItem:first-child:hover { + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } - &:last-child .MenuItem:last-child:hover { - border-bottom-left-radius: 6px; - border-bottom-right-radius: 6px; - } + &:last-child .MenuItem:last-child:hover { + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } - &:last-child { - border-bottom: none; - } -} \ No newline at end of file + &:last-child { + border-bottom: none; + } +} diff --git a/components/HeaderMenu/index.tsx b/components/HeaderMenu/index.tsx index 622c8c2a..1bf30e2d 100644 --- a/components/HeaderMenu/index.tsx +++ b/components/HeaderMenu/index.tsx @@ -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) => { - ) + ); } function unauthItems() { @@ -132,10 +132,10 @@ const HeaderMenu = (props: Props) => { - ) + ); } - return props.authenticated ? authItems() : unauthItems() -} + return props.authenticated ? authItems() : unauthItems(); +}; -export default HeaderMenu +export default HeaderMenu; diff --git a/components/JobDropdown/index.tsx b/components/JobDropdown/index.tsx index 179034f4..b4a0989c 100644 --- a/components/JobDropdown/index.tsx +++ b/components/JobDropdown/index.tsx @@ -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) => void + currentJob?: string; + onChange?: (job?: Job) => void; + onBlur?: (event: React.ChangeEvent) => void; } -type GroupedJob = { [key: string]: Job[] } +type GroupedJob = { [key: string]: Job[] }; const JobDropdown = React.forwardRef( 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() - const [jobs, setJobs] = useState() - const [sortedJobs, setSortedJobs] = useState() + const [currentJob, setCurrentJob] = useState(); + const [jobs, setJobs] = useState(); + const [sortedJobs, setSortedJobs] = useState(); // 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) { 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( - ) - }) + ); + }); - const groupName = jobGroups.find((g) => g.slug === group)?.name[locale] + const groupName = jobGroups.find((g) => g.slug === group)?.name[locale]; return ( {options} - ) + ); } return ( @@ -106,8 +106,8 @@ const JobDropdown = React.forwardRef( ? Object.keys(sortedJobs).map((x) => renderJobGroup(x)) : ""} - ) + ); } -) +); -export default JobDropdown +export default JobDropdown; diff --git a/components/JobSection/index.tsx b/components/JobSection/index.tsx index 55ea8234..3d0f61a0 100644 --- a/components/JobSection/index.tsx +++ b/components/JobSection/index.tsx @@ -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() - const [imageUrl, setImageUrl] = useState("") - const [numSkills, setNumSkills] = useState(4) + const [job, setJob] = useState(); + const [imageUrl, setImageUrl] = useState(""); + const [numSkills, setNumSkills] = useState(4); const [skills, setSkills] = useState<{ [key: number]: JobSkill | undefined }>( [] - ) + ); - const selectRef = React.createRef() + const selectRef = React.createRef(); 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)} - ) - } + ); + }; 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) => { - ) -} + ); +}; -export default JobSection +export default JobSection; diff --git a/components/JobSkillItem/index.tsx b/components/JobSkillItem/index.tsx index 52a1f191..0f6e2031 100644 --- a/components/JobSkillItem/index.tsx +++ b/components/JobSkillItem/index.tsx @@ -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( 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( className={imageClasses} src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-skills/${props.skill.slug}.png`} /> - ) + ); } else { jsx = (
    {props.editable && props.hasJob ? : ""}
    - ) + ); } - return jsx - } + return jsx; + }; const label = () => { - let jsx: React.ReactNode + let jsx: React.ReactNode; if (props.skill) { - jsx =

    {props.skill.name[locale]}

    + jsx =

    {props.skill.name[locale]}

    ; } else if (props.editable && props.hasJob) { - jsx =

    {t("job_skills.state.selectable")}

    + jsx =

    {t("job_skills.state.selectable")}

    ; } else { - jsx =

    {t("job_skills.state.no_skill")}

    + jsx =

    {t("job_skills.state.no_skill")}

    ; } - return jsx - } + return jsx; + }; return (
    {skillImage()} {label()}
    - ) + ); } -) +); -export default JobSkillItem +export default JobSkillItem; diff --git a/components/JobSkillResult/index.tsx b/components/JobSkillResult/index.tsx index 09c08028..d04653fd 100644 --- a/components/JobSkillResult/index.tsx +++ b/components/JobSkillResult/index.tsx @@ -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() + const [group, setGroup] = useState(); 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 (
  • @@ -35,7 +37,7 @@ const JobSkillResult = (props: Props) => {
  • - ) -} + ); +}; -export default JobSkillResult +export default JobSkillResult; diff --git a/components/JobSkillSearchFilterBar/index.tsx b/components/JobSkillSearchFilterBar/index.tsx index 1de78add..7151ad4f 100644 --- a/components/JobSkillSearchFilterBar/index.tsx +++ b/components/JobSkillSearchFilterBar/index.tsx @@ -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) { - setCurrentGroup(parseInt(event.target.value)) + setCurrentGroup(parseInt(event.target.value)); } function onBlur(event: React.ChangeEvent) {} @@ -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 (
    @@ -65,7 +65,7 @@ const JobSkillSearchFilterBar = (props: Props) => {
    - ) -} + ); +}; -export default JobSkillSearchFilterBar +export default JobSkillSearchFilterBar; diff --git a/components/Layout/index.tsx b/components/Layout/index.tsx index 5d03fb7c..a8de5056 100644 --- a/components/Layout/index.tsx +++ b/components/Layout/index.tsx @@ -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 ( <>
    {children}
    - ) -} + ); +}; -export default Layout \ No newline at end of file +export default Layout; diff --git a/components/LoginModal/index.scss b/components/LoginModal/index.scss index f77633b8..d6174992 100644 --- a/components/LoginModal/index.scss +++ b/components/LoginModal/index.scss @@ -1,31 +1,31 @@ .Login.Dialog form { - display: flex; - flex-direction: column; - gap: calc($unit / 2); - margin-bottom: $unit; + display: flex; + flex-direction: column; + gap: calc($unit / 2); + margin-bottom: $unit; - .Button { - font-size: $font-regular; - padding: ($unit * 1.5) ($unit * 2); - width: 100%; + .Button { + font-size: $font-regular; + padding: ($unit * 1.5) ($unit * 2); + width: 100%; - &.btn-disabled { - background: $grey-90; - color: $grey-70; - cursor: not-allowed; - } - - &:not(.btn-disabled) { - background: $grey-90; - color: $grey-40; - - &:hover { - background: $grey-80; - } - } + &.btn-disabled { + background: $grey-90; + color: $grey-70; + cursor: not-allowed; } - input { - background: $grey-90; + &:not(.btn-disabled) { + background: $grey-90; + color: $grey-40; + + &:hover { + background: $grey-80; + } } -} \ No newline at end of file + } + + input { + background: $grey-90; + } +} diff --git a/components/LoginModal/index.tsx b/components/LoginModal/index.tsx index de5f5443..d9abdfdc 100644 --- a/components/LoginModal/index.tsx +++ b/components/LoginModal/index.tsx @@ -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({ email: "", password: "", - }) + }); // States - const [open, setOpen] = useState(false) + const [open, setOpen] = useState(false); // Set up form refs - const emailInput: React.RefObject = React.createRef() - const passwordInput: React.RefObject = React.createRef() - const form: React.RefObject[] = [emailInput, passwordInput] + const emailInput: React.RefObject = React.createRef(); + const passwordInput: React.RefObject = React.createRef(); + const form: React.RefObject[] = [emailInput, passwordInput]; function handleChange(event: React.ChangeEvent) { - 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) => { - ) -} + ); +}; -export default LoginModal +export default LoginModal; diff --git a/components/Party/index.scss b/components/Party/index.scss index 47c0c0f6..c6b14b47 100644 --- a/components/Party/index.scss +++ b/components/Party/index.scss @@ -1,7 +1,7 @@ #Party .Extra { - color: #888; - display: flex; - font-weight: 500; - gap: 8px; - line-height: 34px; -} \ No newline at end of file + color: #888; + display: flex; + font-weight: 500; + gap: 8px; + line-height: 34px; +} diff --git a/components/Party/index.tsx b/components/Party/index.tsx index 677a2ec8..a0107446 100644 --- a/components/Party/index.tsx +++ b/components/Party/index.tsx @@ -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.Weapon) + const { party } = useSnapshot(appState); + const [currentTab, setCurrentTab] = useState(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) { - 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) => { list.forEach((object: GridCharacter) => { if (object.position != null) - appState.grid.characters[object.position] = object - }) - } + appState.grid.characters[object.position] = object; + }); + }; const storeWeapons = (list: Array) => { 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) => { 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) { 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 = ( { createParty={createParty} pushHistory={props.pushHistory} /> - ) + ); const summonGrid = ( { createParty={createParty} pushHistory={props.pushHistory} /> - ) + ); const characterGrid = ( { 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 (
    @@ -264,7 +264,7 @@ const Party = (props: Props) => { /> }
    - ) -} + ); +}; -export default Party +export default Party; diff --git a/components/PartyDetails/index.scss b/components/PartyDetails/index.scss index 50f88ffb..be6c260d 100644 --- a/components/PartyDetails/index.scss +++ b/components/PartyDetails/index.scss @@ -1,153 +1,150 @@ .PartyDetails { - display: none; // This breaks transition, find a workaround - opacity: 0; - margin: 0 auto; - margin-bottom: 100px; - max-width: $unit * 95; - position: relative; + display: none; // This breaks transition, find a workaround + opacity: 0; + margin: 0 auto; + margin-bottom: 100px; + max-width: $unit * 95; + position: relative; - &.Editable { - top: $unit; - height: 0; - z-index: 2; - transition: opacity 0.2s ease-in-out, - top 0.2s ease-in-out; + &.Editable { + top: $unit; + height: 0; + z-index: 2; + transition: opacity 0.2s ease-in-out, top 0.2s ease-in-out; - - &.Visible { - display: block; - height: auto; - margin-bottom: 40vh; - opacity: 1; - top: 0; - } - - fieldset { - display: block; - width: 100%; - - textarea { - min-height: $unit * 20; - width: 100%; - } - } - - .bottom { - display: flex; - flex-direction: row; - gap: $unit; - - .left { - flex-grow: 1; - } - - .right { - display: flex; - flex-direction: row; - gap: $unit; - } - } + &.Visible { + display: block; + height: auto; + margin-bottom: 40vh; + opacity: 1; + top: 0; } - &.ReadOnly { - top: $unit * -1; - transition: opacity 0.2s ease-in-out, - top 0.2s ease-in-out; + fieldset { + display: block; + width: 100%; - &.Visible { - display: block; - height: auto; - opacity: 1; - top: 0; - } - - a:hover { - text-decoration: underline; - } - - p { - font-size: $font-regular; - line-height: $font-regular * 1.2; - white-space: pre-line; - } - - - h1 { - font-size: $font-xlarge; - font-weight: $normal; - text-align: left; - margin-bottom: $unit; - } - - .info { - align-items: center; - display: flex; - flex-direction: row; - gap: $unit; - margin-bottom: $unit * 2; - - .left { - flex-grow: 1; - } - } - - .attribution { - align-items: center; - display: flex; - flex-direction: row; - - & > div { - align-items: center; - display: inline-flex; - font-size: $font-small; - height: 26px; - } - - time { - font-size: $font-small; - } - - & > *:not(:last-child):after { - content: " · "; - margin: 0 calc($unit / 2); - } - } - - .user { - align-items: center; - display: inline-flex; - gap: calc($unit / 2); - margin-top: 1px; - - img, .no-user { - $diameter: 24px; - - border-radius: calc($diameter / 2); - height: $diameter; - width: $diameter; - } - - img.gran { - background-color: #CEE7FE; - } - - img.djeeta { - background-color: #FFE1FE; - } - - .no-user { - background: $grey-80; - } - } + textarea { + min-height: $unit * 20; + width: 100%; + } } + + .bottom { + display: flex; + flex-direction: row; + gap: $unit; + + .left { + flex-grow: 1; + } + + .right { + display: flex; + flex-direction: row; + gap: $unit; + } + } + } + + &.ReadOnly { + top: $unit * -1; + transition: opacity 0.2s ease-in-out, top 0.2s ease-in-out; + + &.Visible { + display: block; + height: auto; + opacity: 1; + top: 0; + } + + a:hover { + text-decoration: underline; + } + + p { + font-size: $font-regular; + line-height: $font-regular * 1.2; + white-space: pre-line; + } + + h1 { + font-size: $font-xlarge; + font-weight: $normal; + text-align: left; + margin-bottom: $unit; + } + + .info { + align-items: center; + display: flex; + flex-direction: row; + gap: $unit; + margin-bottom: $unit * 2; + + .left { + flex-grow: 1; + } + } + + .attribution { + align-items: center; + display: flex; + flex-direction: row; + + & > div { + align-items: center; + display: inline-flex; + font-size: $font-small; + height: 26px; + } + + time { + font-size: $font-small; + } + + & > *:not(:last-child):after { + content: " · "; + margin: 0 calc($unit / 2); + } + } + + .user { + align-items: center; + display: inline-flex; + gap: calc($unit / 2); + margin-top: 1px; + + img, + .no-user { + $diameter: 24px; + + border-radius: calc($diameter / 2); + height: $diameter; + width: $diameter; + } + + img.gran { + background-color: #cee7fe; + } + + img.djeeta { + background-color: #ffe1fe; + } + + .no-user { + background: $grey-80; + } + } + } } .EmptyDetails { - display: none; - justify-content: center; - margin-bottom: $unit * 10; + display: none; + justify-content: center; + margin-bottom: $unit * 10; - &.Visible { - display: flex; - } -} \ No newline at end of file + &.Visible { + display: flex; + } +} diff --git a/components/PartyDetails/index.tsx b/components/PartyDetails/index.tsx index b432f127..3f94a598 100644 --- a/components/PartyDetails/index.tsx +++ b/components/PartyDetails/index.tsx @@ -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 - ) => 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() - const descriptionInput = React.createRef() - const raidSelect = React.createRef() + const nameInput = React.createRef(); + const descriptionInput = React.createRef(); + const raidSelect = React.createRef(); 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) { - 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) { - 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
    - } + ); + else return
    ; + }; const userBlock = () => { return ( @@ -142,8 +142,8 @@ const PartyDetails = (props: Props) => { {userImage()} {party.user ? party.user.username : t("no_user")}
    - ) - } + ); + }; const linkedUserBlock = (user: User) => { return ( @@ -152,8 +152,8 @@ const PartyDetails = (props: Props) => { {userBlock()}
    - ) - } + ); + }; const linkedRaidBlock = (raid: Raid) => { return ( @@ -162,8 +162,8 @@ const PartyDetails = (props: Props) => { {raid.name[locale]} - ) - } + ); + }; const deleteButton = () => { if (party.editable) { @@ -198,11 +198,11 @@ const PartyDetails = (props: Props) => { - ) + ); } else { - return "" + return ""; } - } + }; const editable = (
    @@ -246,7 +246,7 @@ const PartyDetails = (props: Props) => {
    - ) + ); const readOnly = (
    @@ -286,7 +286,7 @@ const PartyDetails = (props: Props) => { "" )}
    - ) + ); const emptyDetails = (
    @@ -298,25 +298,28 @@ const PartyDetails = (props: Props) => {
    )}
    - ) + ); 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 (
    @@ -344,7 +347,7 @@ const PartyDetails = (props: Props) => { : emptyDetails} {editable}
    - ) -} + ); +}; -export default PartyDetails +export default PartyDetails; diff --git a/components/PartySegmentedControl/index.scss b/components/PartySegmentedControl/index.scss index a161a3ca..c4991d17 100644 --- a/components/PartySegmentedControl/index.scss +++ b/components/PartySegmentedControl/index.scss @@ -1,20 +1,20 @@ .PartyNavigation { - display: flex; - gap: 58px; - justify-content: center; - margin: 0 auto; - margin-bottom: $unit * 3; - max-width: 760px; - position: relative; + display: flex; + gap: 58px; + justify-content: center; + margin: 0 auto; + margin-bottom: $unit * 3; + max-width: 760px; + position: relative; } .ExtraSwitch { - color: #888; - display: flex; - font-weight: $normal; - gap: 8px; - line-height: 34px; - height: 100%; - position: absolute; - right: 0px; -} \ No newline at end of file + color: #888; + display: flex; + font-weight: $normal; + gap: 8px; + line-height: 34px; + height: 100%; + position: absolute; + right: 0px; +} diff --git a/components/PartySegmentedControl/index.tsx b/components/PartySegmentedControl/index.tsx index 5a67e268..85657fd4 100644 --- a/components/PartySegmentedControl/index.tsx +++ b/components/PartySegmentedControl/index.tsx @@ -1,98 +1,113 @@ -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) => void - onCheckboxChange: (event: React.ChangeEvent) => void + selectedTab: GridType; + onClick: (event: React.ChangeEvent) => void; + onCheckboxChange: (event: React.ChangeEvent) => 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 - if (party.element == 0 && grid.weapons.mainWeapon) - element = grid.weapons.mainWeapon.element - else - element = party.element + function getElement() { + let element: number = 0; + if (party.element == 0 && grid.weapons.mainWeapon) + 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 = -
    - Extra - -
    + const extraToggle = ( +
    + Extra + +
    + ); - return ( -
    - - {/* + + {/* Class */} - {t('party.segmented_control.characters')} + + {t("party.segmented_control.characters")} + - {t('party.segmented_control.weapons')} + + {t("party.segmented_control.weapons")} + - {t('party.segmented_control.summons')} - + + {t("party.segmented_control.summons")} + + - { - (() => { - if (party.editable && props.selectedTab == GridType.Weapon) { - return extraToggle - } - })() - } -
    - ) -} + {(() => { + if (party.editable && props.selectedTab == GridType.Weapon) { + return extraToggle; + } + })()} +
    + ); +}; -export default PartySegmentedControl +export default PartySegmentedControl; diff --git a/components/RaidDropdown/index.tsx b/components/RaidDropdown/index.tsx index a8202e81..1e430d7c 100644 --- a/components/RaidDropdown/index.tsx +++ b/components/RaidDropdown/index.tsx @@ -1,112 +1,133 @@ -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) => void + showAllRaidsOption: boolean; + currentRaid?: string; + onChange?: (slug?: string) => void; + onBlur?: (event: React.ChangeEvent) => void; } -const RaidDropdown = React.forwardRef(function useFieldSet(props, ref) { +const RaidDropdown = React.forwardRef( + 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() - const [raids, setRaids] = useState() - const [sortedRaids, setSortedRaids] = useState() + const [currentRaid, setCurrentRaid] = useState(); + const [raids, setRaids] = useState(); + const [sortedRaids, setSortedRaids] = useState(); // 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', - name: { - en: 'All raids', - ja: '全て' - }, - slug: 'all', - level: 0, - group: 0, - element: 0 - } - - const numGroups = Math.max.apply(Math, raids.map(raid => raid.group)) - let groupedRaids = [] + id: "0", + name: { + en: "All raids", + ja: "全て", + }, + slug: "all", + level: 0, + group: 0, + element: 0, + }; + + 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) - } - }, [raids, props.currentRaid]) + if (raids && props.currentRaid) { + const raid = raids.find((raid) => raid.slug === props.currentRaid); + setCurrentRaid(raid); + } + }, [raids, props.currentRaid]); // Enable changing select value function handleChange(event: React.ChangeEvent) { - 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) - } + if (raids) { + 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) => { - return ( - - ) - }) - - return ( - - {options} - - ) - } - - return ( - - ) -}) + const options = + sortedRaids && + sortedRaids.length > 0 && + sortedRaids[index].length > 0 && + sortedRaids[index] + .sort((a, b) => a.element - b.element) + .map((item, i) => { + return ( + + ); + }); -export default RaidDropdown + return ( + + {options} + + ); + } + + return ( + + ); + } +); + +export default RaidDropdown; diff --git a/components/SearchFilter/index.scss b/components/SearchFilter/index.scss index 0c1bf29e..bf764414 100644 --- a/components/SearchFilter/index.scss +++ b/components/SearchFilter/index.scss @@ -1,74 +1,74 @@ .DropdownLabel { - align-items: center; - background: $grey-90; - border: none; - border-radius: $unit * 2; - color: $grey-40; - display: flex; - gap: calc($unit / 2); - flex-direction: row; - padding: ($unit) ($unit * 2); + align-items: center; + background: $grey-90; + border: none; + border-radius: $unit * 2; + color: $grey-40; + display: flex; + gap: calc($unit / 2); + flex-direction: row; + padding: ($unit) ($unit * 2); - &:hover { - background: $grey-80; - color: $grey-00; - cursor: pointer; - } - - .count { - color: $grey-60; - font-weight: $medium; - } - - & > .icon { - $diameter: 12px; - height: $diameter; - width: $diameter; - - svg { - transform: scale(0.85); - - path { - fill: $grey-60; - } - } + &:hover { + background: $grey-80; + color: $grey-00; + cursor: pointer; + } + + .count { + color: $grey-60; + font-weight: $medium; + } + + & > .icon { + $diameter: 12px; + height: $diameter; + width: $diameter; + + svg { + transform: scale(0.85); + + path { + fill: $grey-60; + } } + } } .Dropdown { - background: white; - border-radius: $unit; - box-shadow: 0 0 2px rgba(0, 0, 0, 0.18); + background: white; + border-radius: $unit; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.18); + display: flex; + flex-direction: column; + gap: calc($unit / 2); + padding: $unit; + min-width: 120px; + + & > span { + overflow: hidden; + + svg { + fill: white; + filter: drop-shadow(0px 0px 1px rgb(0 0 0 / 0.18)); + } + } + + section { display: flex; + flex-direction: row; + gap: $unit; + } + + .Group { + flex: 1 1 0px; flex-direction: column; - gap: calc($unit / 2); - padding: $unit; - min-width: 120px; + } - & > span { - overflow: hidden; - - svg { - fill: white; - filter: drop-shadow(0px 0px 1px rgb(0 0 0 / 0.18)); - } - } - - section { - display: flex; - flex-direction: row; - gap: $unit; - } - - .Group { - flex: 1 1 0px; - flex-direction: column; - } - - .Label { - color: $grey-60; - font-size: $font-small; - margin-bottom: calc($unit / 2); - padding-left: calc($unit / 2); - } -} \ No newline at end of file + .Label { + color: $grey-60; + font-size: $font-small; + margin-bottom: calc($unit / 2); + padding-left: calc($unit / 2); + } +} diff --git a/components/SearchFilter/index.tsx b/components/SearchFilter/index.tsx index 8d6678fb..b64bf874 100644 --- a/components/SearchFilter/index.tsx +++ b/components/SearchFilter/index.tsx @@ -1,34 +1,34 @@ -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) => { - return ( - - - {props.label} - {props.numSelected} - - - - - - {props.children} - - - - ) -} + return ( + + + {props.label} + {props.numSelected} + + + + + + {props.children} + + + + ); +}; -export default SearchFilter +export default SearchFilter; diff --git a/components/SearchFilterCheckboxItem/index.scss b/components/SearchFilterCheckboxItem/index.scss index 7a951781..bd73abe2 100644 --- a/components/SearchFilterCheckboxItem/index.scss +++ b/components/SearchFilterCheckboxItem/index.scss @@ -1,41 +1,41 @@ .Item { + align-items: center; + border-radius: calc($unit / 2); + color: $grey-40; + font-size: $font-regular; + line-height: 1.2; + min-width: 100px; + position: relative; + padding: $unit; + padding-left: $unit * 3; + + &:hover { + background: $grey-90; + cursor: pointer; + } + + &[data-state="checked"] { + background: $grey-90; + + svg { + fill: $grey-50; + } + } + + .Indicator { + $diameter: 18px; + + display: flex; align-items: center; - border-radius: calc($unit / 2); - color: $grey-40; - font-size: $font-regular; - line-height: 1.2; - min-width: 100px; - position: relative; - padding: $unit; - padding-left: $unit * 3; + justify-content: center; + position: absolute; + left: calc($unit / 2); + height: $diameter; + width: $diameter; - &:hover { - background: $grey-90; - cursor: pointer; + svg { + height: $diameter; + width: $diameter; } - - &[data-state="checked"] { - background: $grey-90; - - svg { - fill: $grey-50; - } - } - - .Indicator { - $diameter: 18px; - - display: flex; - align-items: center; - justify-content: center; - position: absolute; - left: calc($unit / 2); - height: $diameter; - width: $diameter; - - svg { - height: $diameter; - width: $diameter; - } - } -} \ No newline at end of file + } +} diff --git a/components/SearchFilterCheckboxItem/index.tsx b/components/SearchFilterCheckboxItem/index.tsx index b6429fec..f8d4e28a 100644 --- a/components/SearchFilterCheckboxItem/index.tsx +++ b/components/SearchFilterCheckboxItem/index.tsx @@ -1,34 +1,35 @@ -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) - } + function handleCheckedChange(checked: boolean) { + props.onCheckedChange(checked, props.valueKey); + } - return ( - event.preventDefault() }> - - - - {props.children} - - ) -} + return ( + event.preventDefault()} + > + + + + {props.children} + + ); +}; -export default SearchFilterCheckboxItem +export default SearchFilterCheckboxItem; diff --git a/components/SearchModal/index.scss b/components/SearchModal/index.scss index 35862d4f..defd700f 100644 --- a/components/SearchModal/index.scss +++ b/components/SearchModal/index.scss @@ -1,98 +1,98 @@ .Search.Dialog { + display: flex; + flex-direction: column; + min-height: 431px; + width: 600px; + height: 480px; + gap: 0; + padding: 0; + + #Header { + border-bottom: 1px solid transparent; display: flex; flex-direction: column; - min-height: 431px; - width: 600px; - height: 480px; - gap: 0; - padding: 0; + gap: $unit; + padding-bottom: $unit * 2; - #Header { - border-bottom: 1px solid transparent; - display: flex; - flex-direction: column; - gap: $unit; - padding-bottom: $unit * 2; - - &.scrolled { - border-bottom: 1px solid rgba(0, 0, 0, 0.1); - box-shadow: 0 0 8px rgba(0, 0, 0, 0.12); - } - - #Bar { - border-top-left-radius: $unit; - border-top-right-radius: $unit; - display: flex; - gap: $unit * 2.5; - margin: 0; - padding: ($unit * 3) ($unit * 3) 0 ($unit * 3); - position: sticky; - top: 0; - - button { - background: transparent; - border: none; - height: 42px; - padding: 0; - } - - label { - width: 100%; - - .Input { - background: $grey-90; - border: none; - border-radius: calc($unit / 2); - box-sizing: border-box; - font-size: $font-regular; - padding: $unit * 1.5; - text-align: left; - width: 100%; - } - } - } + &.scrolled { + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 0 8px rgba(0, 0, 0, 0.12); } - #Results { - margin: 0; - max-height: 356px; - padding: 0 ($unit * 1.5); - overflow-y: scroll; + #Bar { + border-top-left-radius: $unit; + border-top-right-radius: $unit; + display: flex; + gap: $unit * 2.5; + margin: 0; + padding: ($unit * 3) ($unit * 3) 0 ($unit * 3); + position: sticky; + top: 0; - h5.total { - font-size: $font-regular; - font-weight: $normal; - color: $grey-40; - padding: calc($unit / 2) ($unit * 1.5); - } + button { + background: transparent; + border: none; + height: 42px; + padding: 0; + } - .footer { - align-items: center; - display: flex; - color: $grey-60; - font-size: $font-regular; - font-weight: $normal; - height: $unit * 10; - justify-content: center; - } + label { + width: 100%; - .WeaponResult:last-child { - margin-bottom: $unit * 1.5; + .Input { + background: $grey-90; + border: none; + border-radius: calc($unit / 2); + box-sizing: border-box; + font-size: $font-regular; + padding: $unit * 1.5; + text-align: left; + width: 100%; } + } } + } + + #Results { + margin: 0; + max-height: 356px; + padding: 0 ($unit * 1.5); + overflow-y: scroll; + + h5.total { + font-size: $font-regular; + font-weight: $normal; + color: $grey-40; + padding: calc($unit / 2) ($unit * 1.5); + } + + .footer { + align-items: center; + display: flex; + color: $grey-60; + font-size: $font-regular; + font-weight: $normal; + height: $unit * 10; + justify-content: center; + } + + .WeaponResult:last-child { + margin-bottom: $unit * 1.5; + } + } } .Search.Dialog #NoResults { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - flex-grow: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex-grow: 1; } .Search.Dialog #NoResults h2 { - color: #ccc; - font-size: $font-large; - font-weight: 500; - margin-top: -32px; -} \ No newline at end of file + color: #ccc; + font-size: $font-large; + font-weight: 500; + margin-top: -32px; +} diff --git a/components/SearchModal/index.tsx b/components/SearchModal/index.tsx index c9ecfaf0..bf90ff96 100644 --- a/components/SearchModal/index.tsx +++ b/components/SearchModal/index.tsx @@ -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() - let scrollContainer = React.createRef() + let searchInput = React.createRef(); + let scrollContainer = React.createRef(); - 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([]) + 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([]); // 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) { - 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} - ) + ); } 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) => { - ) -} + ); +}; -export default SearchModal +export default SearchModal; diff --git a/components/Segment/index.scss b/components/Segment/index.scss index ef84f5d8..c53c4e96 100644 --- a/components/Segment/index.scss +++ b/components/Segment/index.scss @@ -1,36 +1,36 @@ .Segment { - color: $grey-50; + color: $grey-50; + cursor: pointer; + font-size: 1.4rem; + font-weight: $normal; + min-width: 100px; + + &:hover label { + background: $grey-90; + color: $grey-40; + } + + & input { + display: none; + + &:checked + label { + background: $grey-90; + color: $grey-00; + } + } + + & label { + border-radius: $unit * 3; + display: block; + text-align: center; + white-space: nowrap; + overflow: hidden; + padding: 8px 12px; + text-overflow: ellipsis; cursor: pointer; - font-size: 1.4rem; - font-weight: $normal; - min-width: 100px; - &:hover label { - background: $grey-90; - color: $grey-40; + &:before { + background: #fff; } - - & input { - display: none; - - &:checked + label { - background: $grey-90; - color: $grey-00; - } - } - - & label { - border-radius: $unit * 3; - display: block; - text-align: center; - white-space: nowrap; - overflow: hidden; - padding: 8px 12px; - text-overflow: ellipsis; - cursor: pointer; - - &:before { - background: #fff; - } - } -} \ No newline at end of file + } +} diff --git a/components/Segment/index.tsx b/components/Segment/index.tsx index a7cdb607..f410894f 100644 --- a/components/Segment/index.tsx +++ b/components/Segment/index.tsx @@ -1,33 +1,29 @@ -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) => void + groupName: string; + name: string; + selected: boolean; + children: string; + onClick: (event: React.ChangeEvent) => void; } const Segment: React.FC = (props: Props) => { - + return ( +
    + + +
    + ); +}; - return ( -
    - - -
    - ) -} - -export default Segment \ No newline at end of file +export default Segment; diff --git a/components/SegmentedControl/index.scss b/components/SegmentedControl/index.scss index 2ae75feb..2d822b27 100644 --- a/components/SegmentedControl/index.scss +++ b/components/SegmentedControl/index.scss @@ -1,88 +1,88 @@ .SegmentedControlWrapper { - display: flex; - justify-content: center; + display: flex; + justify-content: center; } .SegmentedControl { - background: white; - border-radius: $unit * 3; - display: inline-flex; - padding: 3px; - position: relative; - user-select: none; - overflow: hidden; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - z-index: 1; - - &.fire { - .Segment input:checked + label { - background: $fire-bg-dark; - color: $fire-text-dark; - } + background: white; + border-radius: $unit * 3; + display: inline-flex; + padding: 3px; + position: relative; + user-select: none; + overflow: hidden; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + z-index: 1; - .Segment:hover label { - background: $fire-bg-light; - color: $fire-text-light; - } + &.fire { + .Segment input:checked + label { + background: $fire-bg-dark; + color: $fire-text-dark; } - &.water { - .Segment input:checked + label { - background: $water-bg-dark; - color: $water-text-dark; - } + .Segment:hover label { + background: $fire-bg-light; + color: $fire-text-light; + } + } - .Segment:hover label { - background: $water-bg-light; - color: $water-text-light; - } + &.water { + .Segment input:checked + label { + background: $water-bg-dark; + color: $water-text-dark; } - &.earth { - .Segment input:checked + label { - background: $earth-bg-dark; - color: $earth-text-dark; - } + .Segment:hover label { + background: $water-bg-light; + color: $water-text-light; + } + } - .Segment:hover label { - background: $earth-bg-light; - color: $earth-text-light; - } + &.earth { + .Segment input:checked + label { + background: $earth-bg-dark; + color: $earth-text-dark; } - &.wind { - .Segment input:checked + label { - background: $wind-bg-dark; - color: $wind-text-dark; - } + .Segment:hover label { + background: $earth-bg-light; + color: $earth-text-light; + } + } - .Segment:hover label { - background: $wind-bg-light; - color: $wind-text-light; - } + &.wind { + .Segment input:checked + label { + background: $wind-bg-dark; + color: $wind-text-dark; } - &.light { - .Segment input:checked + label { - background: $light-bg-dark; - color: $light-text-dark; - } + .Segment:hover label { + background: $wind-bg-light; + color: $wind-text-light; + } + } - .Segment:hover label { - background: $light-bg-light; - color: $light-text-light; - } + &.light { + .Segment input:checked + label { + background: $light-bg-dark; + color: $light-text-dark; } - &.dark { - .Segment input:checked + label { - background: $dark-bg-dark; - color: $dark-text-dark; - } - - .Segment:hover label { - background: $dark-bg-light; - color: $dark-text-light; - } + .Segment:hover label { + background: $light-bg-light; + color: $light-text-light; } -} \ No newline at end of file + } + + &.dark { + .Segment input:checked + label { + background: $dark-bg-dark; + color: $dark-text-dark; + } + + .Segment:hover label { + background: $dark-bg-light; + color: $dark-text-light; + } + } +} diff --git a/components/SegmentedControl/index.tsx b/components/SegmentedControl/index.tsx index 13d9e023..5862959d 100644 --- a/components/SegmentedControl/index.tsx +++ b/components/SegmentedControl/index.tsx @@ -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 = ({ elementClass, children }) => { - return ( -
    -
    - {children} -
    -
    - ) -} + return ( +
    +
    + {children} +
    +
    + ); +}; -export default SegmentedControl \ No newline at end of file +export default SegmentedControl; diff --git a/components/SignupModal/index.scss b/components/SignupModal/index.scss index 8a537279..5222385f 100644 --- a/components/SignupModal/index.scss +++ b/components/SignupModal/index.scss @@ -1,47 +1,47 @@ .Signup.Dialog form { - display: flex; - flex-direction: column; - gap: calc($unit / 2); - margin-bottom: $unit; + display: flex; + flex-direction: column; + gap: calc($unit / 2); + margin-bottom: $unit; - .Button { - font-size: $font-regular; - padding: ($unit * 1.5) ($unit * 2); - width: 100%; + .Button { + font-size: $font-regular; + padding: ($unit * 1.5) ($unit * 2); + width: 100%; - &.btn-disabled { - background: $grey-90; - color: $grey-70; - cursor: not-allowed; - } - - &:not(.btn-disabled) { - background: $grey-90; - color: $grey-40; - - &:hover { - background: $grey-80; - } - } + &.btn-disabled { + background: $grey-90; + color: $grey-70; + cursor: not-allowed; } - .terms { - color: $grey-40; - font-size: $font-small; - line-height: 1.2; - margin-top: $unit; - text-align: center; + &:not(.btn-disabled) { + background: $grey-90; + color: $grey-40; - a { - color: $blue; - - &:hover { - color: darken($blue, 30); - } - } + &:hover { + background: $grey-80; + } } + } - input { - background: $grey-90; + .terms { + color: $grey-40; + font-size: $font-small; + line-height: 1.2; + margin-top: $unit; + text-align: center; + + a { + color: $blue; + + &:hover { + color: darken($blue, 30); + } } + } + + input { + background: $grey-90; + } } diff --git a/components/SignupModal/index.tsx b/components/SignupModal/index.tsx index c27ffa79..8dab18f1 100644 --- a/components/SignupModal/index.tsx +++ b/components/SignupModal/index.tsx @@ -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({ username: "", email: "", password: "", passwordConfirmation: "", - }) + }); // States - const [open, setOpen] = useState(false) + const [open, setOpen] = useState(false); // Set up form refs - const usernameInput = React.createRef() - const emailInput = React.createRef() - const passwordInput = React.createRef() - const passwordConfirmationInput = React.createRef() + const usernameInput = React.createRef(); + const emailInput = React.createRef(); + const passwordInput = React.createRef(); + const passwordConfirmationInput = React.createRef(); 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) { - 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) { - 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) => { - ) -} + ); +}; -export default SignupModal +export default SignupModal; diff --git a/components/SummonGrid/index.scss b/components/SummonGrid/index.scss index b228e2a4..a43a0cd2 100644 --- a/components/SummonGrid/index.scss +++ b/components/SummonGrid/index.scss @@ -1,26 +1,26 @@ #SummonGrid { + display: grid; + grid-template-columns: auto auto auto; + grid-column-gap: $unit * 2; + justify-content: center; + + & .Label { + color: $grey-50; + font-size: $font-tiny; + font-weight: $medium; + margin-bottom: $unit; + text-align: center; + } + + #grid_summons { display: grid; - grid-template-columns: auto auto auto; + grid-template-columns: auto auto; grid-column-gap: $unit * 2; - justify-content: center; + grid-template-rows: 1fr; + grid-row-gap: $unit * 3; - & .Label { - color: $grey-50; - font-size: $font-tiny; - font-weight: $medium; - margin-bottom: $unit; - text-align: center; - } - - #grid_summons { - display: grid; - grid-template-columns: auto auto; - grid-column-gap: $unit * 2; - grid-template-rows: 1fr; - grid-row-gap: $unit * 3; - - & > li { - list-style: none; - } + & > li { + list-style: none; } + } } diff --git a/components/SummonGrid/index.tsx b/components/SummonGrid/index.tsx index 96a3c652..dd28be45 100644 --- a/components/SummonGrid/index.tsx +++ b/components/SummonGrid/index.tsx @@ -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> - pushHistory?: (path: string) => void + new: boolean; + summons?: GridSummon[]; + createParty: () => Promise>; + 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} /> - ) + ); const friendSummonElement = (
    @@ -243,7 +245,7 @@ const SummonGrid = (props: Props) => { updateUncap={initiateUncapUpdate} />
    - ) + ); const summonGridElement = (
    {t("summons.summons")}
    @@ -260,11 +262,11 @@ const SummonGrid = (props: Props) => { updateUncap={initiateUncapUpdate} /> - ) + ); })}
    - ) + ); const subAuraSummonElement = ( { updateObject={receiveSummonFromSearch} updateUncap={initiateUncapUpdate} /> - ) + ); return (
    @@ -285,7 +287,7 @@ const SummonGrid = (props: Props) => { {subAuraSummonElement}
    - ) -} + ); +}; -export default SummonGrid +export default SummonGrid; diff --git a/components/SummonHovercard/index.tsx b/components/SummonHovercard/index.tsx index 5a391a26..7d325653 100644 --- a/components/SummonHovercard/index.tsx +++ b/components/SummonHovercard/index.tsx @@ -1,80 +1,99 @@ -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 = "" + function summonImage() { + let imgSrc = ""; - if (props.gridSummon) { - const summon = props.gridSummon.object + if (props.gridSummon) { + const summon = props.gridSummon.object; - const upgradedSummons = [ - '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' - - // Generate the correct source for the summon - imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}${suffix}.jpg` - } + const upgradedSummons = [ + "2040094000", + "2040100000", + "2040080000", + "2040098000", + "2040090000", + "2040084000", + "2040003000", + "2040056000", + ]; - return imgSrc + 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`; } - return ( - - - { props.children } - - -
    -
    -

    { props.gridSummon.object.name[locale] }

    - {props.gridSummon.object.name[locale]} -
    -
    -
    - -
    - -
    -
    - {t('buttons.wiki')} - -
    -
    - ) -} + return imgSrc; + } -export default SummonHovercard + return ( + + {props.children} + +
    +
    +

    {props.gridSummon.object.name[locale]}

    + {props.gridSummon.object.name[locale]} +
    +
    +
    + +
    + +
    +
    + + {t("buttons.wiki")} + + +
    +
    + ); +}; +export default SummonHovercard; diff --git a/components/SummonResult/index.scss b/components/SummonResult/index.scss index 0b75c515..e22316c9 100644 --- a/components/SummonResult/index.scss +++ b/components/SummonResult/index.scss @@ -1,63 +1,63 @@ .SummonResult { + border-radius: 6px; + display: flex; + gap: $unit; + padding: $unit * 1.5; + + &:hover { + background: $grey-90; + cursor: pointer; + } + + img { + background: $grey-80; border-radius: 6px; + display: inline-block; + height: auto; + width: 120px; + } + + .Info { display: flex; - gap: $unit; - padding: $unit * 1.5; + flex-direction: column; + flex-grow: 1; + gap: calc($unit / 2); - &:hover { - background: $grey-90; - cursor: pointer; + h5 { + color: #555; + display: inline-block; + font-size: $font-medium; + font-weight: $medium; } - img { - background: $grey-80; - border-radius: 6px; - display: inline-block; - height: auto; - width: 120px; + .UncapIndicator { + justify-content: left; + pointer-events: none; } - .Info { - display: flex; - flex-direction: column; - flex-grow: 1; - gap: calc($unit / 2); + .stars { + display: inline-block; + color: #ffa15e; + font-size: $font-xlarge; - h5 { - color: #555; - display: inline-block; - font-size: $font-medium; - font-weight: $medium; - } - - .UncapIndicator { - justify-content: left; - pointer-events: none; - } - - .stars { - display: inline-block; - color: #FFA15E; - font-size: $font-xlarge; - - & > span { - color: #65DAFF; - } - } - - .tags { - display: flex; - flex-direction: row; - gap: calc($unit / 2); - - .WeaponLabelIcon { - $aspect-ratio: calc(25 / 60); - $height: 22px; - background-size: calc($height / $aspect-ratio) $height; - background-repeat: no-repeat; - height: $height; - width: calc($height / $aspect-ratio); - } - } + & > span { + color: #65daff; + } } + + .tags { + display: flex; + flex-direction: row; + gap: calc($unit / 2); + + .WeaponLabelIcon { + $aspect-ratio: calc(25 / 60); + $height: 22px; + background-size: calc($height / $aspect-ratio) $height; + background-repeat: no-repeat; + height: $height; + width: calc($height / $aspect-ratio); + } + } + } } diff --git a/components/SummonResult/index.tsx b/components/SummonResult/index.tsx index c5c32990..451a9b02 100644 --- a/components/SummonResult/index.tsx +++ b/components/SummonResult/index.tsx @@ -1,41 +1,47 @@ -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 - - return ( -
  • - {summon.name[locale]} -
    -
    {summon.name[locale]}
    - -
    - -
    -
    -
  • - ) -} + const summon = props.data; -export default SummonResult \ No newline at end of file + return ( +
  • + {summon.name[locale]} +
    +
    {summon.name[locale]}
    + +
    + +
    +
    +
  • + ); +}; + +export default SummonResult; diff --git a/components/SummonSearchFilterBar/index.tsx b/components/SummonSearchFilterBar/index.tsx index 14be3c1c..ceb8b768 100644 --- a/components/SummonSearchFilterBar/index.tsx +++ b/components/SummonSearchFilterBar/index.tsx @@ -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(emptyRarityState) - const [elementState, setElementState] = useState(emptyElementState) + const [rarityState, setRarityState] = useState(emptyRarityState); + const [elementState, setElementState] = + useState(emptyElementState); - function rarityMenuOpened(open: boolean) { - if (open) { - setRarityMenu(true) - setElementMenu(false) - } else setRarityMenu(false) - } + function rarityMenuOpened(open: boolean) { + if (open) { + setRarityMenu(true); + setElementMenu(false); + } else setRarityMenu(false); + } - function elementMenuOpened(open: boolean) { - if (open) { - setRarityMenu(false) - setElementMenu(true) - } else setElementMenu(false) - } + function elementMenuOpened(open: boolean) { + if (open) { + setRarityMenu(false); + setElementMenu(true); + } else setElementMenu(false); + } - function handleRarityChange(checked: boolean, key: string) { - let newRarityState = cloneDeep(rarityState) - newRarityState[key].checked = checked - setRarityState(newRarityState) - } + function handleRarityChange(checked: boolean, key: string) { + 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) - } + function handleElementChange(checked: boolean, key: string) { + 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) + 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 filters = { - rarity: checkedRarityFilters, - element: checkedElementFilters + const filters = { + rarity: checkedRarityFilters, + element: checkedElementFilters, + }; + + props.sendFilters(filters); + } + + useEffect(() => { + sendFilters(); + }, [rarityState, elementState]); + + return ( +
    + x.checked) + .filter(Boolean).length } + open={rarityMenu} + onOpenChange={rarityMenuOpened} + > + + {t("filters.labels.rarity")} + + {Array.from(Array(rarities.length)).map((x, i) => { + return ( + + {t(`rarities.${rarities[i]}`)} + + ); + })} + - props.sendFilters(filters) - } + x.checked) + .filter(Boolean).length + } + open={elementMenu} + onOpenChange={elementMenuOpened} + > + + {t("filters.labels.element")} + + {Array.from(Array(elements.length)).map((x, i) => { + return ( + + {t(`elements.${elements[i]}`)} + + ); + })} + +
    + ); +}; - useEffect(() => { - sendFilters() - }, [rarityState, elementState]) - - return ( -
    - x.checked).filter(Boolean).length} open={rarityMenu} onOpenChange={rarityMenuOpened}> - {t('filters.labels.rarity')} - { Array.from(Array(rarities.length)).map((x, i) => { - return ( - - {t(`rarities.${rarities[i]}`)} - - )} - ) } - - - x.checked).filter(Boolean).length} open={elementMenu} onOpenChange={elementMenuOpened}> - {t('filters.labels.element')} - { Array.from(Array(elements.length)).map((x, i) => { - return ( - - {t(`elements.${elements[i]}`)} - - )} - ) } - -
    - ) -} - -export default SummonSearchFilterBar +export default SummonSearchFilterBar; diff --git a/components/SummonUnit/index.scss b/components/SummonUnit/index.scss index ac2e2511..6bc79d77 100644 --- a/components/SummonUnit/index.scss +++ b/components/SummonUnit/index.scss @@ -1,106 +1,107 @@ .SummonUnit { - display: flex; - flex-direction: column; - gap: 4px; + display: flex; + flex-direction: column; + gap: 4px; - &.main .SummonImage, - &.friend .SummonImage { - aspect-ratio: 182 / 315; - width: 182px; - height: auto; + &.main .SummonImage, + &.friend .SummonImage { + aspect-ratio: 182 / 315; + width: 182px; + height: auto; - @media (max-width: $medium-screen) { - width: 20.3vw; - } + @media (max-width: $medium-screen) { + width: 20.3vw; } + } - &.grid { - // max-width: 148px; - // min-height: 141px; - min-height: 180px; + &.grid { + // max-width: 148px; + // min-height: 141px; + min-height: 180px; - @media (max-width: $medium-screen) { - min-height: 16.5vw; - } - - .SummonImage { - aspect-ratio: 148 / 111; - list-style-type: none; - width: 148px; - height: auto; - - @media (max-width: $medium-screen) { - width: 20vw; - } - } - } - - &.friend { - margin-right: 0; - } - - &.main.editable .SummonImage:hover, - &.friend.editable .SummonImage:hover { - transform: $scale-tall; - } - - &.editable .SummonImage:hover { - border: $hover-stroke; - box-shadow: $hover-shadow; - cursor: pointer; - transform: $scale-wide; + @media (max-width: $medium-screen) { + min-height: 16.5vw; } .SummonImage { - background: white; - border: 1px solid rgba(0, 0, 0, 0); - border-radius: $unit; - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - transition: all 0.18s ease-in-out; + aspect-ratio: 148 / 111; + list-style-type: none; + width: 148px; + height: auto; - &:hover .icon svg { - fill: $grey-40; - } + @media (max-width: $medium-screen) { + width: 20vw; + } + } + } - .icon { - position: absolute; - height: $unit * 3; - width: $unit * 3; - z-index: 1; - - svg { - fill: $grey-70; - } - } + &.friend { + margin-right: 0; + } + + &.main.editable .SummonImage:hover, + &.friend.editable .SummonImage:hover { + transform: $scale-tall; + } + + &.editable .SummonImage:hover { + border: $hover-stroke; + box-shadow: $hover-shadow; + cursor: pointer; + transform: $scale-wide; + } + + .SummonImage { + background: white; + border: 1px solid rgba(0, 0, 0, 0); + border-radius: $unit; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + transition: all 0.18s ease-in-out; + + &:hover .icon svg { + fill: $grey-40; } - &.filled h3 { - display: block; - } + .icon { + position: absolute; + height: $unit * 3; + width: $unit * 3; + z-index: 1; - &.filled ul { - display: flex; + svg { + fill: $grey-70; + } } + } - h3, ul { - display: none; - } + &.filled h3 { + display: block; + } - h3 { - color: #333; - font-size: $font-regular; - font-weight: $normal; - line-height: 1.1; - margin: 0; - text-align: center; - } + &.filled ul { + display: flex; + } - img { - position: relative; - width: 100%; - z-index: 2; - } + h3, + ul { + display: none; + } + + h3 { + color: #333; + font-size: $font-regular; + font-weight: $normal; + line-height: 1.1; + margin: 0; + text-align: center; + } + + img { + position: relative; + width: 100%; + z-index: 2; + } } diff --git a/components/SummonUnit/index.tsx b/components/SummonUnit/index.tsx index 462a9db0..1b6451ae 100644 --- a/components/SummonUnit/index.tsx +++ b/components/SummonUnit/index.tsx @@ -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) => { "" )}
    - ) + ); const editableImage = ( { > {image} - ) + ); const unitContent = (
    @@ -131,13 +133,13 @@ const SummonUnit = (props: Props) => { )}

    {summon?.name[locale]}

    - ) + ); const withHovercard = ( {unitContent} - ) + ); - return gridSummon && !props.editable ? withHovercard : unitContent -} + return gridSummon && !props.editable ? withHovercard : unitContent; +}; -export default SummonUnit +export default SummonUnit; diff --git a/components/TextFieldset/index.scss b/components/TextFieldset/index.scss index f84fe540..5013cd18 100644 --- a/components/TextFieldset/index.scss +++ b/components/TextFieldset/index.scss @@ -1,5 +1,6 @@ .Fieldset textarea { - color: $grey-00; - font-family: system-ui, -apple-system, 'Helvetica Neue', Helvetica, Arial, sans-serif; - line-height: 21px; -} \ No newline at end of file + color: $grey-00; + font-family: system-ui, -apple-system, "Helvetica Neue", Helvetica, Arial, + sans-serif; + line-height: 21px; +} diff --git a/components/TextFieldset/index.tsx b/components/TextFieldset/index.tsx index 63b80c61..eac524b8 100644 --- a/components/TextFieldset/index.tsx +++ b/components/TextFieldset/index.tsx @@ -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) => void - onChange?: (event: React.ChangeEvent) => void + fieldName: string; + placeholder: string; + value?: string; + error: string; + onBlur?: (event: React.ChangeEvent) => void; + onChange?: (event: React.ChangeEvent) => void; } -const TextFieldset = React.forwardRef(function fieldSet(props, ref) { +const TextFieldset = React.forwardRef( + function fieldSet(props, ref) { return ( -
    -