Merge pull request #47 from jedmund/frontend-refactor
Frontend refactor
This commit is contained in:
commit
f3255e1381
158 changed files with 9181 additions and 6811 deletions
1
.prettierignore
Normal file
1
.prettierignore
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
utils/api.tsx
|
||||||
5
.prettierrc
Normal file
5
.prettierrc
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
.About.Dialog {
|
.About.Dialog {
|
||||||
width: $unit * 60;
|
width: $unit * 60;
|
||||||
|
|
||||||
section {
|
section {
|
||||||
margin-bottom: $unit;
|
margin-bottom: $unit;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin-bottom: $unit * 3;
|
margin-bottom: $unit * 3;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.DialogDescription {
|
.DialogDescription {
|
||||||
font-size: $font-regular;
|
font-size: $font-regular;
|
||||||
line-height: 1.24;
|
line-height: 1.24;
|
||||||
margin-bottom: $unit;
|
margin-bottom: $unit;
|
||||||
|
|
||||||
&:last-of-type {
|
&:last-of-type {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,56 +6,68 @@ import CrossIcon from '~public/icons/Cross.svg'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
const AboutModal = () => {
|
const AboutModal = () => {
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog.Root>
|
<Dialog.Root>
|
||||||
<Dialog.Trigger asChild>
|
<Dialog.Trigger asChild>
|
||||||
<li className="MenuItem">
|
<li className="MenuItem">
|
||||||
<span>{t('modals.about.title')}</span>
|
<span>{t('modals.about.title')}</span>
|
||||||
</li>
|
</li>
|
||||||
</Dialog.Trigger>
|
</Dialog.Trigger>
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
<Dialog.Content className="About Dialog" onOpenAutoFocus={ (event) => event.preventDefault() }>
|
<Dialog.Content
|
||||||
<div className="DialogHeader">
|
className="About Dialog"
|
||||||
<Dialog.Title className="DialogTitle">{t('menu.about')}</Dialog.Title>
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||||
<Dialog.Close className="DialogClose" asChild>
|
>
|
||||||
<span>
|
<div className="DialogHeader">
|
||||||
<CrossIcon />
|
<Dialog.Title className="DialogTitle">
|
||||||
</span>
|
{t('menu.about')}
|
||||||
</Dialog.Close>
|
</Dialog.Title>
|
||||||
</div>
|
<Dialog.Close className="DialogClose" asChild>
|
||||||
|
<span>
|
||||||
|
<CrossIcon />
|
||||||
|
</span>
|
||||||
|
</Dialog.Close>
|
||||||
|
</div>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<Dialog.Description className="DialogDescription">
|
<Dialog.Description className="DialogDescription">
|
||||||
Granblue.team is a tool to save and share team compositions for <a href="https://game.granbluefantasy.jp">Granblue Fantasy.</a>
|
Granblue.team is a tool to save and share team compositions for{' '}
|
||||||
</Dialog.Description>
|
<a href="https://game.granbluefantasy.jp">Granblue Fantasy.</a>
|
||||||
<Dialog.Description className="DialogDescription">
|
</Dialog.Description>
|
||||||
Start adding things to a team and a URL will be created for you to share it wherever you like, no account needed.
|
<Dialog.Description className="DialogDescription">
|
||||||
</Dialog.Description>
|
Start adding things to a team and a URL will be created for you to
|
||||||
<Dialog.Description className="DialogDescription">
|
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.
|
</Dialog.Description>
|
||||||
</Dialog.Description>
|
<Dialog.Description className="DialogDescription">
|
||||||
</section>
|
You can make an account to save any teams you find for future
|
||||||
|
reference, or to keep all of your teams together in one place.
|
||||||
|
</Dialog.Description>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<Dialog.Title className="DialogTitle">Credits</Dialog.Title>
|
<Dialog.Title className="DialogTitle">Credits</Dialog.Title>
|
||||||
<Dialog.Description className="DialogDescription">
|
<Dialog.Description className="DialogDescription">
|
||||||
Granblue.team was built by <a href="https://twitter.com/jedmund">@jedmund</a> with a lot of help from <a href="https://twitter.com/lalalalinna">@lalalalinna</a> and <a href="https://twitter.com/tarngerine">@tarngerine</a>.
|
Granblue.team was built by{' '}
|
||||||
</Dialog.Description>
|
<a href="https://twitter.com/jedmund">@jedmund</a> with a lot of
|
||||||
</section>
|
help from{' '}
|
||||||
|
<a href="https://twitter.com/lalalalinna">@lalalalinna</a> and{' '}
|
||||||
|
<a href="https://twitter.com/tarngerine">@tarngerine</a>.
|
||||||
|
</Dialog.Description>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<Dialog.Title className="DialogTitle">Open Source</Dialog.Title>
|
<Dialog.Title className="DialogTitle">Open Source</Dialog.Title>
|
||||||
<Dialog.Description className="DialogDescription">
|
<Dialog.Description className="DialogDescription">
|
||||||
This app is open source. You can contribute on Github.
|
This app is open source. You can contribute on Github.
|
||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
</section>
|
</section>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
<Dialog.Overlay className="Overlay" />
|
<Dialog.Overlay className="Overlay" />
|
||||||
</Dialog.Portal>
|
</Dialog.Portal>
|
||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AboutModal
|
export default AboutModal
|
||||||
|
|
@ -1,164 +1,142 @@
|
||||||
.Account.Dialog {
|
.Account.Dialog {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit * 2;
|
||||||
|
width: $unit * 60;
|
||||||
|
|
||||||
|
form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: $unit * 2;
|
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-15;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-state='checked'] {
|
||||||
|
background: $grey-15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Thumb {
|
||||||
|
background: $grey-100;
|
||||||
|
border-radius: 13px;
|
||||||
|
display: block;
|
||||||
|
height: 26px;
|
||||||
|
width: 26px;
|
||||||
|
transition: transform 100ms;
|
||||||
|
transform: translateX(-1px);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-state='checked'] {
|
||||||
|
background: $grey-100;
|
||||||
|
transform: translateX(21px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: $unit * 2;
|
flex-grow: 1;
|
||||||
|
gap: calc($unit / 2);
|
||||||
|
|
||||||
.Switch {
|
label {
|
||||||
$height: 34px;
|
color: var(--text-secondary);
|
||||||
background: $grey-70;
|
font-size: $font-regular;
|
||||||
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 {
|
p {
|
||||||
background: white;
|
color: var(--text-secondary);
|
||||||
border-radius: 13px;
|
font-size: $font-small;
|
||||||
display: block;
|
line-height: 1.1;
|
||||||
height: 26px;
|
max-width: 300px;
|
||||||
width: 26px;
|
|
||||||
transition: transform 100ms;
|
|
||||||
transform: translateX(-1px);
|
|
||||||
|
|
||||||
&:hover {
|
&.jp {
|
||||||
cursor: pointer;
|
max-width: 270px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&[data-state="checked"] {
|
.preview {
|
||||||
background: white;
|
$diameter: 48px;
|
||||||
transform: translateX(21px);
|
background-color: $grey-90;
|
||||||
}
|
border-radius: 999px;
|
||||||
|
height: $diameter;
|
||||||
|
width: $diameter;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: $diameter;
|
||||||
|
width: $diameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Button {
|
&.fire {
|
||||||
font-size: $font-regular;
|
background: $fire-bg-20;
|
||||||
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 {
|
&.water {
|
||||||
align-items: center;
|
background: $water-bg-20;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
section {
|
&.wind {
|
||||||
margin-bottom: $unit;
|
background: $wind-bg-20;
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin-bottom: $unit * 3;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.earth {
|
||||||
|
background: $earth-bg-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
background: $dark-bg-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.light {
|
||||||
|
background: $light-bg-20;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.DialogDescription {
|
section {
|
||||||
font-size: $font-regular;
|
margin-bottom: $unit;
|
||||||
line-height: 1.24;
|
|
||||||
margin-bottom: $unit;
|
|
||||||
|
|
||||||
&:last-of-type {
|
h2 {
|
||||||
margin-bottom: 0;
|
margin-bottom: $unit * 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.DialogDescription {
|
||||||
|
font-size: $font-regular;
|
||||||
|
line-height: 1.24;
|
||||||
|
margin-bottom: $unit;
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,31 @@
|
||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from 'react'
|
||||||
import { getCookie } from "cookies-next"
|
import { getCookie } from 'cookies-next'
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from 'next/router'
|
||||||
import { useSnapshot } from "valtio"
|
import { useSnapshot } from 'valtio'
|
||||||
import { useTranslation } from "next-i18next"
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
import * as Dialog from "@radix-ui/react-dialog"
|
import * as Dialog from '@radix-ui/react-dialog'
|
||||||
import * as Switch from "@radix-ui/react-switch"
|
import * as Switch from '@radix-ui/react-switch'
|
||||||
|
|
||||||
import api from "~utils/api"
|
import api from '~utils/api'
|
||||||
import { accountState } from "~utils/accountState"
|
import { accountState } from '~utils/accountState'
|
||||||
import { pictureData } from "~utils/pictureData"
|
import { pictureData } from '~utils/pictureData'
|
||||||
|
|
||||||
import Button from "~components/Button"
|
import Button from '~components/Button'
|
||||||
|
|
||||||
import CrossIcon from "~public/icons/Cross.svg"
|
import CrossIcon from '~public/icons/Cross.svg'
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
const AccountModal = () => {
|
const AccountModal = () => {
|
||||||
const { account } = useSnapshot(accountState)
|
const { account } = useSnapshot(accountState)
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
const locale =
|
const locale =
|
||||||
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
|
|
||||||
// Cookies
|
// Cookies
|
||||||
const cookie = getCookie("account")
|
const cookie = getCookie('account')
|
||||||
|
|
||||||
const headers = {}
|
const headers = {}
|
||||||
// cookies.account != null
|
// cookies.account != null
|
||||||
|
|
@ -38,8 +38,8 @@ const AccountModal = () => {
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [picture, setPicture] = useState("")
|
const [picture, setPicture] = useState('')
|
||||||
const [language, setLanguage] = useState("")
|
const [language, setLanguage] = useState('')
|
||||||
const [gender, setGender] = useState(0)
|
const [gender, setGender] = useState(0)
|
||||||
const [privateProfile, setPrivateProfile] = useState(false)
|
const [privateProfile, setPrivateProfile] = useState(false)
|
||||||
|
|
||||||
|
|
@ -136,7 +136,7 @@ const AccountModal = () => {
|
||||||
<Dialog.Root open={open} onOpenChange={openChange}>
|
<Dialog.Root open={open} onOpenChange={openChange}>
|
||||||
<Dialog.Trigger asChild>
|
<Dialog.Trigger asChild>
|
||||||
<li className="MenuItem">
|
<li className="MenuItem">
|
||||||
<span>{t("menu.settings")}</span>
|
<span>{t('menu.settings')}</span>
|
||||||
</li>
|
</li>
|
||||||
</Dialog.Trigger>
|
</Dialog.Trigger>
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
|
|
@ -147,7 +147,7 @@ const AccountModal = () => {
|
||||||
<div className="DialogHeader">
|
<div className="DialogHeader">
|
||||||
<div className="DialogTop">
|
<div className="DialogTop">
|
||||||
<Dialog.Title className="SubTitle">
|
<Dialog.Title className="SubTitle">
|
||||||
{t("modals.settings.title")}
|
{t('modals.settings.title')}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<Dialog.Title className="DialogTitle">
|
<Dialog.Title className="DialogTitle">
|
||||||
@{account.user?.username}
|
@{account.user?.username}
|
||||||
|
|
@ -163,7 +163,7 @@ const AccountModal = () => {
|
||||||
<form onSubmit={update}>
|
<form onSubmit={update}>
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<div className="left">
|
<div className="left">
|
||||||
<label>{t("modals.settings.labels.picture")}</label>
|
<label>{t('modals.settings.labels.picture')}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
@ -190,7 +190,7 @@ const AccountModal = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<div className="left">
|
<div className="left">
|
||||||
<label>{t("modals.settings.labels.gender")}</label>
|
<label>{t('modals.settings.labels.gender')}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
|
|
@ -200,16 +200,16 @@ const AccountModal = () => {
|
||||||
ref={genderSelect}
|
ref={genderSelect}
|
||||||
>
|
>
|
||||||
<option key="gran" value="0">
|
<option key="gran" value="0">
|
||||||
{t("modals.settings.gender.gran")}
|
{t('modals.settings.gender.gran')}
|
||||||
</option>
|
</option>
|
||||||
<option key="djeeta" value="1">
|
<option key="djeeta" value="1">
|
||||||
{t("modals.settings.gender.djeeta")}
|
{t('modals.settings.gender.djeeta')}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<div className="left">
|
<div className="left">
|
||||||
<label>{t("modals.settings.labels.language")}</label>
|
<label>{t('modals.settings.labels.language')}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
|
|
@ -219,18 +219,18 @@ const AccountModal = () => {
|
||||||
ref={languageSelect}
|
ref={languageSelect}
|
||||||
>
|
>
|
||||||
<option key="en" value="en">
|
<option key="en" value="en">
|
||||||
{t("modals.settings.language.english")}
|
{t('modals.settings.language.english')}
|
||||||
</option>
|
</option>
|
||||||
<option key="jp" value="ja">
|
<option key="jp" value="ja">
|
||||||
{t("modals.settings.language.japanese")}
|
{t('modals.settings.language.japanese')}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<div className="left">
|
<div className="left">
|
||||||
<label>{t("modals.settings.labels.private")}</label>
|
<label>{t('modals.settings.labels.private')}</label>
|
||||||
<p className={locale}>
|
<p className={locale}>
|
||||||
{t("modals.settings.descriptions.private")}
|
{t('modals.settings.descriptions.private')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -243,7 +243,10 @@ const AccountModal = () => {
|
||||||
</Switch.Root>
|
</Switch.Root>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button>{t("modals.settings.buttons.confirm")}</Button>
|
<Button
|
||||||
|
contained={true}
|
||||||
|
text={t('modals.settings.buttons.confirm')}
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
<Dialog.Overlay className="Overlay" />
|
<Dialog.Overlay className="Overlay" />
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.Alert {
|
.Alert {
|
||||||
background: white;
|
background: $grey-100;
|
||||||
border-radius: $unit;
|
border-radius: $unit;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
|
|
||||||
&:not(.btn-disabled) {
|
&:not(.btn-disabled) {
|
||||||
background: $grey-90;
|
background: $grey-90;
|
||||||
color: $grey-40;
|
color: $grey-50;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $grey-80;
|
background: $grey-80;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import React from "react"
|
import React from 'react'
|
||||||
import * as AlertDialog from "@radix-ui/react-alert-dialog"
|
import * as AlertDialog from '@radix-ui/react-alert-dialog'
|
||||||
|
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
import Button from "~components/Button"
|
import Button from '~components/Button'
|
||||||
import { ButtonType } from "~utils/enums"
|
import { ButtonType } from '~utils/enums'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -23,7 +23,7 @@ const Alert = (props: Props) => {
|
||||||
<AlertDialog.Overlay className="Overlay" onClick={props.cancelAction} />
|
<AlertDialog.Overlay className="Overlay" onClick={props.cancelAction} />
|
||||||
<div className="AlertWrapper">
|
<div className="AlertWrapper">
|
||||||
<AlertDialog.Content className="Alert">
|
<AlertDialog.Content className="Alert">
|
||||||
{props.title ? <AlertDialog.Title>Error</AlertDialog.Title> : ""}
|
{props.title ? <AlertDialog.Title>Error</AlertDialog.Title> : ''}
|
||||||
<AlertDialog.Description className="description">
|
<AlertDialog.Description className="description">
|
||||||
{props.message}
|
{props.message}
|
||||||
</AlertDialog.Description>
|
</AlertDialog.Description>
|
||||||
|
|
@ -38,7 +38,7 @@ const Alert = (props: Props) => {
|
||||||
{props.primaryActionText}
|
{props.primaryActionText}
|
||||||
</AlertDialog.Action>
|
</AlertDialog.Action>
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</AlertDialog.Content>
|
</AlertDialog.Content>
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,48 @@
|
||||||
.AXSelect {
|
.AXSelect {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: $unit;
|
gap: $unit;
|
||||||
|
|
||||||
.AXSet {
|
.AXSet {
|
||||||
&.hidden {
|
&.hidden {
|
||||||
display: none;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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-15;
|
||||||
|
height: $unit * 6;
|
||||||
|
display: block;
|
||||||
|
font-size: $font-regular;
|
||||||
|
padding: $unit;
|
||||||
|
text-align: right;
|
||||||
|
min-width: 100px;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,258 +9,338 @@ import { axData } from '~utils/axData'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface ErrorMap {
|
interface ErrorMap {
|
||||||
[index: string]: string
|
[index: string]: string
|
||||||
axValue1: string
|
axValue1: string
|
||||||
axValue2: string
|
axValue2: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
axType: number
|
axType: number
|
||||||
currentSkills?: SimpleAxSkill[],
|
currentSkills?: SimpleAxSkill[]
|
||||||
sendValidity: (isValid: boolean) => void
|
sendValidity: (isValid: boolean) => void
|
||||||
sendValues: (primaryAxModifier: number, primaryAxValue: number, secondaryAxModifier: number, secondaryAxValue: number) => void
|
sendValues: (
|
||||||
|
primaryAxModifier: number,
|
||||||
|
primaryAxValue: number,
|
||||||
|
secondaryAxModifier: number,
|
||||||
|
secondaryAxValue: number
|
||||||
|
) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const AXSelect = (props: Props) => {
|
const AXSelect = (props: Props) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
const locale =
|
||||||
const { t } = useTranslation('common')
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
// Set up form states and error handling
|
// Set up form states and error handling
|
||||||
const [errors, setErrors] = useState<ErrorMap>({
|
const [errors, setErrors] = useState<ErrorMap>({
|
||||||
axValue1: '',
|
axValue1: '',
|
||||||
axValue2: ''
|
axValue2: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
const primaryErrorClasses = classNames({
|
const primaryErrorClasses = classNames({
|
||||||
'errors': true,
|
errors: true,
|
||||||
'visible': errors.axValue1.length > 0
|
visible: errors.axValue1.length > 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
const secondaryErrorClasses = classNames({
|
const secondaryErrorClasses = classNames({
|
||||||
'errors': true,
|
errors: true,
|
||||||
'visible': errors.axValue2.length > 0
|
visible: errors.axValue2.length > 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Refs
|
// Refs
|
||||||
const primaryAxModifierSelect = React.createRef<HTMLSelectElement>()
|
const primaryAxModifierSelect = React.createRef<HTMLSelectElement>()
|
||||||
const primaryAxValueInput = React.createRef<HTMLInputElement>()
|
const primaryAxValueInput = React.createRef<HTMLInputElement>()
|
||||||
const secondaryAxModifierSelect = React.createRef<HTMLSelectElement>()
|
const secondaryAxModifierSelect = React.createRef<HTMLSelectElement>()
|
||||||
const secondaryAxValueInput = React.createRef<HTMLInputElement>()
|
const secondaryAxValueInput = React.createRef<HTMLInputElement>()
|
||||||
|
|
||||||
// States
|
// States
|
||||||
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
|
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
|
||||||
const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1)
|
const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1)
|
||||||
const [primaryAxValue, setPrimaryAxValue] = useState(0.0)
|
const [primaryAxValue, setPrimaryAxValue] = useState(0.0)
|
||||||
const [secondaryAxValue, setSecondaryAxValue] = useState(0.0)
|
const [secondaryAxValue, setSecondaryAxValue] = useState(0.0)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.currentSkills && props.currentSkills[0]) {
|
if (props.currentSkills && props.currentSkills[0]) {
|
||||||
if (props.currentSkills[0].modifier != null)
|
if (props.currentSkills[0].modifier != null)
|
||||||
setPrimaryAxModifier(props.currentSkills[0].modifier)
|
setPrimaryAxModifier(props.currentSkills[0].modifier)
|
||||||
|
|
||||||
setPrimaryAxValue(props.currentSkills[0].strength)
|
setPrimaryAxValue(props.currentSkills[0].strength)
|
||||||
}
|
|
||||||
|
|
||||||
if (props.currentSkills && props.currentSkills[1]) {
|
|
||||||
if (props.currentSkills[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 (
|
|
||||||
<option key={i} value={ax.id}>{ax.name[locale]}</option>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// If we are loading data from the server, state doesn't set before render,
|
|
||||||
// so our defaultValue is undefined.
|
|
||||||
let modifier = -1;
|
|
||||||
if (primaryAxModifier >= 0)
|
|
||||||
modifier = primaryAxModifier
|
|
||||||
else if (props.currentSkills)
|
|
||||||
modifier = props.currentSkills[0].modifier
|
|
||||||
|
|
||||||
if (modifier >= 0 && axOptions[modifier]) {
|
|
||||||
const primarySkill = axOptions[modifier]
|
|
||||||
|
|
||||||
if (primarySkill.secondary) {
|
|
||||||
const secondaryAxOptions = primarySkill.secondary
|
|
||||||
axOptionElements = secondaryAxOptions.map((ax, i) => {
|
|
||||||
return (
|
|
||||||
<option key={i} value={ax.id}>{ax.name[locale]}</option>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
axOptionElements?.unshift(<option key={-1} value={-1}>{t('ax.no_skill')}</option>)
|
|
||||||
return axOptionElements
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSelectChange(event: React.ChangeEvent<HTMLSelectElement>) {
|
if (props.currentSkills && props.currentSkills[1]) {
|
||||||
const value = parseInt(event.target.value)
|
if (props.currentSkills[1].modifier != null)
|
||||||
|
setSecondaryAxModifier(props.currentSkills[1].modifier)
|
||||||
|
|
||||||
if (primaryAxModifierSelect.current == event.target) {
|
setSecondaryAxValue(props.currentSkills[1].strength)
|
||||||
setPrimaryAxModifier(value)
|
|
||||||
|
|
||||||
if (primaryAxValueInput.current && secondaryAxModifierSelect.current && secondaryAxValueInput.current) {
|
|
||||||
setupInput(axData[props.axType - 1][value], primaryAxValueInput.current)
|
|
||||||
|
|
||||||
secondaryAxModifierSelect.current.value = "-1"
|
|
||||||
secondaryAxValueInput.current.value = ""
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setSecondaryAxModifier(value)
|
|
||||||
|
|
||||||
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
|
|
||||||
const currentAxSkill = (primaryAxSkill.secondary) ?
|
|
||||||
primaryAxSkill.secondary.find(skill => skill.id == value) : undefined
|
|
||||||
|
|
||||||
if (secondaryAxValueInput.current)
|
|
||||||
setupInput(currentAxSkill, secondaryAxValueInput.current)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}, [props.currentSkills])
|
||||||
|
|
||||||
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
useEffect(() => {
|
||||||
const value = parseFloat(event.target.value)
|
props.sendValues(
|
||||||
let newErrors = {...errors}
|
primaryAxModifier,
|
||||||
|
primaryAxValue,
|
||||||
if (primaryAxValueInput.current == event.target) {
|
secondaryAxModifier,
|
||||||
if (handlePrimaryErrors(value))
|
secondaryAxValue
|
||||||
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 (
|
|
||||||
<div className="AXSelect">
|
|
||||||
<div className="AXSet">
|
|
||||||
<div className="fields">
|
|
||||||
<select key="ax1" defaultValue={ (props.currentSkills && props.currentSkills[0]) ? props.currentSkills[0].modifier : -1 } onChange={handleSelectChange} ref={primaryAxModifierSelect}>{ generateOptions(0) }</select>
|
|
||||||
<input defaultValue={ (props.currentSkills && props.currentSkills[0]) ? props.currentSkills[0].strength : 0 } className="Input" type="number" onChange={handleInputChange} ref={primaryAxValueInput} disabled={primaryAxValue != 0} />
|
|
||||||
</div>
|
|
||||||
<p className={primaryErrorClasses}>{errors.axValue1}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={secondarySetClasses}>
|
|
||||||
<div className="fields">
|
|
||||||
<select key="ax2" defaultValue={ (props.currentSkills && props.currentSkills[1]) ? props.currentSkills[1].modifier : -1 } onChange={handleSelectChange} ref={secondaryAxModifierSelect}>{ generateOptions(1) }</select>
|
|
||||||
<input defaultValue={ (props.currentSkills && props.currentSkills[1]) ? props.currentSkills[1].strength : 0 } className="Input" type="number" onChange={handleInputChange} ref={secondaryAxValueInput} disabled={secondaryAxValue != 0} />
|
|
||||||
</div>
|
|
||||||
<p className={secondaryErrorClasses}>{errors.axValue2}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
|
}, [
|
||||||
|
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 (
|
||||||
|
<option key={i} value={ax.id}>
|
||||||
|
{ax.name[locale]}
|
||||||
|
</option>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// If we are loading data from the server, state doesn't set before render,
|
||||||
|
// so our defaultValue is undefined.
|
||||||
|
let modifier = -1
|
||||||
|
if (primaryAxModifier >= 0) modifier = primaryAxModifier
|
||||||
|
else if (props.currentSkills) modifier = props.currentSkills[0].modifier
|
||||||
|
|
||||||
|
if (modifier >= 0 && axOptions[modifier]) {
|
||||||
|
const primarySkill = axOptions[modifier]
|
||||||
|
|
||||||
|
if (primarySkill.secondary) {
|
||||||
|
const secondaryAxOptions = primarySkill.secondary
|
||||||
|
axOptionElements = secondaryAxOptions.map((ax, i) => {
|
||||||
|
return (
|
||||||
|
<option key={i} value={ax.id}>
|
||||||
|
{ax.name[locale]}
|
||||||
|
</option>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
axOptionElements?.unshift(
|
||||||
|
<option key={-1} value={-1}>
|
||||||
|
{t('ax.no_skill')}
|
||||||
|
</option>
|
||||||
|
)
|
||||||
|
return axOptionElements
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelectChange(event: React.ChangeEvent<HTMLSelectElement>) {
|
||||||
|
const value = parseInt(event.target.value)
|
||||||
|
|
||||||
|
if (primaryAxModifierSelect.current == event.target) {
|
||||||
|
setPrimaryAxModifier(value)
|
||||||
|
|
||||||
|
if (
|
||||||
|
primaryAxValueInput.current &&
|
||||||
|
secondaryAxModifierSelect.current &&
|
||||||
|
secondaryAxValueInput.current
|
||||||
|
) {
|
||||||
|
setupInput(axData[props.axType - 1][value], primaryAxValueInput.current)
|
||||||
|
|
||||||
|
secondaryAxModifierSelect.current.value = '-1'
|
||||||
|
secondaryAxValueInput.current.value = ''
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setSecondaryAxModifier(value)
|
||||||
|
|
||||||
|
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
|
||||||
|
const currentAxSkill = primaryAxSkill.secondary
|
||||||
|
? primaryAxSkill.secondary.find((skill) => skill.id == value)
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
if (secondaryAxValueInput.current)
|
||||||
|
setupInput(currentAxSkill, secondaryAxValueInput.current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
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 (
|
||||||
|
<div className="AXSelect">
|
||||||
|
<div className="AXSet">
|
||||||
|
<div className="fields">
|
||||||
|
<select
|
||||||
|
key="ax1"
|
||||||
|
defaultValue={
|
||||||
|
props.currentSkills && props.currentSkills[0]
|
||||||
|
? props.currentSkills[0].modifier
|
||||||
|
: -1
|
||||||
|
}
|
||||||
|
onChange={handleSelectChange}
|
||||||
|
ref={primaryAxModifierSelect}
|
||||||
|
>
|
||||||
|
{generateOptions(0)}
|
||||||
|
</select>
|
||||||
|
<input
|
||||||
|
defaultValue={
|
||||||
|
props.currentSkills && props.currentSkills[0]
|
||||||
|
? props.currentSkills[0].strength
|
||||||
|
: 0
|
||||||
|
}
|
||||||
|
className="Input"
|
||||||
|
type="number"
|
||||||
|
onChange={handleInputChange}
|
||||||
|
ref={primaryAxValueInput}
|
||||||
|
disabled={primaryAxValue != 0}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className={primaryErrorClasses}>{errors.axValue1}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={secondarySetClasses}>
|
||||||
|
<div className="fields">
|
||||||
|
<select
|
||||||
|
key="ax2"
|
||||||
|
defaultValue={
|
||||||
|
props.currentSkills && props.currentSkills[1]
|
||||||
|
? props.currentSkills[1].modifier
|
||||||
|
: -1
|
||||||
|
}
|
||||||
|
onChange={handleSelectChange}
|
||||||
|
ref={secondaryAxModifierSelect}
|
||||||
|
>
|
||||||
|
{generateOptions(1)}
|
||||||
|
</select>
|
||||||
|
<input
|
||||||
|
defaultValue={
|
||||||
|
props.currentSkills && props.currentSkills[1]
|
||||||
|
? props.currentSkills[1].strength
|
||||||
|
: 0
|
||||||
|
}
|
||||||
|
className="Input"
|
||||||
|
type="number"
|
||||||
|
onChange={handleInputChange}
|
||||||
|
ref={secondaryAxValueInput}
|
||||||
|
disabled={secondaryAxValue != 0}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className={secondaryErrorClasses}>{errors.axValue2}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AXSelect
|
export default AXSelect
|
||||||
|
|
@ -1,214 +1,260 @@
|
||||||
.Button {
|
.Button {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
background: var(--button-bg);
|
||||||
|
border: none;
|
||||||
|
border-radius: $input-corner;
|
||||||
|
color: var(--button-text);
|
||||||
|
display: inline-flex;
|
||||||
|
font-size: $font-button;
|
||||||
|
font-weight: $normal;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.Blended:hover {
|
||||||
|
background: var(--button-bg-hover);
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--button-text-hover);
|
||||||
|
|
||||||
|
.Accessory svg {
|
||||||
|
fill: var(--button-text-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Accessory svg.stroke {
|
||||||
|
fill: none;
|
||||||
|
stroke: var(--button-text-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.Blended {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
}
|
||||||
border-radius: 6px;
|
|
||||||
color: $grey-50;
|
&.Contained {
|
||||||
display: inline-flex;
|
background: var(--button-contained-bg);
|
||||||
font-size: $font-button;
|
|
||||||
font-weight: $normal;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: white;
|
background: var(--button-contained-bg-hover);
|
||||||
cursor: pointer;
|
|
||||||
color: $grey-00;
|
|
||||||
|
|
||||||
.icon svg {
|
|
||||||
fill: $grey-00;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon.stroke svg {
|
|
||||||
fill: none;
|
|
||||||
stroke: $grey-00;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.destructive:hover {
|
&.Save:hover .Accessory svg {
|
||||||
background: $error;
|
fill: #ff4d4d;
|
||||||
color: white;
|
stroke: #ff4d4d;
|
||||||
|
|
||||||
.icon svg {
|
|
||||||
fill: white;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.save:hover {
|
&.Active.Save {
|
||||||
color: #FF4D4D;
|
color: #ff4d4d;
|
||||||
|
|
||||||
.icon svg {
|
.Accessory svg {
|
||||||
fill: #FF4D4D;
|
fill: #ff4d4d;
|
||||||
stroke: #FF4D4D;
|
stroke: #ff4d4d;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: darken(#ff4d4d, 30);
|
||||||
|
|
||||||
|
.Accessory svg {
|
||||||
|
fill: darken(#ff4d4d, 30);
|
||||||
|
stroke: darken(#ff4d4d, 30);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.medium {
|
||||||
|
height: $unit * 5.5;
|
||||||
|
padding: ($unit * 1.5) $unit-2x;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
padding: $unit * 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.destructive:hover {
|
||||||
|
background: $error;
|
||||||
|
color: $grey-100;
|
||||||
|
|
||||||
|
.Accessory svg {
|
||||||
|
fill: $grey-100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.save:hover {
|
||||||
|
color: #ff4d4d;
|
||||||
|
|
||||||
|
.Accessory svg {
|
||||||
|
fill: #ff4d4d;
|
||||||
|
stroke: #ff4d4d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.save.Active {
|
||||||
|
color: #ff4d4d;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: darken(#ff4d4d, 30);
|
||||||
|
|
||||||
|
.icon svg {
|
||||||
|
fill: darken(#ff4d4d, 30);
|
||||||
|
stroke: darken(#ff4d4d, 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.modal:hover {
|
||||||
|
background: $grey-90;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.modal.destructive {
|
||||||
|
color: $error;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: darken($error, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Accessory {
|
||||||
|
$dimension: $unit-2x;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: var(--button-text);
|
||||||
|
height: $dimension;
|
||||||
|
width: $dimension;
|
||||||
|
|
||||||
|
&.stroke {
|
||||||
|
fill: none;
|
||||||
|
stroke: var(--button-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.Add {
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.Check {
|
||||||
|
height: 22px;
|
||||||
|
width: 22px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.save.Active {
|
&.check svg {
|
||||||
color: #FF4D4D;
|
margin-top: 1px;
|
||||||
|
height: 14px;
|
||||||
.icon svg {
|
width: auto;
|
||||||
fill: #FF4D4D;
|
|
||||||
stroke: #FF4D4D;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: darken(#FF4D4D, 30);
|
|
||||||
|
|
||||||
.icon svg {
|
|
||||||
fill: darken(#FF4D4D, 30);
|
|
||||||
stroke: darken(#FF4D4D, 30);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.modal:hover {
|
svg &.settings svg {
|
||||||
background: $grey-90;
|
height: 13px;
|
||||||
|
width: 13px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.modal.destructive {
|
&.btn-blue {
|
||||||
color: $error;
|
background: $blue;
|
||||||
|
color: #8b8b8b;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: darken($error, 10)
|
background: #4b9be5;
|
||||||
}
|
color: #233e56;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.btn-red {
|
||||||
|
background: #fa4242;
|
||||||
|
color: #860f0f;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #e91a1a;
|
||||||
|
color: #4e1717;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
color: #4e1717;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
margin-top: 2px;
|
color: #860f0f;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.Active {
|
&.btn-disabled {
|
||||||
background: white;
|
background: #e0e0e0;
|
||||||
|
color: #bababa;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #e0e0e0;
|
||||||
|
color: #bababa;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.btn-blue {
|
&.null {
|
||||||
background: $blue;
|
background: $grey-90;
|
||||||
color: #8b8b8b;
|
color: $grey-55;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #4B9BE5;
|
background: $grey-70;
|
||||||
color: #233E56;
|
color: $grey-15;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.btn-red {
|
&.wind {
|
||||||
background: #fa4242;
|
background: $wind-bg-20;
|
||||||
color: #860f0f;
|
color: $wind-text-10;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #e91a1a;
|
background: darken($wind-bg-20, 10);
|
||||||
color: #4e1717;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
color: #4e1717;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
color: #860f0f;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.btn-disabled {
|
&.fire {
|
||||||
background: #e0e0e0;
|
background: $fire-bg-20;
|
||||||
color: #bababa;
|
color: $fire-text-10;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #e0e0e0;
|
background: darken($fire-bg-20, 10);
|
||||||
color: #bababa;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.null {
|
&.water {
|
||||||
background: $grey-90;
|
background: $water-bg-20;
|
||||||
color: $grey-50;
|
color: $water-text-10;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $grey-70;
|
background: darken($water-bg-20, 10);
|
||||||
color: $grey-00;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.wind {
|
&.earth {
|
||||||
background: $wind-bg-light;
|
background: $earth-bg-20;
|
||||||
color: $wind-text-dark;
|
color: $earth-text-10;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: darken($wind-bg-light, 10);
|
background: darken($earth-bg-20, 10);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.fire {
|
&.dark {
|
||||||
background: $fire-bg-light;
|
background: $dark-bg-10;
|
||||||
color: $fire-text-dark;
|
color: $dark-text-10;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: darken($fire-bg-light, 10);
|
background: darken($dark-bg-10, 10);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.water {
|
&.light {
|
||||||
background: $water-bg-light;
|
background: $light-bg-20;
|
||||||
color: $water-text-dark;
|
color: $light-text-10;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: darken($water-bg-light, 10);
|
background: darken($light-bg-20, 10);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.earth {
|
.Text {
|
||||||
background: $earth-bg-light;
|
color: inherit;
|
||||||
color: $earth-text-dark;
|
display: block;
|
||||||
|
width: 100%;
|
||||||
&: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%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { PropsWithChildren, useEffect, useState } from 'react'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
|
@ -15,127 +15,174 @@ import SettingsIcon from '~public/icons/Settings.svg'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
import { ButtonType } from '~utils/enums'
|
import { ButtonType } from '~utils/enums'
|
||||||
|
import { access } from 'fs'
|
||||||
|
|
||||||
interface Props {
|
interface Props
|
||||||
active?: boolean
|
extends React.DetailedHTMLProps<
|
||||||
disabled?: boolean
|
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
classes?: string[],
|
HTMLButtonElement
|
||||||
icon?: string
|
> {
|
||||||
type?: ButtonType
|
accessoryIcon?: React.ReactNode
|
||||||
children?: React.ReactNode
|
active?: boolean
|
||||||
onClick?: (event: React.MouseEvent<HTMLElement>) => void
|
blended?: boolean
|
||||||
|
contained?: boolean
|
||||||
|
size?: 'small' | 'medium' | 'large'
|
||||||
|
text?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button = (props: Props) => {
|
const defaultProps = {
|
||||||
// States
|
active: false,
|
||||||
const [active, setActive] = useState(false)
|
blended: false,
|
||||||
const [disabled, setDisabled] = useState(false)
|
contained: false,
|
||||||
const [pressed, setPressed] = useState(false)
|
size: 'medium',
|
||||||
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)
|
|
||||||
|
|
||||||
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 = (
|
|
||||||
<span className='icon'>
|
|
||||||
<AddIcon />
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
|
|
||||||
const menuIcon = (
|
|
||||||
<span className='icon'>
|
|
||||||
<MenuIcon />
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
|
|
||||||
const linkIcon = (
|
|
||||||
<span className='icon stroke'>
|
|
||||||
<LinkIcon />
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
|
|
||||||
const checkIcon = (
|
|
||||||
<span className='icon check'>
|
|
||||||
<CheckIcon />
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
|
|
||||||
const crossIcon = (
|
|
||||||
<span className='icon'>
|
|
||||||
<CrossIcon />
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
|
|
||||||
const editIcon = (
|
|
||||||
<span className='icon'>
|
|
||||||
<EditIcon />
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
|
|
||||||
const saveIcon = (
|
|
||||||
<span className='icon stroke'>
|
|
||||||
<SaveIcon />
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
|
|
||||||
const settingsIcon = (
|
|
||||||
<span className='icon settings'>
|
|
||||||
<SettingsIcon />
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
return icon
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMouseDown() {
|
|
||||||
setPressed(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMouseUp() {
|
|
||||||
setPressed(false)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className={classes}
|
|
||||||
disabled={disabled}
|
|
||||||
onMouseDown={handleMouseDown}
|
|
||||||
onMouseUp={handleMouseUp}
|
|
||||||
onClick={props.onClick}>
|
|
||||||
{ getIcon() }
|
|
||||||
|
|
||||||
{ (props.type != ButtonType.IconOnly) ?
|
|
||||||
<span className='text'>
|
|
||||||
{ props.children }
|
|
||||||
</span> : ''
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Button = React.forwardRef<HTMLButtonElement, Props>(function button(
|
||||||
|
{ accessoryIcon, active, blended, contained, size, text, ...props },
|
||||||
|
forwardedRef
|
||||||
|
) {
|
||||||
|
const classes = classNames(
|
||||||
|
{
|
||||||
|
Button: true,
|
||||||
|
Active: active,
|
||||||
|
Blended: blended,
|
||||||
|
Contained: contained,
|
||||||
|
// 'btn-pressed': pressed,
|
||||||
|
// 'btn-disabled': disabled,
|
||||||
|
// save: props.icon === 'save',
|
||||||
|
// destructive: props.type == ButtonType.Destructive,
|
||||||
|
},
|
||||||
|
size,
|
||||||
|
props.className
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasAccessory = () => {
|
||||||
|
if (accessoryIcon) return <span className="Accessory">{accessoryIcon}</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasText = () => {
|
||||||
|
if (text) return <span className="Text">{text}</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button {...props} className={classes} ref={forwardedRef}>
|
||||||
|
{hasAccessory()}
|
||||||
|
{hasText()}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (props.type) setButtonType(props.type)
|
||||||
|
// }, [props.type])
|
||||||
|
|
||||||
|
// const addIcon = (
|
||||||
|
// <span className="icon">
|
||||||
|
// <AddIcon />
|
||||||
|
// </span>
|
||||||
|
// )
|
||||||
|
|
||||||
|
// const menuIcon = (
|
||||||
|
// <span className="icon">
|
||||||
|
// <MenuIcon />
|
||||||
|
// </span>
|
||||||
|
// )
|
||||||
|
|
||||||
|
// const linkIcon = (
|
||||||
|
// <span className="icon stroke">
|
||||||
|
// <LinkIcon />
|
||||||
|
// </span>
|
||||||
|
// )
|
||||||
|
|
||||||
|
// const checkIcon = (
|
||||||
|
// <span className="icon check">
|
||||||
|
// <CheckIcon />
|
||||||
|
// </span>
|
||||||
|
// )
|
||||||
|
|
||||||
|
// const crossIcon = (
|
||||||
|
// <span className="icon">
|
||||||
|
// <CrossIcon />
|
||||||
|
// </span>
|
||||||
|
// )
|
||||||
|
|
||||||
|
// const editIcon = (
|
||||||
|
// <span className="icon">
|
||||||
|
// <EditIcon />
|
||||||
|
// </span>
|
||||||
|
// )
|
||||||
|
|
||||||
|
// const saveIcon = (
|
||||||
|
// <span className="icon stroke">
|
||||||
|
// <SaveIcon />
|
||||||
|
// </span>
|
||||||
|
// )
|
||||||
|
|
||||||
|
// const settingsIcon = (
|
||||||
|
// <span className="icon settings">
|
||||||
|
// <SettingsIcon />
|
||||||
|
// </span>
|
||||||
|
// )
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return icon
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function handleMouseDown() {
|
||||||
|
// setPressed(true)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function handleMouseUp() {
|
||||||
|
// setPressed(false)
|
||||||
|
// }
|
||||||
|
// return (
|
||||||
|
// <button
|
||||||
|
// className={classes}
|
||||||
|
// disabled={disabled}
|
||||||
|
// onMouseDown={handleMouseDown}
|
||||||
|
// onMouseUp={handleMouseUp}
|
||||||
|
// ref={forwardedRef}
|
||||||
|
// {...props}
|
||||||
|
// >
|
||||||
|
// {getIcon()}
|
||||||
|
|
||||||
|
// {props.type != ButtonType.IconOnly ? (
|
||||||
|
// <span className="text">{children}</span>
|
||||||
|
// ) : (
|
||||||
|
// ''
|
||||||
|
// )}
|
||||||
|
// </button>
|
||||||
|
// )
|
||||||
|
})
|
||||||
|
|
||||||
|
Button.defaultProps = defaultProps
|
||||||
|
|
||||||
export default Button
|
export default Button
|
||||||
|
|
@ -1,29 +1,34 @@
|
||||||
.Limited {
|
.Limited {
|
||||||
background: white;
|
$offset: 2px;
|
||||||
border-radius: 6px;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
gap: $unit;
|
|
||||||
padding-right: $unit * 2;
|
|
||||||
|
|
||||||
&:focus-within {
|
background: var(--input-bg);
|
||||||
border: 2px solid $blue;
|
border-radius: $input-corner;
|
||||||
box-shadow: 0 2px rgba(255, 255, 255, 1);
|
border: $offset solid transparent;
|
||||||
}
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
.Counter {
|
gap: $unit;
|
||||||
color: $grey-50;
|
padding-top: 2px;
|
||||||
font-weight: $bold;
|
padding-bottom: 2px;
|
||||||
line-height: 42px;
|
padding-right: calc($unit-2x - $offset);
|
||||||
}
|
|
||||||
|
&:focus-within {
|
||||||
.Input {
|
border: $offset solid $blue;
|
||||||
background: transparent;
|
// box-shadow: 0 2px rgba(255, 255, 255, 1);
|
||||||
border-radius: 0;
|
}
|
||||||
|
|
||||||
&:focus {
|
.Counter {
|
||||||
outline: none;
|
color: $grey-55;
|
||||||
}
|
font-weight: $bold;
|
||||||
|
line-height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Input {
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 0;
|
||||||
|
padding-left: calc($unit-2x - $offset);
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,53 +2,56 @@ import React, { useEffect, useState } from 'react'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
fieldName: string
|
fieldName: string
|
||||||
placeholder: string
|
placeholder: string
|
||||||
value?: string
|
value?: string
|
||||||
limit: number
|
limit: number
|
||||||
error: string
|
error: string
|
||||||
onBlur?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
onBlur?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const CharLimitedFieldset = React.forwardRef<HTMLInputElement, Props>(function useFieldSet(props, ref) {
|
const CharLimitedFieldset = React.forwardRef<HTMLInputElement, Props>(
|
||||||
const fieldType = (['password', 'confirm_password'].includes(props.fieldName)) ? 'password' : 'text'
|
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(() => {
|
useEffect(() => {
|
||||||
setCurrentCount((props.value) ? props.limit - props.value.length : props.limit)
|
setCurrentCount(
|
||||||
|
props.value ? props.limit - props.value.length : props.limit
|
||||||
|
)
|
||||||
}, [props.limit, props.value])
|
}, [props.limit, props.value])
|
||||||
|
|
||||||
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
|
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
setCurrentCount(props.limit - event.currentTarget.value.length)
|
setCurrentCount(props.limit - event.currentTarget.value.length)
|
||||||
if (props.onChange) props.onChange(event)
|
if (props.onChange) props.onChange(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<fieldset className="Fieldset">
|
<fieldset className="Fieldset">
|
||||||
<div className="Limited">
|
<div className="Limited">
|
||||||
<input
|
<input
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
className="Input"
|
className="Input"
|
||||||
type={fieldType}
|
type={fieldType}
|
||||||
name={props.fieldName}
|
name={props.fieldName}
|
||||||
placeholder={props.placeholder}
|
placeholder={props.placeholder}
|
||||||
defaultValue={props.value || ''}
|
defaultValue={props.value || ''}
|
||||||
onBlur={props.onBlur}
|
onBlur={props.onBlur}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
maxLength={props.limit}
|
maxLength={props.limit}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
formNoValidate
|
formNoValidate
|
||||||
/>
|
/>
|
||||||
<span className="Counter">{currentCount}</span>
|
<span className="Counter">{currentCount}</span>
|
||||||
</div>
|
</div>
|
||||||
{
|
{props.error.length > 0 && <p className="InputError">{props.error}</p>}
|
||||||
props.error.length > 0 &&
|
</fieldset>
|
||||||
<p className='InputError'>{props.error}</p>
|
|
||||||
}
|
|
||||||
</fieldset>
|
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export default CharLimitedFieldset
|
export default CharLimitedFieldset
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow {
|
.arrow {
|
||||||
color: $grey-50;
|
color: $grey-55;
|
||||||
font-size: 4rem;
|
font-size: 4rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
|
|
||||||
&:not(.btn-disabled) {
|
&:not(.btn-disabled) {
|
||||||
background: $grey-90;
|
background: $grey-90;
|
||||||
color: $grey-40;
|
color: $grey-50;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $grey-80;
|
background: $grey-80;
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from 'react'
|
||||||
import { setCookie } from "cookies-next"
|
import { setCookie } from 'cookies-next'
|
||||||
import Router, { useRouter } from "next/router"
|
import Router, { useRouter } from 'next/router'
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from 'react-i18next'
|
||||||
import { AxiosResponse } from "axios"
|
import { AxiosResponse } from 'axios'
|
||||||
|
|
||||||
import * as Dialog from "@radix-ui/react-dialog"
|
import * as Dialog from '@radix-ui/react-dialog'
|
||||||
|
|
||||||
import api from "~utils/api"
|
import api from '~utils/api'
|
||||||
import { appState } from "~utils/appState"
|
import { appState } from '~utils/appState'
|
||||||
import { accountState } from "~utils/accountState"
|
import { accountState } from '~utils/accountState'
|
||||||
|
|
||||||
import Button from "~components/Button"
|
import Button from '~components/Button'
|
||||||
|
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean
|
open: boolean
|
||||||
|
|
@ -24,7 +24,7 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const CharacterConflictModal = (props: Props) => {
|
const CharacterConflictModal = (props: Props) => {
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
// States
|
// States
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
@ -35,13 +35,13 @@ const CharacterConflictModal = (props: Props) => {
|
||||||
|
|
||||||
function imageUrl(character?: Character, uncap: number = 0) {
|
function imageUrl(character?: Character, uncap: number = 0) {
|
||||||
// Change the image based on the uncap level
|
// Change the image based on the uncap level
|
||||||
let suffix = "01"
|
let suffix = '01'
|
||||||
if (uncap == 6) suffix = "04"
|
if (uncap == 6) suffix = '04'
|
||||||
else if (uncap == 5) suffix = "03"
|
else if (uncap == 5) suffix = '03'
|
||||||
else if (uncap > 2) suffix = "02"
|
else if (uncap > 2) suffix = '02'
|
||||||
|
|
||||||
// Special casing for Lyria (and Young Cat eventually)
|
// Special casing for Lyria (and Young Cat eventually)
|
||||||
if (character?.granblue_id === "3030182000") {
|
if (character?.granblue_id === '3030182000') {
|
||||||
let element = 1
|
let element = 1
|
||||||
if (
|
if (
|
||||||
appState.grid.weapons.mainWeapon &&
|
appState.grid.weapons.mainWeapon &&
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,31 @@
|
||||||
#CharacterGrid {
|
#CharacterGrid {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
max-width: 761px;
|
max-width: 761px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#grid_characters {
|
#grid_characters {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
max-width: 761px;
|
max-width: 761px;
|
||||||
|
|
||||||
|
@media (max-width: $medium-screen) {
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
margin-right: $unit * 3;
|
||||||
|
|
||||||
@media (max-width: $medium-screen) {
|
@media (max-width: $medium-screen) {
|
||||||
justify-content: space-between;
|
margin-right: inherit;
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
& > * {
|
& > li:last-child {
|
||||||
margin-right: $unit * 3;
|
margin: 0;
|
||||||
|
}
|
||||||
@media (max-width: $medium-screen) {
|
|
||||||
margin-right: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& > li:last-child {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { getCookie } from "cookies-next"
|
import { getCookie } from 'cookies-next'
|
||||||
import { useSnapshot } from "valtio"
|
import { useSnapshot } from 'valtio'
|
||||||
|
|
||||||
import { AxiosResponse } from "axios"
|
import { AxiosResponse } from 'axios'
|
||||||
import debounce from "lodash.debounce"
|
import debounce from 'lodash.debounce'
|
||||||
|
|
||||||
import Alert from "~components/Alert"
|
import Alert from '~components/Alert'
|
||||||
import JobSection from "~components/JobSection"
|
import JobSection from '~components/JobSection'
|
||||||
import CharacterUnit from "~components/CharacterUnit"
|
import CharacterUnit from '~components/CharacterUnit'
|
||||||
import CharacterConflictModal from "~components/CharacterConflictModal"
|
import CharacterConflictModal from '~components/CharacterConflictModal'
|
||||||
|
|
||||||
import type { JobSkillObject, SearchableObject } from "~types"
|
import type { JobSkillObject, SearchableObject } from '~types'
|
||||||
|
|
||||||
import api from "~utils/api"
|
import api from '~utils/api'
|
||||||
import { appState } from "~utils/appState"
|
import { appState } from '~utils/appState'
|
||||||
|
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -31,7 +31,7 @@ const CharacterGrid = (props: Props) => {
|
||||||
const numCharacters: number = 5
|
const numCharacters: number = 5
|
||||||
|
|
||||||
// Cookies
|
// Cookies
|
||||||
const cookie = getCookie("account")
|
const cookie = getCookie('account')
|
||||||
const accountData: AccountCookie = cookie
|
const accountData: AccountCookie = cookie
|
||||||
? JSON.parse(cookie as string)
|
? JSON.parse(cookie as string)
|
||||||
: null
|
: null
|
||||||
|
|
@ -57,7 +57,7 @@ const CharacterGrid = (props: Props) => {
|
||||||
2: undefined,
|
2: undefined,
|
||||||
3: undefined,
|
3: undefined,
|
||||||
})
|
})
|
||||||
const [errorMessage, setErrorMessage] = useState("")
|
const [errorMessage, setErrorMessage] = useState('')
|
||||||
|
|
||||||
// Create a temporary state to store previous character uncap values
|
// Create a temporary state to store previous character uncap values
|
||||||
const [previousUncapValues, setPreviousUncapValues] = useState<{
|
const [previousUncapValues, setPreviousUncapValues] = useState<{
|
||||||
|
|
@ -116,7 +116,7 @@ const CharacterGrid = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCharacterResponse(data: any) {
|
async function handleCharacterResponse(data: any) {
|
||||||
if (data.hasOwnProperty("conflicts")) {
|
if (data.hasOwnProperty('conflicts')) {
|
||||||
setIncoming(data.incoming)
|
setIncoming(data.incoming)
|
||||||
setConflicts(data.conflicts)
|
setConflicts(data.conflicts)
|
||||||
setPosition(data.position)
|
setPosition(data.position)
|
||||||
|
|
@ -185,7 +185,7 @@ const CharacterGrid = (props: Props) => {
|
||||||
const saveJob = function (job: Job) {
|
const saveJob = function (job: Job) {
|
||||||
const payload = {
|
const payload = {
|
||||||
party: {
|
party: {
|
||||||
job_id: job ? job.id : "",
|
job_id: job ? job.id : '',
|
||||||
},
|
},
|
||||||
...headers,
|
...headers,
|
||||||
}
|
}
|
||||||
|
|
@ -231,9 +231,9 @@ const CharacterGrid = (props: Props) => {
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
const data = error.response.data
|
const data = error.response.data
|
||||||
if (data.code == "too_many_skills_of_type") {
|
if (data.code == 'too_many_skills_of_type') {
|
||||||
const message = `You can only add up to 2 ${
|
const message = `You can only add up to 2 ${
|
||||||
data.skill_type === "emp"
|
data.skill_type === 'emp'
|
||||||
? data.skill_type.toUpperCase()
|
? data.skill_type.toUpperCase()
|
||||||
: data.skill_type
|
: data.skill_type
|
||||||
} skills to your party at once.`
|
} skills to your party at once.`
|
||||||
|
|
@ -268,7 +268,7 @@ const CharacterGrid = (props: Props) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (uncapLevel != previousUncapValues[position])
|
if (uncapLevel != previousUncapValues[position])
|
||||||
await api.updateUncap("character", id, uncapLevel).then((response) => {
|
await api.updateUncap('character', id, uncapLevel).then((response) => {
|
||||||
storeGridCharacter(response.data.grid_character)
|
storeGridCharacter(response.data.grid_character)
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -332,7 +332,7 @@ const CharacterGrid = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancelAlert() {
|
function cancelAlert() {
|
||||||
setErrorMessage("")
|
setErrorMessage('')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render: JSX components
|
// Render: JSX components
|
||||||
|
|
@ -342,7 +342,7 @@ const CharacterGrid = (props: Props) => {
|
||||||
open={errorMessage.length > 0}
|
open={errorMessage.length > 0}
|
||||||
message={errorMessage}
|
message={errorMessage}
|
||||||
cancelAction={cancelAlert}
|
cancelAction={cancelAlert}
|
||||||
cancelActionText={"Got it"}
|
cancelActionText={'Got it'}
|
||||||
/>
|
/>
|
||||||
<div id="CharacterGrid">
|
<div id="CharacterGrid">
|
||||||
<JobSection
|
<JobSection
|
||||||
|
|
|
||||||
|
|
@ -10,83 +10,114 @@ import UncapIndicator from '~components/UncapIndicator'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
gridCharacter: GridCharacter
|
gridCharacter: GridCharacter
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
interface KeyNames {
|
interface KeyNames {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
en: string,
|
en: string
|
||||||
jp: string
|
jp: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const CharacterHovercard = (props: Props) => {
|
const CharacterHovercard = (props: Props) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
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 Proficiency = ['none', 'sword', 'dagger', 'axe', 'spear', 'bow', 'staff', 'fist', 'harp', 'gun', 'katana']
|
const Proficiency = [
|
||||||
|
'none',
|
||||||
|
'sword',
|
||||||
|
'dagger',
|
||||||
|
'axe',
|
||||||
|
'spear',
|
||||||
|
'bow',
|
||||||
|
'staff',
|
||||||
|
'fist',
|
||||||
|
'harp',
|
||||||
|
'gun',
|
||||||
|
'katana',
|
||||||
|
]
|
||||||
|
|
||||||
const tintElement = Element[props.gridCharacter.object.element]
|
const tintElement = Element[props.gridCharacter.object.element]
|
||||||
const wikiUrl = `https://gbf.wiki/${props.gridCharacter.object.name.en.replaceAll(' ', '_')}`
|
const wikiUrl = `https://gbf.wiki/${props.gridCharacter.object.name.en.replaceAll(
|
||||||
|
' ',
|
||||||
|
'_'
|
||||||
|
)}`
|
||||||
|
|
||||||
function characterImage() {
|
function characterImage() {
|
||||||
let imgSrc = ""
|
let imgSrc = ''
|
||||||
|
|
||||||
if (props.gridCharacter) {
|
if (props.gridCharacter) {
|
||||||
const character = props.gridCharacter.object
|
const character = props.gridCharacter.object
|
||||||
|
|
||||||
// Change the image based on the uncap level
|
// Change the image based on the uncap level
|
||||||
let suffix = '01'
|
let suffix = '01'
|
||||||
if (props.gridCharacter.uncap_level == 6)
|
if (props.gridCharacter.uncap_level == 6) suffix = '04'
|
||||||
suffix = '04'
|
else if (props.gridCharacter.uncap_level == 5) suffix = '03'
|
||||||
else if (props.gridCharacter.uncap_level == 5)
|
else if (props.gridCharacter.uncap_level > 2) suffix = '02'
|
||||||
suffix = '03'
|
|
||||||
else if (props.gridCharacter.uncap_level > 2)
|
|
||||||
suffix = '02'
|
|
||||||
|
|
||||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_${suffix}.jpg`
|
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_${suffix}.jpg`
|
||||||
}
|
|
||||||
|
|
||||||
return imgSrc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return imgSrc
|
||||||
<HoverCard.Root>
|
}
|
||||||
<HoverCard.Trigger>
|
|
||||||
{ props.children }
|
|
||||||
</HoverCard.Trigger>
|
|
||||||
<HoverCard.Content className="Weapon Hovercard">
|
|
||||||
<div className="top">
|
|
||||||
<div className="title">
|
|
||||||
<h4>{ props.gridCharacter.object.name[locale] }</h4>
|
|
||||||
<img alt={props.gridCharacter.object.name[locale]} src={characterImage()} />
|
|
||||||
</div>
|
|
||||||
<div className="subInfo">
|
|
||||||
<div className="icons">
|
|
||||||
<WeaponLabelIcon labelType={Element[props.gridCharacter.object.element]} />
|
|
||||||
<WeaponLabelIcon labelType={ Proficiency[props.gridCharacter.object.proficiency.proficiency1] } />
|
|
||||||
{ (props.gridCharacter.object.proficiency.proficiency2) ?
|
|
||||||
<WeaponLabelIcon labelType={ Proficiency[props.gridCharacter.object.proficiency.proficiency2] } />
|
|
||||||
: ''}
|
|
||||||
</div>
|
|
||||||
<UncapIndicator
|
|
||||||
type="character"
|
|
||||||
ulb={props.gridCharacter.object.uncap.ulb || false}
|
|
||||||
flb={props.gridCharacter.object.uncap.flb || false}
|
|
||||||
special={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">{t('buttons.wiki')}</a>
|
return (
|
||||||
<HoverCard.Arrow />
|
<HoverCard.Root>
|
||||||
</HoverCard.Content>
|
<HoverCard.Trigger>{props.children}</HoverCard.Trigger>
|
||||||
</HoverCard.Root>
|
<HoverCard.Content className="Weapon Hovercard">
|
||||||
)
|
<div className="top">
|
||||||
|
<div className="title">
|
||||||
|
<h4>{props.gridCharacter.object.name[locale]}</h4>
|
||||||
|
<img
|
||||||
|
alt={props.gridCharacter.object.name[locale]}
|
||||||
|
src={characterImage()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="subInfo">
|
||||||
|
<div className="icons">
|
||||||
|
<WeaponLabelIcon
|
||||||
|
labelType={Element[props.gridCharacter.object.element]}
|
||||||
|
/>
|
||||||
|
<WeaponLabelIcon
|
||||||
|
labelType={
|
||||||
|
Proficiency[
|
||||||
|
props.gridCharacter.object.proficiency.proficiency1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{props.gridCharacter.object.proficiency.proficiency2 ? (
|
||||||
|
<WeaponLabelIcon
|
||||||
|
labelType={
|
||||||
|
Proficiency[
|
||||||
|
props.gridCharacter.object.proficiency.proficiency2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<UncapIndicator
|
||||||
|
type="character"
|
||||||
|
ulb={props.gridCharacter.object.uncap.ulb || false}
|
||||||
|
flb={props.gridCharacter.object.uncap.flb || false}
|
||||||
|
special={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">
|
||||||
|
{t('buttons.wiki')}
|
||||||
|
</a>
|
||||||
|
<HoverCard.Arrow />
|
||||||
|
</HoverCard.Content>
|
||||||
|
</HoverCard.Root>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CharacterHovercard
|
export default CharacterHovercard
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,63 +1,67 @@
|
||||||
.CharacterResult {
|
.CharacterResult {
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
gap: $unit;
|
||||||
|
padding: $unit * 1.5;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--button-contained-bg);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.Info h5 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
background: var(--card-bg);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
display: inline-block;
|
||||||
|
height: 72px;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Info {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: $unit;
|
flex-direction: column;
|
||||||
padding: $unit * 1.5;
|
flex-grow: 1;
|
||||||
|
gap: $unit-half;
|
||||||
|
|
||||||
&:hover {
|
h5 {
|
||||||
background: $grey-90;
|
color: var(--text-secondary);
|
||||||
cursor: pointer;
|
display: inline-block;
|
||||||
|
font-size: $font-medium;
|
||||||
|
font-weight: $medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
.UncapIndicator {
|
||||||
background: $grey-80;
|
justify-content: left;
|
||||||
border-radius: 6px;
|
pointer-events: none;
|
||||||
display: inline-block;
|
|
||||||
height: 72px;
|
|
||||||
width: 120px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Info {
|
.stars {
|
||||||
display: flex;
|
display: inline-block;
|
||||||
flex-direction: column;
|
color: #ffa15e;
|
||||||
flex-grow: 1;
|
font-size: $font-xlarge;
|
||||||
gap: calc($unit / 2);
|
|
||||||
|
|
||||||
h5 {
|
& > span {
|
||||||
color: #555;
|
color: #65daff;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,45 +7,46 @@ import WeaponLabelIcon from '~components/WeaponLabelIcon'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: Character
|
data: Character
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
|
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
|
||||||
|
|
||||||
const CharacterResult = (props: Props) => {
|
const CharacterResult = (props: Props) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
const locale =
|
||||||
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
|
|
||||||
const character = props.data
|
const character = props.data
|
||||||
|
|
||||||
const characterUrl = () => {
|
const characterUrl = () => {
|
||||||
let url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_01.jpg`
|
let url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_01.jpg`
|
||||||
|
|
||||||
if (character.granblue_id === '3030182000') {
|
if (character.granblue_id === '3030182000') {
|
||||||
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_01_01.jpg`
|
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_01_01.jpg`
|
||||||
}
|
|
||||||
|
|
||||||
return url
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return url
|
||||||
<li className="CharacterResult" onClick={props.onClick}>
|
}
|
||||||
<img alt={character.name[locale]} src={characterUrl()} />
|
|
||||||
<div className="Info">
|
return (
|
||||||
<h5>{character.name[locale]}</h5>
|
<li className="CharacterResult" onClick={props.onClick}>
|
||||||
<UncapIndicator
|
<img alt={character.name[locale]} src={characterUrl()} />
|
||||||
type="character"
|
<div className="Info">
|
||||||
flb={character.uncap.flb}
|
<h5>{character.name[locale]}</h5>
|
||||||
ulb={character.uncap.ulb}
|
<UncapIndicator
|
||||||
special={character.special}
|
type="character"
|
||||||
/>
|
flb={character.uncap.flb}
|
||||||
<div className="tags">
|
ulb={character.uncap.ulb}
|
||||||
<WeaponLabelIcon labelType={Element[character.element]} />
|
special={character.special}
|
||||||
</div>
|
/>
|
||||||
</div>
|
<div className="tags">
|
||||||
</li>
|
<WeaponLabelIcon labelType={Element[character.element]} />
|
||||||
)
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CharacterResult
|
export default CharacterResult
|
||||||
|
|
@ -9,197 +9,260 @@ import SearchFilter from '~components/SearchFilter'
|
||||||
import SearchFilterCheckboxItem from '~components/SearchFilterCheckboxItem'
|
import SearchFilterCheckboxItem from '~components/SearchFilterCheckboxItem'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
import { emptyElementState, emptyProficiencyState, emptyRarityState } from '~utils/emptyStates'
|
import {
|
||||||
|
emptyElementState,
|
||||||
|
emptyProficiencyState,
|
||||||
|
emptyRarityState,
|
||||||
|
} from '~utils/emptyStates'
|
||||||
import { elements, proficiencies, rarities } from '~utils/stateValues'
|
import { elements, proficiencies, rarities } from '~utils/stateValues'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
sendFilters: (filters: { [key: string]: number[] }) => void
|
sendFilters: (filters: { [key: string]: number[] }) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const CharacterSearchFilterBar = (props: Props) => {
|
const CharacterSearchFilterBar = (props: Props) => {
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
const [rarityMenu, setRarityMenu] = useState(false)
|
const [rarityMenu, setRarityMenu] = useState(false)
|
||||||
const [elementMenu, setElementMenu] = useState(false)
|
const [elementMenu, setElementMenu] = useState(false)
|
||||||
const [proficiency1Menu, setProficiency1Menu] = useState(false)
|
const [proficiency1Menu, setProficiency1Menu] = useState(false)
|
||||||
const [proficiency2Menu, setProficiency2Menu] = useState(false)
|
const [proficiency2Menu, setProficiency2Menu] = useState(false)
|
||||||
|
|
||||||
const [rarityState, setRarityState] = useState<RarityState>(emptyRarityState)
|
const [rarityState, setRarityState] = useState<RarityState>(emptyRarityState)
|
||||||
const [elementState, setElementState] = useState<ElementState>(emptyElementState)
|
const [elementState, setElementState] =
|
||||||
const [proficiency1State, setProficiency1State] = useState<ProficiencyState>(emptyProficiencyState)
|
useState<ElementState>(emptyElementState)
|
||||||
const [proficiency2State, setProficiency2State] = useState<ProficiencyState>(emptyProficiencyState)
|
const [proficiency1State, setProficiency1State] = useState<ProficiencyState>(
|
||||||
|
emptyProficiencyState
|
||||||
|
)
|
||||||
|
const [proficiency2State, setProficiency2State] = useState<ProficiencyState>(
|
||||||
|
emptyProficiencyState
|
||||||
|
)
|
||||||
|
|
||||||
function rarityMenuOpened(open: boolean) {
|
function rarityMenuOpened(open: boolean) {
|
||||||
if (open) {
|
if (open) {
|
||||||
setRarityMenu(true)
|
setRarityMenu(true)
|
||||||
setElementMenu(false)
|
setElementMenu(false)
|
||||||
setProficiency1Menu(false)
|
setProficiency1Menu(false)
|
||||||
setProficiency2Menu(false)
|
setProficiency2Menu(false)
|
||||||
} else setRarityMenu(false)
|
} else setRarityMenu(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function elementMenuOpened(open: boolean) {
|
||||||
|
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 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 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 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)
|
||||||
|
|
||||||
|
const filters = {
|
||||||
|
rarity: checkedRarityFilters,
|
||||||
|
element: checkedElementFilters,
|
||||||
|
proficiency1: checkedProficiency1Filters,
|
||||||
|
proficiency2: checkedProficiency2Filters,
|
||||||
}
|
}
|
||||||
|
|
||||||
function elementMenuOpened(open: boolean) {
|
props.sendFilters(filters)
|
||||||
if (open) {
|
}
|
||||||
setRarityMenu(false)
|
|
||||||
setElementMenu(true)
|
|
||||||
setProficiency1Menu(false)
|
|
||||||
setProficiency2Menu(false)
|
|
||||||
} else setElementMenu(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
function proficiency1MenuOpened(open: boolean) {
|
useEffect(() => {
|
||||||
if (open) {
|
sendFilters()
|
||||||
setRarityMenu(false)
|
}, [rarityState, elementState, proficiency1State, proficiency2State])
|
||||||
setElementMenu(false)
|
|
||||||
setProficiency1Menu(true)
|
|
||||||
setProficiency2Menu(false)
|
|
||||||
} else setProficiency1Menu(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
function proficiency2MenuOpened(open: boolean) {
|
function renderProficiencyFilter(proficiency: 1 | 2) {
|
||||||
if (open) {
|
const onCheckedChange =
|
||||||
setRarityMenu(false)
|
proficiency == 1 ? handleProficiency1Change : handleProficiency2Change
|
||||||
setElementMenu(false)
|
const numSelected =
|
||||||
setProficiency1Menu(false)
|
proficiency == 1
|
||||||
setProficiency2Menu(true)
|
? Object.values(proficiency1State)
|
||||||
} else setProficiency2Menu(false)
|
.map((x) => x.checked)
|
||||||
}
|
.filter(Boolean).length
|
||||||
|
: Object.values(proficiency2State)
|
||||||
function handleRarityChange(checked: boolean, key: string) {
|
.map((x) => x.checked)
|
||||||
let newRarityState = cloneDeep(rarityState)
|
.filter(Boolean).length
|
||||||
newRarityState[key].checked = checked
|
const open = proficiency == 1 ? proficiency1Menu : proficiency2Menu
|
||||||
setRarityState(newRarityState)
|
const onOpenChange =
|
||||||
}
|
proficiency == 1 ? proficiency1MenuOpened : proficiency2MenuOpened
|
||||||
|
|
||||||
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 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)
|
|
||||||
|
|
||||||
const filters = {
|
|
||||||
rarity: checkedRarityFilters,
|
|
||||||
element: checkedElementFilters,
|
|
||||||
proficiency1: checkedProficiency1Filters,
|
|
||||||
proficiency2: checkedProficiency2Filters
|
|
||||||
}
|
|
||||||
|
|
||||||
props.sendFilters(filters)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<SearchFilter
|
|
||||||
label={`${t('filters.labels.proficiency')} ${proficiency}`}
|
|
||||||
numSelected={numSelected}
|
|
||||||
open={open}
|
|
||||||
onOpenChange={onOpenChange}>
|
|
||||||
<DropdownMenu.Label className="Label">{`${t('filters.labels.proficiency')} ${proficiency}`}</DropdownMenu.Label>
|
|
||||||
<section>
|
|
||||||
<DropdownMenu.Group className="Group">
|
|
||||||
{ Array.from(Array(proficiencies.length / 2)).map((x, i) => {
|
|
||||||
const checked = (proficiency == 1)
|
|
||||||
? proficiency1State[proficiencies[i]].checked
|
|
||||||
: proficiency2State[proficiencies[i]].checked
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SearchFilterCheckboxItem
|
|
||||||
key={proficiencies[i]}
|
|
||||||
onCheckedChange={onCheckedChange}
|
|
||||||
checked={checked}
|
|
||||||
valueKey={proficiencies[i]}>
|
|
||||||
{t(`proficiencies.${proficiencies[i]}`)}
|
|
||||||
</SearchFilterCheckboxItem>
|
|
||||||
)}
|
|
||||||
) }
|
|
||||||
</DropdownMenu.Group>
|
|
||||||
<DropdownMenu.Group className="Group">
|
|
||||||
{ Array.from(Array(proficiencies.length / 2)).map((x, i) => {
|
|
||||||
const checked = (proficiency == 1)
|
|
||||||
? proficiency1State[proficiencies[i + (proficiencies.length / 2)]].checked
|
|
||||||
: proficiency2State[proficiencies[i + (proficiencies.length / 2)]].checked
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SearchFilterCheckboxItem
|
|
||||||
key={proficiencies[i + (proficiencies.length / 2)]}
|
|
||||||
onCheckedChange={onCheckedChange}
|
|
||||||
checked={checked}
|
|
||||||
valueKey={proficiencies[i + (proficiencies.length / 2)]}>
|
|
||||||
{t(`proficiencies.${proficiencies[i + (proficiencies.length / 2)]}`)}
|
|
||||||
</SearchFilterCheckboxItem>
|
|
||||||
)}
|
|
||||||
) }
|
|
||||||
</DropdownMenu.Group>
|
|
||||||
</section>
|
|
||||||
</SearchFilter>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="SearchFilterBar">
|
<SearchFilter
|
||||||
<SearchFilter label={t('filters.labels.rarity')} numSelected={Object.values(rarityState).map(x => x.checked).filter(Boolean).length} open={rarityMenu} onOpenChange={rarityMenuOpened}>
|
label={`${t('filters.labels.proficiency')} ${proficiency}`}
|
||||||
<DropdownMenu.Label className="Label">{t('filters.labels.rarity')}</DropdownMenu.Label>
|
numSelected={numSelected}
|
||||||
{ Array.from(Array(rarities.length)).map((x, i) => {
|
open={open}
|
||||||
return (
|
onOpenChange={onOpenChange}
|
||||||
<SearchFilterCheckboxItem
|
>
|
||||||
key={rarities[i]}
|
<DropdownMenu.Label className="Label">{`${t(
|
||||||
onCheckedChange={handleRarityChange}
|
'filters.labels.proficiency'
|
||||||
checked={rarityState[rarities[i]].checked}
|
)} ${proficiency}`}</DropdownMenu.Label>
|
||||||
valueKey={rarities[i]}>
|
<section>
|
||||||
{t(`rarities.${rarities[i]}`)}
|
<DropdownMenu.Group className="Group">
|
||||||
</SearchFilterCheckboxItem>
|
{Array.from(Array(proficiencies.length / 2)).map((x, i) => {
|
||||||
)}
|
const checked =
|
||||||
) }
|
proficiency == 1
|
||||||
</SearchFilter>
|
? proficiency1State[proficiencies[i]].checked
|
||||||
|
: proficiency2State[proficiencies[i]].checked
|
||||||
|
|
||||||
<SearchFilter label={t('filters.labels.element')} numSelected={Object.values(elementState).map(x => x.checked).filter(Boolean).length} open={elementMenu} onOpenChange={elementMenuOpened}>
|
return (
|
||||||
<DropdownMenu.Label className="Label">{t('filters.labels.element')}</DropdownMenu.Label>
|
<SearchFilterCheckboxItem
|
||||||
{ Array.from(Array(elements.length)).map((x, i) => {
|
key={proficiencies[i]}
|
||||||
return (
|
onCheckedChange={onCheckedChange}
|
||||||
<SearchFilterCheckboxItem
|
checked={checked}
|
||||||
key={elements[i]}
|
valueKey={proficiencies[i]}
|
||||||
onCheckedChange={handleElementChange}
|
>
|
||||||
checked={elementState[elements[i]].checked}
|
{t(`proficiencies.${proficiencies[i]}`)}
|
||||||
valueKey={elements[i]}>
|
</SearchFilterCheckboxItem>
|
||||||
{t(`elements.${elements[i]}`)}
|
)
|
||||||
</SearchFilterCheckboxItem>
|
})}
|
||||||
)}
|
</DropdownMenu.Group>
|
||||||
) }
|
<DropdownMenu.Group className="Group">
|
||||||
</SearchFilter>
|
{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) }
|
return (
|
||||||
{ renderProficiencyFilter(2) }
|
<SearchFilterCheckboxItem
|
||||||
</div>
|
key={proficiencies[i + proficiencies.length / 2]}
|
||||||
|
onCheckedChange={onCheckedChange}
|
||||||
|
checked={checked}
|
||||||
|
valueKey={proficiencies[i + proficiencies.length / 2]}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
`proficiencies.${
|
||||||
|
proficiencies[i + proficiencies.length / 2]
|
||||||
|
}`
|
||||||
|
)}
|
||||||
|
</SearchFilterCheckboxItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</DropdownMenu.Group>
|
||||||
|
</section>
|
||||||
|
</SearchFilter>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="SearchFilterBar">
|
||||||
|
<SearchFilter
|
||||||
|
label={t('filters.labels.rarity')}
|
||||||
|
numSelected={
|
||||||
|
Object.values(rarityState)
|
||||||
|
.map((x) => x.checked)
|
||||||
|
.filter(Boolean).length
|
||||||
|
}
|
||||||
|
open={rarityMenu}
|
||||||
|
onOpenChange={rarityMenuOpened}
|
||||||
|
>
|
||||||
|
<DropdownMenu.Label className="Label">
|
||||||
|
{t('filters.labels.rarity')}
|
||||||
|
</DropdownMenu.Label>
|
||||||
|
{Array.from(Array(rarities.length)).map((x, i) => {
|
||||||
|
return (
|
||||||
|
<SearchFilterCheckboxItem
|
||||||
|
key={rarities[i]}
|
||||||
|
onCheckedChange={handleRarityChange}
|
||||||
|
checked={rarityState[rarities[i]].checked}
|
||||||
|
valueKey={rarities[i]}
|
||||||
|
>
|
||||||
|
{t(`rarities.${rarities[i]}`)}
|
||||||
|
</SearchFilterCheckboxItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</SearchFilter>
|
||||||
|
|
||||||
|
<SearchFilter
|
||||||
|
label={t('filters.labels.element')}
|
||||||
|
numSelected={
|
||||||
|
Object.values(elementState)
|
||||||
|
.map((x) => x.checked)
|
||||||
|
.filter(Boolean).length
|
||||||
|
}
|
||||||
|
open={elementMenu}
|
||||||
|
onOpenChange={elementMenuOpened}
|
||||||
|
>
|
||||||
|
<DropdownMenu.Label className="Label">
|
||||||
|
{t('filters.labels.element')}
|
||||||
|
</DropdownMenu.Label>
|
||||||
|
{Array.from(Array(elements.length)).map((x, i) => {
|
||||||
|
return (
|
||||||
|
<SearchFilterCheckboxItem
|
||||||
|
key={elements[i]}
|
||||||
|
onCheckedChange={handleElementChange}
|
||||||
|
checked={elementState[elements[i]].checked}
|
||||||
|
valueKey={elements[i]}
|
||||||
|
>
|
||||||
|
{t(`elements.${elements[i]}`)}
|
||||||
|
</SearchFilterCheckboxItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</SearchFilter>
|
||||||
|
|
||||||
|
{renderProficiencyFilter(1)}
|
||||||
|
{renderProficiencyFilter(2)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CharacterSearchFilterBar
|
export default CharacterSearchFilterBar
|
||||||
|
|
|
||||||
|
|
@ -1,79 +1,78 @@
|
||||||
.CharacterUnit {
|
.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;
|
display: flex;
|
||||||
flex-direction: column;
|
}
|
||||||
gap: calc($unit / 2);
|
|
||||||
min-height: 320px;
|
|
||||||
max-width: 200px;
|
|
||||||
margin-bottom: $unit * 4;
|
|
||||||
|
|
||||||
&.editable .CharacterImage:hover {
|
h3,
|
||||||
border: $hover-stroke;
|
ul {
|
||||||
box-shadow: $hover-shadow;
|
display: none;
|
||||||
cursor: pointer;
|
}
|
||||||
transform: $scale-tall;
|
|
||||||
|
h3 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
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: var(--card-bg);
|
||||||
|
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 {
|
&:hover .icon svg {
|
||||||
display: block;
|
fill: var(--icon-secondary-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.filled ul {
|
.icon {
|
||||||
display: flex;
|
position: absolute;
|
||||||
}
|
height: $unit * 3;
|
||||||
|
width: $unit * 3;
|
||||||
h3,
|
z-index: 1;
|
||||||
ul {
|
|
||||||
display: none;
|
svg {
|
||||||
}
|
fill: var(--icon-secondary);
|
||||||
|
}
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from 'next/router'
|
||||||
import { useSnapshot } from "valtio"
|
import { useSnapshot } from 'valtio'
|
||||||
import { useTranslation } from "next-i18next"
|
import { useTranslation } from 'next-i18next'
|
||||||
import classnames from "classnames"
|
import classnames from 'classnames'
|
||||||
|
|
||||||
import { appState } from "~utils/appState"
|
import { appState } from '~utils/appState'
|
||||||
|
|
||||||
import CharacterHovercard from "~components/CharacterHovercard"
|
import CharacterHovercard from '~components/CharacterHovercard'
|
||||||
import SearchModal from "~components/SearchModal"
|
import SearchModal from '~components/SearchModal'
|
||||||
import UncapIndicator from "~components/UncapIndicator"
|
import UncapIndicator from '~components/UncapIndicator'
|
||||||
import PlusIcon from "~public/icons/Add.svg"
|
import PlusIcon from '~public/icons/Add.svg'
|
||||||
|
|
||||||
import type { SearchableObject } from "~types"
|
import type { SearchableObject } from '~types'
|
||||||
|
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
gridCharacter?: GridCharacter
|
gridCharacter?: GridCharacter
|
||||||
|
|
@ -24,15 +24,15 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const CharacterUnit = (props: Props) => {
|
const CharacterUnit = (props: Props) => {
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
const { party, grid } = useSnapshot(appState)
|
const { party, grid } = useSnapshot(appState)
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale =
|
const locale =
|
||||||
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
|
|
||||||
const [imageUrl, setImageUrl] = useState("")
|
const [imageUrl, setImageUrl] = useState('')
|
||||||
|
|
||||||
const classes = classnames({
|
const classes = classnames({
|
||||||
CharacterUnit: true,
|
CharacterUnit: true,
|
||||||
|
|
@ -48,19 +48,19 @@ const CharacterUnit = (props: Props) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
function generateImageUrl() {
|
function generateImageUrl() {
|
||||||
let imgSrc = ""
|
let imgSrc = ''
|
||||||
|
|
||||||
if (props.gridCharacter) {
|
if (props.gridCharacter) {
|
||||||
const character = props.gridCharacter.object!
|
const character = props.gridCharacter.object!
|
||||||
|
|
||||||
// Change the image based on the uncap level
|
// Change the image based on the uncap level
|
||||||
let suffix = "01"
|
let suffix = '01'
|
||||||
if (props.gridCharacter.uncap_level == 6) suffix = "04"
|
if (props.gridCharacter.uncap_level == 6) suffix = '04'
|
||||||
else if (props.gridCharacter.uncap_level == 5) suffix = "03"
|
else if (props.gridCharacter.uncap_level == 5) suffix = '03'
|
||||||
else if (props.gridCharacter.uncap_level > 2) suffix = "02"
|
else if (props.gridCharacter.uncap_level > 2) suffix = '02'
|
||||||
|
|
||||||
// Special casing for Lyria (and Young Cat eventually)
|
// Special casing for Lyria (and Young Cat eventually)
|
||||||
if (props.gridCharacter.object.granblue_id === "3030182000") {
|
if (props.gridCharacter.object.granblue_id === '3030182000') {
|
||||||
let element = 1
|
let element = 1
|
||||||
if (grid.weapons.mainWeapon && grid.weapons.mainWeapon.element) {
|
if (grid.weapons.mainWeapon && grid.weapons.mainWeapon.element) {
|
||||||
element = grid.weapons.mainWeapon.element
|
element = grid.weapons.mainWeapon.element
|
||||||
|
|
@ -90,14 +90,14 @@ const CharacterUnit = (props: Props) => {
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
const editableImage = (
|
const editableImage = (
|
||||||
<SearchModal
|
<SearchModal
|
||||||
placeholderText={t("search.placeholders.character")}
|
placeholderText={t('search.placeholders.character')}
|
||||||
fromPosition={props.position}
|
fromPosition={props.position}
|
||||||
object="characters"
|
object="characters"
|
||||||
send={props.updateObject}
|
send={props.updateObject}
|
||||||
|
|
@ -119,7 +119,7 @@ const CharacterUnit = (props: Props) => {
|
||||||
special={character.special}
|
special={character.special}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)}
|
)}
|
||||||
<h3 className="CharacterName">{character?.name[locale]}</h3>
|
<h3 className="CharacterName">{character?.name[locale]}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
87
components/Dialog/index.scss
Normal file
87
components/Dialog/index.scss
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
.Dialog {
|
||||||
|
$multiplier: 4;
|
||||||
|
|
||||||
|
animation: 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0s 1 normal none running
|
||||||
|
openModal;
|
||||||
|
background: var(--dialog-bg);
|
||||||
|
border-radius: $card-corner;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit * $multiplier;
|
||||||
|
height: auto;
|
||||||
|
min-width: $unit * 48;
|
||||||
|
min-height: $unit-12x;
|
||||||
|
padding: $unit * $multiplier;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 21;
|
||||||
|
|
||||||
|
.DialogHeader {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: $unit;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
gap: $unit;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: $font-small;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.DialogClose {
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: $error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: $grey-50;
|
||||||
|
float: right;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.DialogTitle {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: $font-xlarge;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DialogTop {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
gap: calc($unit / 2);
|
||||||
|
|
||||||
|
.SubTitle {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: $font-small;
|
||||||
|
font-weight: $medium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.DialogDescription {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
39
components/Dialog/index.tsx
Normal file
39
components/Dialog/index.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React from 'react'
|
||||||
|
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
|
interface Props
|
||||||
|
extends React.DetailedHTMLProps<
|
||||||
|
React.DialogHTMLAttributes<HTMLDivElement>,
|
||||||
|
HTMLDivElement
|
||||||
|
> {}
|
||||||
|
|
||||||
|
export const DialogContent = React.forwardRef<HTMLDivElement, Props>(
|
||||||
|
function dialog({ children, ...props }, forwardedRef) {
|
||||||
|
const classes = classNames(
|
||||||
|
{
|
||||||
|
Dialog: true,
|
||||||
|
},
|
||||||
|
props.className
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Portal>
|
||||||
|
<DialogPrimitive.Overlay className="Overlay" />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
className={classes}
|
||||||
|
{...props}
|
||||||
|
ref={forwardedRef}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPrimitive.Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const Dialog = DialogPrimitive.Root
|
||||||
|
export const DialogTrigger = DialogPrimitive.Trigger
|
||||||
|
export const DialogClose = DialogPrimitive.Close
|
||||||
|
|
@ -1,64 +1,65 @@
|
||||||
.ToggleGroup {
|
.ToggleGroup {
|
||||||
$height: 36px;
|
$height: 36px;
|
||||||
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.14);
|
border: 1px solid rgba(0, 0, 0, 0.14);
|
||||||
border-radius: $height;
|
border-radius: $height;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: $height;
|
height: $height;
|
||||||
gap: calc($unit / 4);
|
gap: calc($unit / 4);
|
||||||
padding: calc($unit / 2);
|
padding: calc($unit / 2);
|
||||||
|
|
||||||
.ToggleItem {
|
.ToggleItem {
|
||||||
background: white;
|
background: $grey-100;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
color: $grey-40;
|
color: $grey-50;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
font-size: $font-regular;
|
font-size: $font-regular;
|
||||||
padding: ($unit) $unit * 2;
|
padding: ($unit) $unit * 2;
|
||||||
|
|
||||||
&.ja {
|
&.ja {
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
padding-bottom: 10px;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&[data-state='on'] {
|
||||||
|
background: $grey-80;
|
||||||
|
color: $grey-15;
|
||||||
|
|
||||||
|
&.fire {
|
||||||
|
background: $fire-bg-20;
|
||||||
|
color: $fire-text-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.water {
|
||||||
|
background: $water-bg-20;
|
||||||
|
color: $water-text-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.earth {
|
||||||
|
background: $earth-bg-20;
|
||||||
|
color: $earth-text-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.wind {
|
||||||
|
background: $wind-bg-20;
|
||||||
|
color: $wind-text-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
background: $dark-bg-10;
|
||||||
|
color: $dark-text-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.light {
|
||||||
|
background: $light-bg-20;
|
||||||
|
color: $light-text-10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,40 +7,75 @@ import * as ToggleGroup from '@radix-ui/react-toggle-group'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
currentElement: number
|
currentElement: number
|
||||||
sendValue: (value: string) => void
|
sendValue: (value: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ElementToggle = (props: Props) => {
|
const ElementToggle = (props: Props) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
const locale =
|
||||||
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToggleGroup.Root className="ToggleGroup" type="single" defaultValue={`${props.currentElement}`} aria-label="Element" onValueChange={props.sendValue}>
|
<ToggleGroup.Root
|
||||||
<ToggleGroup.Item className={`ToggleItem ${locale}`} value="0" aria-label="null">
|
className="ToggleGroup"
|
||||||
{t('elements.null')}
|
type="single"
|
||||||
</ToggleGroup.Item>
|
defaultValue={`${props.currentElement}`}
|
||||||
<ToggleGroup.Item className={`ToggleItem wind ${locale}`} value="1" aria-label="wind">
|
aria-label="Element"
|
||||||
{t('elements.wind')}
|
onValueChange={props.sendValue}
|
||||||
</ToggleGroup.Item>
|
>
|
||||||
<ToggleGroup.Item className={`ToggleItem fire ${locale}`} value="2" aria-label="fire">
|
<ToggleGroup.Item
|
||||||
{t('elements.fire')}
|
className={`ToggleItem ${locale}`}
|
||||||
</ToggleGroup.Item>
|
value="0"
|
||||||
<ToggleGroup.Item className={`ToggleItem water ${locale}`} value="3" aria-label="water">
|
aria-label="null"
|
||||||
{t('elements.water')}
|
>
|
||||||
</ToggleGroup.Item>
|
{t('elements.null')}
|
||||||
<ToggleGroup.Item className={`ToggleItem earth ${locale}`} value="4" aria-label="earth">
|
</ToggleGroup.Item>
|
||||||
{t('elements.earth')}
|
<ToggleGroup.Item
|
||||||
</ToggleGroup.Item>
|
className={`ToggleItem wind ${locale}`}
|
||||||
<ToggleGroup.Item className={`ToggleItem dark ${locale}`} value="5" aria-label="dark">
|
value="1"
|
||||||
{t('elements.dark')}
|
aria-label="wind"
|
||||||
</ToggleGroup.Item>
|
>
|
||||||
<ToggleGroup.Item className={`ToggleItem light ${locale}`} value="6" aria-label="light">
|
{t('elements.wind')}
|
||||||
{t('elements.light')}
|
</ToggleGroup.Item>
|
||||||
</ToggleGroup.Item>
|
<ToggleGroup.Item
|
||||||
</ToggleGroup.Root>
|
className={`ToggleItem fire ${locale}`}
|
||||||
)
|
value="2"
|
||||||
|
aria-label="fire"
|
||||||
|
>
|
||||||
|
{t('elements.fire')}
|
||||||
|
</ToggleGroup.Item>
|
||||||
|
<ToggleGroup.Item
|
||||||
|
className={`ToggleItem water ${locale}`}
|
||||||
|
value="3"
|
||||||
|
aria-label="water"
|
||||||
|
>
|
||||||
|
{t('elements.water')}
|
||||||
|
</ToggleGroup.Item>
|
||||||
|
<ToggleGroup.Item
|
||||||
|
className={`ToggleItem earth ${locale}`}
|
||||||
|
value="4"
|
||||||
|
aria-label="earth"
|
||||||
|
>
|
||||||
|
{t('elements.earth')}
|
||||||
|
</ToggleGroup.Item>
|
||||||
|
<ToggleGroup.Item
|
||||||
|
className={`ToggleItem dark ${locale}`}
|
||||||
|
value="5"
|
||||||
|
aria-label="dark"
|
||||||
|
>
|
||||||
|
{t('elements.dark')}
|
||||||
|
</ToggleGroup.Item>
|
||||||
|
<ToggleGroup.Item
|
||||||
|
className={`ToggleItem light ${locale}`}
|
||||||
|
value="6"
|
||||||
|
aria-label="light"
|
||||||
|
>
|
||||||
|
{t('elements.light')}
|
||||||
|
</ToggleGroup.Item>
|
||||||
|
</ToggleGroup.Root>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ElementToggle
|
export default ElementToggle
|
||||||
|
|
@ -1,55 +1,55 @@
|
||||||
#ExtraSummons {
|
#ExtraSummons {
|
||||||
background: #FFEBD9;
|
background: var(--subaura-orange-bg);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-sizing: border-box;
|
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: var(--subaura-orange-text);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: 20px auto;
|
line-height: 1.2;
|
||||||
max-width: 727px;
|
font-weight: 500;
|
||||||
padding: 16px 16px 16px 0;
|
margin-right: 16px;
|
||||||
position: relative;
|
text-align: center;
|
||||||
left: 9px;
|
width: 387px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: $medium-screen) {
|
#grid_summons {
|
||||||
left: auto;
|
display: grid;
|
||||||
max-width: auto;
|
grid-template-columns: auto auto;
|
||||||
width: 100%;
|
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 {
|
.SummonUnit .SummonImage {
|
||||||
color: #825B39;
|
background: var(--subaura-orange-card-bg);
|
||||||
display: flex;
|
}
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
line-height: 1.2;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-right: 16px;
|
|
||||||
text-align: center;
|
|
||||||
width: 387px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#grid_summons {
|
.SummonUnit .SummonImage .icon svg {
|
||||||
display: grid;
|
fill: var(--subaura-orange-secondary);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import React from "react"
|
import React from 'react'
|
||||||
import { useTranslation } from "next-i18next"
|
import { useTranslation } from 'next-i18next'
|
||||||
import SummonUnit from "~components/SummonUnit"
|
import SummonUnit from '~components/SummonUnit'
|
||||||
import { SearchableObject } from "~types"
|
import { SearchableObject } from '~types'
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -18,11 +18,11 @@ interface Props {
|
||||||
const ExtraSummons = (props: Props) => {
|
const ExtraSummons = (props: Props) => {
|
||||||
const numSummons: number = 2
|
const numSummons: number = 2
|
||||||
|
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="ExtraSummons">
|
<div id="ExtraSummons">
|
||||||
<span>{t("summons.subaura")}</span>
|
<span>{t('summons.subaura')}</span>
|
||||||
<ul id="grid_summons">
|
<ul id="grid_summons">
|
||||||
{Array.from(Array(numSummons)).map((x, i) => {
|
{Array.from(Array(numSummons)).map((x, i) => {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,47 @@
|
||||||
#ExtraGrid {
|
#ExtraGrid {
|
||||||
background: #ECEBFF;
|
background: var(--extra-purple-bg);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-sizing: border-box;
|
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: var(--extra-purple-text);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-grow: 1;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: 20px auto;
|
line-height: 1.2;
|
||||||
max-width: 766px;
|
font-weight: 500;
|
||||||
padding: 16px 16px 16px 0;
|
margin-right: 16px;
|
||||||
position: relative;
|
text-align: center;
|
||||||
left: 8px;
|
}
|
||||||
|
|
||||||
@media (max-width: $medium-screen) {
|
.grid_weapons {
|
||||||
left: auto;
|
display: flex;
|
||||||
max-width: auto;
|
flex-direction: row;
|
||||||
width: 100%;
|
flex-wrap: wrap;
|
||||||
}
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
max-width: 528px;
|
||||||
|
}
|
||||||
|
|
||||||
& > span {
|
.WeaponUnit .WeaponImage {
|
||||||
color: #4F3C79;
|
background: var(--extra-purple-card-bg);
|
||||||
display: flex;
|
}
|
||||||
align-items: center;
|
|
||||||
flex-grow: 1;
|
|
||||||
justify-content: center;
|
|
||||||
line-height: 1.2;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-right: 16px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid_weapons {
|
.WeaponUnit .WeaponImage .icon svg {
|
||||||
display: flex;
|
fill: var(--extra-purple-secondary);
|
||||||
flex-direction: row;
|
}
|
||||||
flex-wrap: wrap;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
max-width: 528px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.WeaponUnit .WeaponImage {
|
|
||||||
background: #D5D3F6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.WeaponUnit .WeaponImage .icon svg {
|
|
||||||
fill: #8F8AC6;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import React from "react"
|
import React from 'react'
|
||||||
import { useTranslation } from "next-i18next"
|
import { useTranslation } from 'next-i18next'
|
||||||
import WeaponUnit from "~components/WeaponUnit"
|
import WeaponUnit from '~components/WeaponUnit'
|
||||||
|
|
||||||
import type { SearchableObject } from "~types"
|
import type { SearchableObject } from '~types'
|
||||||
|
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -18,17 +18,17 @@ interface Props {
|
||||||
|
|
||||||
const ExtraWeapons = (props: Props) => {
|
const ExtraWeapons = (props: Props) => {
|
||||||
const numWeapons: number = 3
|
const numWeapons: number = 3
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="ExtraGrid">
|
<div id="ExtraGrid">
|
||||||
<span>{t("extra_weapons")}</span>
|
<span>{t('extra_weapons')}</span>
|
||||||
<ul className="grid_weapons">
|
<ul className="grid_weapons">
|
||||||
{Array.from(Array(numWeapons)).map((x, i) => {
|
{Array.from(Array(numWeapons)).map((x, i) => {
|
||||||
return (
|
return (
|
||||||
<li key={`grid_unit_${i}`}>
|
<li key={`grid_unit_${i}`}>
|
||||||
<WeaponUnit
|
<WeaponUnit
|
||||||
editable={props.editable}
|
editable={i < 2 ? props.editable : false}
|
||||||
position={props.offset + i}
|
position={props.offset + i}
|
||||||
unitType={1}
|
unitType={1}
|
||||||
gridWeapon={props.grid[props.offset + i]}
|
gridWeapon={props.grid[props.offset + i]}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
.Fieldset {
|
|
||||||
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%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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 */
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import './index.scss'
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
fieldName: string
|
|
||||||
placeholder: string
|
|
||||||
value?: string
|
|
||||||
error: string
|
|
||||||
onBlur?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
|
||||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const Fieldset = React.forwardRef<HTMLInputElement, Props>(function fieldSet(props, ref) {
|
|
||||||
const fieldType = (['password', 'confirm_password'].includes(props.fieldName)) ? 'password' : 'text'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<fieldset className="Fieldset">
|
|
||||||
<input
|
|
||||||
autoComplete="off"
|
|
||||||
className="Input"
|
|
||||||
type={fieldType}
|
|
||||||
name={props.fieldName}
|
|
||||||
placeholder={props.placeholder}
|
|
||||||
defaultValue={props.value || ''}
|
|
||||||
onBlur={props.onBlur}
|
|
||||||
onChange={props.onChange}
|
|
||||||
ref={ref}
|
|
||||||
formNoValidate
|
|
||||||
/>
|
|
||||||
{
|
|
||||||
props.error.length > 0 &&
|
|
||||||
<p className='InputError'>{props.error}</p>
|
|
||||||
}
|
|
||||||
</fieldset>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default Fieldset
|
|
||||||
|
|
@ -1,63 +1,76 @@
|
||||||
.FilterBar {
|
.FilterBar {
|
||||||
|
align-items: center;
|
||||||
|
background: var(--bar-bg);
|
||||||
|
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: var(--text-primary);
|
||||||
|
font-size: $font-regular;
|
||||||
|
font-weight: $normal;
|
||||||
|
flex-grow: 1;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
select,
|
||||||
|
.SelectTrigger {
|
||||||
|
// background: url("/icons/Arrow.svg"), $grey-90;
|
||||||
|
// background-repeat: no-repeat;
|
||||||
|
// background-position-y: center;
|
||||||
|
// background-position-x: 95%;
|
||||||
|
// background-size: $unit * 1.5;
|
||||||
|
background-color: var(--select-contained-bg);
|
||||||
|
color: $grey-55;
|
||||||
|
font-size: $font-small;
|
||||||
|
margin: 0;
|
||||||
|
max-width: 200px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--select-contained-bg-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.SelectTrigger {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: $font-small;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.UserInfo {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: white;
|
|
||||||
border-radius: 6px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: $unit * 2;
|
flex-grow: 1;
|
||||||
margin: 0 auto;
|
gap: $unit * 1.5;
|
||||||
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 {
|
img {
|
||||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.14);
|
$diameter: $unit * 6;
|
||||||
}
|
border-radius: $diameter / 2;
|
||||||
|
height: $diameter;
|
||||||
h1 {
|
width: $diameter;
|
||||||
color: $grey-20;
|
|
||||||
font-size: $font-regular;
|
&.gran {
|
||||||
font-weight: $normal;
|
background-color: #cee7fe;
|
||||||
flex-grow: 1;
|
}
|
||||||
text-align: left;
|
|
||||||
}
|
&.djeeta {
|
||||||
|
background-color: #ffe1fe;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,79 +1,145 @@
|
||||||
import React from 'react'
|
import React, { useState } from 'react'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
|
||||||
import RaidDropdown from '~components/RaidDropdown'
|
import RaidDropdown from '~components/RaidDropdown'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
import Select from '~components/Select'
|
||||||
|
import SelectItem from '~components/SelectItem'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
scrolled: boolean
|
scrolled: boolean
|
||||||
|
element?: number
|
||||||
|
raidSlug?: string
|
||||||
|
recency?: number
|
||||||
|
onFilter: ({
|
||||||
|
element,
|
||||||
|
raidSlug,
|
||||||
|
recency,
|
||||||
|
}: {
|
||||||
element?: number
|
element?: number
|
||||||
raidSlug?: string
|
raidSlug?: string
|
||||||
recency?: number
|
recency?: number
|
||||||
onFilter: ({element, raidSlug, recency} : { element?: number, raidSlug?: string, recency?: number}) => void
|
}) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilterBar = (props: Props) => {
|
const FilterBar = (props: Props) => {
|
||||||
// Set up translation
|
// Set up translation
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
// Set up refs for filter dropdowns
|
const [recencyOpen, setRecencyOpen] = useState(false)
|
||||||
const elementSelect = React.createRef<HTMLSelectElement>()
|
const [elementOpen, setElementOpen] = useState(false)
|
||||||
const raidSelect = React.createRef<HTMLSelectElement>()
|
|
||||||
const recencySelect = React.createRef<HTMLSelectElement>()
|
|
||||||
|
|
||||||
// Set up classes object for showing shadow on scroll
|
// Set up refs for filter dropdowns
|
||||||
const classes = classNames({
|
const elementSelect = React.createRef<HTMLSelectElement>()
|
||||||
'FilterBar': true,
|
const raidSelect = React.createRef<HTMLSelectElement>()
|
||||||
'shadow': props.scrolled
|
const recencySelect = React.createRef<HTMLSelectElement>()
|
||||||
})
|
|
||||||
|
|
||||||
function elementSelectChanged() {
|
// Set up classes object for showing shadow on scroll
|
||||||
const elementValue = (elementSelect.current) ? parseInt(elementSelect.current.value) : -1
|
const classes = classNames({
|
||||||
props.onFilter({ element: elementValue })
|
FilterBar: true,
|
||||||
}
|
shadow: props.scrolled,
|
||||||
|
})
|
||||||
|
|
||||||
function recencySelectChanged() {
|
function openElementSelect() {
|
||||||
const recencyValue = (recencySelect.current) ? parseInt(recencySelect.current.value) : -1
|
setElementOpen(!elementOpen)
|
||||||
props.onFilter({ recency: recencyValue })
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function raidSelectChanged(slug?: string) {
|
function openRecencySelect() {
|
||||||
props.onFilter({ raidSlug: slug })
|
setRecencyOpen(!recencyOpen)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
function elementSelectChanged(value: string) {
|
||||||
<div className={classes}>
|
const elementValue = parseInt(value)
|
||||||
{props.children}
|
props.onFilter({ element: elementValue })
|
||||||
<select onChange={elementSelectChanged} ref={elementSelect} value={props.element}>
|
}
|
||||||
<option data-element="all" key={-1} value={-1}>{t('elements.full.all')}</option>
|
|
||||||
<option data-element="null" key={0} value={0}>{t('elements.full.null')}</option>
|
function recencySelectChanged(value: string) {
|
||||||
<option data-element="wind" key={1} value={1}>{t('elements.full.wind')}</option>
|
const recencyValue = parseInt(value)
|
||||||
<option data-element="fire" key={2} value={2}>{t('elements.full.fire')}</option>
|
props.onFilter({ recency: recencyValue })
|
||||||
<option data-element="water" key={3} value={3}>{t('elements.full.water')}</option>
|
}
|
||||||
<option data-element="earth" key={4} value={4}>{t('elements.full.earth')}</option>
|
|
||||||
<option data-element="dark" key={5} value={5}>{t('elements.full.dark')}</option>
|
function raidSelectChanged(slug?: string) {
|
||||||
<option data-element="light" key={6} value={6}>{t('elements.full.light')}</option>
|
props.onFilter({ raidSlug: slug })
|
||||||
</select>
|
}
|
||||||
<RaidDropdown
|
|
||||||
currentRaid={props.raidSlug}
|
return (
|
||||||
showAllRaidsOption={true}
|
<div className={classes}>
|
||||||
onChange={raidSelectChanged}
|
{props.children}
|
||||||
ref={raidSelect}
|
<Select
|
||||||
/>
|
defaultValue={-1}
|
||||||
<select onChange={recencySelectChanged} ref={recencySelect}>
|
trigger={'All elements'}
|
||||||
<option key={-1} value={-1}>{t('recency.all_time')}</option>
|
open={elementOpen}
|
||||||
<option key={86400} value={86400}>{t('recency.last_day')}</option>
|
onChange={elementSelectChanged}
|
||||||
<option key={604800} value={604800}>{t('recency.last_week')}</option>
|
onClick={openElementSelect}
|
||||||
<option key={2629746} value={2629746}>{t('recency.last_month')}</option>
|
>
|
||||||
<option key={7889238} value={7889238}>{t('recency.last_3_months')}</option>
|
<SelectItem data-element="all" key={-1} value={-1}>
|
||||||
<option key={15778476} value={15778476}>{t('recency.last_6_months')}</option>
|
{t('elements.full.all')}
|
||||||
<option key={31556952} value={31556952}>{t('recency.last_year')}</option>
|
</SelectItem>
|
||||||
</select>
|
<SelectItem data-element="null" key={0} value={0}>
|
||||||
</div>
|
{t('elements.full.null')}
|
||||||
)
|
</SelectItem>
|
||||||
|
<SelectItem data-element="wind" key={1} value={1}>
|
||||||
|
{t('elements.full.wind')}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem data-element="fire" key={2} value={2}>
|
||||||
|
{t('elements.full.fire')}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem data-element="water" key={3} value={3}>
|
||||||
|
{t('elements.full.water')}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem data-element="earth" key={4} value={4}>
|
||||||
|
{t('elements.full.earth')}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem data-element="dark" key={5} value={5}>
|
||||||
|
{t('elements.full.dark')}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem data-element="light" key={6} value={6}>
|
||||||
|
{t('elements.full.light')}
|
||||||
|
</SelectItem>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<RaidDropdown
|
||||||
|
currentRaid={props.raidSlug}
|
||||||
|
defaultRaid="all"
|
||||||
|
showAllRaidsOption={true}
|
||||||
|
onChange={raidSelectChanged}
|
||||||
|
ref={raidSelect}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
defaultValue={-1}
|
||||||
|
trigger={'All time'}
|
||||||
|
open={recencyOpen}
|
||||||
|
onChange={recencySelectChanged}
|
||||||
|
onClick={openRecencySelect}
|
||||||
|
>
|
||||||
|
<SelectItem key={-1} value={-1}>
|
||||||
|
{t('recency.all_time')}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem key={86400} value={86400}>
|
||||||
|
{t('recency.last_day')}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem key={604800} value={604800}>
|
||||||
|
{t('recency.last_week')}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem key={2629746} value={2629746}>
|
||||||
|
{t('recency.last_month')}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem key={7889238} value={7889238}>
|
||||||
|
{t('recency.last_3_months')}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem key={15778476} value={15778476}>
|
||||||
|
{t('recency.last_6_months')}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem key={31556952} value={31556952}>
|
||||||
|
{t('recency.last_year')}
|
||||||
|
</SelectItem>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FilterBar
|
export default FilterBar
|
||||||
|
|
|
||||||
|
|
@ -1,148 +1,147 @@
|
||||||
.GridRep {
|
.GridRep {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit;
|
||||||
|
padding: $unit * 2;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--grid-rep-hover);
|
||||||
|
|
||||||
|
h2,
|
||||||
|
.Grid {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Grid .weapon {
|
||||||
|
box-shadow: inset 0 0 0 1px var(--grid-border-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.weapon {
|
||||||
|
background: var(--card-bg);
|
||||||
|
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;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: $unit;
|
gap: calc($unit / 2);
|
||||||
padding: $unit * 2;
|
|
||||||
|
|
||||||
&:hover {
|
h2 {
|
||||||
background: white;
|
color: var(--text-primary);
|
||||||
|
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 {
|
&.empty {
|
||||||
cursor: pointer;
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Grid .weapon {
|
|
||||||
box-shadow: inset 0 0 0 1px $grey-80;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Grid {
|
.top {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-shrink: 0;
|
gap: calc($unit / 2);
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.weapon {
|
.info {
|
||||||
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;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
gap: calc($unit / 2);
|
gap: calc($unit / 2);
|
||||||
|
}
|
||||||
|
|
||||||
h2 {
|
button svg {
|
||||||
color: $grey-00;
|
width: 14px;
|
||||||
font-size: $font-regular;
|
height: 14px;
|
||||||
overflow: hidden;
|
}
|
||||||
padding-bottom: 1px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
max-width: 258px; // Can we not do this?
|
|
||||||
|
|
||||||
&.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.raid,
|
||||||
|
.user,
|
||||||
|
time {
|
||||||
|
color: $grey-55;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from 'next/router'
|
||||||
import { useSnapshot } from "valtio"
|
import { useSnapshot } from 'valtio'
|
||||||
import { useTranslation } from "next-i18next"
|
import { useTranslation } from 'next-i18next'
|
||||||
import classNames from "classnames"
|
import classNames from 'classnames'
|
||||||
|
|
||||||
import { accountState } from "~utils/accountState"
|
import { accountState } from '~utils/accountState'
|
||||||
import { formatTimeAgo } from "~utils/timeAgo"
|
import { formatTimeAgo } from '~utils/timeAgo'
|
||||||
|
|
||||||
import Button from "~components/Button"
|
import Button from '~components/Button'
|
||||||
import { ButtonType } from "~utils/enums"
|
|
||||||
|
|
||||||
import "./index.scss"
|
import SaveIcon from '~public/icons/Save.svg'
|
||||||
|
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
shortcode: string
|
shortcode: string
|
||||||
|
|
@ -32,9 +33,9 @@ const GridRep = (props: Props) => {
|
||||||
const { account } = useSnapshot(accountState)
|
const { account } = useSnapshot(accountState)
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
const locale =
|
const locale =
|
||||||
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
|
|
||||||
const [mainhand, setMainhand] = useState<Weapon>()
|
const [mainhand, setMainhand] = useState<Weapon>()
|
||||||
const [weapons, setWeapons] = useState<GridArray<Weapon>>({})
|
const [weapons, setWeapons] = useState<GridArray<Weapon>>({})
|
||||||
|
|
@ -75,7 +76,7 @@ const GridRep = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateMainhandImage() {
|
function generateMainhandImage() {
|
||||||
let url = ""
|
let url = ''
|
||||||
|
|
||||||
if (mainhand) {
|
if (mainhand) {
|
||||||
if (mainhand.element == 0 && props.grid[0].element) {
|
if (mainhand.element == 0 && props.grid[0].element) {
|
||||||
|
|
@ -88,12 +89,12 @@ const GridRep = (props: Props) => {
|
||||||
return mainhand && props.grid[0] ? (
|
return mainhand && props.grid[0] ? (
|
||||||
<img alt={mainhand.name[locale]} src={url} />
|
<img alt={mainhand.name[locale]} src={url} />
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateGridImage(position: number) {
|
function generateGridImage(position: number) {
|
||||||
let url = ""
|
let url = ''
|
||||||
|
|
||||||
const weapon = weapons[position]
|
const weapon = weapons[position]
|
||||||
const gridWeapon = grid[position]
|
const gridWeapon = grid[position]
|
||||||
|
|
@ -109,7 +110,7 @@ const GridRep = (props: Props) => {
|
||||||
return weapons[position] ? (
|
return weapons[position] ? (
|
||||||
<img alt={weapons[position]?.name[locale]} src={url} />
|
<img alt={weapons[position]?.name[locale]} src={url} />
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,11 +135,11 @@ const GridRep = (props: Props) => {
|
||||||
const details = (
|
const details = (
|
||||||
<div className="Details">
|
<div className="Details">
|
||||||
<h2 className={titleClass} onClick={navigate}>
|
<h2 className={titleClass} onClick={navigate}>
|
||||||
{props.name ? props.name : t("no_title")}
|
{props.name ? props.name : t('no_title')}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="bottom">
|
<div className="bottom">
|
||||||
<div className={raidClass}>
|
<div className={raidClass}>
|
||||||
{props.raid ? props.raid.name[locale] : t("no_raid")}
|
{props.raid ? props.raid.name[locale] : t('no_raid')}
|
||||||
</div>
|
</div>
|
||||||
<time className="last-updated" dateTime={props.createdAt.toISOString()}>
|
<time className="last-updated" dateTime={props.createdAt.toISOString()}>
|
||||||
{formatTimeAgo(props.createdAt, locale)}
|
{formatTimeAgo(props.createdAt, locale)}
|
||||||
|
|
@ -152,29 +153,31 @@ const GridRep = (props: Props) => {
|
||||||
<div className="top">
|
<div className="top">
|
||||||
<div className="info">
|
<div className="info">
|
||||||
<h2 className={titleClass} onClick={navigate}>
|
<h2 className={titleClass} onClick={navigate}>
|
||||||
{props.name ? props.name : t("no_title")}
|
{props.name ? props.name : t('no_title')}
|
||||||
</h2>
|
</h2>
|
||||||
<div className={raidClass}>
|
<div className={raidClass}>
|
||||||
{props.raid ? props.raid.name[locale] : t("no_raid")}
|
{props.raid ? props.raid.name[locale] : t('no_raid')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{account.authorized &&
|
{account.authorized &&
|
||||||
((props.user && account.user && account.user.id !== props.user.id) ||
|
((props.user && account.user && account.user.id !== props.user.id) ||
|
||||||
!props.user) ? (
|
!props.user) ? (
|
||||||
<Button
|
<Button
|
||||||
|
className="Save"
|
||||||
|
accessoryIcon={<SaveIcon class="stroke" />}
|
||||||
active={props.favorited}
|
active={props.favorited}
|
||||||
icon="save"
|
contained={true}
|
||||||
type={ButtonType.IconOnly}
|
size="small"
|
||||||
onClick={sendSaveData}
|
onClick={sendSaveData}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="bottom">
|
<div className="bottom">
|
||||||
<div className={userClass}>
|
<div className={userClass}>
|
||||||
{userImage()}
|
{userImage()}
|
||||||
{props.user ? props.user.username : t("no_user")}
|
{props.user ? props.user.username : t('no_user')}
|
||||||
</div>
|
</div>
|
||||||
<time className="last-updated" dateTime={props.createdAt.toISOString()}>
|
<time className="last-updated" dateTime={props.createdAt.toISOString()}>
|
||||||
{formatTimeAgo(props.createdAt, locale)}
|
{formatTimeAgo(props.createdAt, locale)}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import classNames from "classnames"
|
import classNames from 'classnames'
|
||||||
import React from "react"
|
import React from 'react'
|
||||||
|
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,37 @@
|
||||||
.Header {
|
.Header {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: $unit-2x;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&.bottom {
|
||||||
|
position: sticky;
|
||||||
|
bottom: $unit * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#right > div {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 34px;
|
gap: 8px;
|
||||||
width: 100%;
|
}
|
||||||
|
|
||||||
&.bottom {
|
.dropdown {
|
||||||
position: sticky;
|
display: inline-block;
|
||||||
bottom: $unit * 2;
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
padding-right: 50px;
|
||||||
|
|
||||||
|
.Button {
|
||||||
|
background: var(--button-bg-hover);
|
||||||
|
color: var(--button-text-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Menu {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#right > div {
|
.push {
|
||||||
display: flex;
|
margin-left: auto;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,19 @@ import React from 'react'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
position: 'top' | 'bottom'
|
position: 'top' | 'bottom'
|
||||||
left: JSX.Element,
|
left: JSX.Element
|
||||||
right: JSX.Element
|
right: JSX.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
const Header = (props: Props) => {
|
const Header = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<nav className={`Header ${props.position}`}>
|
<nav className={`Header ${props.position}`}>
|
||||||
<div id="left">{ props.left }</div>
|
<div id="left">{props.left}</div>
|
||||||
<div className="push" />
|
<div className="push" />
|
||||||
<div id="right">{ props.right }</div>
|
<div id="right">{props.right}</div>
|
||||||
</nav>
|
</nav>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Header
|
export default Header
|
||||||
|
|
@ -1,147 +1,150 @@
|
||||||
.Menu {
|
.Menu {
|
||||||
background: white;
|
background: var(--menu-bg);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
display: none;
|
display: none;
|
||||||
min-width: 220px;
|
min-width: 220px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: $unit * 5; // This shouldn't be hardcoded. How to calculate it?
|
top: $unit * 5; // This shouldn't be hardcoded. How to calculate it?
|
||||||
z-index: 10;
|
// Also, add space that doesn't make the menu disappear if you move your mouse slowly
|
||||||
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MenuItem {
|
.MenuItem {
|
||||||
color: $grey-40;
|
color: var(--text-tertiary);
|
||||||
font-weight: $normal;
|
font-weight: $normal;
|
||||||
|
|
||||||
&:hover:not(.disabled) {
|
&:hover:not(.disabled) {
|
||||||
background: $grey-100;
|
background: var(--menu-bg-item-hover);
|
||||||
color: $grey-00;
|
color: var(--text-primary);
|
||||||
cursor: pointer;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: $grey-40;
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.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: $grey-100;
|
||||||
|
border-radius: calc($diameter / 2);
|
||||||
display: block;
|
display: block;
|
||||||
padding: 12px 12px;
|
height: $diameter;
|
||||||
}
|
width: $diameter;
|
||||||
|
transition: transform 100ms;
|
||||||
& > div {
|
transform: translateX(-2px);
|
||||||
align-items: center;
|
z-index: 3;
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
padding: 10px 12px;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
i.tag {
|
cursor: pointer;
|
||||||
background: $grey-60;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
&[data-state='checked'] {
|
||||||
flex-grow: 1;
|
background: $grey-100;
|
||||||
|
transform: translateX(17px);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
.left,
|
||||||
$diameter: 32px;
|
.right {
|
||||||
border-radius: calc($diameter / 2);
|
color: $grey-100;
|
||||||
height: $diameter;
|
font-size: 10px;
|
||||||
width: $diameter;
|
font-weight: $bold;
|
||||||
}
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
top: 6px;
|
||||||
|
left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
top: 6px;
|
||||||
|
right: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $grey-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > a,
|
||||||
|
& > span {
|
||||||
|
display: block;
|
||||||
|
padding: 12px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 10px 12px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
i.tag {
|
||||||
|
background: var(--tag-bg);
|
||||||
|
color: var(--tag-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
$diameter: 32px;
|
||||||
|
border-radius: calc($diameter / 2);
|
||||||
|
height: $diameter;
|
||||||
|
width: $diameter;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.MenuGroup {
|
.MenuGroup {
|
||||||
border-bottom: 1px solid #f5f5f5;
|
border-bottom: 1px solid var(--menu-separator);
|
||||||
|
|
||||||
&:first-child .MenuItem:first-child:hover {
|
&:first-child .MenuItem:first-child:hover {
|
||||||
border-top-left-radius: 6px;
|
border-top-left-radius: 6px;
|
||||||
border-top-right-radius: 6px;
|
border-top-right-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child .MenuItem:last-child:hover {
|
&:last-child .MenuItem:last-child:hover {
|
||||||
border-bottom-left-radius: 6px;
|
border-bottom-left-radius: 6px;
|
||||||
border-bottom-right-radius: 6px;
|
border-bottom-right-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from 'react'
|
||||||
import { getCookie, setCookie } from "cookies-next"
|
import { getCookie, setCookie } from 'cookies-next'
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from 'next/router'
|
||||||
import { useTranslation } from "next-i18next"
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
import Link from "next/link"
|
import Link from 'next/link'
|
||||||
import * as Switch from "@radix-ui/react-switch"
|
import * as Switch from '@radix-ui/react-switch'
|
||||||
|
|
||||||
import AboutModal from "~components/AboutModal"
|
import AboutModal from '~components/AboutModal'
|
||||||
import AccountModal from "~components/AccountModal"
|
import AccountModal from '~components/AccountModal'
|
||||||
import LoginModal from "~components/LoginModal"
|
import LoginModal from '~components/LoginModal'
|
||||||
import SignupModal from "~components/SignupModal"
|
import SignupModal from '~components/SignupModal'
|
||||||
|
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
authenticated: boolean
|
authenticated: boolean
|
||||||
|
|
@ -21,30 +21,30 @@ interface Props {
|
||||||
|
|
||||||
const HeaderMenu = (props: Props) => {
|
const HeaderMenu = (props: Props) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
const accountCookie = getCookie("account")
|
const accountCookie = getCookie('account')
|
||||||
const accountData: AccountCookie = accountCookie
|
const accountData: AccountCookie = accountCookie
|
||||||
? JSON.parse(accountCookie as string)
|
? JSON.parse(accountCookie as string)
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const userCookie = getCookie("user")
|
const userCookie = getCookie('user')
|
||||||
const userData: UserCookie = userCookie
|
const userData: UserCookie = userCookie
|
||||||
? JSON.parse(userCookie as string)
|
? JSON.parse(userCookie as string)
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const localeCookie = getCookie("NEXT_LOCALE")
|
const localeCookie = getCookie('NEXT_LOCALE')
|
||||||
|
|
||||||
const [checked, setChecked] = useState(false)
|
const [checked, setChecked] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const locale = localeCookie
|
const locale = localeCookie
|
||||||
setChecked(locale === "ja" ? true : false)
|
setChecked(locale === 'ja' ? true : false)
|
||||||
}, [localeCookie])
|
}, [localeCookie])
|
||||||
|
|
||||||
function handleCheckedChange(value: boolean) {
|
function handleCheckedChange(value: boolean) {
|
||||||
const language = value ? "ja" : "en"
|
const language = value ? 'ja' : 'en'
|
||||||
setCookie("NEXT_LOCALE", language, { path: "/" })
|
setCookie('NEXT_LOCALE', language, { path: '/' })
|
||||||
router.push(router.asPath, undefined, { locale: language })
|
router.push(router.asPath, undefined, { locale: language })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@ const HeaderMenu = (props: Props) => {
|
||||||
<ul className="Menu auth">
|
<ul className="Menu auth">
|
||||||
<div className="MenuGroup">
|
<div className="MenuGroup">
|
||||||
<li className="MenuItem profile">
|
<li className="MenuItem profile">
|
||||||
<Link href={`/${accountData.username}` || ""} passHref>
|
<Link href={`/${accountData.username}` || ''} passHref>
|
||||||
<div>
|
<div>
|
||||||
<span>{accountData.username}</span>
|
<span>{accountData.username}</span>
|
||||||
<img
|
<img
|
||||||
|
|
@ -68,18 +68,18 @@ const HeaderMenu = (props: Props) => {
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="MenuItem">
|
<li className="MenuItem">
|
||||||
<Link href={`/saved` || ""}>{t("menu.saved")}</Link>
|
<Link href={`/saved` || ''}>{t('menu.saved')}</Link>
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
<div className="MenuGroup">
|
<div className="MenuGroup">
|
||||||
<li className="MenuItem">
|
<li className="MenuItem">
|
||||||
<Link href="/teams">{t("menu.teams")}</Link>
|
<Link href="/teams">{t('menu.teams')}</Link>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li className="MenuItem disabled">
|
<li className="MenuItem disabled">
|
||||||
<div>
|
<div>
|
||||||
<span>{t("menu.guides")}</span>
|
<span>{t('menu.guides')}</span>
|
||||||
<i className="tag">{t("coming_soon")}</i>
|
<i className="tag">{t('coming_soon')}</i>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -87,7 +87,7 @@ const HeaderMenu = (props: Props) => {
|
||||||
<AboutModal />
|
<AboutModal />
|
||||||
<AccountModal />
|
<AccountModal />
|
||||||
<li className="MenuItem" onClick={props.logout}>
|
<li className="MenuItem" onClick={props.logout}>
|
||||||
<span>{t("menu.logout")}</span>
|
<span>{t('menu.logout')}</span>
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -100,7 +100,7 @@ const HeaderMenu = (props: Props) => {
|
||||||
<ul className="Menu unauth">
|
<ul className="Menu unauth">
|
||||||
<div className="MenuGroup">
|
<div className="MenuGroup">
|
||||||
<li className="MenuItem language">
|
<li className="MenuItem language">
|
||||||
<span>{t("menu.language")}</span>
|
<span>{t('menu.language')}</span>
|
||||||
<Switch.Root
|
<Switch.Root
|
||||||
className="Switch"
|
className="Switch"
|
||||||
onCheckedChange={handleCheckedChange}
|
onCheckedChange={handleCheckedChange}
|
||||||
|
|
@ -114,13 +114,13 @@ const HeaderMenu = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
<div className="MenuGroup">
|
<div className="MenuGroup">
|
||||||
<li className="MenuItem">
|
<li className="MenuItem">
|
||||||
<Link href="/teams">{t("menu.teams")}</Link>
|
<Link href="/teams">{t('menu.teams')}</Link>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li className="MenuItem disabled">
|
<li className="MenuItem disabled">
|
||||||
<div>
|
<div>
|
||||||
<span>{t("menu.guides")}</span>
|
<span>{t('menu.guides')}</span>
|
||||||
<i className="tag">{t("coming_soon")}</i>
|
<i className="tag">{t('coming_soon')}</i>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
23
components/Input/index.scss
Normal file
23
components/Input/index.scss
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
.Input {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
background-color: var(--input-bg);
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
padding: $unit-2x;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.InputError {
|
||||||
|
color: $error;
|
||||||
|
font-size: $font-tiny;
|
||||||
|
margin: $unit 0;
|
||||||
|
padding: calc($unit / 2) ($unit * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
::placeholder {
|
||||||
|
/* Chrome, Firefox, Opera, Safari 10.1+ */
|
||||||
|
color: var(--text-secondary) !important;
|
||||||
|
opacity: 1; /* Firefox */
|
||||||
|
}
|
||||||
38
components/Input/index.tsx
Normal file
38
components/Input/index.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import React from 'react'
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
|
interface Props
|
||||||
|
extends React.DetailedHTMLProps<
|
||||||
|
React.InputHTMLAttributes<HTMLInputElement>,
|
||||||
|
HTMLInputElement
|
||||||
|
> {
|
||||||
|
error?: string
|
||||||
|
label?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Input = React.forwardRef<HTMLInputElement, Props>(function input(
|
||||||
|
props: Props,
|
||||||
|
forwardedRef
|
||||||
|
) {
|
||||||
|
const classes = classNames({ Input: true }, props.className)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label className="Label" htmlFor={props.name}>
|
||||||
|
<input
|
||||||
|
{...props}
|
||||||
|
autoComplete="off"
|
||||||
|
className={classes}
|
||||||
|
defaultValue={props.value || ''}
|
||||||
|
ref={forwardedRef}
|
||||||
|
formNoValidate
|
||||||
|
/>
|
||||||
|
{props.label}
|
||||||
|
{props.error && props.error.length > 0 && (
|
||||||
|
<p className="InputError">{props.error}</p>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Input
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
.Job.SelectTrigger {
|
||||||
|
margin-bottom: $unit;
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from 'next/router'
|
||||||
import { useSnapshot } from "valtio"
|
import { useSnapshot } from 'valtio'
|
||||||
|
|
||||||
import { appState } from "~utils/appState"
|
import Select from '~components/Select'
|
||||||
import { jobGroups } from "~utils/jobGroups"
|
import SelectItem from '~components/SelectItem'
|
||||||
|
import SelectGroup from '~components/SelectGroup'
|
||||||
|
|
||||||
import "./index.scss"
|
import { appState } from '~utils/appState'
|
||||||
|
import { jobGroups } from '~utils/jobGroups'
|
||||||
|
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -20,12 +24,13 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
|
||||||
function useFieldSet(props, ref) {
|
function useFieldSet(props, ref) {
|
||||||
// Set up router for locale
|
// Set up router for locale
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale = router.locale || "en"
|
const locale = router.locale || 'en'
|
||||||
|
|
||||||
// Create snapshot of app state
|
// Create snapshot of app state
|
||||||
const { party } = useSnapshot(appState)
|
const { party } = useSnapshot(appState)
|
||||||
|
|
||||||
// Set up local states for storing jobs
|
// Set up local states for storing jobs
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
const [currentJob, setCurrentJob] = useState<Job>()
|
const [currentJob, setCurrentJob] = useState<Job>()
|
||||||
const [jobs, setJobs] = useState<Job[]>()
|
const [jobs, setJobs] = useState<Job[]>()
|
||||||
const [sortedJobs, setSortedJobs] = useState<GroupedJob>()
|
const [sortedJobs, setSortedJobs] = useState<GroupedJob>()
|
||||||
|
|
@ -58,10 +63,14 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
|
||||||
}
|
}
|
||||||
}, [appState, props.currentJob])
|
}, [appState, props.currentJob])
|
||||||
|
|
||||||
|
function openJobSelect() {
|
||||||
|
setOpen(!open)
|
||||||
|
}
|
||||||
|
|
||||||
// Enable changing select value
|
// Enable changing select value
|
||||||
function handleChange(event: React.ChangeEvent<HTMLSelectElement>) {
|
function handleChange(value: string) {
|
||||||
if (jobs) {
|
if (jobs) {
|
||||||
const job = jobs.find((job) => job.id === event.target.value)
|
const job = jobs.find((job) => job.id === value)
|
||||||
if (props.onChange) props.onChange(job)
|
if (props.onChange) props.onChange(job)
|
||||||
setCurrentJob(job)
|
setCurrentJob(job)
|
||||||
}
|
}
|
||||||
|
|
@ -76,36 +85,37 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
|
||||||
.sort((a, b) => a.order - b.order)
|
.sort((a, b) => a.order - b.order)
|
||||||
.map((item, i) => {
|
.map((item, i) => {
|
||||||
return (
|
return (
|
||||||
<option key={i} value={item.id}>
|
<SelectItem key={i} value={item.id}>
|
||||||
{item.name[locale]}
|
{item.name[locale]}
|
||||||
</option>
|
</SelectItem>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const groupName = jobGroups.find((g) => g.slug === group)?.name[locale]
|
const groupName = jobGroups.find((g) => g.slug === group)?.name[locale]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<optgroup key={group} label={groupName}>
|
<SelectGroup key={group} label={groupName} separator={false}>
|
||||||
{options}
|
{options}
|
||||||
</optgroup>
|
</SelectGroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<select
|
<Select
|
||||||
key={currentJob ? currentJob.id : -1}
|
trigger={'Select a class...'}
|
||||||
value={currentJob ? currentJob.id : -1}
|
placeholder={'Select a class...'}
|
||||||
onBlur={props.onBlur}
|
open={open}
|
||||||
|
onClick={openJobSelect}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
ref={ref}
|
triggerClass="Job"
|
||||||
>
|
>
|
||||||
<option key="no-job" value={-1}>
|
<SelectItem key={-1} value="no-job">
|
||||||
No class
|
No class
|
||||||
</option>
|
</SelectItem>
|
||||||
{sortedJobs
|
{sortedJobs
|
||||||
? Object.keys(sortedJobs).map((x) => renderJobGroup(x))
|
? Object.keys(sortedJobs).map((x) => renderJobGroup(x))
|
||||||
: ""}
|
: ''}
|
||||||
</select>
|
</Select>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.JobImage {
|
.JobImage {
|
||||||
$height: 249px;
|
$height: 252px;
|
||||||
$width: 447px;
|
$width: 447px;
|
||||||
|
|
||||||
background: url("/images/background_a.jpg");
|
background: url('/images/background_a.jpg');
|
||||||
background-size: 500px 281px;
|
background-size: 500px 281px;
|
||||||
border-radius: $unit;
|
border-radius: $unit;
|
||||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
|
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
import React, { ForwardedRef, useEffect, useState } from "react"
|
import React, { ForwardedRef, useEffect, useState } from 'react'
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from 'next/router'
|
||||||
import { useSnapshot } from "valtio"
|
import { useSnapshot } from 'valtio'
|
||||||
import { useTranslation } from "next-i18next"
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
import JobDropdown from "~components/JobDropdown"
|
import JobDropdown from '~components/JobDropdown'
|
||||||
import JobSkillItem from "~components/JobSkillItem"
|
import JobSkillItem from '~components/JobSkillItem'
|
||||||
import SearchModal from "~components/SearchModal"
|
import SearchModal from '~components/SearchModal'
|
||||||
|
|
||||||
import { appState } from "~utils/appState"
|
import { appState } from '~utils/appState'
|
||||||
|
|
||||||
import type { JobSkillObject, SearchableObject } from "~types"
|
import type { JobSkillObject, SearchableObject } from '~types'
|
||||||
|
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -24,14 +24,14 @@ interface Props {
|
||||||
|
|
||||||
const JobSection = (props: Props) => {
|
const JobSection = (props: Props) => {
|
||||||
const { party } = useSnapshot(appState)
|
const { party } = useSnapshot(appState)
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale =
|
const locale =
|
||||||
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
|
|
||||||
const [job, setJob] = useState<Job>()
|
const [job, setJob] = useState<Job>()
|
||||||
const [imageUrl, setImageUrl] = useState("")
|
const [imageUrl, setImageUrl] = useState('')
|
||||||
const [numSkills, setNumSkills] = useState(4)
|
const [numSkills, setNumSkills] = useState(4)
|
||||||
const [skills, setSkills] = useState<{ [key: number]: JobSkill | undefined }>(
|
const [skills, setSkills] = useState<{ [key: number]: JobSkill | undefined }>(
|
||||||
[]
|
[]
|
||||||
|
|
@ -62,7 +62,7 @@ const JobSection = (props: Props) => {
|
||||||
if (job) {
|
if (job) {
|
||||||
if ((party.job && job.id != party.job.id) || !party.job)
|
if ((party.job && job.id != party.job.id) || !party.job)
|
||||||
appState.party.job = job
|
appState.party.job = job
|
||||||
if (job.row === "1") setNumSkills(3)
|
if (job.row === '1') setNumSkills(3)
|
||||||
else setNumSkills(4)
|
else setNumSkills(4)
|
||||||
}
|
}
|
||||||
}, [job])
|
}, [job])
|
||||||
|
|
@ -75,11 +75,11 @@ const JobSection = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateImageUrl() {
|
function generateImageUrl() {
|
||||||
let imgSrc = ""
|
let imgSrc = ''
|
||||||
|
|
||||||
if (job) {
|
if (job) {
|
||||||
const slug = job?.name.en.replaceAll(" ", "-").toLowerCase()
|
const slug = job?.name.en.replaceAll(' ', '-').toLowerCase()
|
||||||
const gender = party.user && party.user.gender == 1 ? "b" : "a"
|
const gender = party.user && party.user.gender == 1 ? 'b' : 'a'
|
||||||
|
|
||||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/jobs/${slug}_${gender}.png`
|
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/jobs/${slug}_${gender}.png`
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +101,7 @@ const JobSection = (props: Props) => {
|
||||||
skill={skills[index]}
|
skill={skills[index]}
|
||||||
editable={canEditSkill(skills[index])}
|
editable={canEditSkill(skills[index])}
|
||||||
key={`skill-${index}`}
|
key={`skill-${index}`}
|
||||||
hasJob={job != undefined && job.id != "-1"}
|
hasJob={job != undefined && job.id != '-1'}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +109,7 @@ const JobSection = (props: Props) => {
|
||||||
const editableSkillItem = (index: number) => {
|
const editableSkillItem = (index: number) => {
|
||||||
return (
|
return (
|
||||||
<SearchModal
|
<SearchModal
|
||||||
placeholderText={t("search.placeholders.job_skill")}
|
placeholderText={t('search.placeholders.job_skill')}
|
||||||
fromPosition={index}
|
fromPosition={index}
|
||||||
object="job_skills"
|
object="job_skills"
|
||||||
job={job}
|
job={job}
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
& p.placeholder {
|
& p.placeholder {
|
||||||
color: $grey-20;
|
color: var(--text-tertiary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
& svg {
|
||||||
|
fill: var(--icon-tertiary-hover);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& > img,
|
& > img,
|
||||||
& > div.placeholder {
|
& > div.placeholder {
|
||||||
background: white;
|
background: var(--card-bg);
|
||||||
border-radius: calc($unit / 2);
|
border-radius: calc($unit / 2);
|
||||||
border: 1px solid rgba(0, 0, 0, 0);
|
border: 1px solid rgba(0, 0, 0, 0);
|
||||||
width: $unit * 5;
|
width: $unit * 5;
|
||||||
|
|
@ -34,13 +38,17 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
& > svg {
|
& > svg {
|
||||||
fill: $grey-60;
|
fill: var(--icon-secondary);
|
||||||
width: $unit * 2;
|
width: $unit * 2;
|
||||||
height: $unit * 2;
|
height: $unit * 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.placeholder {
|
p {
|
||||||
color: $grey-50;
|
color: var(--text-primary);
|
||||||
|
|
||||||
|
&.placeholder {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import React from "react"
|
import React from 'react'
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from 'next/router'
|
||||||
import { useTranslation } from "next-i18next"
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
import classNames from "classnames"
|
import classNames from 'classnames'
|
||||||
import PlusIcon from "~public/icons/Add.svg"
|
import PlusIcon from '~public/icons/Add.svg'
|
||||||
|
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props extends React.ComponentPropsWithoutRef<"div"> {
|
interface Props extends React.ComponentPropsWithoutRef<'div'> {
|
||||||
skill?: JobSkill
|
skill?: JobSkill
|
||||||
editable: boolean
|
editable: boolean
|
||||||
hasJob: boolean
|
hasJob: boolean
|
||||||
|
|
@ -17,11 +17,11 @@ interface Props extends React.ComponentPropsWithoutRef<"div"> {
|
||||||
const JobSkillItem = React.forwardRef<HTMLDivElement, Props>(
|
const JobSkillItem = React.forwardRef<HTMLDivElement, Props>(
|
||||||
function useJobSkillItem({ ...props }, forwardedRef) {
|
function useJobSkillItem({ ...props }, forwardedRef) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
const locale =
|
const locale =
|
||||||
router.locale && ["en", "ja"].includes(router.locale)
|
router.locale && ['en', 'ja'].includes(router.locale)
|
||||||
? router.locale
|
? router.locale
|
||||||
: "en"
|
: 'en'
|
||||||
|
|
||||||
const classes = classNames({
|
const classes = classNames({
|
||||||
JobSkill: true,
|
JobSkill: true,
|
||||||
|
|
@ -47,7 +47,7 @@ const JobSkillItem = React.forwardRef<HTMLDivElement, Props>(
|
||||||
} else {
|
} else {
|
||||||
jsx = (
|
jsx = (
|
||||||
<div className={imageClasses}>
|
<div className={imageClasses}>
|
||||||
{props.editable && props.hasJob ? <PlusIcon /> : ""}
|
{props.editable && props.hasJob ? <PlusIcon /> : ''}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -61,9 +61,9 @@ const JobSkillItem = React.forwardRef<HTMLDivElement, Props>(
|
||||||
if (props.skill) {
|
if (props.skill) {
|
||||||
jsx = <p>{props.skill.name[locale]}</p>
|
jsx = <p>{props.skill.name[locale]}</p>
|
||||||
} else if (props.editable && props.hasJob) {
|
} else if (props.editable && props.hasJob) {
|
||||||
jsx = <p className="placeholder">{t("job_skills.state.selectable")}</p>
|
jsx = <p className="placeholder">{t('job_skills.state.selectable')}</p>
|
||||||
} else {
|
} else {
|
||||||
jsx = <p className="placeholder">{t("job_skills.state.no_skill")}</p>
|
jsx = <p className="placeholder">{t('job_skills.state.no_skill')}</p>
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsx
|
return jsx
|
||||||
|
|
|
||||||
|
|
@ -6,57 +6,57 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $grey-90;
|
background: var(--button-contained-bg);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.Info .skill.pill {
|
.Info h5 {
|
||||||
background: $grey-80;
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Info {
|
.Info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: calc($unit / 2);
|
gap: $unit-half;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.skill.pill {
|
.skill.pill {
|
||||||
background: $grey-90;
|
background: $grey-90;
|
||||||
border-radius: $unit * 2;
|
border-radius: $unit * 2;
|
||||||
color: $grey-00;
|
color: $grey-15;
|
||||||
display: inline;
|
display: inline;
|
||||||
font-size: $font-tiny;
|
font-size: $font-tiny;
|
||||||
font-weight: $medium;
|
font-weight: $medium;
|
||||||
padding: calc($unit / 2) $unit;
|
padding: $unit-half $unit;
|
||||||
|
|
||||||
&.buffing {
|
&.buffing {
|
||||||
background-color: $light-bg-dark;
|
background-color: $light-bg-10;
|
||||||
color: $light-text-dark;
|
color: $light-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.debuffing {
|
&.debuffing {
|
||||||
background-color: $water-bg-dark;
|
background-color: $water-bg-10;
|
||||||
color: $water-text-dark;
|
color: $water-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.healing {
|
&.healing {
|
||||||
background-color: $wind-bg-dark;
|
background-color: $wind-bg-10;
|
||||||
color: $wind-text-dark;
|
color: $wind-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.damaging {
|
&.damaging {
|
||||||
background-color: $fire-bg-dark;
|
background-color: $fire-bg-10;
|
||||||
color: $fire-text-dark;
|
color: $fire-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.field {
|
&.field {
|
||||||
background-color: $dark-bg-dark;
|
background-color: $dark-bg-20;
|
||||||
color: $dark-text-dark;
|
color: $dark-text-10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h5 {
|
h5 {
|
||||||
color: #555;
|
color: var(--text-secondary);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: $font-medium;
|
font-size: $font-medium;
|
||||||
font-weight: $medium;
|
font-weight: $medium;
|
||||||
|
|
@ -65,7 +65,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: $unit * 6;
|
width: $unit-6x;
|
||||||
height: $unit * 6;
|
height: $unit-6x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from 'next/router'
|
||||||
import { SkillGroup, skillClassification } from "~utils/skillGroups"
|
import { SkillGroup, skillClassification } from '~utils/skillGroups'
|
||||||
|
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: JobSkill
|
data: JobSkill
|
||||||
|
|
@ -12,7 +12,7 @@ interface Props {
|
||||||
const JobSkillResult = (props: Props) => {
|
const JobSkillResult = (props: Props) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale =
|
const locale =
|
||||||
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
|
|
||||||
const skill = props.data
|
const skill = props.data
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ const JobSkillResult = (props: Props) => {
|
||||||
<img alt={skill.name[locale]} src={jobSkillUrl()} />
|
<img alt={skill.name[locale]} src={jobSkillUrl()} />
|
||||||
<div className="Info">
|
<div className="Info">
|
||||||
<h5>{skill.name[locale]}</h5>
|
<h5>{skill.name[locale]}</h5>
|
||||||
<div className={`skill pill ${group?.name["en"].toLowerCase()}`}>
|
<div className={`skill pill ${group?.name['en'].toLowerCase()}`}>
|
||||||
{group?.name[locale]}
|
{group?.name[locale]}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
.SearchFilterBar select {
|
.SearchFilterBar .SelectTrigger {
|
||||||
background-color: $grey-90;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useRouter } from "next/router"
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useTranslation } from "react-i18next"
|
|
||||||
|
|
||||||
import { skillGroups } from "~utils/skillGroups"
|
import Select from '~components/Select'
|
||||||
|
import SelectItem from '~components/SelectItem'
|
||||||
|
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
sendFilters: (filters: { [key: string]: number }) => void
|
sendFilters: (filters: { [key: string]: number }) => void
|
||||||
|
|
@ -12,15 +12,18 @@ interface Props {
|
||||||
|
|
||||||
const JobSkillSearchFilterBar = (props: Props) => {
|
const JobSkillSearchFilterBar = (props: Props) => {
|
||||||
// Set up translation
|
// Set up translation
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
const [currentGroup, setCurrentGroup] = useState(-1)
|
const [currentGroup, setCurrentGroup] = useState(-1)
|
||||||
|
|
||||||
function onChange(event: React.ChangeEvent<HTMLSelectElement>) {
|
function openSelect() {
|
||||||
setCurrentGroup(parseInt(event.target.value))
|
setOpen(!open)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBlur(event: React.ChangeEvent<HTMLSelectElement>) {}
|
function onChange(value: string) {
|
||||||
|
setCurrentGroup(parseInt(value))
|
||||||
|
}
|
||||||
|
|
||||||
function sendFilters() {
|
function sendFilters() {
|
||||||
const filters = {
|
const filters = {
|
||||||
|
|
@ -36,34 +39,35 @@ const JobSkillSearchFilterBar = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="SearchFilterBar">
|
<div className="SearchFilterBar">
|
||||||
<select
|
<Select
|
||||||
key="job-skill-groups"
|
defaultValue={-1}
|
||||||
value={currentGroup}
|
trigger={'All elements'}
|
||||||
onBlur={onBlur}
|
open={open}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
onClick={openSelect}
|
||||||
>
|
>
|
||||||
<option key="all" value={-1}>
|
<SelectItem key="all" value={-1}>
|
||||||
{t(`job_skills.all`)}
|
{t(`job_skills.all`)}
|
||||||
</option>
|
</SelectItem>
|
||||||
<option key="damaging" value={2}>
|
<SelectItem key="damaging" value={2}>
|
||||||
{t(`job_skills.damaging`)}
|
{t(`job_skills.damaging`)}
|
||||||
</option>
|
</SelectItem>
|
||||||
<option key="buffing" value={0}>
|
<SelectItem key="buffing" value={0}>
|
||||||
{t(`job_skills.buffing`)}
|
{t(`job_skills.buffing`)}
|
||||||
</option>
|
</SelectItem>
|
||||||
<option key="debuffing" value={1}>
|
<SelectItem key="debuffing" value={1}>
|
||||||
{t(`job_skills.debuffing`)}
|
{t(`job_skills.debuffing`)}
|
||||||
</option>
|
</SelectItem>
|
||||||
<option key="healing" value={3}>
|
<SelectItem key="healing" value={3}>
|
||||||
{t(`job_skills.healing`)}
|
{t(`job_skills.healing`)}
|
||||||
</option>
|
</SelectItem>
|
||||||
<option key="emp" value={4}>
|
<SelectItem key="emp" value={4}>
|
||||||
{t(`job_skills.emp`)}
|
{t(`job_skills.emp`)}
|
||||||
</option>
|
</SelectItem>
|
||||||
<option key="base" value={5}>
|
<SelectItem key="base" value={5}>
|
||||||
{t(`job_skills.base`)}
|
{t(`job_skills.base`)}
|
||||||
</option>
|
</SelectItem>
|
||||||
</select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ import type { ReactElement } from 'react'
|
||||||
import TopHeader from '~components/TopHeader'
|
import TopHeader from '~components/TopHeader'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: ReactElement
|
children: ReactElement
|
||||||
}
|
}
|
||||||
|
|
||||||
const Layout = ({children}: Props) => {
|
const Layout = ({ children }: Props) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TopHeader />
|
<TopHeader />
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,31 @@
|
||||||
.Login.Dialog form {
|
.Login.Dialog form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: calc($unit / 2);
|
gap: calc($unit / 2);
|
||||||
margin-bottom: $unit;
|
margin-bottom: $unit;
|
||||||
|
|
||||||
.Button {
|
.Button {
|
||||||
font-size: $font-regular;
|
font-size: $font-regular;
|
||||||
padding: ($unit * 1.5) ($unit * 2);
|
padding: ($unit * 1.5) ($unit * 2);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&.btn-disabled {
|
&.btn-disabled {
|
||||||
background: $grey-90;
|
background: $grey-90;
|
||||||
color: $grey-70;
|
color: $grey-70;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.btn-disabled) {
|
|
||||||
background: $grey-90;
|
|
||||||
color: $grey-40;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $grey-80;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
&:not(.btn-disabled) {
|
||||||
background: $grey-90;
|
background: $grey-90;
|
||||||
|
color: $grey-50;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $grey-80;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
background: $grey-90;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
import React, { useState } from "react"
|
import React, { useState } from 'react'
|
||||||
import { setCookie } from "cookies-next"
|
import { setCookie } from 'cookies-next'
|
||||||
import Router, { useRouter } from "next/router"
|
import Router, { useRouter } from 'next/router'
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from 'react-i18next'
|
||||||
import { AxiosResponse } from "axios"
|
import { AxiosResponse } from 'axios'
|
||||||
|
|
||||||
import * as Dialog from "@radix-ui/react-dialog"
|
import * as Dialog from '@radix-ui/react-dialog'
|
||||||
|
|
||||||
import api from "~utils/api"
|
import api from '~utils/api'
|
||||||
import { accountState } from "~utils/accountState"
|
import { accountState } from '~utils/accountState'
|
||||||
|
|
||||||
import Button from "~components/Button"
|
import Button from '~components/Button'
|
||||||
import Fieldset from "~components/Fieldset"
|
import Fieldset from '~components/Input'
|
||||||
|
|
||||||
import CrossIcon from "~public/icons/Cross.svg"
|
import CrossIcon from '~public/icons/Cross.svg'
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {}
|
interface Props {}
|
||||||
|
|
||||||
|
|
@ -28,13 +28,13 @@ const emailRegex =
|
||||||
|
|
||||||
const LoginModal = (props: Props) => {
|
const LoginModal = (props: Props) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
// Set up form states and error handling
|
// Set up form states and error handling
|
||||||
const [formValid, setFormValid] = useState(false)
|
const [formValid, setFormValid] = useState(false)
|
||||||
const [errors, setErrors] = useState<ErrorMap>({
|
const [errors, setErrors] = useState<ErrorMap>({
|
||||||
email: "",
|
email: '',
|
||||||
password: "",
|
password: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
// States
|
// States
|
||||||
|
|
@ -50,17 +50,17 @@ const LoginModal = (props: Props) => {
|
||||||
let newErrors = { ...errors }
|
let newErrors = { ...errors }
|
||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "email":
|
case 'email':
|
||||||
if (value.length == 0)
|
if (value.length == 0)
|
||||||
newErrors.email = t("modals.login.errors.empty_email")
|
newErrors.email = t('modals.login.errors.empty_email')
|
||||||
else if (!emailRegex.test(value))
|
else if (!emailRegex.test(value))
|
||||||
newErrors.email = t("modals.login.errors.invalid_email")
|
newErrors.email = t('modals.login.errors.invalid_email')
|
||||||
else newErrors.email = ""
|
else newErrors.email = ''
|
||||||
break
|
break
|
||||||
|
|
||||||
case "password":
|
case 'password':
|
||||||
newErrors.password =
|
newErrors.password =
|
||||||
value.length == 0 ? t("modals.login.errors.empty_password") : ""
|
value.length == 0 ? t('modals.login.errors.empty_password') : ''
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -91,7 +91,7 @@ const LoginModal = (props: Props) => {
|
||||||
const body = {
|
const body = {
|
||||||
email: emailInput.current?.value,
|
email: emailInput.current?.value,
|
||||||
password: passwordInput.current?.value,
|
password: passwordInput.current?.value,
|
||||||
grant_type: "password",
|
grant_type: 'password',
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formValid) {
|
if (formValid) {
|
||||||
|
|
@ -119,7 +119,7 @@ const LoginModal = (props: Props) => {
|
||||||
token: response.data.access_token,
|
token: response.data.access_token,
|
||||||
}
|
}
|
||||||
|
|
||||||
setCookie("account", cookieObj, { path: "/" })
|
setCookie('account', cookieObj, { path: '/' })
|
||||||
}
|
}
|
||||||
|
|
||||||
function storeUserInfo(response: AxiosResponse) {
|
function storeUserInfo(response: AxiosResponse) {
|
||||||
|
|
@ -132,7 +132,7 @@ const LoginModal = (props: Props) => {
|
||||||
gender: user.gender,
|
gender: user.gender,
|
||||||
}
|
}
|
||||||
|
|
||||||
setCookie("user", cookieObj, { path: "/" })
|
setCookie('user', cookieObj, { path: '/' })
|
||||||
|
|
||||||
accountState.account.user = {
|
accountState.account.user = {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
|
|
@ -142,7 +142,7 @@ const LoginModal = (props: Props) => {
|
||||||
gender: user.gender,
|
gender: user.gender,
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Authorizing account...")
|
console.log('Authorizing account...')
|
||||||
accountState.account.authorized = true
|
accountState.account.authorized = true
|
||||||
|
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
|
|
@ -151,7 +151,7 @@ const LoginModal = (props: Props) => {
|
||||||
|
|
||||||
function changeLanguage(newLanguage: string) {
|
function changeLanguage(newLanguage: string) {
|
||||||
if (newLanguage !== router.locale) {
|
if (newLanguage !== router.locale) {
|
||||||
setCookie("NEXT_LOCALE", newLanguage, { path: "/" })
|
setCookie('NEXT_LOCALE', newLanguage, { path: '/' })
|
||||||
router.push(router.asPath, undefined, { locale: newLanguage })
|
router.push(router.asPath, undefined, { locale: newLanguage })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -159,8 +159,8 @@ const LoginModal = (props: Props) => {
|
||||||
function openChange(open: boolean) {
|
function openChange(open: boolean) {
|
||||||
setOpen(open)
|
setOpen(open)
|
||||||
setErrors({
|
setErrors({
|
||||||
email: "",
|
email: '',
|
||||||
password: "",
|
password: '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,7 +168,7 @@ const LoginModal = (props: Props) => {
|
||||||
<Dialog.Root open={open} onOpenChange={openChange}>
|
<Dialog.Root open={open} onOpenChange={openChange}>
|
||||||
<Dialog.Trigger asChild>
|
<Dialog.Trigger asChild>
|
||||||
<li className="MenuItem">
|
<li className="MenuItem">
|
||||||
<span>{t("menu.login")}</span>
|
<span>{t('menu.login')}</span>
|
||||||
</li>
|
</li>
|
||||||
</Dialog.Trigger>
|
</Dialog.Trigger>
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
|
|
@ -178,7 +178,7 @@ const LoginModal = (props: Props) => {
|
||||||
>
|
>
|
||||||
<div className="DialogHeader">
|
<div className="DialogHeader">
|
||||||
<Dialog.Title className="DialogTitle">
|
<Dialog.Title className="DialogTitle">
|
||||||
{t("modals.login.title")}
|
{t('modals.login.title')}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<Dialog.Close className="DialogClose" asChild>
|
<Dialog.Close className="DialogClose" asChild>
|
||||||
<span>
|
<span>
|
||||||
|
|
@ -190,7 +190,7 @@ const LoginModal = (props: Props) => {
|
||||||
<form className="form" onSubmit={login}>
|
<form className="form" onSubmit={login}>
|
||||||
<Fieldset
|
<Fieldset
|
||||||
fieldName="email"
|
fieldName="email"
|
||||||
placeholder={t("modals.login.placeholders.email")}
|
placeholder={t('modals.login.placeholders.email')}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
error={errors.email}
|
error={errors.email}
|
||||||
ref={emailInput}
|
ref={emailInput}
|
||||||
|
|
@ -198,13 +198,13 @@ const LoginModal = (props: Props) => {
|
||||||
|
|
||||||
<Fieldset
|
<Fieldset
|
||||||
fieldName="password"
|
fieldName="password"
|
||||||
placeholder={t("modals.login.placeholders.password")}
|
placeholder={t('modals.login.placeholders.password')}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
error={errors.password}
|
error={errors.password}
|
||||||
ref={passwordInput}
|
ref={passwordInput}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button>{t("modals.login.buttons.confirm")}</Button>
|
<Button>{t('modals.login.buttons.confirm')}</Button>
|
||||||
</form>
|
</form>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
<Dialog.Overlay className="Overlay" />
|
<Dialog.Overlay className="Overlay" />
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#Party .Extra {
|
#Party .Extra {
|
||||||
color: #888;
|
color: #888;
|
||||||
display: flex;
|
display: flex;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
line-height: 34px;
|
line-height: 34px;
|
||||||
}
|
}
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from 'next/router'
|
||||||
import { useSnapshot } from "valtio"
|
import { useSnapshot } from 'valtio'
|
||||||
import { getCookie } from "cookies-next"
|
import { getCookie } from 'cookies-next'
|
||||||
import clonedeep from "lodash.clonedeep"
|
import clonedeep from 'lodash.clonedeep'
|
||||||
|
|
||||||
import PartySegmentedControl from "~components/PartySegmentedControl"
|
import PartySegmentedControl from '~components/PartySegmentedControl'
|
||||||
import PartyDetails from "~components/PartyDetails"
|
import PartyDetails from '~components/PartyDetails'
|
||||||
import WeaponGrid from "~components/WeaponGrid"
|
import WeaponGrid from '~components/WeaponGrid'
|
||||||
import SummonGrid from "~components/SummonGrid"
|
import SummonGrid from '~components/SummonGrid'
|
||||||
import CharacterGrid from "~components/CharacterGrid"
|
import CharacterGrid from '~components/CharacterGrid'
|
||||||
|
|
||||||
import api from "~utils/api"
|
import api from '~utils/api'
|
||||||
import { appState, initialAppState } from "~utils/appState"
|
import { appState, initialAppState } from '~utils/appState'
|
||||||
import { GridType, TeamElement } from "~utils/enums"
|
import { GridType, TeamElement } from '~utils/enums'
|
||||||
|
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -26,7 +26,7 @@ interface Props {
|
||||||
|
|
||||||
const Party = (props: Props) => {
|
const Party = (props: Props) => {
|
||||||
// Cookies
|
// Cookies
|
||||||
const cookie = getCookie("account")
|
const cookie = getCookie('account')
|
||||||
const accountData: AccountCookie = cookie
|
const accountData: AccountCookie = cookie
|
||||||
? JSON.parse(cookie as string)
|
? JSON.parse(cookie as string)
|
||||||
: null
|
: null
|
||||||
|
|
@ -113,7 +113,7 @@ const Party = (props: Props) => {
|
||||||
.destroy({ id: appState.party.id, params: headers })
|
.destroy({ id: appState.party.id, params: headers })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// Push to route
|
// Push to route
|
||||||
router.push("/")
|
router.push('/')
|
||||||
|
|
||||||
// Clean state
|
// Clean state
|
||||||
const resetState = clonedeep(initialAppState)
|
const resetState = clonedeep(initialAppState)
|
||||||
|
|
@ -188,16 +188,16 @@ const Party = (props: Props) => {
|
||||||
// Methods: Navigating with segmented control
|
// Methods: Navigating with segmented control
|
||||||
function segmentClicked(event: React.ChangeEvent<HTMLInputElement>) {
|
function segmentClicked(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
switch (event.target.value) {
|
switch (event.target.value) {
|
||||||
case "class":
|
case 'class':
|
||||||
setCurrentTab(GridType.Class)
|
setCurrentTab(GridType.Class)
|
||||||
break
|
break
|
||||||
case "characters":
|
case 'characters':
|
||||||
setCurrentTab(GridType.Character)
|
setCurrentTab(GridType.Character)
|
||||||
break
|
break
|
||||||
case "weapons":
|
case 'weapons':
|
||||||
setCurrentTab(GridType.Weapon)
|
setCurrentTab(GridType.Weapon)
|
||||||
break
|
break
|
||||||
case "summons":
|
case 'summons':
|
||||||
setCurrentTab(GridType.Summon)
|
setCurrentTab(GridType.Summon)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
|
|
@ -253,7 +253,7 @@ const Party = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<React.Fragment>
|
||||||
{navigation}
|
{navigation}
|
||||||
<section id="Party">{currentGrid()}</section>
|
<section id="Party">{currentGrid()}</section>
|
||||||
{
|
{
|
||||||
|
|
@ -263,7 +263,7 @@ const Party = (props: Props) => {
|
||||||
deleteCallback={deleteTeam}
|
deleteCallback={deleteTeam}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,153 +1,159 @@
|
||||||
.PartyDetails {
|
.PartyDetails {
|
||||||
display: none; // This breaks transition, find a workaround
|
display: none; // This breaks transition, find a workaround
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
margin: 0 auto;
|
margin: $unit-4x auto 0;
|
||||||
margin-bottom: 100px;
|
max-width: $unit * 94;
|
||||||
max-width: $unit * 95;
|
position: relative;
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&.Editable {
|
&.Editable {
|
||||||
top: $unit;
|
top: $unit;
|
||||||
height: 0;
|
height: 0;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
transition: opacity 0.2s ease-in-out,
|
transition: opacity 0.2s ease-in-out, top 0.2s ease-in-out;
|
||||||
top 0.2s ease-in-out;
|
|
||||||
|
|
||||||
|
&.Visible {
|
||||||
&.Visible {
|
display: flex;
|
||||||
display: block;
|
flex-direction: column;
|
||||||
height: auto;
|
gap: $unit;
|
||||||
margin-bottom: 40vh;
|
height: auto;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
top: 0;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ReadOnly {
|
fieldset {
|
||||||
top: $unit * -1;
|
display: block;
|
||||||
transition: opacity 0.2s ease-in-out,
|
width: 100%;
|
||||||
top 0.2s ease-in-out;
|
|
||||||
|
|
||||||
&.Visible {
|
textarea {
|
||||||
display: block;
|
min-height: $unit * 20;
|
||||||
height: auto;
|
width: 100%;
|
||||||
opacity: 1;
|
}
|
||||||
top: 0;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
.bottom {
|
||||||
text-decoration: underline;
|
display: flex;
|
||||||
}
|
flex-direction: row;
|
||||||
|
gap: $unit;
|
||||||
|
margin-bottom: $unit-12x;
|
||||||
|
|
||||||
p {
|
.left {
|
||||||
font-size: $font-regular;
|
flex-grow: 1;
|
||||||
line-height: $font-regular * 1.2;
|
}
|
||||||
white-space: pre-line;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
.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;
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: $font-xlarge;
|
color: var(--text-primary);
|
||||||
font-weight: $normal;
|
|
||||||
text-align: left;
|
&.empty {
|
||||||
margin-bottom: $unit;
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 {
|
.EmptyDetails {
|
||||||
display: none;
|
display: none;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: $unit * 10;
|
margin: $unit-4x 0 $unit-10x;
|
||||||
|
|
||||||
&.Visible {
|
&.Visible {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,34 +1,37 @@
|
||||||
import React, { useState } from "react"
|
import React, { useState } from 'react'
|
||||||
import Head from "next/head"
|
import Head from 'next/head'
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from 'next/router'
|
||||||
import { useSnapshot } from "valtio"
|
import { useSnapshot } from 'valtio'
|
||||||
import { useTranslation } from "next-i18next"
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
import Linkify from "react-linkify"
|
import Linkify from 'react-linkify'
|
||||||
import classNames from "classnames"
|
import classNames from 'classnames'
|
||||||
|
|
||||||
import * as AlertDialog from "@radix-ui/react-alert-dialog"
|
import * as AlertDialog from '@radix-ui/react-alert-dialog'
|
||||||
import CrossIcon from "~public/icons/Cross.svg"
|
|
||||||
|
|
||||||
import Button from "~components/Button"
|
import Button from '~components/Button'
|
||||||
import CharLimitedFieldset from "~components/CharLimitedFieldset"
|
import CharLimitedFieldset from '~components/CharLimitedFieldset'
|
||||||
import RaidDropdown from "~components/RaidDropdown"
|
import RaidDropdown from '~components/RaidDropdown'
|
||||||
import TextFieldset from "~components/TextFieldset"
|
import TextFieldset from '~components/TextFieldset'
|
||||||
|
|
||||||
import { accountState } from "~utils/accountState"
|
import { accountState } from '~utils/accountState'
|
||||||
import { appState } from "~utils/appState"
|
import { appState } from '~utils/appState'
|
||||||
|
|
||||||
import "./index.scss"
|
import CheckIcon from '~public/icons/Check.svg'
|
||||||
import Link from "next/link"
|
import CrossIcon from '~public/icons/Cross.svg'
|
||||||
import { formatTimeAgo } from "~utils/timeAgo"
|
import EditIcon from '~public/icons/Edit.svg'
|
||||||
|
|
||||||
|
import './index.scss'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { formatTimeAgo } from '~utils/timeAgo'
|
||||||
|
|
||||||
const emptyRaid: Raid = {
|
const emptyRaid: Raid = {
|
||||||
id: "",
|
id: '',
|
||||||
name: {
|
name: {
|
||||||
en: "",
|
en: '',
|
||||||
ja: "",
|
ja: '',
|
||||||
},
|
},
|
||||||
slug: "",
|
slug: '',
|
||||||
level: 0,
|
level: 0,
|
||||||
group: 0,
|
group: 0,
|
||||||
element: 0,
|
element: 0,
|
||||||
|
|
@ -47,9 +50,9 @@ const PartyDetails = (props: Props) => {
|
||||||
const { party, raids } = useSnapshot(appState)
|
const { party, raids } = useSnapshot(appState)
|
||||||
const { account } = useSnapshot(accountState)
|
const { account } = useSnapshot(accountState)
|
||||||
|
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale = router.locale || "en"
|
const locale = router.locale || 'en'
|
||||||
|
|
||||||
const nameInput = React.createRef<HTMLInputElement>()
|
const nameInput = React.createRef<HTMLInputElement>()
|
||||||
const descriptionInput = React.createRef<HTMLTextAreaElement>()
|
const descriptionInput = React.createRef<HTMLTextAreaElement>()
|
||||||
|
|
@ -87,8 +90,8 @@ const PartyDetails = (props: Props) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const [errors, setErrors] = useState<{ [key: string]: string }>({
|
const [errors, setErrors] = useState<{ [key: string]: string }>({
|
||||||
name: "",
|
name: '',
|
||||||
description: "",
|
description: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
|
@ -140,7 +143,7 @@ const PartyDetails = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className={userClass}>
|
<div className={userClass}>
|
||||||
{userImage()}
|
{userImage()}
|
||||||
{party.user ? party.user.username : t("no_user")}
|
{party.user ? party.user.username : t('no_user')}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -169,30 +172,30 @@ const PartyDetails = (props: Props) => {
|
||||||
if (party.editable) {
|
if (party.editable) {
|
||||||
return (
|
return (
|
||||||
<AlertDialog.Root>
|
<AlertDialog.Root>
|
||||||
<AlertDialog.Trigger className="Button destructive">
|
<AlertDialog.Trigger className="Button Blended medium destructive">
|
||||||
<span className="icon">
|
<span className="Accessory">
|
||||||
<CrossIcon />
|
<CrossIcon />
|
||||||
</span>
|
</span>
|
||||||
<span className="text">{t("buttons.delete")}</span>
|
<span className="Text">{t('buttons.delete')}</span>
|
||||||
</AlertDialog.Trigger>
|
</AlertDialog.Trigger>
|
||||||
<AlertDialog.Portal>
|
<AlertDialog.Portal>
|
||||||
<AlertDialog.Overlay className="Overlay" />
|
<AlertDialog.Overlay className="Overlay" />
|
||||||
<AlertDialog.Content className="Dialog">
|
<AlertDialog.Content className="Dialog">
|
||||||
<AlertDialog.Title className="DialogTitle">
|
<AlertDialog.Title className="DialogTitle">
|
||||||
{t("modals.delete_team.title")}
|
{t('modals.delete_team.title')}
|
||||||
</AlertDialog.Title>
|
</AlertDialog.Title>
|
||||||
<AlertDialog.Description className="DialogDescription">
|
<AlertDialog.Description className="DialogDescription">
|
||||||
{t("modals.delete_team.description")}
|
{t('modals.delete_team.description')}
|
||||||
</AlertDialog.Description>
|
</AlertDialog.Description>
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
<AlertDialog.Cancel className="Button modal">
|
<AlertDialog.Cancel className="Button modal">
|
||||||
{t("modals.delete_team.buttons.cancel")}
|
{t('modals.delete_team.buttons.cancel')}
|
||||||
</AlertDialog.Cancel>
|
</AlertDialog.Cancel>
|
||||||
<AlertDialog.Action
|
<AlertDialog.Action
|
||||||
className="Button modal destructive"
|
className="Button modal destructive"
|
||||||
onClick={(e) => props.deleteCallback(e)}
|
onClick={(e) => props.deleteCallback(e)}
|
||||||
>
|
>
|
||||||
{t("modals.delete_team.buttons.confirm")}
|
{t('modals.delete_team.buttons.confirm')}
|
||||||
</AlertDialog.Action>
|
</AlertDialog.Action>
|
||||||
</div>
|
</div>
|
||||||
</AlertDialog.Content>
|
</AlertDialog.Content>
|
||||||
|
|
@ -200,7 +203,7 @@ const PartyDetails = (props: Props) => {
|
||||||
</AlertDialog.Root>
|
</AlertDialog.Root>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return ""
|
return ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,13 +220,13 @@ const PartyDetails = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
<RaidDropdown
|
<RaidDropdown
|
||||||
showAllRaidsOption={false}
|
showAllRaidsOption={false}
|
||||||
currentRaid={party.raid?.slug || ""}
|
currentRaid={party.raid?.slug || ''}
|
||||||
ref={raidSelect}
|
ref={raidSelect}
|
||||||
/>
|
/>
|
||||||
<TextFieldset
|
<TextFieldset
|
||||||
fieldName="name"
|
fieldName="name"
|
||||||
placeholder={
|
placeholder={
|
||||||
"Write your notes here\n\n\nWatch out for the 50% trigger!\nMake sure to click Fediel’s 1 first\nGood luck with RNG!"
|
'Write your notes here\n\n\nWatch out for the 50% trigger!\nMake sure to click Fediel’s 1 first\nGood luck with RNG!'
|
||||||
}
|
}
|
||||||
value={party.description}
|
value={party.description}
|
||||||
onChange={handleTextAreaChange}
|
onChange={handleTextAreaChange}
|
||||||
|
|
@ -233,16 +236,15 @@ const PartyDetails = (props: Props) => {
|
||||||
|
|
||||||
<div className="bottom">
|
<div className="bottom">
|
||||||
<div className="left">
|
<div className="left">
|
||||||
{router.pathname !== "/new" ? deleteButton() : ""}
|
{router.pathname !== '/new' ? deleteButton() : ''}
|
||||||
</div>
|
</div>
|
||||||
<div className="right">
|
<div className="right">
|
||||||
<Button active={true} onClick={toggleDetails}>
|
<Button text={t('buttons.cancel')} onClick={toggleDetails} />
|
||||||
{t("buttons.cancel")}
|
<Button
|
||||||
</Button>
|
accessoryIcon={<CheckIcon className="Check" />}
|
||||||
|
text={t('buttons.save_info')}
|
||||||
<Button active={true} icon="check" onClick={updateDetails}>
|
onClick={updateDetails}
|
||||||
{t("buttons.save_info")}
|
/>
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -252,10 +254,12 @@ const PartyDetails = (props: Props) => {
|
||||||
<section className={readOnlyClasses}>
|
<section className={readOnlyClasses}>
|
||||||
<div className="info">
|
<div className="info">
|
||||||
<div className="left">
|
<div className="left">
|
||||||
{party.name ? <h1>{party.name}</h1> : ""}
|
<h1 className={!party.name ? 'empty' : ''}>
|
||||||
|
{party.name ? party.name : 'Untitled'}
|
||||||
|
</h1>
|
||||||
<div className="attribution">
|
<div className="attribution">
|
||||||
{party.user ? linkedUserBlock(party.user) : userBlock()}
|
{party.user ? linkedUserBlock(party.user) : userBlock()}
|
||||||
{party.raid ? linkedRaidBlock(party.raid) : ""}
|
{party.raid ? linkedRaidBlock(party.raid) : ''}
|
||||||
{party.created_at != undefined ? (
|
{party.created_at != undefined ? (
|
||||||
<time
|
<time
|
||||||
className="last-updated"
|
className="last-updated"
|
||||||
|
|
@ -264,15 +268,17 @@ const PartyDetails = (props: Props) => {
|
||||||
{formatTimeAgo(new Date(party.created_at), locale)}
|
{formatTimeAgo(new Date(party.created_at), locale)}
|
||||||
</time>
|
</time>
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="right">
|
<div className="right">
|
||||||
{party.editable ? (
|
{party.editable ? (
|
||||||
<Button active={true} icon="edit" onClick={toggleDetails}>
|
<Button
|
||||||
{t("buttons.show_info")}
|
accessoryIcon={<EditIcon />}
|
||||||
</Button>
|
text={t('buttons.show_info')}
|
||||||
|
onClick={toggleDetails}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div />
|
||||||
)}
|
)}
|
||||||
|
|
@ -283,7 +289,7 @@ const PartyDetails = (props: Props) => {
|
||||||
<Linkify>{party.description}</Linkify>
|
<Linkify>{party.description}</Linkify>
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|
@ -291,59 +297,24 @@ const PartyDetails = (props: Props) => {
|
||||||
const emptyDetails = (
|
const emptyDetails = (
|
||||||
<div className={emptyClasses}>
|
<div className={emptyClasses}>
|
||||||
{party.editable ? (
|
{party.editable ? (
|
||||||
<Button active={true} icon="edit" onClick={toggleDetails}>
|
<Button
|
||||||
{t("buttons.show_info")}
|
accessoryIcon={<EditIcon />}
|
||||||
</Button>
|
text={t('buttons.show_info')}
|
||||||
|
onClick={toggleDetails}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
const generateTitle = () => {
|
|
||||||
let title = party.raid ? `[${party.raid?.name[locale]}] ` : ""
|
|
||||||
|
|
||||||
const username =
|
|
||||||
party.user != null ? `@${party.user?.username}` : t("header.anonymous")
|
|
||||||
|
|
||||||
if (party.name != null)
|
|
||||||
title += t("header.byline", { partyName: party.name, username: username })
|
|
||||||
else if (party.name == null && party.editable && router.route === "/new")
|
|
||||||
title = t("header.new_team")
|
|
||||||
else
|
|
||||||
title += t("header.untitled_team", {
|
|
||||||
username: username,
|
|
||||||
})
|
|
||||||
|
|
||||||
return title
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<React.Fragment>
|
||||||
<Head>
|
|
||||||
<title>{generateTitle()}</title>
|
|
||||||
|
|
||||||
<meta property="og:title" content={generateTitle()} />
|
|
||||||
<meta
|
|
||||||
property="og:description"
|
|
||||||
content={party.description ? party.description : ""}
|
|
||||||
/>
|
|
||||||
<meta property="og:url" content="https://app.granblue.team" />
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
|
||||||
<meta property="twitter:domain" content="app.granblue.team" />
|
|
||||||
<meta name="twitter:title" content={generateTitle()} />
|
|
||||||
<meta
|
|
||||||
name="twitter:description"
|
|
||||||
content={party.description ? party.description : ""}
|
|
||||||
/>
|
|
||||||
</Head>
|
|
||||||
{editable && (party.name || party.description || party.raid)
|
{editable && (party.name || party.description || party.raid)
|
||||||
? readOnly
|
? readOnly
|
||||||
: emptyDetails}
|
: emptyDetails}
|
||||||
{editable}
|
{editable}
|
||||||
</div>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
.PartyNavigation {
|
.PartyNavigation {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 58px;
|
gap: 58px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
margin-bottom: $unit * 3;
|
margin-bottom: $unit * 3;
|
||||||
max-width: 760px;
|
max-width: 760px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ExtraSwitch {
|
.ExtraSwitch {
|
||||||
color: #888;
|
color: #888;
|
||||||
display: flex;
|
display: flex;
|
||||||
font-weight: $normal;
|
font-weight: $normal;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
line-height: 34px;
|
line-height: 34px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
}
|
}
|
||||||
|
|
@ -10,89 +10,104 @@ import ToggleSwitch from '~components/ToggleSwitch'
|
||||||
|
|
||||||
import { GridType } from '~utils/enums'
|
import { GridType } from '~utils/enums'
|
||||||
|
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
selectedTab: GridType
|
selectedTab: GridType
|
||||||
onClick: (event: React.ChangeEvent<HTMLInputElement>) => void
|
onClick: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||||
onCheckboxChange: (event: React.ChangeEvent<HTMLInputElement>) => void
|
onCheckboxChange: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const PartySegmentedControl = (props: Props) => {
|
const PartySegmentedControl = (props: Props) => {
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
const { party, grid } = useSnapshot(appState)
|
const { party, grid } = useSnapshot(appState)
|
||||||
|
|
||||||
function getElement() {
|
function getElement() {
|
||||||
let element: number = 0
|
let element: number = 0
|
||||||
if (party.element == 0 && grid.weapons.mainWeapon)
|
if (party.element == 0 && grid.weapons.mainWeapon)
|
||||||
element = grid.weapons.mainWeapon.element
|
element = grid.weapons.mainWeapon.element
|
||||||
else
|
else element = party.element
|
||||||
element = party.element
|
|
||||||
|
|
||||||
switch(element) {
|
switch (element) {
|
||||||
case 1: return "wind"; break
|
case 1:
|
||||||
case 2: return "fire"; break
|
return 'wind'
|
||||||
case 3: return "water"; break
|
break
|
||||||
case 4: return "earth"; break
|
case 2:
|
||||||
case 5: return "dark"; break
|
return 'fire'
|
||||||
case 6: return "light"; break
|
break
|
||||||
}
|
case 3:
|
||||||
|
return 'water'
|
||||||
|
break
|
||||||
|
case 4:
|
||||||
|
return 'earth'
|
||||||
|
break
|
||||||
|
case 5:
|
||||||
|
return 'dark'
|
||||||
|
break
|
||||||
|
case 6:
|
||||||
|
return 'light'
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const extraToggle =
|
const extraToggle = (
|
||||||
<div className="ExtraSwitch">
|
<div className="ExtraSwitch">
|
||||||
Extra
|
Extra
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
name="ExtraSwitch"
|
name="ExtraSwitch"
|
||||||
editable={party.editable}
|
editable={party.editable}
|
||||||
checked={party.extra}
|
checked={party.extra}
|
||||||
onChange={props.onCheckboxChange}
|
onChange={props.onCheckboxChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="PartyNavigation">
|
<div className="PartyNavigation">
|
||||||
<SegmentedControl elementClass={getElement()}>
|
<SegmentedControl elementClass={getElement()}>
|
||||||
{/* <Segment
|
{/* <Segment
|
||||||
groupName="grid"
|
groupName="grid"
|
||||||
name="class"
|
name="class"
|
||||||
selected={props.selectedTab === GridType.Class}
|
selected={props.selectedTab === GridType.Class}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
>Class</Segment> */}
|
>Class</Segment> */}
|
||||||
|
|
||||||
<Segment
|
<Segment
|
||||||
groupName="grid"
|
groupName="grid"
|
||||||
name="characters"
|
name="characters"
|
||||||
selected={props.selectedTab == GridType.Character}
|
selected={props.selectedTab == GridType.Character}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
>{t('party.segmented_control.characters')}</Segment>
|
>
|
||||||
|
{t('party.segmented_control.characters')}
|
||||||
|
</Segment>
|
||||||
|
|
||||||
<Segment
|
<Segment
|
||||||
groupName="grid"
|
groupName="grid"
|
||||||
name="weapons"
|
name="weapons"
|
||||||
selected={props.selectedTab == GridType.Weapon}
|
selected={props.selectedTab == GridType.Weapon}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
>{t('party.segmented_control.weapons')}</Segment>
|
>
|
||||||
|
{t('party.segmented_control.weapons')}
|
||||||
|
</Segment>
|
||||||
|
|
||||||
<Segment
|
<Segment
|
||||||
groupName="grid"
|
groupName="grid"
|
||||||
name="summons"
|
name="summons"
|
||||||
selected={props.selectedTab == GridType.Summon}
|
selected={props.selectedTab == GridType.Summon}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
>{t('party.segmented_control.summons')}</Segment>
|
>
|
||||||
</SegmentedControl>
|
{t('party.segmented_control.summons')}
|
||||||
|
</Segment>
|
||||||
|
</SegmentedControl>
|
||||||
|
|
||||||
{
|
{(() => {
|
||||||
(() => {
|
if (party.editable && props.selectedTab == GridType.Weapon) {
|
||||||
if (party.editable && props.selectedTab == GridType.Weapon) {
|
return extraToggle
|
||||||
return extraToggle
|
}
|
||||||
}
|
})()}
|
||||||
})()
|
</div>
|
||||||
}
|
)
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PartySegmentedControl
|
export default PartySegmentedControl
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
|
import Select from '~components/Select'
|
||||||
|
import SelectItem from '~components/SelectItem'
|
||||||
|
import SelectGroup from '~components/SelectGroup'
|
||||||
|
|
||||||
import api from '~utils/api'
|
import api from '~utils/api'
|
||||||
import { appState } from '~utils/appState'
|
import { appState } from '~utils/appState'
|
||||||
import { raidGroups } from '~utils/raidGroups'
|
import { raidGroups } from '~utils/raidGroups'
|
||||||
|
|
@ -9,104 +13,136 @@ import './index.scss'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
showAllRaidsOption: boolean
|
showAllRaidsOption: boolean
|
||||||
currentRaid?: string
|
currentRaid?: string
|
||||||
onChange?: (slug?: string) => void
|
defaultRaid?: string
|
||||||
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
onChange?: (slug?: string) => void
|
||||||
|
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const RaidDropdown = React.forwardRef<HTMLSelectElement, Props>(function useFieldSet(props, ref) {
|
const RaidDropdown = React.forwardRef<HTMLSelectElement, Props>(
|
||||||
|
function useFieldSet(props, ref) {
|
||||||
// Set up router for locale
|
// Set up router for locale
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale = router.locale || 'en'
|
const locale = router.locale || 'en'
|
||||||
|
|
||||||
// Set up local states for storing raids
|
// Set up local states for storing raids
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
const [currentRaid, setCurrentRaid] = useState<Raid>()
|
const [currentRaid, setCurrentRaid] = useState<Raid>()
|
||||||
const [raids, setRaids] = useState<Raid[]>()
|
const [raids, setRaids] = useState<Raid[]>()
|
||||||
const [sortedRaids, setSortedRaids] = useState<Raid[][]>()
|
const [sortedRaids, setSortedRaids] = useState<Raid[][]>()
|
||||||
|
|
||||||
|
function openRaidSelect() {
|
||||||
|
setOpen(!open)
|
||||||
|
}
|
||||||
|
|
||||||
// Organize raids into groups on mount
|
// Organize raids into groups on mount
|
||||||
const organizeRaids = useCallback((raids: Raid[]) => {
|
const organizeRaids = useCallback(
|
||||||
|
(raids: Raid[]) => {
|
||||||
// Set up empty raid for "All raids"
|
// Set up empty raid for "All raids"
|
||||||
const all = {
|
const all = {
|
||||||
id: '0',
|
id: '0',
|
||||||
name: {
|
name: {
|
||||||
en: 'All raids',
|
en: 'All raids',
|
||||||
ja: '全て'
|
ja: '全て',
|
||||||
},
|
},
|
||||||
slug: 'all',
|
slug: 'all',
|
||||||
level: 0,
|
level: 0,
|
||||||
group: 0,
|
group: 0,
|
||||||
element: 0
|
element: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
const numGroups = Math.max.apply(Math, raids.map(raid => raid.group))
|
const numGroups = Math.max.apply(
|
||||||
|
Math,
|
||||||
|
raids.map((raid) => raid.group)
|
||||||
|
)
|
||||||
let groupedRaids = []
|
let groupedRaids = []
|
||||||
|
|
||||||
for (let i = 0; i <= numGroups; i++) {
|
for (let i = 0; i <= numGroups; i++) {
|
||||||
groupedRaids[i] = raids.filter(raid => raid.group == i)
|
groupedRaids[i] = raids.filter((raid) => raid.group == i)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.showAllRaidsOption) {
|
if (props.showAllRaidsOption) {
|
||||||
raids.unshift(all)
|
raids.unshift(all)
|
||||||
groupedRaids[0].unshift(all)
|
groupedRaids[0].unshift(all)
|
||||||
}
|
}
|
||||||
|
|
||||||
setRaids(raids)
|
setRaids(raids)
|
||||||
setSortedRaids(groupedRaids)
|
setSortedRaids(groupedRaids)
|
||||||
appState.raids = raids
|
appState.raids = raids
|
||||||
}, [props.showAllRaidsOption])
|
},
|
||||||
|
[props.showAllRaidsOption]
|
||||||
|
)
|
||||||
|
|
||||||
// Fetch all raids on mount
|
// Fetch all raids on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.endpoints.raids.getAll()
|
api.endpoints.raids
|
||||||
.then(response => organizeRaids(response.data.map((r: any) => r.raid)))
|
.getAll()
|
||||||
|
.then((response) =>
|
||||||
|
organizeRaids(response.data.map((r: any) => r.raid))
|
||||||
|
)
|
||||||
}, [organizeRaids])
|
}, [organizeRaids])
|
||||||
|
|
||||||
// Set current raid on mount
|
// Set current raid on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (raids && props.currentRaid) {
|
if (raids && props.currentRaid) {
|
||||||
const raid = raids.find(raid => raid.slug === props.currentRaid)
|
const raid = raids.find((raid) => raid.slug === props.currentRaid)
|
||||||
setCurrentRaid(raid)
|
setCurrentRaid(raid)
|
||||||
}
|
}
|
||||||
}, [raids, props.currentRaid])
|
}, [raids, props.currentRaid])
|
||||||
|
|
||||||
// Enable changing select value
|
// Enable changing select value
|
||||||
function handleChange(event: React.ChangeEvent<HTMLSelectElement>) {
|
function handleChange(value: string) {
|
||||||
if (props.onChange) props.onChange(event.target.value)
|
console.log(value)
|
||||||
|
if (props.onChange) props.onChange(value)
|
||||||
|
|
||||||
if (raids) {
|
if (raids) {
|
||||||
const raid = raids.find(raid => raid.slug === event.target.value)
|
const raid = raids.find((raid) => raid.slug === value)
|
||||||
setCurrentRaid(raid)
|
setCurrentRaid(raid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render JSX for each raid option, sorted into optgroups
|
// Render JSX for each raid option, sorted into optgroups
|
||||||
function renderRaidGroup(index: number) {
|
function renderRaidGroup(index: number) {
|
||||||
const options = sortedRaids && sortedRaids.length > 0 && sortedRaids[index].length > 0 &&
|
const options =
|
||||||
sortedRaids[index].sort((a, b) => a.element - b.element).map((item, i) => {
|
sortedRaids &&
|
||||||
return (
|
sortedRaids.length > 0 &&
|
||||||
<option key={i} value={item.slug}>{item.name[locale]}</option>
|
sortedRaids[index].length > 0 &&
|
||||||
)
|
sortedRaids[index]
|
||||||
})
|
.sort((a, b) => a.element - b.element)
|
||||||
|
.map((item, i) => {
|
||||||
return (
|
return (
|
||||||
<optgroup key={index} label={raidGroups[index].name[locale]}>
|
<SelectItem key={i} value={item.slug}>
|
||||||
{options}
|
{item.name[locale]}
|
||||||
</optgroup>
|
</SelectItem>
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<SelectGroup
|
||||||
|
key={index}
|
||||||
|
label={raidGroups[index].name[locale]}
|
||||||
|
separator={false}
|
||||||
|
>
|
||||||
|
{options}
|
||||||
|
</SelectGroup>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<select
|
<Select
|
||||||
key={currentRaid?.slug}
|
defaultValue={props.defaultRaid}
|
||||||
value={currentRaid?.slug}
|
trigger={'Select a raid...'}
|
||||||
onBlur={props.onBlur}
|
placeholder={'Select a raid...'}
|
||||||
onChange={handleChange}
|
open={open}
|
||||||
ref={ref}>
|
onClick={openRaidSelect}
|
||||||
{ Array.from(Array(sortedRaids?.length)).map((x, i) => renderRaidGroup(i)) }
|
onChange={handleChange}
|
||||||
</select>
|
>
|
||||||
|
{Array.from(Array(sortedRaids?.length)).map((x, i) =>
|
||||||
|
renderRaidGroup(i)
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export default RaidDropdown
|
export default RaidDropdown
|
||||||
|
|
|
||||||
|
|
@ -1,74 +1,72 @@
|
||||||
.DropdownLabel {
|
button.DropdownLabel {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: $grey-90;
|
background: var(--button-contained-bg);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: $unit * 2;
|
border-radius: $unit-2x;
|
||||||
color: $grey-40;
|
color: var(--text-secondary);
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: calc($unit / 2);
|
font-size: $font-small;
|
||||||
flex-direction: row;
|
gap: $unit-half;
|
||||||
padding: ($unit) ($unit * 2);
|
flex-direction: row;
|
||||||
|
padding: $unit ($unit * 1.5) $unit $unit-2x;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $grey-80;
|
background: var(--button-contained-bg-hover);
|
||||||
color: $grey-00;
|
color: var(--text-primary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.count {
|
.count {
|
||||||
color: $grey-60;
|
color: var(--text-tertiary);
|
||||||
font-weight: $medium;
|
font-weight: $medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .icon {
|
& > .icon {
|
||||||
$diameter: 12px;
|
$diameter: 16px;
|
||||||
height: $diameter;
|
height: $diameter;
|
||||||
width: $diameter;
|
width: $diameter;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
transform: scale(0.85);
|
path {
|
||||||
|
fill: $grey-60;
|
||||||
path {
|
}
|
||||||
fill: $grey-60;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Dropdown {
|
.Dropdown {
|
||||||
background: white;
|
background: var(--button-contained-bg);
|
||||||
border-radius: $unit;
|
border-radius: $unit;
|
||||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.18);
|
box-shadow: 0 0 2px rgba(0, 0, 0, 0.18);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: $unit;
|
||||||
|
min-width: 120px;
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: var(--button-contained-bg);
|
||||||
|
filter: drop-shadow(0px 0px 1px rgb(0 0 0 / 0.18));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: $unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Group {
|
||||||
|
flex: 1 1 0px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: calc($unit / 2);
|
}
|
||||||
padding: $unit;
|
|
||||||
min-width: 120px;
|
|
||||||
|
|
||||||
& > span {
|
.Label {
|
||||||
overflow: hidden;
|
color: var(--text-tertiary);
|
||||||
|
font-size: $font-small;
|
||||||
svg {
|
margin-bottom: $unit-half;
|
||||||
fill: white;
|
padding: $unit-half 0 $unit $unit-half;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,29 +6,29 @@ import ArrowIcon from '~public/icons/Arrow.svg'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
label: string
|
label: string
|
||||||
open: boolean
|
open: boolean
|
||||||
numSelected: number
|
numSelected: number
|
||||||
onOpenChange: (open: boolean) => void
|
onOpenChange: (open: boolean) => void
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchFilter = (props: Props) => {
|
const SearchFilter = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Root open={props.open} onOpenChange={props.onOpenChange}>
|
<DropdownMenu.Root open={props.open} onOpenChange={props.onOpenChange}>
|
||||||
<DropdownMenu.Trigger className="DropdownLabel">
|
<DropdownMenu.Trigger className="DropdownLabel">
|
||||||
{props.label}
|
{props.label}
|
||||||
<span className="count">{props.numSelected}</span>
|
<span className="count">{props.numSelected}</span>
|
||||||
<span className="icon">
|
<span className="icon">
|
||||||
<ArrowIcon />
|
<ArrowIcon />
|
||||||
</span>
|
</span>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<DropdownMenu.Content className="Dropdown" sideOffset={4}>
|
<DropdownMenu.Content className="Dropdown" sideOffset={4}>
|
||||||
{props.children}
|
{props.children}
|
||||||
<DropdownMenu.Arrow />
|
<DropdownMenu.Arrow />
|
||||||
</DropdownMenu.Content>
|
</DropdownMenu.Content>
|
||||||
</DropdownMenu.Root>
|
</DropdownMenu.Root>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SearchFilter
|
export default SearchFilter
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,42 @@
|
||||||
.Item {
|
.Item {
|
||||||
|
align-items: center;
|
||||||
|
border-radius: calc($unit / 2);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: $font-regular;
|
||||||
|
line-height: 1.2;
|
||||||
|
min-width: 100px;
|
||||||
|
position: relative;
|
||||||
|
padding: $unit;
|
||||||
|
padding-left: $unit * 3.5;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--button-contained-bg-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-state='checked'] {
|
||||||
|
background: var(--button-contained-bg-hover);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: var(--text-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Indicator {
|
||||||
|
$diameter: 20px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: calc($unit / 2);
|
justify-content: center;
|
||||||
color: $grey-40;
|
position: absolute;
|
||||||
font-size: $font-regular;
|
left: calc($unit / 2);
|
||||||
line-height: 1.2;
|
height: $diameter;
|
||||||
min-width: 100px;
|
width: $diameter;
|
||||||
position: relative;
|
|
||||||
padding: $unit;
|
|
||||||
padding-left: $unit * 3;
|
|
||||||
|
|
||||||
&:hover {
|
svg {
|
||||||
background: $grey-90;
|
height: $diameter;
|
||||||
cursor: pointer;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,29 +6,30 @@ import CheckIcon from '~public/icons/Check.svg'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
checked?: boolean
|
checked?: boolean
|
||||||
valueKey: string
|
valueKey: string
|
||||||
onCheckedChange: (open: boolean, key: string) => void
|
onCheckedChange: (open: boolean, key: string) => void
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchFilterCheckboxItem = (props: Props) => {
|
const SearchFilterCheckboxItem = (props: Props) => {
|
||||||
function handleCheckedChange(checked: boolean) {
|
function handleCheckedChange(checked: boolean) {
|
||||||
props.onCheckedChange(checked, props.valueKey)
|
props.onCheckedChange(checked, props.valueKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.CheckboxItem
|
<DropdownMenu.CheckboxItem
|
||||||
className="Item"
|
className="Item"
|
||||||
checked={props.checked || false}
|
checked={props.checked || false}
|
||||||
onCheckedChange={handleCheckedChange}
|
onCheckedChange={handleCheckedChange}
|
||||||
onSelect={ (event) => event.preventDefault() }>
|
onSelect={(event) => event.preventDefault()}
|
||||||
<DropdownMenu.ItemIndicator className="Indicator">
|
>
|
||||||
<CheckIcon />
|
<DropdownMenu.ItemIndicator className="Indicator">
|
||||||
</DropdownMenu.ItemIndicator>
|
<CheckIcon />
|
||||||
{props.children}
|
</DropdownMenu.ItemIndicator>
|
||||||
</DropdownMenu.CheckboxItem>
|
{props.children}
|
||||||
)
|
</DropdownMenu.CheckboxItem>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SearchFilterCheckboxItem
|
export default SearchFilterCheckboxItem
|
||||||
|
|
|
||||||
|
|
@ -1,98 +1,99 @@
|
||||||
.Search.Dialog {
|
.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;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 431px;
|
gap: $unit;
|
||||||
width: 600px;
|
padding-bottom: $unit * 2;
|
||||||
height: 480px;
|
|
||||||
gap: 0;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
#Header {
|
&.scrolled {
|
||||||
border-bottom: 1px solid transparent;
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
display: flex;
|
box-shadow: 0 0 8px rgba(0, 0, 0, 0.12);
|
||||||
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%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#Results {
|
#Bar {
|
||||||
margin: 0;
|
align-items: center;
|
||||||
max-height: 356px;
|
border-top-left-radius: $unit;
|
||||||
padding: 0 ($unit * 1.5);
|
border-top-right-radius: $unit;
|
||||||
overflow-y: scroll;
|
display: flex;
|
||||||
|
gap: $unit * 2.5;
|
||||||
|
margin: 0;
|
||||||
|
padding: ($unit * 3) ($unit * 3) 0 ($unit * 3);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
h5.total {
|
button {
|
||||||
font-size: $font-regular;
|
background: transparent;
|
||||||
font-weight: $normal;
|
border: none;
|
||||||
color: $grey-40;
|
height: 42px;
|
||||||
padding: calc($unit / 2) ($unit * 1.5);
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
label {
|
||||||
align-items: center;
|
width: 100%;
|
||||||
display: flex;
|
|
||||||
color: $grey-60;
|
|
||||||
font-size: $font-regular;
|
|
||||||
font-weight: $normal;
|
|
||||||
height: $unit * 10;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.WeaponResult:last-child {
|
// .Input {
|
||||||
margin-bottom: $unit * 1.5;
|
// 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: var(--text-tertiary);
|
||||||
|
padding: $unit-half ($unit * 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: $font-regular;
|
||||||
|
font-weight: $normal;
|
||||||
|
height: $unit-10x;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WeaponResult:last-child {
|
||||||
|
margin-bottom: $unit * 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Search.Dialog #NoResults {
|
.Search.Dialog #NoResults {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Search.Dialog #NoResults h2 {
|
.Search.Dialog #NoResults h2 {
|
||||||
color: #ccc;
|
color: var(--text-secondary);
|
||||||
font-size: $font-large;
|
font-size: $font-large;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-top: -32px;
|
margin-top: $unit-4x * -1;
|
||||||
}
|
}
|
||||||
|
|
@ -1,35 +1,41 @@
|
||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from 'react'
|
||||||
import { getCookie, setCookie } from "cookies-next"
|
import { getCookie, setCookie } from 'cookies-next'
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from 'next/router'
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from 'react-i18next'
|
||||||
import InfiniteScroll from "react-infinite-scroll-component"
|
import InfiniteScroll from 'react-infinite-scroll-component'
|
||||||
|
|
||||||
import api from "~utils/api"
|
import api from '~utils/api'
|
||||||
|
|
||||||
import * as Dialog from "@radix-ui/react-dialog"
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogContent,
|
||||||
|
DialogClose,
|
||||||
|
} from '~components/Dialog'
|
||||||
|
|
||||||
import CharacterSearchFilterBar from "~components/CharacterSearchFilterBar"
|
import Input from '~components/Input'
|
||||||
import WeaponSearchFilterBar from "~components/WeaponSearchFilterBar"
|
import CharacterSearchFilterBar from '~components/CharacterSearchFilterBar'
|
||||||
import SummonSearchFilterBar from "~components/SummonSearchFilterBar"
|
import WeaponSearchFilterBar from '~components/WeaponSearchFilterBar'
|
||||||
import JobSkillSearchFilterBar from "~components/JobSkillSearchFilterBar"
|
import SummonSearchFilterBar from '~components/SummonSearchFilterBar'
|
||||||
|
import JobSkillSearchFilterBar from '~components/JobSkillSearchFilterBar'
|
||||||
|
|
||||||
import CharacterResult from "~components/CharacterResult"
|
import CharacterResult from '~components/CharacterResult'
|
||||||
import WeaponResult from "~components/WeaponResult"
|
import WeaponResult from '~components/WeaponResult'
|
||||||
import SummonResult from "~components/SummonResult"
|
import SummonResult from '~components/SummonResult'
|
||||||
import JobSkillResult from "~components/JobSkillResult"
|
import JobSkillResult from '~components/JobSkillResult'
|
||||||
|
|
||||||
import type { SearchableObject, SearchableObjectArray } from "~types"
|
import type { SearchableObject, SearchableObjectArray } from '~types'
|
||||||
|
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
import CrossIcon from "~public/icons/Cross.svg"
|
import CrossIcon from '~public/icons/Cross.svg'
|
||||||
import cloneDeep from "lodash.clonedeep"
|
import cloneDeep from 'lodash.clonedeep'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
send: (object: SearchableObject, position: number) => any
|
send: (object: SearchableObject, position: number) => any
|
||||||
placeholderText: string
|
placeholderText: string
|
||||||
fromPosition: number
|
fromPosition: number
|
||||||
job?: Job
|
job?: Job
|
||||||
object: "weapons" | "characters" | "summons" | "job_skills"
|
object: 'weapons' | 'characters' | 'summons' | 'job_skills'
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,7 +45,7 @@ const SearchModal = (props: Props) => {
|
||||||
const locale = router.locale
|
const locale = router.locale
|
||||||
|
|
||||||
// Set up translation
|
// Set up translation
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
let searchInput = React.createRef<HTMLInputElement>()
|
let searchInput = React.createRef<HTMLInputElement>()
|
||||||
let scrollContainer = React.createRef<HTMLDivElement>()
|
let scrollContainer = React.createRef<HTMLDivElement>()
|
||||||
|
|
@ -47,7 +53,7 @@ const SearchModal = (props: Props) => {
|
||||||
const [firstLoad, setFirstLoad] = useState(true)
|
const [firstLoad, setFirstLoad] = useState(true)
|
||||||
const [filters, setFilters] = useState<{ [key: string]: any }>()
|
const [filters, setFilters] = useState<{ [key: string]: any }>()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [query, setQuery] = useState("")
|
const [query, setQuery] = useState('')
|
||||||
const [results, setResults] = useState<SearchableObjectArray>([])
|
const [results, setResults] = useState<SearchableObjectArray>([])
|
||||||
|
|
||||||
// Pagination states
|
// Pagination states
|
||||||
|
|
@ -64,7 +70,7 @@ const SearchModal = (props: Props) => {
|
||||||
if (text.length) {
|
if (text.length) {
|
||||||
setQuery(text)
|
setQuery(text)
|
||||||
} else {
|
} else {
|
||||||
setQuery("")
|
setQuery('')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,7 +119,7 @@ const SearchModal = (props: Props) => {
|
||||||
: []
|
: []
|
||||||
let recents: SearchableObjectArray = []
|
let recents: SearchableObjectArray = []
|
||||||
|
|
||||||
if (props.object === "weapons") {
|
if (props.object === 'weapons') {
|
||||||
recents = cloneDeep(cookieObj as Weapon[]) || []
|
recents = cloneDeep(cookieObj as Weapon[]) || []
|
||||||
if (
|
if (
|
||||||
!recents.find(
|
!recents.find(
|
||||||
|
|
@ -123,7 +129,7 @@ const SearchModal = (props: Props) => {
|
||||||
) {
|
) {
|
||||||
recents.unshift(result as Weapon)
|
recents.unshift(result as Weapon)
|
||||||
}
|
}
|
||||||
} else if (props.object === "summons") {
|
} else if (props.object === 'summons') {
|
||||||
recents = cloneDeep(cookieObj as Summon[]) || []
|
recents = cloneDeep(cookieObj as Summon[]) || []
|
||||||
if (
|
if (
|
||||||
!recents.find(
|
!recents.find(
|
||||||
|
|
@ -136,7 +142,7 @@ const SearchModal = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recents && recents.length > 5) recents.pop()
|
if (recents && recents.length > 5) recents.pop()
|
||||||
setCookie(`recent_${props.object}`, recents, { path: "/" })
|
setCookie(`recent_${props.object}`, recents, { path: '/' })
|
||||||
sendData(result)
|
sendData(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,16 +198,16 @@ const SearchModal = (props: Props) => {
|
||||||
let jsx
|
let jsx
|
||||||
|
|
||||||
switch (props.object) {
|
switch (props.object) {
|
||||||
case "weapons":
|
case 'weapons':
|
||||||
jsx = renderWeaponSearchResults()
|
jsx = renderWeaponSearchResults()
|
||||||
break
|
break
|
||||||
case "summons":
|
case 'summons':
|
||||||
jsx = renderSummonSearchResults(results)
|
jsx = renderSummonSearchResults(results)
|
||||||
break
|
break
|
||||||
case "characters":
|
case 'characters':
|
||||||
jsx = renderCharacterSearchResults(results)
|
jsx = renderCharacterSearchResults(results)
|
||||||
break
|
break
|
||||||
case "job_skills":
|
case 'job_skills':
|
||||||
jsx = renderJobSkillSearchResults(results)
|
jsx = renderJobSkillSearchResults(results)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -305,7 +311,7 @@ const SearchModal = (props: Props) => {
|
||||||
|
|
||||||
function openChange() {
|
function openChange() {
|
||||||
if (open) {
|
if (open) {
|
||||||
setQuery("")
|
setQuery('')
|
||||||
setFirstLoad(true)
|
setFirstLoad(true)
|
||||||
setResults([])
|
setResults([])
|
||||||
setRecordCount(0)
|
setRecordCount(0)
|
||||||
|
|
@ -317,61 +323,54 @@ const SearchModal = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog.Root open={open} onOpenChange={openChange}>
|
<Dialog open={open} onOpenChange={openChange}>
|
||||||
<Dialog.Trigger asChild>{props.children}</Dialog.Trigger>
|
<DialogTrigger asChild>{props.children}</DialogTrigger>
|
||||||
<Dialog.Portal>
|
<DialogContent className="Search Dialog">
|
||||||
<Dialog.Content className="Search Dialog">
|
<div id="Header">
|
||||||
<div id="Header">
|
<div id="Bar">
|
||||||
<div id="Bar">
|
<Input
|
||||||
<label className="search_label" htmlFor="search_input">
|
autoComplete="off"
|
||||||
<input
|
className="Search"
|
||||||
autoComplete="off"
|
name="query"
|
||||||
type="text"
|
placeholder={props.placeholderText}
|
||||||
name="query"
|
ref={searchInput}
|
||||||
className="Input"
|
value={query}
|
||||||
id="search_input"
|
onChange={inputChanged}
|
||||||
ref={searchInput}
|
/>
|
||||||
value={query}
|
<DialogClose className="DialogClose" onClick={openChange}>
|
||||||
placeholder={props.placeholderText}
|
<CrossIcon />
|
||||||
onChange={inputChanged}
|
</DialogClose>
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<Dialog.Close className="DialogClose" onClick={openChange}>
|
|
||||||
<CrossIcon />
|
|
||||||
</Dialog.Close>
|
|
||||||
</div>
|
|
||||||
{props.object === "characters" ? (
|
|
||||||
<CharacterSearchFilterBar sendFilters={receiveFilters} />
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
{props.object === "weapons" ? (
|
|
||||||
<WeaponSearchFilterBar sendFilters={receiveFilters} />
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
{props.object === "summons" ? (
|
|
||||||
<SummonSearchFilterBar sendFilters={receiveFilters} />
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
{props.object === "job_skills" ? (
|
|
||||||
<JobSkillSearchFilterBar sendFilters={receiveFilters} />
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
{props.object === 'characters' ? (
|
||||||
|
<CharacterSearchFilterBar sendFilters={receiveFilters} />
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
{props.object === 'weapons' ? (
|
||||||
|
<WeaponSearchFilterBar sendFilters={receiveFilters} />
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
{props.object === 'summons' ? (
|
||||||
|
<SummonSearchFilterBar sendFilters={receiveFilters} />
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
{props.object === 'job_skills' ? (
|
||||||
|
<JobSkillSearchFilterBar sendFilters={receiveFilters} />
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="Results" ref={scrollContainer}>
|
<div id="Results" ref={scrollContainer}>
|
||||||
<h5 className="total">
|
<h5 className="total">
|
||||||
{t("search.result_count", { record_count: recordCount })}
|
{t('search.result_count', { record_count: recordCount })}
|
||||||
</h5>
|
</h5>
|
||||||
{open ? renderResults() : ""}
|
{open ? renderResults() : ''}
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Content>
|
</DialogContent>
|
||||||
<Dialog.Overlay className="Overlay" />
|
</Dialog>
|
||||||
</Dialog.Portal>
|
|
||||||
</Dialog.Root>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,36 @@
|
||||||
.Segment {
|
.Segment {
|
||||||
color: $grey-50;
|
color: $grey-55;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: $normal;
|
||||||
|
min-width: 100px;
|
||||||
|
|
||||||
|
&:hover label {
|
||||||
|
background: var(--page-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
& input {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&:checked + label {
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& label {
|
||||||
|
border-radius: $unit * 3;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 8px 12px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1.4rem;
|
|
||||||
font-weight: $normal;
|
|
||||||
min-width: 100px;
|
|
||||||
|
|
||||||
&:hover label {
|
&:before {
|
||||||
background: $grey-90;
|
background: #fff;
|
||||||
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;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,31 +3,27 @@ import React from 'react'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
groupName: string
|
groupName: string
|
||||||
name: string
|
name: string
|
||||||
selected: boolean
|
selected: boolean
|
||||||
children: string
|
children: string
|
||||||
onClick: (event: React.ChangeEvent<HTMLInputElement>) => void
|
onClick: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Segment: React.FC<Props> = (props: Props) => {
|
const Segment: React.FC<Props> = (props: Props) => {
|
||||||
|
return (
|
||||||
|
<div className="Segment">
|
||||||
return (
|
<input
|
||||||
<div className="Segment">
|
name={props.groupName}
|
||||||
<input
|
id={props.name}
|
||||||
name={props.groupName}
|
value={props.name}
|
||||||
id={props.name}
|
type="radio"
|
||||||
value={props.name}
|
checked={props.selected}
|
||||||
type="radio"
|
onChange={props.onClick}
|
||||||
checked={props.selected}
|
/>
|
||||||
onChange={props.onClick}
|
<label htmlFor={props.name}>{props.children}</label>
|
||||||
/>
|
</div>
|
||||||
<label htmlFor={props.name}>
|
)
|
||||||
{props.children}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Segment
|
export default Segment
|
||||||
|
|
@ -1,88 +1,87 @@
|
||||||
.SegmentedControlWrapper {
|
.SegmentedControlWrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.SegmentedControl {
|
.SegmentedControl {
|
||||||
background: white;
|
background: var(--card-bg);
|
||||||
border-radius: $unit * 3;
|
border-radius: $unit * 3;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
position: relative;
|
position: relative;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
&.fire {
|
&.fire {
|
||||||
.Segment input:checked + label {
|
.Segment input:checked + label {
|
||||||
background: $fire-bg-dark;
|
background: $fire-bg-10;
|
||||||
color: $fire-text-dark;
|
color: $fire-text-10;
|
||||||
}
|
|
||||||
|
|
||||||
.Segment:hover label {
|
|
||||||
background: $fire-bg-light;
|
|
||||||
color: $fire-text-light;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.water {
|
.Segment:hover label {
|
||||||
.Segment input:checked + label {
|
background: var(--fire-hover-bg);
|
||||||
background: $water-bg-dark;
|
color: var(--fire-hover-text);
|
||||||
color: $water-text-dark;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Segment:hover label {
|
&.water {
|
||||||
background: $water-bg-light;
|
.Segment input:checked + label {
|
||||||
color: $water-text-light;
|
background: $water-bg-10;
|
||||||
}
|
color: $water-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.earth {
|
.Segment:hover label {
|
||||||
.Segment input:checked + label {
|
background: var(--water-hover-bg);
|
||||||
background: $earth-bg-dark;
|
color: var(--water-hover-text);
|
||||||
color: $earth-text-dark;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Segment:hover label {
|
&.earth {
|
||||||
background: $earth-bg-light;
|
.Segment input:checked + label {
|
||||||
color: $earth-text-light;
|
background: $earth-bg-10;
|
||||||
}
|
color: $earth-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.wind {
|
.Segment:hover label {
|
||||||
.Segment input:checked + label {
|
background: var(--earth-hover-bg);
|
||||||
background: $wind-bg-dark;
|
color: var(--earth-hover-text);
|
||||||
color: $wind-text-dark;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Segment:hover label {
|
&.wind {
|
||||||
background: $wind-bg-light;
|
.Segment input:checked + label {
|
||||||
color: $wind-text-light;
|
background: $wind-bg-10;
|
||||||
}
|
color: $wind-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.light {
|
.Segment:hover label {
|
||||||
.Segment input:checked + label {
|
background: var(--wind-hover-bg);
|
||||||
background: $light-bg-dark;
|
color: var(--wind-hover-text);
|
||||||
color: $light-text-dark;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Segment:hover label {
|
&.light {
|
||||||
background: $light-bg-light;
|
.Segment input:checked + label {
|
||||||
color: $light-text-light;
|
background: $light-bg-10;
|
||||||
}
|
color: $light-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dark {
|
.Segment:hover label {
|
||||||
.Segment input:checked + label {
|
background: var(--light-hover-bg);
|
||||||
background: $dark-bg-dark;
|
color: var(--light-hover-text);
|
||||||
color: $dark-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Segment:hover label {
|
|
||||||
background: $dark-bg-light;
|
|
||||||
color: $dark-text-light;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
.Segment input:checked + label {
|
||||||
|
background: $dark-bg-10;
|
||||||
|
color: $dark-text-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Segment:hover label {
|
||||||
|
background: var(--dark-hover-bg);
|
||||||
|
color: var(--dark-hover-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,17 +3,17 @@ import React from 'react'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
elementClass?: string
|
elementClass?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const SegmentedControl: React.FC<Props> = ({ elementClass, children }) => {
|
const SegmentedControl: React.FC<Props> = ({ elementClass, children }) => {
|
||||||
return (
|
return (
|
||||||
<div className="SegmentedControlWrapper">
|
<div className="SegmentedControlWrapper">
|
||||||
<div className={`SegmentedControl ${(elementClass) ? elementClass : ''}`}>
|
<div className={`SegmentedControl ${elementClass ? elementClass : ''}`}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SegmentedControl
|
export default SegmentedControl
|
||||||
60
components/Select/index.scss
Normal file
60
components/Select/index.scss
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
.SelectTrigger {
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--input-bg);
|
||||||
|
border-radius: $input-corner;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
padding: $unit-2x $unit-2x;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--input-bg-hover);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-placeholder] > span:not(.SelectIcon) {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
& > span:not(.SelectIcon) {
|
||||||
|
color: var(--text-primary);
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: $font-regular;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SelectIcon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: var(--icon-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Select {
|
||||||
|
background: var(--select-bg);
|
||||||
|
border-radius: $input-corner;
|
||||||
|
border: $hover-stroke;
|
||||||
|
box-shadow: $hover-shadow;
|
||||||
|
padding: 0 $unit;
|
||||||
|
z-index: 40;
|
||||||
|
|
||||||
|
.Scroll.Up,
|
||||||
|
.Scroll.Down {
|
||||||
|
padding: $unit 0;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&:hover svg {
|
||||||
|
fill: var(--icon-secondary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: var(--icon-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Scroll.Up {
|
||||||
|
transform: scale(1, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
55
components/Select/index.tsx
Normal file
55
components/Select/index.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import React from 'react'
|
||||||
|
import * as RadixSelect from '@radix-ui/react-select'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
import ArrowIcon from '~public/icons/Arrow.svg'
|
||||||
|
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
|
// Props
|
||||||
|
interface Props {
|
||||||
|
open: boolean
|
||||||
|
defaultValue?: string | number
|
||||||
|
placeholder?: string
|
||||||
|
trigger?: React.ReactNode
|
||||||
|
children?: React.ReactNode
|
||||||
|
onClick?: () => void
|
||||||
|
onChange?: (value: string) => void
|
||||||
|
triggerClass?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Select = React.forwardRef<HTMLSelectElement, Props>(function useFieldSet(
|
||||||
|
props,
|
||||||
|
ref
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<RadixSelect.Root
|
||||||
|
defaultValue={props.defaultValue as string}
|
||||||
|
onValueChange={props.onChange}
|
||||||
|
>
|
||||||
|
<RadixSelect.Trigger
|
||||||
|
className={classNames('SelectTrigger', props.triggerClass)}
|
||||||
|
placeholder={props.placeholder}
|
||||||
|
>
|
||||||
|
<RadixSelect.Value placeholder={props.placeholder} />
|
||||||
|
<RadixSelect.Icon className="SelectIcon">
|
||||||
|
<ArrowIcon />
|
||||||
|
</RadixSelect.Icon>
|
||||||
|
</RadixSelect.Trigger>
|
||||||
|
|
||||||
|
<RadixSelect.Portal className="Select">
|
||||||
|
<RadixSelect.Content>
|
||||||
|
<RadixSelect.ScrollUpButton className="Scroll Up">
|
||||||
|
<ArrowIcon />
|
||||||
|
</RadixSelect.ScrollUpButton>
|
||||||
|
<RadixSelect.Viewport>{props.children}</RadixSelect.Viewport>
|
||||||
|
<RadixSelect.ScrollDownButton className="Scroll Down">
|
||||||
|
<ArrowIcon />
|
||||||
|
</RadixSelect.ScrollDownButton>
|
||||||
|
</RadixSelect.Content>
|
||||||
|
</RadixSelect.Portal>
|
||||||
|
</RadixSelect.Root>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Select
|
||||||
25
components/SelectGroup/index.scss
Normal file
25
components/SelectGroup/index.scss
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
.SelectGroup {
|
||||||
|
.Label {
|
||||||
|
align-items: center;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: $font-small;
|
||||||
|
font-weight: $medium;
|
||||||
|
gap: $unit;
|
||||||
|
padding: $unit $unit-2x $unit-half;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
padding-top: $unit-2x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Separator {
|
||||||
|
background: var(--select-separator);
|
||||||
|
border-radius: 1px;
|
||||||
|
display: block;
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
components/SelectGroup/index.tsx
Normal file
33
components/SelectGroup/index.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import React from 'react'
|
||||||
|
import * as RadixSelect from '@radix-ui/react-select'
|
||||||
|
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
|
// Props
|
||||||
|
interface Props {
|
||||||
|
label?: string
|
||||||
|
separator?: boolean
|
||||||
|
children?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
separator: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
const SelectGroup = (props: Props) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<RadixSelect.Group className="SelectGroup">
|
||||||
|
<RadixSelect.Label className="Label">
|
||||||
|
{props.label}
|
||||||
|
<RadixSelect.Separator className="Separator" />
|
||||||
|
</RadixSelect.Label>
|
||||||
|
{props.children}
|
||||||
|
</RadixSelect.Group>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectGroup.defaultProps = defaultProps
|
||||||
|
|
||||||
|
export default SelectGroup
|
||||||
27
components/SelectItem/index.scss
Normal file
27
components/SelectItem/index.scss
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
.SelectItem {
|
||||||
|
border-radius: $item-corner;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: $font-regular;
|
||||||
|
padding: ($unit * 1.5) $unit-2x;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--option-bg-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border: 2px solid $blue;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: $unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: $unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
components/SelectItem/index.tsx
Normal file
27
components/SelectItem/index.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import React, { ComponentProps } from 'react'
|
||||||
|
import * as Select from '@radix-ui/react-select'
|
||||||
|
|
||||||
|
import './index.scss'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
interface Props extends ComponentProps<'div'> {
|
||||||
|
value: string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
const SelectItem = React.forwardRef<HTMLDivElement, Props>(function selectItem(
|
||||||
|
{ children, ...props },
|
||||||
|
forwardedRef
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<Select.Item
|
||||||
|
className={classNames('SelectItem', props.className)}
|
||||||
|
{...props}
|
||||||
|
ref={forwardedRef}
|
||||||
|
value={`${props.value}`}
|
||||||
|
>
|
||||||
|
<Select.ItemText>{children}</Select.ItemText>
|
||||||
|
</Select.Item>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default SelectItem
|
||||||
|
|
@ -1,47 +1,47 @@
|
||||||
.Signup.Dialog form {
|
.Signup.Dialog form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: calc($unit / 2);
|
gap: calc($unit / 2);
|
||||||
margin-bottom: $unit;
|
margin-bottom: $unit;
|
||||||
|
|
||||||
.Button {
|
.Button {
|
||||||
font-size: $font-regular;
|
font-size: $font-regular;
|
||||||
padding: ($unit * 1.5) ($unit * 2);
|
padding: ($unit * 1.5) ($unit * 2);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&.btn-disabled {
|
&.btn-disabled {
|
||||||
background: $grey-90;
|
background: $grey-90;
|
||||||
color: $grey-70;
|
color: $grey-70;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.btn-disabled) {
|
|
||||||
background: $grey-90;
|
|
||||||
color: $grey-40;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $grey-80;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.terms {
|
&:not(.btn-disabled) {
|
||||||
color: $grey-40;
|
background: $grey-90;
|
||||||
font-size: $font-small;
|
color: $grey-50;
|
||||||
line-height: 1.2;
|
|
||||||
margin-top: $unit;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
a {
|
&:hover {
|
||||||
color: $blue;
|
background: $grey-80;
|
||||||
|
}
|
||||||
&:hover {
|
|
||||||
color: darken($blue, 30);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
input {
|
.terms {
|
||||||
background: $grey-90;
|
color: $grey-50;
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from 'react'
|
||||||
import Link from "next/link"
|
import Link from 'next/link'
|
||||||
import { setCookie } from "cookies-next"
|
import { setCookie } from 'cookies-next'
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from 'next/router'
|
||||||
import { Trans, useTranslation } from "next-i18next"
|
import { Trans, useTranslation } from 'next-i18next'
|
||||||
import { AxiosResponse } from "axios"
|
import { AxiosResponse } from 'axios'
|
||||||
|
|
||||||
import * as Dialog from "@radix-ui/react-dialog"
|
import * as Dialog from '@radix-ui/react-dialog'
|
||||||
|
|
||||||
import api from "~utils/api"
|
import api from '~utils/api'
|
||||||
import { accountState } from "~utils/accountState"
|
import { accountState } from '~utils/accountState'
|
||||||
|
|
||||||
import Button from "~components/Button"
|
import Button from '~components/Button'
|
||||||
import Fieldset from "~components/Fieldset"
|
import Fieldset from '~components/Input'
|
||||||
|
|
||||||
import CrossIcon from "~public/icons/Cross.svg"
|
import CrossIcon from '~public/icons/Cross.svg'
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {}
|
interface Props {}
|
||||||
|
|
||||||
|
|
@ -31,15 +31,15 @@ const emailRegex =
|
||||||
|
|
||||||
const SignupModal = (props: Props) => {
|
const SignupModal = (props: Props) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
// Set up form states and error handling
|
// Set up form states and error handling
|
||||||
const [formValid, setFormValid] = useState(false)
|
const [formValid, setFormValid] = useState(false)
|
||||||
const [errors, setErrors] = useState<ErrorMap>({
|
const [errors, setErrors] = useState<ErrorMap>({
|
||||||
username: "",
|
username: '',
|
||||||
email: "",
|
email: '',
|
||||||
password: "",
|
password: '',
|
||||||
passwordConfirmation: "",
|
passwordConfirmation: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
// States
|
// States
|
||||||
|
|
@ -90,7 +90,7 @@ const SignupModal = (props: Props) => {
|
||||||
token: user.token,
|
token: user.token,
|
||||||
}
|
}
|
||||||
|
|
||||||
setCookie("account", cookieObj, { path: "/" })
|
setCookie('account', cookieObj, { path: '/' })
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchUserInfo(id: string) {
|
function fetchUserInfo(id: string) {
|
||||||
|
|
@ -108,7 +108,7 @@ const SignupModal = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Set language
|
// TODO: Set language
|
||||||
setCookie("user", cookieObj, { path: "/" })
|
setCookie('user', cookieObj, { path: '/' })
|
||||||
|
|
||||||
accountState.account.user = {
|
accountState.account.user = {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
|
|
@ -151,13 +151,13 @@ const SignupModal = (props: Props) => {
|
||||||
|
|
||||||
if (available) {
|
if (available) {
|
||||||
// Continue checking for errors
|
// Continue checking for errors
|
||||||
newErrors[fieldName] = ""
|
newErrors[fieldName] = ''
|
||||||
setErrors(newErrors)
|
setErrors(newErrors)
|
||||||
setFormValid(true)
|
setFormValid(true)
|
||||||
|
|
||||||
validateName(fieldName, value)
|
validateName(fieldName, value)
|
||||||
} else {
|
} else {
|
||||||
newErrors[fieldName] = t("modals.signup.errors.field_in_use", {
|
newErrors[fieldName] = t('modals.signup.errors.field_in_use', {
|
||||||
field: fieldName,
|
field: fieldName,
|
||||||
})
|
})
|
||||||
setErrors(newErrors)
|
setErrors(newErrors)
|
||||||
|
|
@ -169,19 +169,19 @@ const SignupModal = (props: Props) => {
|
||||||
let newErrors = { ...errors }
|
let newErrors = { ...errors }
|
||||||
|
|
||||||
switch (fieldName) {
|
switch (fieldName) {
|
||||||
case "username":
|
case 'username':
|
||||||
if (value.length < 3)
|
if (value.length < 3)
|
||||||
newErrors.username = t("modals.signup.errors.username_too_short")
|
newErrors.username = t('modals.signup.errors.username_too_short')
|
||||||
else if (value.length > 20)
|
else if (value.length > 20)
|
||||||
newErrors.username = t("modals.signup.errors.username_too_long")
|
newErrors.username = t('modals.signup.errors.username_too_long')
|
||||||
else newErrors.username = ""
|
else newErrors.username = ''
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
case "email":
|
case 'email':
|
||||||
newErrors.email = emailRegex.test(value)
|
newErrors.email = emailRegex.test(value)
|
||||||
? ""
|
? ''
|
||||||
: t("modals.signup.errors.invalid_email")
|
: t('modals.signup.errors.invalid_email')
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -198,25 +198,25 @@ const SignupModal = (props: Props) => {
|
||||||
let newErrors = { ...errors }
|
let newErrors = { ...errors }
|
||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "password":
|
case 'password':
|
||||||
newErrors.password = passwordInput.current?.value.includes(
|
newErrors.password = passwordInput.current?.value.includes(
|
||||||
usernameInput.current?.value!
|
usernameInput.current?.value!
|
||||||
)
|
)
|
||||||
? t("modals.signup.errors.password_contains_username")
|
? t('modals.signup.errors.password_contains_username')
|
||||||
: ""
|
: ''
|
||||||
break
|
break
|
||||||
|
|
||||||
case "password":
|
case 'password':
|
||||||
newErrors.password =
|
newErrors.password =
|
||||||
value.length < 8 ? t("modals.signup.errors.password_too_short") : ""
|
value.length < 8 ? t('modals.signup.errors.password_too_short') : ''
|
||||||
break
|
break
|
||||||
|
|
||||||
case "confirm_password":
|
case 'confirm_password':
|
||||||
newErrors.passwordConfirmation =
|
newErrors.passwordConfirmation =
|
||||||
passwordInput.current?.value ===
|
passwordInput.current?.value ===
|
||||||
passwordConfirmationInput.current?.value
|
passwordConfirmationInput.current?.value
|
||||||
? ""
|
? ''
|
||||||
: t("modals.signup.errors.passwords_dont_match")
|
: t('modals.signup.errors.passwords_dont_match')
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -243,10 +243,10 @@ const SignupModal = (props: Props) => {
|
||||||
function openChange(open: boolean) {
|
function openChange(open: boolean) {
|
||||||
setOpen(open)
|
setOpen(open)
|
||||||
setErrors({
|
setErrors({
|
||||||
username: "",
|
username: '',
|
||||||
email: "",
|
email: '',
|
||||||
password: "",
|
password: '',
|
||||||
passwordConfirmation: "",
|
passwordConfirmation: '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,7 +254,7 @@ const SignupModal = (props: Props) => {
|
||||||
<Dialog.Root open={open} onOpenChange={openChange}>
|
<Dialog.Root open={open} onOpenChange={openChange}>
|
||||||
<Dialog.Trigger asChild>
|
<Dialog.Trigger asChild>
|
||||||
<li className="MenuItem">
|
<li className="MenuItem">
|
||||||
<span>{t("menu.signup")}</span>
|
<span>{t('menu.signup')}</span>
|
||||||
</li>
|
</li>
|
||||||
</Dialog.Trigger>
|
</Dialog.Trigger>
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
|
|
@ -264,7 +264,7 @@ const SignupModal = (props: Props) => {
|
||||||
>
|
>
|
||||||
<div className="DialogHeader">
|
<div className="DialogHeader">
|
||||||
<Dialog.Title className="DialogTitle">
|
<Dialog.Title className="DialogTitle">
|
||||||
{t("modals.signup.title")}
|
{t('modals.signup.title')}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<Dialog.Close className="DialogClose" asChild>
|
<Dialog.Close className="DialogClose" asChild>
|
||||||
<span>
|
<span>
|
||||||
|
|
@ -276,7 +276,7 @@ const SignupModal = (props: Props) => {
|
||||||
<form className="form" onSubmit={register}>
|
<form className="form" onSubmit={register}>
|
||||||
<Fieldset
|
<Fieldset
|
||||||
fieldName="username"
|
fieldName="username"
|
||||||
placeholder={t("modals.signup.placeholders.username")}
|
placeholder={t('modals.signup.placeholders.username')}
|
||||||
onChange={handleNameChange}
|
onChange={handleNameChange}
|
||||||
error={errors.username}
|
error={errors.username}
|
||||||
ref={usernameInput}
|
ref={usernameInput}
|
||||||
|
|
@ -284,7 +284,7 @@ const SignupModal = (props: Props) => {
|
||||||
|
|
||||||
<Fieldset
|
<Fieldset
|
||||||
fieldName="email"
|
fieldName="email"
|
||||||
placeholder={t("modals.signup.placeholders.email")}
|
placeholder={t('modals.signup.placeholders.email')}
|
||||||
onChange={handleNameChange}
|
onChange={handleNameChange}
|
||||||
error={errors.email}
|
error={errors.email}
|
||||||
ref={emailInput}
|
ref={emailInput}
|
||||||
|
|
@ -292,7 +292,7 @@ const SignupModal = (props: Props) => {
|
||||||
|
|
||||||
<Fieldset
|
<Fieldset
|
||||||
fieldName="password"
|
fieldName="password"
|
||||||
placeholder={t("modals.signup.placeholders.password")}
|
placeholder={t('modals.signup.placeholders.password')}
|
||||||
onChange={handlePasswordChange}
|
onChange={handlePasswordChange}
|
||||||
error={errors.password}
|
error={errors.password}
|
||||||
ref={passwordInput}
|
ref={passwordInput}
|
||||||
|
|
@ -300,13 +300,13 @@ const SignupModal = (props: Props) => {
|
||||||
|
|
||||||
<Fieldset
|
<Fieldset
|
||||||
fieldName="confirm_password"
|
fieldName="confirm_password"
|
||||||
placeholder={t("modals.signup.placeholders.password_confirm")}
|
placeholder={t('modals.signup.placeholders.password_confirm')}
|
||||||
onChange={handlePasswordChange}
|
onChange={handlePasswordChange}
|
||||||
error={errors.passwordConfirmation}
|
error={errors.passwordConfirmation}
|
||||||
ref={passwordConfirmationInput}
|
ref={passwordConfirmationInput}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button>{t("modals.signup.buttons.confirm")}</Button>
|
<Button>{t('modals.signup.buttons.confirm')}</Button>
|
||||||
|
|
||||||
<Dialog.Description className="terms">
|
<Dialog.Description className="terms">
|
||||||
{/* <Trans i18nKey="modals.signup.agreement">
|
{/* <Trans i18nKey="modals.signup.agreement">
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,26 @@
|
||||||
#SummonGrid {
|
#SummonGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto auto;
|
||||||
|
grid-column-gap: $unit * 2;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
& .Label {
|
||||||
|
color: $grey-55;
|
||||||
|
font-size: $font-tiny;
|
||||||
|
font-weight: $medium;
|
||||||
|
margin-bottom: $unit;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid_summons {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto auto auto;
|
grid-template-columns: auto auto;
|
||||||
grid-column-gap: $unit * 2;
|
grid-column-gap: $unit * 2;
|
||||||
justify-content: center;
|
grid-template-rows: 1fr;
|
||||||
|
grid-row-gap: $unit * 3;
|
||||||
|
|
||||||
& .Label {
|
& > li {
|
||||||
color: $grey-50;
|
list-style: none;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { getCookie } from "cookies-next"
|
import { getCookie } from 'cookies-next'
|
||||||
import { useSnapshot } from "valtio"
|
import { useSnapshot } from 'valtio'
|
||||||
import { useTranslation } from "next-i18next"
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
import { AxiosResponse } from "axios"
|
import { AxiosResponse } from 'axios'
|
||||||
import debounce from "lodash.debounce"
|
import debounce from 'lodash.debounce'
|
||||||
|
|
||||||
import SummonUnit from "~components/SummonUnit"
|
import SummonUnit from '~components/SummonUnit'
|
||||||
import ExtraSummons from "~components/ExtraSummons"
|
import ExtraSummons from '~components/ExtraSummons'
|
||||||
|
|
||||||
import api from "~utils/api"
|
import api from '~utils/api'
|
||||||
import { appState } from "~utils/appState"
|
import { appState } from '~utils/appState'
|
||||||
import type { SearchableObject } from "~types"
|
import type { SearchableObject } from '~types'
|
||||||
|
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -29,7 +29,7 @@ const SummonGrid = (props: Props) => {
|
||||||
const numSummons: number = 4
|
const numSummons: number = 4
|
||||||
|
|
||||||
// Cookies
|
// Cookies
|
||||||
const cookie = getCookie("account")
|
const cookie = getCookie('account')
|
||||||
const accountData: AccountCookie = cookie
|
const accountData: AccountCookie = cookie
|
||||||
? JSON.parse(cookie as string)
|
? JSON.parse(cookie as string)
|
||||||
: null
|
: null
|
||||||
|
|
@ -38,7 +38,7 @@ const SummonGrid = (props: Props) => {
|
||||||
: {}
|
: {}
|
||||||
|
|
||||||
// Localization
|
// Localization
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
// Set up state for view management
|
// Set up state for view management
|
||||||
const { party, grid } = useSnapshot(appState)
|
const { party, grid } = useSnapshot(appState)
|
||||||
|
|
@ -141,7 +141,7 @@ const SummonGrid = (props: Props) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (uncapLevel != previousUncapValues[position])
|
if (uncapLevel != previousUncapValues[position])
|
||||||
await api.updateUncap("summon", id, uncapLevel).then((response) => {
|
await api.updateUncap('summon', id, uncapLevel).then((response) => {
|
||||||
storeGridSummon(response.data.grid_summon)
|
storeGridSummon(response.data.grid_summon)
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -217,7 +217,7 @@ const SummonGrid = (props: Props) => {
|
||||||
// Render: JSX components
|
// Render: JSX components
|
||||||
const mainSummonElement = (
|
const mainSummonElement = (
|
||||||
<div className="LabeledUnit">
|
<div className="LabeledUnit">
|
||||||
<div className="Label">{t("summons.main")}</div>
|
<div className="Label">{t('summons.main')}</div>
|
||||||
<SummonUnit
|
<SummonUnit
|
||||||
gridSummon={grid.summons.mainSummon}
|
gridSummon={grid.summons.mainSummon}
|
||||||
editable={party.editable}
|
editable={party.editable}
|
||||||
|
|
@ -232,7 +232,7 @@ const SummonGrid = (props: Props) => {
|
||||||
|
|
||||||
const friendSummonElement = (
|
const friendSummonElement = (
|
||||||
<div className="LabeledUnit">
|
<div className="LabeledUnit">
|
||||||
<div className="Label">{t("summons.friend")}</div>
|
<div className="Label">{t('summons.friend')}</div>
|
||||||
<SummonUnit
|
<SummonUnit
|
||||||
gridSummon={grid.summons.friendSummon}
|
gridSummon={grid.summons.friendSummon}
|
||||||
editable={party.editable}
|
editable={party.editable}
|
||||||
|
|
@ -246,7 +246,7 @@ const SummonGrid = (props: Props) => {
|
||||||
)
|
)
|
||||||
const summonGridElement = (
|
const summonGridElement = (
|
||||||
<div id="LabeledGrid">
|
<div id="LabeledGrid">
|
||||||
<div className="Label">{t("summons.summons")}</div>
|
<div className="Label">{t('summons.summons')}</div>
|
||||||
<ul id="grid_summons">
|
<ul id="grid_summons">
|
||||||
{Array.from(Array(numSummons)).map((x, i) => {
|
{Array.from(Array(numSummons)).map((x, i) => {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -10,71 +10,88 @@ import UncapIndicator from '~components/UncapIndicator'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
gridSummon: GridSummon
|
gridSummon: GridSummon
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const SummonHovercard = (props: Props) => {
|
const SummonHovercard = (props: Props) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
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 tintElement = Element[props.gridSummon.object.element]
|
||||||
const wikiUrl = `https://gbf.wiki/${props.gridSummon.object.name.en.replaceAll(' ', '_')}`
|
const wikiUrl = `https://gbf.wiki/${props.gridSummon.object.name.en.replaceAll(
|
||||||
|
' ',
|
||||||
|
'_'
|
||||||
|
)}`
|
||||||
|
|
||||||
function summonImage() {
|
function summonImage() {
|
||||||
let imgSrc = ""
|
let imgSrc = ''
|
||||||
|
|
||||||
if (props.gridSummon) {
|
if (props.gridSummon) {
|
||||||
const summon = props.gridSummon.object
|
const summon = props.gridSummon.object
|
||||||
|
|
||||||
const upgradedSummons = [
|
const upgradedSummons = [
|
||||||
'2040094000', '2040100000', '2040080000', '2040098000',
|
'2040094000',
|
||||||
'2040090000', '2040084000', '2040003000', '2040056000'
|
'2040100000',
|
||||||
]
|
'2040080000',
|
||||||
|
'2040098000',
|
||||||
|
'2040090000',
|
||||||
|
'2040084000',
|
||||||
|
'2040003000',
|
||||||
|
'2040056000',
|
||||||
|
]
|
||||||
|
|
||||||
let suffix = ''
|
let suffix = ''
|
||||||
if (upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 && props.gridSummon.uncap_level == 5)
|
if (
|
||||||
suffix = '_02'
|
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 &&
|
||||||
|
props.gridSummon.uncap_level == 5
|
||||||
|
)
|
||||||
|
suffix = '_02'
|
||||||
|
|
||||||
// Generate the correct source for the summon
|
// Generate the correct source for the summon
|
||||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}${suffix}.jpg`
|
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}${suffix}.jpg`
|
||||||
}
|
|
||||||
|
|
||||||
return imgSrc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return imgSrc
|
||||||
<HoverCard.Root>
|
}
|
||||||
<HoverCard.Trigger>
|
|
||||||
{ props.children }
|
return (
|
||||||
</HoverCard.Trigger>
|
<HoverCard.Root>
|
||||||
<HoverCard.Content className="Weapon Hovercard">
|
<HoverCard.Trigger>{props.children}</HoverCard.Trigger>
|
||||||
<div className="top">
|
<HoverCard.Content className="Weapon Hovercard">
|
||||||
<div className="title">
|
<div className="top">
|
||||||
<h4>{ props.gridSummon.object.name[locale] }</h4>
|
<div className="title">
|
||||||
<img alt={props.gridSummon.object.name[locale]} src={summonImage()} />
|
<h4>{props.gridSummon.object.name[locale]}</h4>
|
||||||
</div>
|
<img
|
||||||
<div className="subInfo">
|
alt={props.gridSummon.object.name[locale]}
|
||||||
<div className="icons">
|
src={summonImage()}
|
||||||
<WeaponLabelIcon labelType={Element[props.gridSummon.object.element]}/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<UncapIndicator
|
<div className="subInfo">
|
||||||
type="summon"
|
<div className="icons">
|
||||||
ulb={props.gridSummon.object.uncap.ulb || false}
|
<WeaponLabelIcon
|
||||||
flb={props.gridSummon.object.uncap.flb || false}
|
labelType={Element[props.gridSummon.object.element]}
|
||||||
special={false}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<UncapIndicator
|
||||||
</div>
|
type="summon"
|
||||||
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">{t('buttons.wiki')}</a>
|
ulb={props.gridSummon.object.uncap.ulb || false}
|
||||||
<HoverCard.Arrow />
|
flb={props.gridSummon.object.uncap.flb || false}
|
||||||
</HoverCard.Content>
|
special={false}
|
||||||
</HoverCard.Root>
|
/>
|
||||||
)
|
</div>
|
||||||
|
</div>
|
||||||
|
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">
|
||||||
|
{t('buttons.wiki')}
|
||||||
|
</a>
|
||||||
|
<HoverCard.Arrow />
|
||||||
|
</HoverCard.Content>
|
||||||
|
</HoverCard.Root>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SummonHovercard
|
export default SummonHovercard
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,63 +1,67 @@
|
||||||
.SummonResult {
|
.SummonResult {
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
gap: $unit;
|
||||||
|
padding: $unit * 1.5;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--button-contained-bg);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.Info h5 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
background: $grey-80;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
display: inline-block;
|
||||||
|
height: auto;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Info {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: $unit;
|
flex-direction: column;
|
||||||
padding: $unit * 1.5;
|
flex-grow: 1;
|
||||||
|
gap: $unit-half;
|
||||||
|
|
||||||
&:hover {
|
h5 {
|
||||||
background: $grey-90;
|
color: var(--text-secondary);
|
||||||
cursor: pointer;
|
display: inline-block;
|
||||||
|
font-size: $font-medium;
|
||||||
|
font-weight: $medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
.UncapIndicator {
|
||||||
background: $grey-80;
|
justify-content: left;
|
||||||
border-radius: 6px;
|
pointer-events: none;
|
||||||
display: inline-block;
|
|
||||||
height: auto;
|
|
||||||
width: 120px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Info {
|
.stars {
|
||||||
display: flex;
|
display: inline-block;
|
||||||
flex-direction: column;
|
color: #ffa15e;
|
||||||
flex-grow: 1;
|
font-size: $font-xlarge;
|
||||||
gap: calc($unit / 2);
|
|
||||||
|
|
||||||
h5 {
|
& > span {
|
||||||
color: #555;
|
color: #65daff;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,35 +7,39 @@ import WeaponLabelIcon from '~components/WeaponLabelIcon'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: Summon
|
data: Summon
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
|
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
|
||||||
|
|
||||||
const SummonResult = (props: Props) => {
|
const SummonResult = (props: Props) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
const locale =
|
||||||
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
|
|
||||||
const summon = props.data
|
const summon = props.data
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="SummonResult" onClick={props.onClick}>
|
<li className="SummonResult" onClick={props.onClick}>
|
||||||
<img alt={summon.name[locale]} src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}.jpg`} />
|
<img
|
||||||
<div className="Info">
|
alt={summon.name[locale]}
|
||||||
<h5>{summon.name[locale]}</h5>
|
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}.jpg`}
|
||||||
<UncapIndicator
|
/>
|
||||||
type="summon"
|
<div className="Info">
|
||||||
flb={summon.uncap.flb}
|
<h5>{summon.name[locale]}</h5>
|
||||||
ulb={summon.uncap.ulb}
|
<UncapIndicator
|
||||||
special={false}
|
type="summon"
|
||||||
/>
|
flb={summon.uncap.flb}
|
||||||
<div className="tags">
|
ulb={summon.uncap.ulb}
|
||||||
<WeaponLabelIcon labelType={Element[summon.element]} />
|
special={false}
|
||||||
</div>
|
/>
|
||||||
</div>
|
<div className="tags">
|
||||||
</li>
|
<WeaponLabelIcon labelType={Element[summon.element]} />
|
||||||
)
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SummonResult
|
export default SummonResult
|
||||||
|
|
@ -13,93 +13,122 @@ import { emptyElementState, emptyRarityState } from '~utils/emptyStates'
|
||||||
import { elements, rarities } from '~utils/stateValues'
|
import { elements, rarities } from '~utils/stateValues'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
sendFilters: (filters: { [key: string]: number[] }) => void
|
sendFilters: (filters: { [key: string]: number[] }) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SummonSearchFilterBar = (props: Props) => {
|
const SummonSearchFilterBar = (props: Props) => {
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
const [rarityMenu, setRarityMenu] = useState(false)
|
const [rarityMenu, setRarityMenu] = useState(false)
|
||||||
const [elementMenu, setElementMenu] = useState(false)
|
const [elementMenu, setElementMenu] = useState(false)
|
||||||
|
|
||||||
const [rarityState, setRarityState] = useState<RarityState>(emptyRarityState)
|
const [rarityState, setRarityState] = useState<RarityState>(emptyRarityState)
|
||||||
const [elementState, setElementState] = useState<ElementState>(emptyElementState)
|
const [elementState, setElementState] =
|
||||||
|
useState<ElementState>(emptyElementState)
|
||||||
|
|
||||||
function rarityMenuOpened(open: boolean) {
|
function rarityMenuOpened(open: boolean) {
|
||||||
if (open) {
|
if (open) {
|
||||||
setRarityMenu(true)
|
setRarityMenu(true)
|
||||||
setElementMenu(false)
|
setElementMenu(false)
|
||||||
} else setRarityMenu(false)
|
} else setRarityMenu(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function elementMenuOpened(open: boolean) {
|
||||||
|
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 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)
|
||||||
|
|
||||||
|
const filters = {
|
||||||
|
rarity: checkedRarityFilters,
|
||||||
|
element: checkedElementFilters,
|
||||||
}
|
}
|
||||||
|
|
||||||
function elementMenuOpened(open: boolean) {
|
props.sendFilters(filters)
|
||||||
if (open) {
|
}
|
||||||
setRarityMenu(false)
|
|
||||||
setElementMenu(true)
|
|
||||||
} else setElementMenu(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleRarityChange(checked: boolean, key: string) {
|
useEffect(() => {
|
||||||
let newRarityState = cloneDeep(rarityState)
|
sendFilters()
|
||||||
newRarityState[key].checked = checked
|
}, [rarityState, elementState])
|
||||||
setRarityState(newRarityState)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleElementChange(checked: boolean, key: string) {
|
return (
|
||||||
let newElementState = cloneDeep(elementState)
|
<div className="SearchFilterBar">
|
||||||
newElementState[key].checked = checked
|
<SearchFilter
|
||||||
setElementState(newElementState)
|
label={t('filters.labels.rarity')}
|
||||||
}
|
numSelected={
|
||||||
|
Object.values(rarityState)
|
||||||
function sendFilters() {
|
.map((x) => x.checked)
|
||||||
const checkedRarityFilters = Object.values(rarityState).filter(x => x.checked).map((x, i) => x.id)
|
.filter(Boolean).length
|
||||||
const checkedElementFilters = Object.values(elementState).filter(x => x.checked).map((x, i) => x.id)
|
|
||||||
|
|
||||||
const filters = {
|
|
||||||
rarity: checkedRarityFilters,
|
|
||||||
element: checkedElementFilters
|
|
||||||
}
|
}
|
||||||
|
open={rarityMenu}
|
||||||
|
onOpenChange={rarityMenuOpened}
|
||||||
|
>
|
||||||
|
<DropdownMenu.Label className="Label">
|
||||||
|
{t('filters.labels.rarity')}
|
||||||
|
</DropdownMenu.Label>
|
||||||
|
{Array.from(Array(rarities.length)).map((x, i) => {
|
||||||
|
return (
|
||||||
|
<SearchFilterCheckboxItem
|
||||||
|
key={rarities[i]}
|
||||||
|
onCheckedChange={handleRarityChange}
|
||||||
|
checked={rarityState[rarities[i]].checked}
|
||||||
|
valueKey={rarities[i]}
|
||||||
|
>
|
||||||
|
{t(`rarities.${rarities[i]}`)}
|
||||||
|
</SearchFilterCheckboxItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</SearchFilter>
|
||||||
|
|
||||||
props.sendFilters(filters)
|
<SearchFilter
|
||||||
}
|
label={t('filters.labels.element')}
|
||||||
|
numSelected={
|
||||||
useEffect(() => {
|
Object.values(elementState)
|
||||||
sendFilters()
|
.map((x) => x.checked)
|
||||||
}, [rarityState, elementState])
|
.filter(Boolean).length
|
||||||
|
}
|
||||||
return (
|
open={elementMenu}
|
||||||
<div className="SearchFilterBar">
|
onOpenChange={elementMenuOpened}
|
||||||
<SearchFilter label={t('filters.labels.rarity')} numSelected={Object.values(rarityState).map(x => x.checked).filter(Boolean).length} open={rarityMenu} onOpenChange={rarityMenuOpened}>
|
>
|
||||||
<DropdownMenu.Label className="Label">{t('filters.labels.rarity')}</DropdownMenu.Label>
|
<DropdownMenu.Label className="Label">
|
||||||
{ Array.from(Array(rarities.length)).map((x, i) => {
|
{t('filters.labels.element')}
|
||||||
return (
|
</DropdownMenu.Label>
|
||||||
<SearchFilterCheckboxItem
|
{Array.from(Array(elements.length)).map((x, i) => {
|
||||||
key={rarities[i]}
|
return (
|
||||||
onCheckedChange={handleRarityChange}
|
<SearchFilterCheckboxItem
|
||||||
checked={rarityState[rarities[i]].checked}
|
key={elements[i]}
|
||||||
valueKey={rarities[i]}>
|
onCheckedChange={handleElementChange}
|
||||||
{t(`rarities.${rarities[i]}`)}
|
checked={elementState[elements[i]].checked}
|
||||||
</SearchFilterCheckboxItem>
|
valueKey={elements[i]}
|
||||||
)}
|
>
|
||||||
) }
|
{t(`elements.${elements[i]}`)}
|
||||||
</SearchFilter>
|
</SearchFilterCheckboxItem>
|
||||||
|
)
|
||||||
<SearchFilter label={t('filters.labels.element')} numSelected={Object.values(elementState).map(x => x.checked).filter(Boolean).length} open={elementMenu} onOpenChange={elementMenuOpened}>
|
})}
|
||||||
<DropdownMenu.Label className="Label">{t('filters.labels.element')}</DropdownMenu.Label>
|
</SearchFilter>
|
||||||
{ Array.from(Array(elements.length)).map((x, i) => {
|
</div>
|
||||||
return (
|
)
|
||||||
<SearchFilterCheckboxItem
|
|
||||||
key={elements[i]}
|
|
||||||
onCheckedChange={handleElementChange}
|
|
||||||
checked={elementState[elements[i]].checked}
|
|
||||||
valueKey={elements[i]}>
|
|
||||||
{t(`elements.${elements[i]}`)}
|
|
||||||
</SearchFilterCheckboxItem>
|
|
||||||
)}
|
|
||||||
) }
|
|
||||||
</SearchFilter>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SummonSearchFilterBar
|
export default SummonSearchFilterBar
|
||||||
|
|
|
||||||
|
|
@ -1,106 +1,107 @@
|
||||||
.SummonUnit {
|
.SummonUnit {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
|
||||||
&.main .SummonImage,
|
&.main .SummonImage,
|
||||||
&.friend .SummonImage {
|
&.friend .SummonImage {
|
||||||
aspect-ratio: 182 / 315;
|
aspect-ratio: 182 / 315;
|
||||||
width: 182px;
|
width: 182px;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|
||||||
@media (max-width: $medium-screen) {
|
@media (max-width: $medium-screen) {
|
||||||
width: 20.3vw;
|
width: 20.3vw;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.grid {
|
&.grid {
|
||||||
// max-width: 148px;
|
// max-width: 148px;
|
||||||
// min-height: 141px;
|
// min-height: 141px;
|
||||||
min-height: 180px;
|
min-height: 180px;
|
||||||
|
|
||||||
@media (max-width: $medium-screen) {
|
@media (max-width: $medium-screen) {
|
||||||
min-height: 16.5vw;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.SummonImage {
|
.SummonImage {
|
||||||
background: white;
|
aspect-ratio: 148 / 111;
|
||||||
border: 1px solid rgba(0, 0, 0, 0);
|
list-style-type: none;
|
||||||
border-radius: $unit;
|
width: 148px;
|
||||||
display: flex;
|
height: auto;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: all 0.18s ease-in-out;
|
|
||||||
|
|
||||||
&:hover .icon svg {
|
@media (max-width: $medium-screen) {
|
||||||
fill: $grey-40;
|
width: 20vw;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
&.friend {
|
||||||
position: absolute;
|
margin-right: 0;
|
||||||
height: $unit * 3;
|
}
|
||||||
width: $unit * 3;
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
svg {
|
&.main.editable .SummonImage:hover,
|
||||||
fill: $grey-70;
|
&.friend.editable .SummonImage:hover {
|
||||||
}
|
transform: $scale-tall;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.editable .SummonImage:hover {
|
||||||
|
border: $hover-stroke;
|
||||||
|
box-shadow: $hover-shadow;
|
||||||
|
cursor: pointer;
|
||||||
|
transform: $scale-wide;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SummonImage {
|
||||||
|
background: var(--card-bg);
|
||||||
|
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: var(--icon-secondary-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.filled h3 {
|
.icon {
|
||||||
display: block;
|
position: absolute;
|
||||||
}
|
height: $unit * 3;
|
||||||
|
width: $unit * 3;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
&.filled ul {
|
svg {
|
||||||
display: flex;
|
fill: var(--icon-secondary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
h3, ul {
|
&.filled h3 {
|
||||||
display: none;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
&.filled ul {
|
||||||
color: #333;
|
display: flex;
|
||||||
font-size: $font-regular;
|
}
|
||||||
font-weight: $normal;
|
|
||||||
line-height: 1.1;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
h3,
|
||||||
position: relative;
|
ul {
|
||||||
width: 100%;
|
display: none;
|
||||||
z-index: 2;
|
}
|
||||||
}
|
|
||||||
|
h3 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: $font-regular;
|
||||||
|
font-weight: $normal;
|
||||||
|
line-height: 1.1;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from 'next/router'
|
||||||
import { useTranslation } from "next-i18next"
|
import { useTranslation } from 'next-i18next'
|
||||||
import classnames from "classnames"
|
import classnames from 'classnames'
|
||||||
|
|
||||||
import SearchModal from "~components/SearchModal"
|
import SearchModal from '~components/SearchModal'
|
||||||
import SummonHovercard from "~components/SummonHovercard"
|
import SummonHovercard from '~components/SummonHovercard'
|
||||||
import UncapIndicator from "~components/UncapIndicator"
|
import UncapIndicator from '~components/UncapIndicator'
|
||||||
import PlusIcon from "~public/icons/Add.svg"
|
import PlusIcon from '~public/icons/Add.svg'
|
||||||
|
|
||||||
import type { SearchableObject } from "~types"
|
import type { SearchableObject } from '~types'
|
||||||
|
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
gridSummon: GridSummon | undefined
|
gridSummon: GridSummon | undefined
|
||||||
|
|
@ -22,13 +22,13 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const SummonUnit = (props: Props) => {
|
const SummonUnit = (props: Props) => {
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
const [imageUrl, setImageUrl] = useState("")
|
const [imageUrl, setImageUrl] = useState('')
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale =
|
const locale =
|
||||||
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
|
|
||||||
const classes = classnames({
|
const classes = classnames({
|
||||||
SummonUnit: true,
|
SummonUnit: true,
|
||||||
|
|
@ -47,33 +47,33 @@ const SummonUnit = (props: Props) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
function generateImageUrl() {
|
function generateImageUrl() {
|
||||||
let imgSrc = ""
|
let imgSrc = ''
|
||||||
if (props.gridSummon) {
|
if (props.gridSummon) {
|
||||||
const summon = props.gridSummon.object!
|
const summon = props.gridSummon.object!
|
||||||
|
|
||||||
const upgradedSummons = [
|
const upgradedSummons = [
|
||||||
"2040094000",
|
'2040094000',
|
||||||
"2040100000",
|
'2040100000',
|
||||||
"2040080000",
|
'2040080000',
|
||||||
"2040098000",
|
'2040098000',
|
||||||
"2040090000",
|
'2040090000',
|
||||||
"2040084000",
|
'2040084000',
|
||||||
"2040003000",
|
'2040003000',
|
||||||
"2040056000",
|
'2040056000',
|
||||||
"2040020000",
|
'2040020000',
|
||||||
"2040034000",
|
'2040034000',
|
||||||
"2040028000",
|
'2040028000',
|
||||||
"2040027000",
|
'2040027000',
|
||||||
"2040046000",
|
'2040046000',
|
||||||
"2040047000",
|
'2040047000',
|
||||||
]
|
]
|
||||||
|
|
||||||
let suffix = ""
|
let suffix = ''
|
||||||
if (
|
if (
|
||||||
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 &&
|
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 &&
|
||||||
props.gridSummon.uncap_level == 5
|
props.gridSummon.uncap_level == 5
|
||||||
)
|
)
|
||||||
suffix = "_02"
|
suffix = '_02'
|
||||||
|
|
||||||
// Generate the correct source for the summon
|
// Generate the correct source for the summon
|
||||||
if (props.unitType == 0 || props.unitType == 2)
|
if (props.unitType == 0 || props.unitType == 2)
|
||||||
|
|
@ -98,14 +98,14 @@ const SummonUnit = (props: Props) => {
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
const editableImage = (
|
const editableImage = (
|
||||||
<SearchModal
|
<SearchModal
|
||||||
placeholderText={t("search.placeholders.summon")}
|
placeholderText={t('search.placeholders.summon')}
|
||||||
fromPosition={props.position}
|
fromPosition={props.position}
|
||||||
object="summons"
|
object="summons"
|
||||||
send={props.updateObject}
|
send={props.updateObject}
|
||||||
|
|
@ -127,7 +127,7 @@ const SummonUnit = (props: Props) => {
|
||||||
special={false}
|
special={false}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)}
|
)}
|
||||||
<h3 className="SummonName">{summon?.name[locale]}</h3>
|
<h3 className="SummonName">{summon?.name[locale]}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,18 @@
|
||||||
.Fieldset textarea {
|
.Fieldset textarea {
|
||||||
color: $grey-00;
|
$offset: 2px;
|
||||||
font-family: system-ui, -apple-system, 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
||||||
line-height: 21px;
|
background-color: var(--input-bg);
|
||||||
|
border: $offset solid transparent;
|
||||||
|
border-radius: $input-corner;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: var(--text-primary);
|
||||||
|
display: block;
|
||||||
|
line-height: 21px;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
padding: $unit-2x calc($unit-2x - $offset);
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
border: $offset solid $blue;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,32 +2,31 @@ import React from 'react'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
fieldName: string
|
fieldName: string
|
||||||
placeholder: string
|
placeholder: string
|
||||||
value?: string
|
value?: string
|
||||||
error: string
|
error: string
|
||||||
onBlur?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
|
onBlur?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
|
||||||
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
|
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const TextFieldset = React.forwardRef<HTMLTextAreaElement, Props>(function fieldSet(props, ref) {
|
const TextFieldset = React.forwardRef<HTMLTextAreaElement, Props>(
|
||||||
|
function fieldSet(props, ref) {
|
||||||
return (
|
return (
|
||||||
<fieldset className="Fieldset">
|
<fieldset className="Fieldset">
|
||||||
<textarea
|
<textarea
|
||||||
className="Input"
|
className="Input"
|
||||||
name={props.fieldName}
|
name={props.fieldName}
|
||||||
placeholder={props.placeholder}
|
placeholder={props.placeholder}
|
||||||
defaultValue={props.value || ''}
|
defaultValue={props.value || ''}
|
||||||
onBlur={props.onBlur}
|
onBlur={props.onBlur}
|
||||||
onChange={props.onChange}
|
onChange={props.onChange}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
/>
|
/>
|
||||||
{
|
{props.error.length > 0 && <p className="InputError">{props.error}</p>}
|
||||||
props.error.length > 0 &&
|
</fieldset>
|
||||||
<p className='InputError'>{props.error}</p>
|
|
||||||
}
|
|
||||||
</fieldset>
|
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export default TextFieldset
|
export default TextFieldset
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
.toggle-switch {
|
.toggle-switch {
|
||||||
background: #fff;
|
background: var(--card-bg);
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&-switch {
|
&-switch {
|
||||||
background: #e4e4e4;
|
background: var(--switch-nub); // #e4e4e4;
|
||||||
display: block;
|
display: block;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
|
|
@ -40,14 +40,18 @@
|
||||||
right: 24px;
|
right: 24px;
|
||||||
border-radius: 17px;
|
border-radius: 17px;
|
||||||
transition: all 0.18s ease-in 0s;
|
transition: all 0.18s ease-in 0s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--background);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-checkbox:checked + &-label {
|
&-checkbox:checked + &-label {
|
||||||
background: #ECEBFF;
|
background: var(--extra-purple-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-checkbox:checked + &-label &-switch {
|
&-checkbox:checked + &-label &-switch {
|
||||||
background: #8C86FF;
|
background: var(--extra-purple-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-checkbox:checked + &-label {
|
&-checkbox:checked + &-label {
|
||||||
|
|
|
||||||
|
|
@ -3,29 +3,29 @@ import React from 'react'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name: string
|
name: string
|
||||||
checked: boolean
|
checked: boolean
|
||||||
editable: boolean
|
editable: boolean
|
||||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
|
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ToggleSwitch: React.FC<Props> = (props: Props) => {
|
const ToggleSwitch: React.FC<Props> = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className="toggle-switch">
|
<div className="toggle-switch">
|
||||||
<input
|
<input
|
||||||
className="toggle-switch-checkbox"
|
className="toggle-switch-checkbox"
|
||||||
name={props.name}
|
name={props.name}
|
||||||
id={props.name}
|
id={props.name}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={props.checked}
|
checked={props.checked}
|
||||||
disabled={!props.editable}
|
disabled={!props.editable}
|
||||||
onChange={props.onChange}
|
onChange={props.onChange}
|
||||||
/>
|
/>
|
||||||
<label className="toggle-switch-label" htmlFor={props.name}>
|
<label className="toggle-switch-label" htmlFor={props.name}>
|
||||||
<span className="toggle-switch-switch" />
|
<span className="toggle-switch-switch" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ToggleSwitch
|
export default ToggleSwitch
|
||||||
|
|
@ -1,25 +1,30 @@
|
||||||
import React from "react"
|
import React from 'react'
|
||||||
import { useSnapshot } from "valtio"
|
import { useSnapshot } from 'valtio'
|
||||||
import { getCookie, deleteCookie } from "cookies-next"
|
import { getCookie, deleteCookie } from 'cookies-next'
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from 'next/router'
|
||||||
import { useTranslation } from "next-i18next"
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
import clonedeep from "lodash.clonedeep"
|
import clonedeep from 'lodash.clonedeep'
|
||||||
|
|
||||||
import api from "~utils/api"
|
import api from '~utils/api'
|
||||||
import { accountState, initialAccountState } from "~utils/accountState"
|
import { accountState, initialAccountState } from '~utils/accountState'
|
||||||
import { appState, initialAppState } from "~utils/appState"
|
import { appState, initialAppState } from '~utils/appState'
|
||||||
|
|
||||||
import Header from "~components/Header"
|
import Header from '~components/Header'
|
||||||
import Button from "~components/Button"
|
import Button from '~components/Button'
|
||||||
import HeaderMenu from "~components/HeaderMenu"
|
import HeaderMenu from '~components/HeaderMenu'
|
||||||
|
|
||||||
|
import AddIcon from '~public/icons/Add.svg'
|
||||||
|
import LinkIcon from '~public/icons/Link.svg'
|
||||||
|
import MenuIcon from '~public/icons/Menu.svg'
|
||||||
|
import SaveIcon from '~public/icons/Save.svg'
|
||||||
|
|
||||||
const TopHeader = () => {
|
const TopHeader = () => {
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
// Cookies
|
// Cookies
|
||||||
const accountCookie = getCookie("account")
|
const accountCookie = getCookie('account')
|
||||||
const userCookie = getCookie("user")
|
const userCookie = getCookie('user')
|
||||||
|
|
||||||
const headers = {}
|
const headers = {}
|
||||||
// accountCookies.account != null
|
// accountCookies.account != null
|
||||||
|
|
@ -33,19 +38,19 @@ const TopHeader = () => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
function copyToClipboard() {
|
function copyToClipboard() {
|
||||||
const el = document.createElement("input")
|
const el = document.createElement('input')
|
||||||
el.value = window.location.href
|
el.value = window.location.href
|
||||||
el.id = "url-input"
|
el.id = 'url-input'
|
||||||
document.body.appendChild(el)
|
document.body.appendChild(el)
|
||||||
|
|
||||||
el.select()
|
el.select()
|
||||||
document.execCommand("copy")
|
document.execCommand('copy')
|
||||||
el.remove()
|
el.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
function newParty() {
|
function newParty() {
|
||||||
// Push the root URL
|
// Push the root URL
|
||||||
router.push("/")
|
router.push('/')
|
||||||
|
|
||||||
// Clean state
|
// Clean state
|
||||||
const resetState = clonedeep(initialAppState)
|
const resetState = clonedeep(initialAppState)
|
||||||
|
|
@ -58,18 +63,18 @@ const TopHeader = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
deleteCookie("account")
|
deleteCookie('account')
|
||||||
deleteCookie("user")
|
deleteCookie('user')
|
||||||
|
|
||||||
// Clean state
|
// Clean state
|
||||||
const resetState = clonedeep(initialAccountState)
|
const resetState = clonedeep(initialAccountState)
|
||||||
Object.keys(resetState).forEach((key) => {
|
Object.keys(resetState).forEach((key) => {
|
||||||
if (key !== "language") accountState[key] = resetState[key]
|
if (key !== 'language') accountState[key] = resetState[key]
|
||||||
})
|
})
|
||||||
|
|
||||||
if (router.route != "/new") appState.party.editable = false
|
if (router.route != '/new') appState.party.editable = false
|
||||||
|
|
||||||
router.push("/")
|
router.push('/')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,7 +88,7 @@ const TopHeader = () => {
|
||||||
api.saveTeam({ id: party.id, params: headers }).then((response) => {
|
api.saveTeam({ id: party.id, params: headers }).then((response) => {
|
||||||
if (response.status == 201) appState.party.favorited = true
|
if (response.status == 201) appState.party.favorited = true
|
||||||
})
|
})
|
||||||
else console.error("Failed to save team: No party ID")
|
else console.error('Failed to save team: No party ID')
|
||||||
}
|
}
|
||||||
|
|
||||||
function unsaveFavorite() {
|
function unsaveFavorite() {
|
||||||
|
|
@ -91,13 +96,29 @@ const TopHeader = () => {
|
||||||
api.unsaveTeam({ id: party.id, params: headers }).then((response) => {
|
api.unsaveTeam({ id: party.id, params: headers }).then((response) => {
|
||||||
if (response.status == 200) appState.party.favorited = false
|
if (response.status == 200) appState.party.favorited = false
|
||||||
})
|
})
|
||||||
else console.error("Failed to unsave team: No party ID")
|
else console.error('Failed to unsave team: No party ID')
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyButton = () => {
|
||||||
|
if (router.route === '/p/[party]')
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
accessoryIcon={<LinkIcon className="stroke" />}
|
||||||
|
blended={true}
|
||||||
|
text={t('buttons.copy')}
|
||||||
|
onClick={copyToClipboard}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const leftNav = () => {
|
const leftNav = () => {
|
||||||
return (
|
return (
|
||||||
<div className="dropdown">
|
<div className="dropdown">
|
||||||
<Button icon="menu">{t("buttons.menu")}</Button>
|
<Button
|
||||||
|
accessoryIcon={<MenuIcon />}
|
||||||
|
blended={true}
|
||||||
|
text={t('buttons.menu')}
|
||||||
|
/>
|
||||||
{account.user ? (
|
{account.user ? (
|
||||||
<HeaderMenu
|
<HeaderMenu
|
||||||
authenticated={account.authorized}
|
authenticated={account.authorized}
|
||||||
|
|
@ -114,36 +135,41 @@ const TopHeader = () => {
|
||||||
const saveButton = () => {
|
const saveButton = () => {
|
||||||
if (party.favorited)
|
if (party.favorited)
|
||||||
return (
|
return (
|
||||||
<Button icon="save" active={true} onClick={toggleFavorite}>
|
<Button
|
||||||
Saved
|
accessoryIcon={<SaveIcon />}
|
||||||
</Button>
|
blended={true}
|
||||||
|
text="Saved"
|
||||||
|
onClick={toggleFavorite}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
return (
|
return (
|
||||||
<Button icon="save" onClick={toggleFavorite}>
|
<Button
|
||||||
Save
|
accessoryIcon={<SaveIcon />}
|
||||||
</Button>
|
blended={true}
|
||||||
|
text="Save"
|
||||||
|
onClick={toggleFavorite}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const rightNav = () => {
|
const rightNav = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{router.route === "/p/[party]" &&
|
{router.route === '/p/[party]' &&
|
||||||
account.user &&
|
account.user &&
|
||||||
(!party.user || party.user.id !== account.user.id)
|
(!party.user || party.user.id !== account.user.id)
|
||||||
? saveButton()
|
? saveButton()
|
||||||
: ""}
|
: ''}
|
||||||
{router.route === "/p/[party]" ? (
|
|
||||||
<Button icon="link" onClick={copyToClipboard}>
|
{copyButton()}
|
||||||
{t("buttons.copy")}
|
|
||||||
</Button>
|
<Button
|
||||||
) : (
|
accessoryIcon={<AddIcon className="Add" />}
|
||||||
""
|
blended={true}
|
||||||
)}
|
text={t('buttons.new')}
|
||||||
<Button icon="new" onClick={newParty}>
|
onClick={newParty}
|
||||||
{t("buttons.new")}
|
/>
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
.UncapIndicator {
|
.UncapIndicator {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import React, { useEffect, useRef, useState } from "react"
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import UncapStar from "~components/UncapStar"
|
import UncapStar from '~components/UncapStar'
|
||||||
|
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
type: "character" | "weapon" | "summon"
|
type: 'character' | 'weapon' | 'summon'
|
||||||
rarity?: number
|
rarity?: number
|
||||||
uncapLevel?: number
|
uncapLevel?: number
|
||||||
flb: boolean
|
flb: boolean
|
||||||
|
|
@ -20,7 +20,7 @@ const UncapIndicator = (props: Props) => {
|
||||||
function setNumStars() {
|
function setNumStars() {
|
||||||
let numStars
|
let numStars
|
||||||
|
|
||||||
if (props.type === "character") {
|
if (props.type === 'character') {
|
||||||
if (props.special) {
|
if (props.special) {
|
||||||
if (props.ulb) {
|
if (props.ulb) {
|
||||||
numStars = 5
|
numStars = 5
|
||||||
|
|
@ -109,13 +109,13 @@ const UncapIndicator = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<ul className="UncapIndicator">
|
<ul className="UncapIndicator">
|
||||||
{Array.from(Array(numStars)).map((x, i) => {
|
{Array.from(Array(numStars)).map((x, i) => {
|
||||||
if (props.type === "character" && i > 4) {
|
if (props.type === 'character' && i > 4) {
|
||||||
if (props.special) return ulb(i)
|
if (props.special) return ulb(i)
|
||||||
else return transcendence(i)
|
else return transcendence(i)
|
||||||
} else if (
|
} else if (
|
||||||
(props.special && props.type === "character" && i == 3) ||
|
(props.special && props.type === 'character' && i == 3) ||
|
||||||
(props.type === "character" && i == 4) ||
|
(props.type === 'character' && i == 4) ||
|
||||||
(props.type !== "character" && i > 2)
|
(props.type !== 'character' && i > 2)
|
||||||
) {
|
) {
|
||||||
return flb(i)
|
return flb(i)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,55 @@
|
||||||
.UncapStar {
|
.UncapStar {
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: 18px 18px;
|
background-size: 18px 18px;
|
||||||
display: block;
|
display: block;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
width: 18px;
|
width: 18px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.empty,
|
||||||
|
&.empty.mlb,
|
||||||
|
&.empty.flb,
|
||||||
|
&.empty.ulb,
|
||||||
|
&.empty.special {
|
||||||
|
background: url('/icons/uncap/empty.svg');
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: scale(1.2);
|
background: url('/icons/uncap/empty-hover.svg');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.empty,
|
&.mlb {
|
||||||
&.empty.mlb,
|
background: url('/icons/uncap/yellow.svg');
|
||||||
&.empty.flb,
|
|
||||||
&.empty.ulb,
|
|
||||||
&.empty.special {
|
|
||||||
background: url('/icons/uncap/empty.svg');
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: url('/icons/uncap/empty-hover.svg');
|
background: url('/icons/uncap/yellow-hover.svg');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.mlb {
|
&.special {
|
||||||
background: url('/icons/uncap/yellow.svg');
|
background: url('/icons/uncap/red.svg');
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: url('/icons/uncap/yellow-hover.svg');
|
background: url('/icons/uncap/red-hover.svg');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.special {
|
&.flb {
|
||||||
background: url('/icons/uncap/red.svg');
|
background: url('/icons/uncap/blue.svg');
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: url('/icons/uncap/red-hover.svg');
|
background: url('/icons/uncap/blue-hover.svg');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.flb {
|
&.ulb {
|
||||||
background: url('/icons/uncap/blue.svg');
|
background: url('/icons/uncap/purple.svg');
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: url('/icons/uncap/blue-hover.svg');
|
background: url('/icons/uncap/purple-hover.svg');
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ulb {
|
|
||||||
background: url('/icons/uncap/purple.svg');
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: url('/icons/uncap/purple-hover.svg');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,39 +4,36 @@ import classnames from 'classnames'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
empty: boolean
|
empty: boolean
|
||||||
special: boolean
|
special: boolean
|
||||||
flb: boolean
|
flb: boolean
|
||||||
ulb: boolean
|
ulb: boolean
|
||||||
index: number
|
index: number
|
||||||
onClick: (index: number, empty: boolean) => void
|
onClick: (index: number, empty: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const UncapStar = (props: Props) => {
|
const UncapStar = (props: Props) => {
|
||||||
const classes = classnames({
|
const classes = classnames({
|
||||||
UncapStar: true,
|
UncapStar: true,
|
||||||
'empty': props.empty,
|
empty: props.empty,
|
||||||
'special': props.special,
|
special: props.special,
|
||||||
'mlb': !props.special,
|
mlb: !props.special,
|
||||||
'flb': props.flb,
|
flb: props.flb,
|
||||||
'ulb': props.ulb
|
ulb: props.ulb,
|
||||||
|
})
|
||||||
|
|
||||||
})
|
function clicked() {
|
||||||
|
props.onClick(props.index, props.empty)
|
||||||
|
}
|
||||||
|
|
||||||
function clicked() {
|
return <li className={classes} onClick={clicked}></li>
|
||||||
props.onClick(props.index, props.empty)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li className={classes} onClick={clicked}></li>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UncapStar.defaultProps = {
|
UncapStar.defaultProps = {
|
||||||
empty: false,
|
empty: false,
|
||||||
special: false,
|
special: false,
|
||||||
flb: false,
|
flb: false,
|
||||||
ulb: false
|
ulb: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UncapStar
|
export default UncapStar
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue