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
|
||||||
|
}
|
||||||
|
|
@ -16,9 +16,14 @@ const AboutModal = () => {
|
||||||
</li>
|
</li>
|
||||||
</Dialog.Trigger>
|
</Dialog.Trigger>
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
<Dialog.Content className="About Dialog" onOpenAutoFocus={ (event) => event.preventDefault() }>
|
<Dialog.Content
|
||||||
|
className="About Dialog"
|
||||||
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||||
|
>
|
||||||
<div className="DialogHeader">
|
<div className="DialogHeader">
|
||||||
<Dialog.Title className="DialogTitle">{t('menu.about')}</Dialog.Title>
|
<Dialog.Title className="DialogTitle">
|
||||||
|
{t('menu.about')}
|
||||||
|
</Dialog.Title>
|
||||||
<Dialog.Close className="DialogClose" asChild>
|
<Dialog.Close className="DialogClose" asChild>
|
||||||
<span>
|
<span>
|
||||||
<CrossIcon />
|
<CrossIcon />
|
||||||
|
|
@ -28,20 +33,27 @@ const AboutModal = () => {
|
||||||
|
|
||||||
<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{' '}
|
||||||
|
<a href="https://game.granbluefantasy.jp">Granblue Fantasy.</a>
|
||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
<Dialog.Description className="DialogDescription">
|
<Dialog.Description className="DialogDescription">
|
||||||
Start adding things to a team and a URL will be created for you to share it wherever you like, no account needed.
|
Start adding things to a team and a URL will be created for you to
|
||||||
|
share it wherever you like, no account needed.
|
||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
<Dialog.Description className="DialogDescription">
|
<Dialog.Description className="DialogDescription">
|
||||||
You can make an account to save any teams you find for future reference, or to keep all of your teams together in one place.
|
You can make an account to save any teams you find for future
|
||||||
|
reference, or to keep all of your teams together in one place.
|
||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
</section>
|
</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{' '}
|
||||||
|
<a href="https://twitter.com/jedmund">@jedmund</a> with a lot of
|
||||||
|
help from{' '}
|
||||||
|
<a href="https://twitter.com/lalalalinna">@lalalalinna</a> and{' '}
|
||||||
|
<a href="https://twitter.com/tarngerine">@tarngerine</a>.
|
||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,16 @@
|
||||||
height: $height;
|
height: $height;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: 0 0 0 2px $grey-00;
|
box-shadow: 0 0 0 2px $grey-15;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-state="checked"] {
|
&[data-state='checked'] {
|
||||||
background: $grey-00;
|
background: $grey-15;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Thumb {
|
.Thumb {
|
||||||
background: white;
|
background: $grey-100;
|
||||||
border-radius: 13px;
|
border-radius: 13px;
|
||||||
display: block;
|
display: block;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
|
|
@ -40,34 +40,12 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-state="checked"] {
|
&[data-state='checked'] {
|
||||||
background: white;
|
background: $grey-100;
|
||||||
transform: translateX(21px);
|
transform: translateX(21px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Button {
|
|
||||||
font-size: $font-regular;
|
|
||||||
padding: ($unit * 1.5) ($unit * 2);
|
|
||||||
margin-top: $unit * 2;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&.btn-disabled {
|
|
||||||
background: $grey-90;
|
|
||||||
color: $grey-70;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.btn-disabled) {
|
|
||||||
background: $grey-90;
|
|
||||||
color: $grey-40;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $grey-80;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.field {
|
.field {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -89,12 +67,12 @@
|
||||||
gap: calc($unit / 2);
|
gap: calc($unit / 2);
|
||||||
|
|
||||||
label {
|
label {
|
||||||
color: $grey-00;
|
color: var(--text-secondary);
|
||||||
font-size: $font-regular;
|
font-size: $font-regular;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color: $grey-60;
|
color: var(--text-secondary);
|
||||||
font-size: $font-small;
|
font-size: $font-small;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
|
|
@ -118,27 +96,27 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.fire {
|
&.fire {
|
||||||
background: $fire-bg-light;
|
background: $fire-bg-20;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.water {
|
&.water {
|
||||||
background: $water-bg-light;
|
background: $water-bg-20;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.wind {
|
&.wind {
|
||||||
background: $wind-bg-light;
|
background: $wind-bg-20;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.earth {
|
&.earth {
|
||||||
background: $earth-bg-light;
|
background: $earth-bg-20;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dark {
|
&.dark {
|
||||||
background: $dark-bg-light;
|
background: $dark-bg-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.light {
|
&.light {
|
||||||
background: $light-bg-light;
|
background: $light-bg-20;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
background-color: $grey-90;
|
background-color: $grey-90;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
color: $grey-00;
|
color: $grey-15;
|
||||||
height: $unit * 6;
|
height: $unit * 6;
|
||||||
display: block;
|
display: block;
|
||||||
font-size: $font-regular;
|
font-size: $font-regular;
|
||||||
|
|
|
||||||
|
|
@ -16,30 +16,36 @@ interface ErrorMap {
|
||||||
|
|
||||||
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 =
|
||||||
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
const { t } = useTranslation('common')
|
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
|
||||||
|
|
@ -71,17 +77,30 @@ const AXSelect = (props: Props) => {
|
||||||
}, [props.currentSkills])
|
}, [props.currentSkills])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
props.sendValues(primaryAxModifier, primaryAxValue, secondaryAxModifier, secondaryAxValue)
|
props.sendValues(
|
||||||
}, [props, primaryAxModifier, primaryAxValue, secondaryAxModifier, secondaryAxValue])
|
primaryAxModifier,
|
||||||
|
primaryAxValue,
|
||||||
|
secondaryAxModifier,
|
||||||
|
secondaryAxValue
|
||||||
|
)
|
||||||
|
}, [
|
||||||
|
props,
|
||||||
|
primaryAxModifier,
|
||||||
|
primaryAxValue,
|
||||||
|
secondaryAxModifier,
|
||||||
|
secondaryAxValue,
|
||||||
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
props.sendValidity(primaryAxValue > 0 && errors.axValue1 === '' && errors.axValue2 === '')
|
props.sendValidity(
|
||||||
|
primaryAxValue > 0 && errors.axValue1 === '' && errors.axValue2 === ''
|
||||||
|
)
|
||||||
}, [props, primaryAxValue, errors])
|
}, [props, primaryAxValue, errors])
|
||||||
|
|
||||||
// Classes
|
// Classes
|
||||||
const secondarySetClasses = classNames({
|
const secondarySetClasses = classNames({
|
||||||
'AXSet': true,
|
AXSet: true,
|
||||||
'hidden': primaryAxModifier < 0
|
hidden: primaryAxModifier < 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
function generateOptions(modifierSet: number) {
|
function generateOptions(modifierSet: number) {
|
||||||
|
|
@ -91,17 +110,17 @@ const AXSelect = (props: Props) => {
|
||||||
if (modifierSet == 0) {
|
if (modifierSet == 0) {
|
||||||
axOptionElements = axOptions.map((ax, i) => {
|
axOptionElements = axOptions.map((ax, i) => {
|
||||||
return (
|
return (
|
||||||
<option key={i} value={ax.id}>{ax.name[locale]}</option>
|
<option key={i} value={ax.id}>
|
||||||
|
{ax.name[locale]}
|
||||||
|
</option>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// If we are loading data from the server, state doesn't set before render,
|
// If we are loading data from the server, state doesn't set before render,
|
||||||
// so our defaultValue is undefined.
|
// so our defaultValue is undefined.
|
||||||
let modifier = -1;
|
let modifier = -1
|
||||||
if (primaryAxModifier >= 0)
|
if (primaryAxModifier >= 0) modifier = primaryAxModifier
|
||||||
modifier = primaryAxModifier
|
else if (props.currentSkills) modifier = props.currentSkills[0].modifier
|
||||||
else if (props.currentSkills)
|
|
||||||
modifier = props.currentSkills[0].modifier
|
|
||||||
|
|
||||||
if (modifier >= 0 && axOptions[modifier]) {
|
if (modifier >= 0 && axOptions[modifier]) {
|
||||||
const primarySkill = axOptions[modifier]
|
const primarySkill = axOptions[modifier]
|
||||||
|
|
@ -110,14 +129,20 @@ const AXSelect = (props: Props) => {
|
||||||
const secondaryAxOptions = primarySkill.secondary
|
const secondaryAxOptions = primarySkill.secondary
|
||||||
axOptionElements = secondaryAxOptions.map((ax, i) => {
|
axOptionElements = secondaryAxOptions.map((ax, i) => {
|
||||||
return (
|
return (
|
||||||
<option key={i} value={ax.id}>{ax.name[locale]}</option>
|
<option key={i} value={ax.id}>
|
||||||
|
{ax.name[locale]}
|
||||||
|
</option>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
axOptionElements?.unshift(<option key={-1} value={-1}>{t('ax.no_skill')}</option>)
|
axOptionElements?.unshift(
|
||||||
|
<option key={-1} value={-1}>
|
||||||
|
{t('ax.no_skill')}
|
||||||
|
</option>
|
||||||
|
)
|
||||||
return axOptionElements
|
return axOptionElements
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,18 +152,23 @@ const AXSelect = (props: Props) => {
|
||||||
if (primaryAxModifierSelect.current == event.target) {
|
if (primaryAxModifierSelect.current == event.target) {
|
||||||
setPrimaryAxModifier(value)
|
setPrimaryAxModifier(value)
|
||||||
|
|
||||||
if (primaryAxValueInput.current && secondaryAxModifierSelect.current && secondaryAxValueInput.current) {
|
if (
|
||||||
|
primaryAxValueInput.current &&
|
||||||
|
secondaryAxModifierSelect.current &&
|
||||||
|
secondaryAxValueInput.current
|
||||||
|
) {
|
||||||
setupInput(axData[props.axType - 1][value], primaryAxValueInput.current)
|
setupInput(axData[props.axType - 1][value], primaryAxValueInput.current)
|
||||||
|
|
||||||
secondaryAxModifierSelect.current.value = "-1"
|
secondaryAxModifierSelect.current.value = '-1'
|
||||||
secondaryAxValueInput.current.value = ""
|
secondaryAxValueInput.current.value = ''
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setSecondaryAxModifier(value)
|
setSecondaryAxModifier(value)
|
||||||
|
|
||||||
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
|
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
|
||||||
const currentAxSkill = (primaryAxSkill.secondary) ?
|
const currentAxSkill = primaryAxSkill.secondary
|
||||||
primaryAxSkill.secondary.find(skill => skill.id == value) : undefined
|
? primaryAxSkill.secondary.find((skill) => skill.id == value)
|
||||||
|
: undefined
|
||||||
|
|
||||||
if (secondaryAxValueInput.current)
|
if (secondaryAxValueInput.current)
|
||||||
setupInput(currentAxSkill, secondaryAxValueInput.current)
|
setupInput(currentAxSkill, secondaryAxValueInput.current)
|
||||||
|
|
@ -150,11 +180,9 @@ const AXSelect = (props: Props) => {
|
||||||
let newErrors = { ...errors }
|
let newErrors = { ...errors }
|
||||||
|
|
||||||
if (primaryAxValueInput.current == event.target) {
|
if (primaryAxValueInput.current == event.target) {
|
||||||
if (handlePrimaryErrors(value))
|
if (handlePrimaryErrors(value)) setPrimaryAxValue(value)
|
||||||
setPrimaryAxValue(value)
|
|
||||||
} else {
|
} else {
|
||||||
if (handleSecondaryErrors(value))
|
if (handleSecondaryErrors(value)) setSecondaryAxValue(value)
|
||||||
setSecondaryAxValue(value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,16 +194,18 @@ const AXSelect = (props: Props) => {
|
||||||
newErrors.axValue1 = t('ax.errors.value_too_low', {
|
newErrors.axValue1 = t('ax.errors.value_too_low', {
|
||||||
name: primaryAxSkill.name[locale],
|
name: primaryAxSkill.name[locale],
|
||||||
minValue: primaryAxSkill.minValue,
|
minValue: primaryAxSkill.minValue,
|
||||||
suffix: (primaryAxSkill.suffix) ? primaryAxSkill.suffix : ''
|
suffix: primaryAxSkill.suffix ? primaryAxSkill.suffix : '',
|
||||||
})
|
})
|
||||||
} else if (value > primaryAxSkill.maxValue) {
|
} else if (value > primaryAxSkill.maxValue) {
|
||||||
newErrors.axValue1 = t('ax.errors.value_too_high', {
|
newErrors.axValue1 = t('ax.errors.value_too_high', {
|
||||||
name: primaryAxSkill.name[locale],
|
name: primaryAxSkill.name[locale],
|
||||||
maxValue: primaryAxSkill.minValue,
|
maxValue: primaryAxSkill.minValue,
|
||||||
suffix: (primaryAxSkill.suffix) ? primaryAxSkill.suffix : ''
|
suffix: primaryAxSkill.suffix ? primaryAxSkill.suffix : '',
|
||||||
})
|
})
|
||||||
} else if (!value || value <= 0) {
|
} else if (!value || value <= 0) {
|
||||||
newErrors.axValue1 = t('ax.errors.value_empty', { name: primaryAxSkill.name[locale] })
|
newErrors.axValue1 = t('ax.errors.value_empty', {
|
||||||
|
name: primaryAxSkill.name[locale],
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
newErrors.axValue1 = ''
|
newErrors.axValue1 = ''
|
||||||
}
|
}
|
||||||
|
|
@ -190,25 +220,31 @@ const AXSelect = (props: Props) => {
|
||||||
let newErrors = { ...errors }
|
let newErrors = { ...errors }
|
||||||
|
|
||||||
if (primaryAxSkill.secondary) {
|
if (primaryAxSkill.secondary) {
|
||||||
const secondaryAxSkill = primaryAxSkill.secondary.find(skill => skill.id == secondaryAxModifier)
|
const secondaryAxSkill = primaryAxSkill.secondary.find(
|
||||||
|
(skill) => skill.id == secondaryAxModifier
|
||||||
|
)
|
||||||
|
|
||||||
if (secondaryAxSkill) {
|
if (secondaryAxSkill) {
|
||||||
if (value < secondaryAxSkill.minValue) {
|
if (value < secondaryAxSkill.minValue) {
|
||||||
newErrors.axValue2 = t('ax.errors.value_too_low', {
|
newErrors.axValue2 = t('ax.errors.value_too_low', {
|
||||||
name: secondaryAxSkill.name[locale],
|
name: secondaryAxSkill.name[locale],
|
||||||
minValue: secondaryAxSkill.minValue,
|
minValue: secondaryAxSkill.minValue,
|
||||||
suffix: (secondaryAxSkill.suffix) ? secondaryAxSkill.suffix : ''
|
suffix: secondaryAxSkill.suffix ? secondaryAxSkill.suffix : '',
|
||||||
})
|
})
|
||||||
} else if (value > secondaryAxSkill.maxValue) {
|
} else if (value > secondaryAxSkill.maxValue) {
|
||||||
newErrors.axValue2 = t('ax.errors.value_too_high', {
|
newErrors.axValue2 = t('ax.errors.value_too_high', {
|
||||||
name: secondaryAxSkill.name[locale],
|
name: secondaryAxSkill.name[locale],
|
||||||
maxValue: secondaryAxSkill.minValue,
|
maxValue: secondaryAxSkill.minValue,
|
||||||
suffix: (secondaryAxSkill.suffix) ? secondaryAxSkill.suffix : ''
|
suffix: secondaryAxSkill.suffix ? secondaryAxSkill.suffix : '',
|
||||||
})
|
})
|
||||||
} else if (!secondaryAxSkill.suffix && value % 1 !== 0) {
|
} else if (!secondaryAxSkill.suffix && value % 1 !== 0) {
|
||||||
newErrors.axValue2 = t('ax.errors.value_not_whole', { name: secondaryAxSkill.name[locale] })
|
newErrors.axValue2 = t('ax.errors.value_not_whole', {
|
||||||
|
name: secondaryAxSkill.name[locale],
|
||||||
|
})
|
||||||
} else if (primaryAxValue <= 0) {
|
} else if (primaryAxValue <= 0) {
|
||||||
newErrors.axValue1 = t('ax.errors.value_empty', { name: primaryAxSkill.name[locale] })
|
newErrors.axValue1 = t('ax.errors.value_empty', {
|
||||||
|
name: primaryAxSkill.name[locale],
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
newErrors.axValue2 = ''
|
newErrors.axValue2 = ''
|
||||||
}
|
}
|
||||||
|
|
@ -228,7 +264,7 @@ const AXSelect = (props: Props) => {
|
||||||
element.placeholder = rangeString
|
element.placeholder = rangeString
|
||||||
element.min = `${ax.minValue}`
|
element.min = `${ax.minValue}`
|
||||||
element.max = `${ax.maxValue}`
|
element.max = `${ax.maxValue}`
|
||||||
element.step = (ax.suffix) ? "0.5" : "1"
|
element.step = ax.suffix ? '0.5' : '1'
|
||||||
} else {
|
} else {
|
||||||
if (primaryAxValueInput.current && secondaryAxValueInput.current) {
|
if (primaryAxValueInput.current && secondaryAxValueInput.current) {
|
||||||
if (primaryAxValueInput.current == element) {
|
if (primaryAxValueInput.current == element) {
|
||||||
|
|
@ -246,16 +282,60 @@ const AXSelect = (props: Props) => {
|
||||||
<div className="AXSelect">
|
<div className="AXSelect">
|
||||||
<div className="AXSet">
|
<div className="AXSet">
|
||||||
<div className="fields">
|
<div className="fields">
|
||||||
<select key="ax1" defaultValue={ (props.currentSkills && props.currentSkills[0]) ? props.currentSkills[0].modifier : -1 } onChange={handleSelectChange} ref={primaryAxModifierSelect}>{ generateOptions(0) }</select>
|
<select
|
||||||
<input defaultValue={ (props.currentSkills && props.currentSkills[0]) ? props.currentSkills[0].strength : 0 } className="Input" type="number" onChange={handleInputChange} ref={primaryAxValueInput} disabled={primaryAxValue != 0} />
|
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>
|
</div>
|
||||||
<p className={primaryErrorClasses}>{errors.axValue1}</p>
|
<p className={primaryErrorClasses}>{errors.axValue1}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={secondarySetClasses}>
|
<div className={secondarySetClasses}>
|
||||||
<div className="fields">
|
<div className="fields">
|
||||||
<select key="ax2" defaultValue={ (props.currentSkills && props.currentSkills[1]) ? props.currentSkills[1].modifier : -1 } onChange={handleSelectChange} ref={secondaryAxModifierSelect}>{ generateOptions(1) }</select>
|
<select
|
||||||
<input defaultValue={ (props.currentSkills && props.currentSkills[1]) ? props.currentSkills[1].strength : 0 } className="Input" type="number" onChange={handleInputChange} ref={secondaryAxValueInput} disabled={secondaryAxValue != 0} />
|
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>
|
</div>
|
||||||
<p className={secondaryErrorClasses}>{errors.axValue2}</p>
|
<p className={secondaryErrorClasses}>{errors.axValue2}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,62 +1,101 @@
|
||||||
.Button {
|
.Button {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: transparent;
|
background: var(--button-bg);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: $input-corner;
|
||||||
color: $grey-50;
|
color: var(--button-text);
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
font-size: $font-button;
|
font-size: $font-button;
|
||||||
font-weight: $normal;
|
font-weight: $normal;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
padding: 8px 12px;
|
|
||||||
|
&: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;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.Contained {
|
||||||
|
background: var(--button-contained-bg);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: white;
|
background: var(--button-contained-bg-hover);
|
||||||
cursor: pointer;
|
|
||||||
color: $grey-00;
|
|
||||||
|
|
||||||
.icon svg {
|
|
||||||
fill: $grey-00;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon.stroke svg {
|
&.Save:hover .Accessory svg {
|
||||||
fill: none;
|
fill: #ff4d4d;
|
||||||
stroke: $grey-00;
|
stroke: #ff4d4d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.Active.Save {
|
||||||
|
color: #ff4d4d;
|
||||||
|
|
||||||
|
.Accessory svg {
|
||||||
|
fill: #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 {
|
&.destructive:hover {
|
||||||
background: $error;
|
background: $error;
|
||||||
color: white;
|
color: $grey-100;
|
||||||
|
|
||||||
.icon svg {
|
.Accessory svg {
|
||||||
fill: white;
|
fill: $grey-100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.save:hover {
|
&.save:hover {
|
||||||
color: #FF4D4D;
|
color: #ff4d4d;
|
||||||
|
|
||||||
.icon svg {
|
.Accessory svg {
|
||||||
fill: #FF4D4D;
|
fill: #ff4d4d;
|
||||||
stroke: #FF4D4D;
|
stroke: #ff4d4d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.save.Active {
|
&.save.Active {
|
||||||
color: #FF4D4D;
|
color: #ff4d4d;
|
||||||
|
|
||||||
.icon svg {
|
|
||||||
fill: #FF4D4D;
|
|
||||||
stroke: #FF4D4D;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: darken(#FF4D4D, 30);
|
color: darken(#ff4d4d, 30);
|
||||||
|
|
||||||
.icon svg {
|
.icon svg {
|
||||||
fill: darken(#FF4D4D, 30);
|
fill: darken(#ff4d4d, 30);
|
||||||
stroke: darken(#FF4D4D, 30);
|
stroke: darken(#ff4d4d, 30);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -69,17 +108,34 @@
|
||||||
color: $error;
|
color: $error;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: darken($error, 10)
|
color: darken($error, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.Accessory {
|
||||||
margin-top: 2px;
|
$dimension: $unit-2x;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
fill: $grey-50;
|
fill: var(--button-text);
|
||||||
height: 12px;
|
height: $dimension;
|
||||||
width: 12px;
|
width: $dimension;
|
||||||
|
|
||||||
|
&.stroke {
|
||||||
|
fill: none;
|
||||||
|
stroke: var(--button-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.Add {
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.Check {
|
||||||
|
height: 22px;
|
||||||
|
width: 22px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.check svg {
|
&.check svg {
|
||||||
|
|
@ -88,28 +144,19 @@
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.stroke svg {
|
svg &.settings svg {
|
||||||
fill: none;
|
|
||||||
stroke: $grey-50;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.settings svg {
|
|
||||||
height: 13px;
|
height: 13px;
|
||||||
width: 13px;
|
width: 13px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.Active {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.btn-blue {
|
&.btn-blue {
|
||||||
background: $blue;
|
background: $blue;
|
||||||
color: #8b8b8b;
|
color: #8b8b8b;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #4B9BE5;
|
background: #4b9be5;
|
||||||
color: #233E56;
|
color: #233e56;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,70 +190,69 @@
|
||||||
|
|
||||||
&.null {
|
&.null {
|
||||||
background: $grey-90;
|
background: $grey-90;
|
||||||
color: $grey-50;
|
color: $grey-55;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $grey-70;
|
background: $grey-70;
|
||||||
color: $grey-00;
|
color: $grey-15;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.wind {
|
&.wind {
|
||||||
background: $wind-bg-light;
|
background: $wind-bg-20;
|
||||||
color: $wind-text-dark;
|
color: $wind-text-10;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: darken($wind-bg-light, 10);
|
background: darken($wind-bg-20, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.fire {
|
&.fire {
|
||||||
background: $fire-bg-light;
|
background: $fire-bg-20;
|
||||||
color: $fire-text-dark;
|
color: $fire-text-10;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: darken($fire-bg-light, 10);
|
background: darken($fire-bg-20, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.water {
|
&.water {
|
||||||
background: $water-bg-light;
|
background: $water-bg-20;
|
||||||
color: $water-text-dark;
|
color: $water-text-10;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: darken($water-bg-light, 10);
|
background: darken($water-bg-20, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.earth {
|
&.earth {
|
||||||
background: $earth-bg-light;
|
background: $earth-bg-20;
|
||||||
color: $earth-text-dark;
|
color: $earth-text-10;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: darken($earth-bg-light, 10);
|
background: darken($earth-bg-20, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dark {
|
&.dark {
|
||||||
background: $dark-bg-light;
|
background: $dark-bg-10;
|
||||||
color: $dark-text-dark;
|
color: $dark-text-10;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: darken($dark-bg-light, 10);
|
background: darken($dark-bg-10, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
&.light {
|
&.light {
|
||||||
background: $light-bg-light;
|
background: $light-bg-20;
|
||||||
color: $light-text-dark;
|
color: $light-text-10;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: darken($light-bg-light, 10);
|
background: darken($light-bg-20, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
.Text {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
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
|
||||||
|
extends React.DetailedHTMLProps<
|
||||||
|
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
HTMLButtonElement
|
||||||
|
> {
|
||||||
|
accessoryIcon?: React.ReactNode
|
||||||
active?: boolean
|
active?: boolean
|
||||||
disabled?: boolean
|
blended?: boolean
|
||||||
classes?: string[],
|
contained?: boolean
|
||||||
icon?: string
|
size?: 'small' | 'medium' | 'large'
|
||||||
type?: ButtonType
|
text?: string
|
||||||
children?: React.ReactNode
|
|
||||||
onClick?: (event: React.MouseEvent<HTMLElement>) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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({
|
const Button = React.forwardRef<HTMLButtonElement, Props>(function button(
|
||||||
|
{ accessoryIcon, active, blended, contained, size, text, ...props },
|
||||||
|
forwardedRef
|
||||||
|
) {
|
||||||
|
const classes = classNames(
|
||||||
|
{
|
||||||
Button: true,
|
Button: true,
|
||||||
'Active': active,
|
Active: active,
|
||||||
'btn-pressed': pressed,
|
Blended: blended,
|
||||||
'btn-disabled': disabled,
|
Contained: contained,
|
||||||
'save': props.icon === 'save',
|
// 'btn-pressed': pressed,
|
||||||
'destructive': props.type == ButtonType.Destructive
|
// 'btn-disabled': disabled,
|
||||||
}, props.classes)
|
// save: props.icon === 'save',
|
||||||
|
// destructive: props.type == ButtonType.Destructive,
|
||||||
useEffect(() => {
|
},
|
||||||
if (props.active) setActive(props.active)
|
size,
|
||||||
if (props.disabled) setDisabled(props.disabled)
|
props.className
|
||||||
if (props.type) setButtonType(props.type)
|
|
||||||
}, [props.active, props.disabled, props.type])
|
|
||||||
|
|
||||||
const addIcon = (
|
|
||||||
<span className='icon'>
|
|
||||||
<AddIcon />
|
|
||||||
</span>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const menuIcon = (
|
const hasAccessory = () => {
|
||||||
<span className='icon'>
|
if (accessoryIcon) return <span className="Accessory">{accessoryIcon}</span>
|
||||||
<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
|
const hasText = () => {
|
||||||
|
if (text) return <span className="Text">{text}</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMouseDown() {
|
|
||||||
setPressed(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMouseUp() {
|
|
||||||
setPressed(false)
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button {...props} className={classes} ref={forwardedRef}>
|
||||||
className={classes}
|
{hasAccessory()}
|
||||||
disabled={disabled}
|
{hasText()}
|
||||||
onMouseDown={handleMouseDown}
|
|
||||||
onMouseUp={handleMouseUp}
|
|
||||||
onClick={props.onClick}>
|
|
||||||
{ getIcon() }
|
|
||||||
|
|
||||||
{ (props.type != ButtonType.IconOnly) ?
|
|
||||||
<span className='text'>
|
|
||||||
{ props.children }
|
|
||||||
</span> : ''
|
|
||||||
}
|
|
||||||
</button>
|
</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,19 +1,23 @@
|
||||||
.Limited {
|
.Limited {
|
||||||
background: white;
|
$offset: 2px;
|
||||||
border-radius: 6px;
|
|
||||||
border: 2px solid transparent;
|
background: var(--input-bg);
|
||||||
|
border-radius: $input-corner;
|
||||||
|
border: $offset solid transparent;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: $unit;
|
gap: $unit;
|
||||||
padding-right: $unit * 2;
|
padding-top: 2px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
padding-right: calc($unit-2x - $offset);
|
||||||
|
|
||||||
&:focus-within {
|
&:focus-within {
|
||||||
border: 2px solid $blue;
|
border: $offset solid $blue;
|
||||||
box-shadow: 0 2px rgba(255, 255, 255, 1);
|
// box-shadow: 0 2px rgba(255, 255, 255, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Counter {
|
.Counter {
|
||||||
color: $grey-50;
|
color: $grey-55;
|
||||||
font-weight: $bold;
|
font-weight: $bold;
|
||||||
line-height: 42px;
|
line-height: 42px;
|
||||||
}
|
}
|
||||||
|
|
@ -21,6 +25,7 @@
|
||||||
.Input {
|
.Input {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
padding-left: calc($unit-2x - $offset);
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,18 @@ interface Props {
|
||||||
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>) {
|
||||||
|
|
@ -43,12 +48,10 @@ const CharLimitedFieldset = React.forwardRef<HTMLInputElement, Props>(function u
|
||||||
/>
|
/>
|
||||||
<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 &&
|
|
||||||
<p className='InputError'>{props.error}</p>
|
|
||||||
}
|
|
||||||
</fieldset>
|
</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,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
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ interface Props {
|
||||||
|
|
||||||
interface KeyNames {
|
interface KeyNames {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
en: string,
|
en: string
|
||||||
jp: string
|
jp: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -24,28 +24,41 @@ interface KeyNames {
|
||||||
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`
|
||||||
}
|
}
|
||||||
|
|
@ -55,22 +68,39 @@ const CharacterHovercard = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HoverCard.Root>
|
<HoverCard.Root>
|
||||||
<HoverCard.Trigger>
|
<HoverCard.Trigger>{props.children}</HoverCard.Trigger>
|
||||||
{ props.children }
|
|
||||||
</HoverCard.Trigger>
|
|
||||||
<HoverCard.Content className="Weapon Hovercard">
|
<HoverCard.Content className="Weapon Hovercard">
|
||||||
<div className="top">
|
<div className="top">
|
||||||
<div className="title">
|
<div className="title">
|
||||||
<h4>{props.gridCharacter.object.name[locale]}</h4>
|
<h4>{props.gridCharacter.object.name[locale]}</h4>
|
||||||
<img alt={props.gridCharacter.object.name[locale]} src={characterImage()} />
|
<img
|
||||||
|
alt={props.gridCharacter.object.name[locale]}
|
||||||
|
src={characterImage()}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="subInfo">
|
<div className="subInfo">
|
||||||
<div className="icons">
|
<div className="icons">
|
||||||
<WeaponLabelIcon labelType={Element[props.gridCharacter.object.element]} />
|
<WeaponLabelIcon
|
||||||
<WeaponLabelIcon labelType={ Proficiency[props.gridCharacter.object.proficiency.proficiency1] } />
|
labelType={Element[props.gridCharacter.object.element]}
|
||||||
{ (props.gridCharacter.object.proficiency.proficiency2) ?
|
/>
|
||||||
<WeaponLabelIcon labelType={ Proficiency[props.gridCharacter.object.proficiency.proficiency2] } />
|
<WeaponLabelIcon
|
||||||
: ''}
|
labelType={
|
||||||
|
Proficiency[
|
||||||
|
props.gridCharacter.object.proficiency.proficiency1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{props.gridCharacter.object.proficiency.proficiency2 ? (
|
||||||
|
<WeaponLabelIcon
|
||||||
|
labelType={
|
||||||
|
Proficiency[
|
||||||
|
props.gridCharacter.object.proficiency.proficiency2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<UncapIndicator
|
<UncapIndicator
|
||||||
type="character"
|
type="character"
|
||||||
|
|
@ -81,7 +111,9 @@ const CharacterHovercard = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">{t('buttons.wiki')}</a>
|
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">
|
||||||
|
{t('buttons.wiki')}
|
||||||
|
</a>
|
||||||
<HoverCard.Arrow />
|
<HoverCard.Arrow />
|
||||||
</HoverCard.Content>
|
</HoverCard.Content>
|
||||||
</HoverCard.Root>
|
</HoverCard.Root>
|
||||||
|
|
@ -89,4 +121,3 @@ const CharacterHovercard = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CharacterHovercard
|
export default CharacterHovercard
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,16 @@
|
||||||
padding: $unit * 1.5;
|
padding: $unit * 1.5;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $grey-90;
|
background: var(--button-contained-bg);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
.Info h5 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
background: $grey-80;
|
background: var(--card-bg);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 72px;
|
height: 72px;
|
||||||
|
|
@ -21,10 +25,10 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
gap: calc($unit / 2);
|
gap: $unit-half;
|
||||||
|
|
||||||
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;
|
||||||
|
|
@ -37,11 +41,11 @@
|
||||||
|
|
||||||
.stars {
|
.stars {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: #FFA15E;
|
color: #ffa15e;
|
||||||
font-size: $font-xlarge;
|
font-size: $font-xlarge;
|
||||||
|
|
||||||
& > span {
|
& > span {
|
||||||
color: #65DAFF;
|
color: #65daff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ 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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,11 @@ 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 {
|
||||||
|
|
@ -25,9 +29,14 @@ const CharacterSearchFilterBar = (props: Props) => {
|
||||||
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) {
|
||||||
|
|
@ -90,16 +99,24 @@ const CharacterSearchFilterBar = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendFilters() {
|
function sendFilters() {
|
||||||
const checkedRarityFilters = Object.values(rarityState).filter(x => x.checked).map((x, i) => x.id)
|
const checkedRarityFilters = Object.values(rarityState)
|
||||||
const checkedElementFilters = Object.values(elementState).filter(x => x.checked).map((x, i) => x.id)
|
.filter((x) => x.checked)
|
||||||
const checkedProficiency1Filters = Object.values(proficiency1State).filter(x => x.checked).map((x, i) => x.id)
|
.map((x, i) => x.id)
|
||||||
const checkedProficiency2Filters = Object.values(proficiency2State).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 = {
|
const filters = {
|
||||||
rarity: checkedRarityFilters,
|
rarity: checkedRarityFilters,
|
||||||
element: checkedElementFilters,
|
element: checkedElementFilters,
|
||||||
proficiency1: checkedProficiency1Filters,
|
proficiency1: checkedProficiency1Filters,
|
||||||
proficiency2: checkedProficiency2Filters
|
proficiency2: checkedProficiency2Filters,
|
||||||
}
|
}
|
||||||
|
|
||||||
props.sendFilters(filters)
|
props.sendFilters(filters)
|
||||||
|
|
@ -110,24 +127,35 @@ const CharacterSearchFilterBar = (props: Props) => {
|
||||||
}, [rarityState, elementState, proficiency1State, proficiency2State])
|
}, [rarityState, elementState, proficiency1State, proficiency2State])
|
||||||
|
|
||||||
function renderProficiencyFilter(proficiency: 1 | 2) {
|
function renderProficiencyFilter(proficiency: 1 | 2) {
|
||||||
const onCheckedChange = (proficiency == 1) ? handleProficiency1Change : handleProficiency2Change
|
const onCheckedChange =
|
||||||
const numSelected = (proficiency == 1)
|
proficiency == 1 ? handleProficiency1Change : handleProficiency2Change
|
||||||
? Object.values(proficiency1State).map(x => x.checked).filter(Boolean).length
|
const numSelected =
|
||||||
: Object.values(proficiency2State).map(x => x.checked).filter(Boolean).length
|
proficiency == 1
|
||||||
const open = (proficiency == 1) ? proficiency1Menu : proficiency2Menu
|
? Object.values(proficiency1State)
|
||||||
const onOpenChange = (proficiency == 1) ? proficiency1MenuOpened : proficiency2MenuOpened
|
.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 (
|
return (
|
||||||
<SearchFilter
|
<SearchFilter
|
||||||
label={`${t('filters.labels.proficiency')} ${proficiency}`}
|
label={`${t('filters.labels.proficiency')} ${proficiency}`}
|
||||||
numSelected={numSelected}
|
numSelected={numSelected}
|
||||||
open={open}
|
open={open}
|
||||||
onOpenChange={onOpenChange}>
|
onOpenChange={onOpenChange}
|
||||||
<DropdownMenu.Label className="Label">{`${t('filters.labels.proficiency')} ${proficiency}`}</DropdownMenu.Label>
|
>
|
||||||
|
<DropdownMenu.Label className="Label">{`${t(
|
||||||
|
'filters.labels.proficiency'
|
||||||
|
)} ${proficiency}`}</DropdownMenu.Label>
|
||||||
<section>
|
<section>
|
||||||
<DropdownMenu.Group className="Group">
|
<DropdownMenu.Group className="Group">
|
||||||
{Array.from(Array(proficiencies.length / 2)).map((x, i) => {
|
{Array.from(Array(proficiencies.length / 2)).map((x, i) => {
|
||||||
const checked = (proficiency == 1)
|
const checked =
|
||||||
|
proficiency == 1
|
||||||
? proficiency1State[proficiencies[i]].checked
|
? proficiency1State[proficiencies[i]].checked
|
||||||
: proficiency2State[proficiencies[i]].checked
|
: proficiency2State[proficiencies[i]].checked
|
||||||
|
|
||||||
|
|
@ -136,28 +164,39 @@ const CharacterSearchFilterBar = (props: Props) => {
|
||||||
key={proficiencies[i]}
|
key={proficiencies[i]}
|
||||||
onCheckedChange={onCheckedChange}
|
onCheckedChange={onCheckedChange}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
valueKey={proficiencies[i]}>
|
valueKey={proficiencies[i]}
|
||||||
|
>
|
||||||
{t(`proficiencies.${proficiencies[i]}`)}
|
{t(`proficiencies.${proficiencies[i]}`)}
|
||||||
</SearchFilterCheckboxItem>
|
</SearchFilterCheckboxItem>
|
||||||
)}
|
)
|
||||||
) }
|
})}
|
||||||
</DropdownMenu.Group>
|
</DropdownMenu.Group>
|
||||||
<DropdownMenu.Group className="Group">
|
<DropdownMenu.Group className="Group">
|
||||||
{Array.from(Array(proficiencies.length / 2)).map((x, i) => {
|
{Array.from(Array(proficiencies.length / 2)).map((x, i) => {
|
||||||
const checked = (proficiency == 1)
|
const checked =
|
||||||
? proficiency1State[proficiencies[i + (proficiencies.length / 2)]].checked
|
proficiency == 1
|
||||||
: proficiency2State[proficiencies[i + (proficiencies.length / 2)]].checked
|
? proficiency1State[
|
||||||
|
proficiencies[i + proficiencies.length / 2]
|
||||||
|
].checked
|
||||||
|
: proficiency2State[
|
||||||
|
proficiencies[i + proficiencies.length / 2]
|
||||||
|
].checked
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SearchFilterCheckboxItem
|
<SearchFilterCheckboxItem
|
||||||
key={proficiencies[i + (proficiencies.length / 2)]}
|
key={proficiencies[i + proficiencies.length / 2]}
|
||||||
onCheckedChange={onCheckedChange}
|
onCheckedChange={onCheckedChange}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
valueKey={proficiencies[i + (proficiencies.length / 2)]}>
|
valueKey={proficiencies[i + proficiencies.length / 2]}
|
||||||
{t(`proficiencies.${proficiencies[i + (proficiencies.length / 2)]}`)}
|
>
|
||||||
|
{t(
|
||||||
|
`proficiencies.${
|
||||||
|
proficiencies[i + proficiencies.length / 2]
|
||||||
|
}`
|
||||||
|
)}
|
||||||
</SearchFilterCheckboxItem>
|
</SearchFilterCheckboxItem>
|
||||||
)}
|
)
|
||||||
) }
|
})}
|
||||||
</DropdownMenu.Group>
|
</DropdownMenu.Group>
|
||||||
</section>
|
</section>
|
||||||
</SearchFilter>
|
</SearchFilter>
|
||||||
|
|
@ -166,34 +205,58 @@ const CharacterSearchFilterBar = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="SearchFilterBar">
|
<div className="SearchFilterBar">
|
||||||
<SearchFilter label={t('filters.labels.rarity')} numSelected={Object.values(rarityState).map(x => x.checked).filter(Boolean).length} open={rarityMenu} onOpenChange={rarityMenuOpened}>
|
<SearchFilter
|
||||||
<DropdownMenu.Label className="Label">{t('filters.labels.rarity')}</DropdownMenu.Label>
|
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) => {
|
{Array.from(Array(rarities.length)).map((x, i) => {
|
||||||
return (
|
return (
|
||||||
<SearchFilterCheckboxItem
|
<SearchFilterCheckboxItem
|
||||||
key={rarities[i]}
|
key={rarities[i]}
|
||||||
onCheckedChange={handleRarityChange}
|
onCheckedChange={handleRarityChange}
|
||||||
checked={rarityState[rarities[i]].checked}
|
checked={rarityState[rarities[i]].checked}
|
||||||
valueKey={rarities[i]}>
|
valueKey={rarities[i]}
|
||||||
|
>
|
||||||
{t(`rarities.${rarities[i]}`)}
|
{t(`rarities.${rarities[i]}`)}
|
||||||
</SearchFilterCheckboxItem>
|
</SearchFilterCheckboxItem>
|
||||||
)}
|
)
|
||||||
) }
|
})}
|
||||||
</SearchFilter>
|
</SearchFilter>
|
||||||
|
|
||||||
<SearchFilter label={t('filters.labels.element')} numSelected={Object.values(elementState).map(x => x.checked).filter(Boolean).length} open={elementMenu} onOpenChange={elementMenuOpened}>
|
<SearchFilter
|
||||||
<DropdownMenu.Label className="Label">{t('filters.labels.element')}</DropdownMenu.Label>
|
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) => {
|
{Array.from(Array(elements.length)).map((x, i) => {
|
||||||
return (
|
return (
|
||||||
<SearchFilterCheckboxItem
|
<SearchFilterCheckboxItem
|
||||||
key={elements[i]}
|
key={elements[i]}
|
||||||
onCheckedChange={handleElementChange}
|
onCheckedChange={handleElementChange}
|
||||||
checked={elementState[elements[i]].checked}
|
checked={elementState[elements[i]].checked}
|
||||||
valueKey={elements[i]}>
|
valueKey={elements[i]}
|
||||||
|
>
|
||||||
{t(`elements.${elements[i]}`)}
|
{t(`elements.${elements[i]}`)}
|
||||||
</SearchFilterCheckboxItem>
|
</SearchFilterCheckboxItem>
|
||||||
)}
|
)
|
||||||
) }
|
})}
|
||||||
</SearchFilter>
|
</SearchFilter>
|
||||||
|
|
||||||
{renderProficiencyFilter(1)}
|
{renderProficiencyFilter(1)}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
color: #333;
|
color: var(--text-primary);
|
||||||
font-size: $font-regular;
|
font-size: $font-regular;
|
||||||
font-weight: $normal;
|
font-weight: $normal;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
|
|
@ -43,10 +43,9 @@
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.CharacterImage {
|
.CharacterImage {
|
||||||
aspect-ratio: 131 / 273;
|
aspect-ratio: 131 / 273;
|
||||||
background: white;
|
background: var(--card-bg);
|
||||||
border: 1px solid rgba(0, 0, 0, 0);
|
border: 1px solid rgba(0, 0, 0, 0);
|
||||||
border-radius: $unit;
|
border-radius: $unit;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -62,7 +61,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover .icon svg {
|
&:hover .icon svg {
|
||||||
color: $grey-40;
|
fill: var(--icon-secondary-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
|
@ -72,7 +71,7 @@
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
fill: $grey-70;
|
fill: var(--icon-secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -9,10 +9,10 @@
|
||||||
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;
|
||||||
|
|
@ -26,38 +26,39 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover, &[data-state="on"] {
|
&:hover,
|
||||||
|
&[data-state='on'] {
|
||||||
background: $grey-80;
|
background: $grey-80;
|
||||||
color: $grey-00;
|
color: $grey-15;
|
||||||
|
|
||||||
&.fire {
|
&.fire {
|
||||||
background: $fire-bg-light;
|
background: $fire-bg-20;
|
||||||
color: $fire-text-dark;
|
color: $fire-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.water {
|
&.water {
|
||||||
background: $water-bg-light;
|
background: $water-bg-20;
|
||||||
color: $water-text-dark;
|
color: $water-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.earth {
|
&.earth {
|
||||||
background: $earth-bg-light;
|
background: $earth-bg-20;
|
||||||
color: $earth-text-dark;
|
color: $earth-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.wind {
|
&.wind {
|
||||||
background: $wind-bg-light;
|
background: $wind-bg-20;
|
||||||
color: $wind-text-dark;
|
color: $wind-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dark {
|
&.dark {
|
||||||
background: $dark-bg-light;
|
background: $dark-bg-10;
|
||||||
color: $dark-text-dark;
|
color: $dark-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.light {
|
&.light {
|
||||||
background: $light-bg-light;
|
background: $light-bg-20;
|
||||||
color: $light-text-dark;
|
color: $light-text-10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,29 +14,64 @@ interface Props {
|
||||||
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"
|
||||||
|
type="single"
|
||||||
|
defaultValue={`${props.currentElement}`}
|
||||||
|
aria-label="Element"
|
||||||
|
onValueChange={props.sendValue}
|
||||||
|
>
|
||||||
|
<ToggleGroup.Item
|
||||||
|
className={`ToggleItem ${locale}`}
|
||||||
|
value="0"
|
||||||
|
aria-label="null"
|
||||||
|
>
|
||||||
{t('elements.null')}
|
{t('elements.null')}
|
||||||
</ToggleGroup.Item>
|
</ToggleGroup.Item>
|
||||||
<ToggleGroup.Item className={`ToggleItem wind ${locale}`} value="1" aria-label="wind">
|
<ToggleGroup.Item
|
||||||
|
className={`ToggleItem wind ${locale}`}
|
||||||
|
value="1"
|
||||||
|
aria-label="wind"
|
||||||
|
>
|
||||||
{t('elements.wind')}
|
{t('elements.wind')}
|
||||||
</ToggleGroup.Item>
|
</ToggleGroup.Item>
|
||||||
<ToggleGroup.Item className={`ToggleItem fire ${locale}`} value="2" aria-label="fire">
|
<ToggleGroup.Item
|
||||||
|
className={`ToggleItem fire ${locale}`}
|
||||||
|
value="2"
|
||||||
|
aria-label="fire"
|
||||||
|
>
|
||||||
{t('elements.fire')}
|
{t('elements.fire')}
|
||||||
</ToggleGroup.Item>
|
</ToggleGroup.Item>
|
||||||
<ToggleGroup.Item className={`ToggleItem water ${locale}`} value="3" aria-label="water">
|
<ToggleGroup.Item
|
||||||
|
className={`ToggleItem water ${locale}`}
|
||||||
|
value="3"
|
||||||
|
aria-label="water"
|
||||||
|
>
|
||||||
{t('elements.water')}
|
{t('elements.water')}
|
||||||
</ToggleGroup.Item>
|
</ToggleGroup.Item>
|
||||||
<ToggleGroup.Item className={`ToggleItem earth ${locale}`} value="4" aria-label="earth">
|
<ToggleGroup.Item
|
||||||
|
className={`ToggleItem earth ${locale}`}
|
||||||
|
value="4"
|
||||||
|
aria-label="earth"
|
||||||
|
>
|
||||||
{t('elements.earth')}
|
{t('elements.earth')}
|
||||||
</ToggleGroup.Item>
|
</ToggleGroup.Item>
|
||||||
<ToggleGroup.Item className={`ToggleItem dark ${locale}`} value="5" aria-label="dark">
|
<ToggleGroup.Item
|
||||||
|
className={`ToggleItem dark ${locale}`}
|
||||||
|
value="5"
|
||||||
|
aria-label="dark"
|
||||||
|
>
|
||||||
{t('elements.dark')}
|
{t('elements.dark')}
|
||||||
</ToggleGroup.Item>
|
</ToggleGroup.Item>
|
||||||
<ToggleGroup.Item className={`ToggleItem light ${locale}`} value="6" aria-label="light">
|
<ToggleGroup.Item
|
||||||
|
className={`ToggleItem light ${locale}`}
|
||||||
|
value="6"
|
||||||
|
aria-label="light"
|
||||||
|
>
|
||||||
{t('elements.light')}
|
{t('elements.light')}
|
||||||
</ToggleGroup.Item>
|
</ToggleGroup.Item>
|
||||||
</ToggleGroup.Root>
|
</ToggleGroup.Root>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#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;
|
display: flex;
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
& > span {
|
& > span {
|
||||||
color: #825B39;
|
color: var(--subaura-orange-text);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
@ -46,10 +46,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.SummonUnit .SummonImage {
|
.SummonUnit .SummonImage {
|
||||||
background: #facea7;
|
background: var(--subaura-orange-card-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.SummonUnit .SummonImage .icon svg {
|
.SummonUnit .SummonImage .icon svg {
|
||||||
fill: #a8703f;
|
fill: var(--subaura-orange-secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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,5 +1,5 @@
|
||||||
#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;
|
display: flex;
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
& > span {
|
& > span {
|
||||||
color: #4F3C79;
|
color: var(--extra-purple-text);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
@ -38,10 +38,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.WeaponUnit .WeaponImage {
|
.WeaponUnit .WeaponImage {
|
||||||
background: #D5D3F6;
|
background: var(--extra-purple-card-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.WeaponUnit .WeaponImage .icon svg {
|
.WeaponUnit .WeaponImage .icon svg {
|
||||||
fill: #8F8AC6;
|
fill: var(--extra-purple-secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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,6 +1,6 @@
|
||||||
.FilterBar {
|
.FilterBar {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: white;
|
background: var(--bar-bg);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
@ -18,25 +18,38 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
color: $grey-20;
|
color: var(--text-primary);
|
||||||
font-size: $font-regular;
|
font-size: $font-regular;
|
||||||
font-weight: $normal;
|
font-weight: $normal;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select,
|
||||||
background: url('/icons/Arrow.svg'), $grey-90;
|
.SelectTrigger {
|
||||||
background-repeat: no-repeat;
|
// background: url("/icons/Arrow.svg"), $grey-90;
|
||||||
background-position-y: center;
|
// background-repeat: no-repeat;
|
||||||
background-position-x: 95%;
|
// background-position-y: center;
|
||||||
background-size: $unit * 1.5;
|
// background-position-x: 95%;
|
||||||
color: $grey-50;
|
// background-size: $unit * 1.5;
|
||||||
|
background-color: var(--select-contained-bg);
|
||||||
|
color: $grey-55;
|
||||||
font-size: $font-small;
|
font-size: $font-small;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--select-contained-bg-hover);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.SelectTrigger {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: $font-small;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.UserInfo {
|
.UserInfo {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -52,11 +65,11 @@
|
||||||
width: $diameter;
|
width: $diameter;
|
||||||
|
|
||||||
&.gran {
|
&.gran {
|
||||||
background-color: #CEE7FE;
|
background-color: #cee7fe;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.djeeta {
|
&.djeeta {
|
||||||
background-color: #FFE1FE;
|
background-color: #ffe1fe;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
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
|
||||||
|
|
@ -12,13 +14,24 @@ interface Props {
|
||||||
element?: number
|
element?: number
|
||||||
raidSlug?: string
|
raidSlug?: string
|
||||||
recency?: number
|
recency?: number
|
||||||
onFilter: ({element, raidSlug, recency} : { element?: number, raidSlug?: string, recency?: number}) => void
|
onFilter: ({
|
||||||
|
element,
|
||||||
|
raidSlug,
|
||||||
|
recency,
|
||||||
|
}: {
|
||||||
|
element?: number
|
||||||
|
raidSlug?: string
|
||||||
|
recency?: number
|
||||||
|
}) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilterBar = (props: Props) => {
|
const FilterBar = (props: Props) => {
|
||||||
// Set up translation
|
// Set up translation
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
|
const [recencyOpen, setRecencyOpen] = useState(false)
|
||||||
|
const [elementOpen, setElementOpen] = useState(false)
|
||||||
|
|
||||||
// Set up refs for filter dropdowns
|
// Set up refs for filter dropdowns
|
||||||
const elementSelect = React.createRef<HTMLSelectElement>()
|
const elementSelect = React.createRef<HTMLSelectElement>()
|
||||||
const raidSelect = React.createRef<HTMLSelectElement>()
|
const raidSelect = React.createRef<HTMLSelectElement>()
|
||||||
|
|
@ -26,17 +39,25 @@ const FilterBar = (props: Props) => {
|
||||||
|
|
||||||
// Set up classes object for showing shadow on scroll
|
// Set up classes object for showing shadow on scroll
|
||||||
const classes = classNames({
|
const classes = classNames({
|
||||||
'FilterBar': true,
|
FilterBar: true,
|
||||||
'shadow': props.scrolled
|
shadow: props.scrolled,
|
||||||
})
|
})
|
||||||
|
|
||||||
function elementSelectChanged() {
|
function openElementSelect() {
|
||||||
const elementValue = (elementSelect.current) ? parseInt(elementSelect.current.value) : -1
|
setElementOpen(!elementOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
function openRecencySelect() {
|
||||||
|
setRecencyOpen(!recencyOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
function elementSelectChanged(value: string) {
|
||||||
|
const elementValue = parseInt(value)
|
||||||
props.onFilter({ element: elementValue })
|
props.onFilter({ element: elementValue })
|
||||||
}
|
}
|
||||||
|
|
||||||
function recencySelectChanged() {
|
function recencySelectChanged(value: string) {
|
||||||
const recencyValue = (recencySelect.current) ? parseInt(recencySelect.current.value) : -1
|
const recencyValue = parseInt(value)
|
||||||
props.onFilter({ recency: recencyValue })
|
props.onFilter({ recency: recencyValue })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,31 +68,76 @@ const FilterBar = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
{props.children}
|
{props.children}
|
||||||
<select onChange={elementSelectChanged} ref={elementSelect} value={props.element}>
|
<Select
|
||||||
<option data-element="all" key={-1} value={-1}>{t('elements.full.all')}</option>
|
defaultValue={-1}
|
||||||
<option data-element="null" key={0} value={0}>{t('elements.full.null')}</option>
|
trigger={'All elements'}
|
||||||
<option data-element="wind" key={1} value={1}>{t('elements.full.wind')}</option>
|
open={elementOpen}
|
||||||
<option data-element="fire" key={2} value={2}>{t('elements.full.fire')}</option>
|
onChange={elementSelectChanged}
|
||||||
<option data-element="water" key={3} value={3}>{t('elements.full.water')}</option>
|
onClick={openElementSelect}
|
||||||
<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>
|
<SelectItem data-element="all" key={-1} value={-1}>
|
||||||
<option data-element="light" key={6} value={6}>{t('elements.full.light')}</option>
|
{t('elements.full.all')}
|
||||||
</select>
|
</SelectItem>
|
||||||
|
<SelectItem data-element="null" key={0} value={0}>
|
||||||
|
{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
|
<RaidDropdown
|
||||||
currentRaid={props.raidSlug}
|
currentRaid={props.raidSlug}
|
||||||
|
defaultRaid="all"
|
||||||
showAllRaidsOption={true}
|
showAllRaidsOption={true}
|
||||||
onChange={raidSelectChanged}
|
onChange={raidSelectChanged}
|
||||||
ref={raidSelect}
|
ref={raidSelect}
|
||||||
/>
|
/>
|
||||||
<select onChange={recencySelectChanged} ref={recencySelect}>
|
|
||||||
<option key={-1} value={-1}>{t('recency.all_time')}</option>
|
<Select
|
||||||
<option key={86400} value={86400}>{t('recency.last_day')}</option>
|
defaultValue={-1}
|
||||||
<option key={604800} value={604800}>{t('recency.last_week')}</option>
|
trigger={'All time'}
|
||||||
<option key={2629746} value={2629746}>{t('recency.last_month')}</option>
|
open={recencyOpen}
|
||||||
<option key={7889238} value={7889238}>{t('recency.last_3_months')}</option>
|
onChange={recencySelectChanged}
|
||||||
<option key={15778476} value={15778476}>{t('recency.last_6_months')}</option>
|
onClick={openRecencySelect}
|
||||||
<option key={31556952} value={31556952}>{t('recency.last_year')}</option>
|
>
|
||||||
</select>
|
<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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,15 @@
|
||||||
padding: $unit * 2;
|
padding: $unit * 2;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: white;
|
background: var(--grid-rep-hover);
|
||||||
|
|
||||||
h2, .Grid {
|
h2,
|
||||||
|
.Grid {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Grid .weapon {
|
.Grid .weapon {
|
||||||
box-shadow: inset 0 0 0 1px $grey-80;
|
box-shadow: inset 0 0 0 1px var(--grid-border-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,7 +24,7 @@
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
.weapon {
|
.weapon {
|
||||||
background: white;
|
background: var(--card-bg);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,8 +50,8 @@
|
||||||
width: 70px;
|
width: 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid_mainhand img[src*="jpg"],
|
.grid_mainhand img[src*='jpg'],
|
||||||
.grid_weapon img[src*="jpg"] {
|
.grid_weapon img[src*='jpg'] {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
@ -63,7 +64,7 @@
|
||||||
gap: calc($unit / 2);
|
gap: calc($unit / 2);
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
color: $grey-00;
|
color: var(--text-primary);
|
||||||
font-size: $font-regular;
|
font-size: $font-regular;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-bottom: 1px;
|
padding-bottom: 1px;
|
||||||
|
|
@ -72,7 +73,7 @@
|
||||||
max-width: 258px; // Can we not do this?
|
max-width: 258px; // Can we not do this?
|
||||||
|
|
||||||
&.empty {
|
&.empty {
|
||||||
color: $grey-50;
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,11 +94,6 @@
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover,
|
|
||||||
button.Active {
|
|
||||||
background: $grey-90;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom {
|
.bottom {
|
||||||
|
|
@ -105,12 +101,15 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.raid, .user, time {
|
.raid,
|
||||||
color: $grey-50;
|
.user,
|
||||||
|
time {
|
||||||
|
color: $grey-55;
|
||||||
font-size: $font-small;
|
font-size: $font-small;
|
||||||
}
|
}
|
||||||
|
|
||||||
.raid, .user {
|
.raid,
|
||||||
|
.user {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,8 +122,8 @@
|
||||||
gap: calc($unit / 2);
|
gap: calc($unit / 2);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
img,
|
||||||
img, .no-user {
|
.no-user {
|
||||||
$diameter: 18px;
|
$diameter: 18px;
|
||||||
|
|
||||||
border-radius: calc($diameter / 2);
|
border-radius: calc($diameter / 2);
|
||||||
|
|
@ -133,11 +132,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
img.gran {
|
img.gran {
|
||||||
background-color: #CEE7FE;
|
background-color: #cee7fe;
|
||||||
}
|
}
|
||||||
|
|
||||||
img.djeeta {
|
img.djeeta {
|
||||||
background-color: #FFE1FE;
|
background-color: #ffe1fe;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-user {
|
.no-user {
|
||||||
|
|
|
||||||
|
|
@ -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,6 +1,6 @@
|
||||||
.Header {
|
.Header {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 34px;
|
margin-bottom: $unit-2x;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&.bottom {
|
&.bottom {
|
||||||
|
|
@ -19,10 +19,10 @@
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
padding-right: 50px;
|
padding-right: 50px;
|
||||||
padding-bottom: 16px;
|
|
||||||
|
|
||||||
.Button {
|
.Button {
|
||||||
background: white;
|
background: var(--button-bg-hover);
|
||||||
|
color: var(--button-text-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Menu {
|
.Menu {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,25 @@
|
||||||
.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?
|
||||||
|
// Also, add space that doesn't make the menu disappear if you move your mouse slowly
|
||||||
z-index: 10;
|
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 {
|
a {
|
||||||
color: $grey-00;
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,7 +55,7 @@
|
||||||
.Thumb {
|
.Thumb {
|
||||||
$diameter: 18px;
|
$diameter: 18px;
|
||||||
|
|
||||||
background: white;
|
background: $grey-100;
|
||||||
border-radius: calc($diameter / 2);
|
border-radius: calc($diameter / 2);
|
||||||
display: block;
|
display: block;
|
||||||
height: $diameter;
|
height: $diameter;
|
||||||
|
|
@ -67,14 +68,15 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-state="checked"] {
|
&[data-state='checked'] {
|
||||||
background: white;
|
background: $grey-100;
|
||||||
transform: translateX(17px);
|
transform: translateX(17px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.left, .right {
|
.left,
|
||||||
color: white;
|
.right {
|
||||||
|
color: $grey-100;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
font-weight: $bold;
|
font-weight: $bold;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -94,10 +96,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: $grey-40;
|
color: $grey-50;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > a, & > span {
|
& > a,
|
||||||
|
& > span {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 12px 12px;
|
padding: 12px 12px;
|
||||||
}
|
}
|
||||||
|
|
@ -110,8 +113,8 @@
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
i.tag {
|
i.tag {
|
||||||
background: $grey-60;
|
background: var(--tag-bg);
|
||||||
color: white;
|
color: var(--tag-text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,7 +132,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,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,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,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,23 +1,21 @@
|
||||||
.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: block;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit;
|
||||||
height: auto;
|
height: auto;
|
||||||
margin-bottom: 40vh;
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
@ -36,6 +34,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: $unit;
|
gap: $unit;
|
||||||
|
margin-bottom: $unit-12x;
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
@ -51,8 +50,7 @@
|
||||||
|
|
||||||
&.ReadOnly {
|
&.ReadOnly {
|
||||||
top: $unit * -1;
|
top: $unit * -1;
|
||||||
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: block;
|
display: block;
|
||||||
|
|
@ -71,7 +69,6 @@
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: $font-xlarge;
|
font-size: $font-xlarge;
|
||||||
font-weight: $normal;
|
font-weight: $normal;
|
||||||
|
|
@ -88,6 +85,14 @@
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
|
||||||
|
&.empty {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,7 +113,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
& > *:not(:last-child):after {
|
& > *:not(:last-child):after {
|
||||||
content: " · ";
|
content: ' · ';
|
||||||
margin: 0 calc($unit / 2);
|
margin: 0 calc($unit / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -119,7 +124,8 @@
|
||||||
gap: calc($unit / 2);
|
gap: calc($unit / 2);
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
|
|
||||||
img, .no-user {
|
img,
|
||||||
|
.no-user {
|
||||||
$diameter: 24px;
|
$diameter: 24px;
|
||||||
|
|
||||||
border-radius: calc($diameter / 2);
|
border-radius: calc($diameter / 2);
|
||||||
|
|
@ -128,11 +134,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
img.gran {
|
img.gran {
|
||||||
background-color: #CEE7FE;
|
background-color: #cee7fe;
|
||||||
}
|
}
|
||||||
|
|
||||||
img.djeeta {
|
img.djeeta {
|
||||||
background-color: #FFE1FE;
|
background-color: #ffe1fe;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-user {
|
.no-user {
|
||||||
|
|
@ -145,7 +151,7 @@
|
||||||
.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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ 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 {
|
||||||
|
|
@ -28,20 +27,31 @@ const PartySegmentedControl = (props: Props) => {
|
||||||
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
|
||||||
|
|
@ -51,6 +61,7 @@ const PartySegmentedControl = (props: Props) => {
|
||||||
onChange={props.onCheckboxChange}
|
onChange={props.onCheckboxChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="PartyNavigation">
|
<div className="PartyNavigation">
|
||||||
|
|
@ -67,30 +78,34 @@ const PartySegmentedControl = (props: Props) => {
|
||||||
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>
|
>
|
||||||
|
{t('party.segmented_control.summons')}
|
||||||
|
</Segment>
|
||||||
</SegmentedControl>
|
</SegmentedControl>
|
||||||
|
|
||||||
{
|
{(() => {
|
||||||
(() => {
|
|
||||||
if (party.editable && props.selectedTab == GridType.Weapon) {
|
if (party.editable && props.selectedTab == GridType.Weapon) {
|
||||||
return extraToggle
|
return extraToggle
|
||||||
}
|
}
|
||||||
})()
|
})()}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
@ -11,40 +15,51 @@ import './index.scss'
|
||||||
interface Props {
|
interface Props {
|
||||||
showAllRaidsOption: boolean
|
showAllRaidsOption: boolean
|
||||||
currentRaid?: string
|
currentRaid?: string
|
||||||
|
defaultRaid?: string
|
||||||
onChange?: (slug?: string) => void
|
onChange?: (slug?: string) => void
|
||||||
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => 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) {
|
||||||
|
|
@ -55,58 +70,79 @@ const RaidDropdown = React.forwardRef<HTMLSelectElement, Props>(function useFiel
|
||||||
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 &&
|
||||||
|
sortedRaids.length > 0 &&
|
||||||
|
sortedRaids[index].length > 0 &&
|
||||||
|
sortedRaids[index]
|
||||||
|
.sort((a, b) => a.element - b.element)
|
||||||
|
.map((item, i) => {
|
||||||
return (
|
return (
|
||||||
<option key={i} value={item.slug}>{item.name[locale]}</option>
|
<SelectItem key={i} value={item.slug}>
|
||||||
|
{item.name[locale]}
|
||||||
|
</SelectItem>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<optgroup key={index} label={raidGroups[index].name[locale]}>
|
<SelectGroup
|
||||||
|
key={index}
|
||||||
|
label={raidGroups[index].name[locale]}
|
||||||
|
separator={false}
|
||||||
|
>
|
||||||
{options}
|
{options}
|
||||||
</optgroup>
|
</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...'}
|
||||||
|
open={open}
|
||||||
|
onClick={openRaidSelect}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
ref={ref}>
|
>
|
||||||
{ Array.from(Array(sortedRaids?.length)).map((x, i) => renderRaidGroup(i)) }
|
{Array.from(Array(sortedRaids?.length)).map((x, i) =>
|
||||||
</select>
|
renderRaidGroup(i)
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
})
|
|
||||||
|
|
||||||
export default RaidDropdown
|
export default RaidDropdown
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,32 @@
|
||||||
.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;
|
||||||
|
gap: $unit-half;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
padding: ($unit) ($unit * 2);
|
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 {
|
path {
|
||||||
fill: $grey-60;
|
fill: $grey-60;
|
||||||
}
|
}
|
||||||
|
|
@ -36,12 +35,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: calc($unit / 2);
|
|
||||||
padding: $unit;
|
padding: $unit;
|
||||||
min-width: 120px;
|
min-width: 120px;
|
||||||
|
|
||||||
|
|
@ -49,7 +47,7 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
fill: white;
|
fill: var(--button-contained-bg);
|
||||||
filter: drop-shadow(0px 0px 1px rgb(0 0 0 / 0.18));
|
filter: drop-shadow(0px 0px 1px rgb(0 0 0 / 0.18));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -66,9 +64,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.Label {
|
.Label {
|
||||||
color: $grey-60;
|
color: var(--text-tertiary);
|
||||||
font-size: $font-small;
|
font-size: $font-small;
|
||||||
margin-bottom: calc($unit / 2);
|
margin-bottom: $unit-half;
|
||||||
padding-left: calc($unit / 2);
|
padding: $unit-half 0 $unit $unit-half;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,29 +1,30 @@
|
||||||
.Item {
|
.Item {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: calc($unit / 2);
|
border-radius: calc($unit / 2);
|
||||||
color: $grey-40;
|
color: var(--text-secondary);
|
||||||
font-size: $font-regular;
|
font-size: $font-regular;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: $unit;
|
padding: $unit;
|
||||||
padding-left: $unit * 3;
|
padding-left: $unit * 3.5;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $grey-90;
|
background: var(--button-contained-bg-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-state="checked"] {
|
&[data-state='checked'] {
|
||||||
background: $grey-90;
|
background: var(--button-contained-bg-hover);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
fill: $grey-50;
|
fill: var(--text-secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Indicator {
|
.Indicator {
|
||||||
$diameter: 18px;
|
$diameter: 20px;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ const SearchFilterCheckboxItem = (props: Props) => {
|
||||||
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">
|
<DropdownMenu.ItemIndicator className="Indicator">
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
</DropdownMenu.ItemIndicator>
|
</DropdownMenu.ItemIndicator>
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#Bar {
|
#Bar {
|
||||||
|
align-items: center;
|
||||||
border-top-left-radius: $unit;
|
border-top-left-radius: $unit;
|
||||||
border-top-right-radius: $unit;
|
border-top-right-radius: $unit;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -39,16 +40,16 @@
|
||||||
label {
|
label {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.Input {
|
// .Input {
|
||||||
background: $grey-90;
|
// background: $grey-90;
|
||||||
border: none;
|
// border: none;
|
||||||
border-radius: calc($unit / 2);
|
// border-radius: calc($unit / 2);
|
||||||
box-sizing: border-box;
|
// box-sizing: border-box;
|
||||||
font-size: $font-regular;
|
// font-size: $font-regular;
|
||||||
padding: $unit * 1.5;
|
// padding: $unit * 1.5;
|
||||||
text-align: left;
|
// text-align: left;
|
||||||
width: 100%;
|
// width: 100%;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -62,17 +63,17 @@
|
||||||
h5.total {
|
h5.total {
|
||||||
font-size: $font-regular;
|
font-size: $font-regular;
|
||||||
font-weight: $normal;
|
font-weight: $normal;
|
||||||
color: $grey-40;
|
color: var(--text-tertiary);
|
||||||
padding: calc($unit / 2) ($unit * 1.5);
|
padding: $unit-half ($unit * 1.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
color: $grey-60;
|
color: var(--text-tertiary);
|
||||||
font-size: $font-regular;
|
font-size: $font-regular;
|
||||||
font-weight: $normal;
|
font-weight: $normal;
|
||||||
height: $unit * 10;
|
height: $unit-10x;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,8 +92,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.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">
|
||||||
<label className="search_label" htmlFor="search_input">
|
<Input
|
||||||
<input
|
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
type="text"
|
className="Search"
|
||||||
name="query"
|
name="query"
|
||||||
className="Input"
|
placeholder={props.placeholderText}
|
||||||
id="search_input"
|
|
||||||
ref={searchInput}
|
ref={searchInput}
|
||||||
value={query}
|
value={query}
|
||||||
placeholder={props.placeholderText}
|
|
||||||
onChange={inputChanged}
|
onChange={inputChanged}
|
||||||
/>
|
/>
|
||||||
</label>
|
<DialogClose className="DialogClose" onClick={openChange}>
|
||||||
<Dialog.Close className="DialogClose" onClick={openChange}>
|
|
||||||
<CrossIcon />
|
<CrossIcon />
|
||||||
</Dialog.Close>
|
</DialogClose>
|
||||||
</div>
|
</div>
|
||||||
{props.object === "characters" ? (
|
{props.object === 'characters' ? (
|
||||||
<CharacterSearchFilterBar sendFilters={receiveFilters} />
|
<CharacterSearchFilterBar sendFilters={receiveFilters} />
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)}
|
)}
|
||||||
{props.object === "weapons" ? (
|
{props.object === 'weapons' ? (
|
||||||
<WeaponSearchFilterBar sendFilters={receiveFilters} />
|
<WeaponSearchFilterBar sendFilters={receiveFilters} />
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)}
|
)}
|
||||||
{props.object === "summons" ? (
|
{props.object === 'summons' ? (
|
||||||
<SummonSearchFilterBar sendFilters={receiveFilters} />
|
<SummonSearchFilterBar sendFilters={receiveFilters} />
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)}
|
)}
|
||||||
{props.object === "job_skills" ? (
|
{props.object === 'job_skills' ? (
|
||||||
<JobSkillSearchFilterBar sendFilters={receiveFilters} />
|
<JobSkillSearchFilterBar sendFilters={receiveFilters} />
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="Results" ref={scrollContainer}>
|
<div id="Results" ref={scrollContainer}>
|
||||||
<h5 className="total">
|
<h5 className="total">
|
||||||
{t("search.result_count", { record_count: recordCount })}
|
{t('search.result_count', { record_count: recordCount })}
|
||||||
</h5>
|
</h5>
|
||||||
{open ? renderResults() : ""}
|
{open ? renderResults() : ''}
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Content>
|
</DialogContent>
|
||||||
<Dialog.Overlay className="Overlay" />
|
</Dialog>
|
||||||
</Dialog.Portal>
|
|
||||||
</Dialog.Root>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
.Segment {
|
.Segment {
|
||||||
color: $grey-50;
|
color: $grey-55;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
font-weight: $normal;
|
font-weight: $normal;
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
|
|
||||||
&:hover label {
|
&:hover label {
|
||||||
background: $grey-90;
|
background: var(--page-hover);
|
||||||
color: $grey-40;
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
& input {
|
& input {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
&:checked + label {
|
&:checked + label {
|
||||||
background: $grey-90;
|
background: var(--background);
|
||||||
color: $grey-00;
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Segment: React.FC<Props> = (props: Props) => {
|
const Segment: React.FC<Props> = (props: Props) => {
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Segment">
|
<div className="Segment">
|
||||||
<input
|
<input
|
||||||
|
|
@ -23,9 +21,7 @@ const Segment: React.FC<Props> = (props: Props) => {
|
||||||
checked={props.selected}
|
checked={props.selected}
|
||||||
onChange={props.onClick}
|
onChange={props.onClick}
|
||||||
/>
|
/>
|
||||||
<label htmlFor={props.name}>
|
<label htmlFor={props.name}>{props.children}</label>
|
||||||
{props.children}
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
||||||
|
|
@ -12,77 +12,76 @@
|
||||||
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 {
|
.Segment:hover label {
|
||||||
background: $fire-bg-light;
|
background: var(--fire-hover-bg);
|
||||||
color: $fire-text-light;
|
color: var(--fire-hover-text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.water {
|
&.water {
|
||||||
.Segment input:checked + label {
|
.Segment input:checked + label {
|
||||||
background: $water-bg-dark;
|
background: $water-bg-10;
|
||||||
color: $water-text-dark;
|
color: $water-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Segment:hover label {
|
.Segment:hover label {
|
||||||
background: $water-bg-light;
|
background: var(--water-hover-bg);
|
||||||
color: $water-text-light;
|
color: var(--water-hover-text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.earth {
|
&.earth {
|
||||||
.Segment input:checked + label {
|
.Segment input:checked + label {
|
||||||
background: $earth-bg-dark;
|
background: $earth-bg-10;
|
||||||
color: $earth-text-dark;
|
color: $earth-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Segment:hover label {
|
.Segment:hover label {
|
||||||
background: $earth-bg-light;
|
background: var(--earth-hover-bg);
|
||||||
color: $earth-text-light;
|
color: var(--earth-hover-text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.wind {
|
&.wind {
|
||||||
.Segment input:checked + label {
|
.Segment input:checked + label {
|
||||||
background: $wind-bg-dark;
|
background: $wind-bg-10;
|
||||||
color: $wind-text-dark;
|
color: $wind-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Segment:hover label {
|
.Segment:hover label {
|
||||||
background: $wind-bg-light;
|
background: var(--wind-hover-bg);
|
||||||
color: $wind-text-light;
|
color: var(--wind-hover-text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.light {
|
&.light {
|
||||||
.Segment input:checked + label {
|
.Segment input:checked + label {
|
||||||
background: $light-bg-dark;
|
background: $light-bg-10;
|
||||||
color: $light-text-dark;
|
color: $light-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Segment:hover label {
|
.Segment:hover label {
|
||||||
background: $light-bg-light;
|
background: var(--light-hover-bg);
|
||||||
color: $light-text-light;
|
color: var(--light-hover-text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dark {
|
&.dark {
|
||||||
.Segment input:checked + label {
|
.Segment input:checked + label {
|
||||||
background: $dark-bg-dark;
|
background: $dark-bg-10;
|
||||||
color: $dark-text-dark;
|
color: $dark-text-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Segment:hover label {
|
.Segment:hover label {
|
||||||
background: $dark-bg-light;
|
background: var(--dark-hover-bg);
|
||||||
color: $dark-text-light;
|
color: var(--dark-hover-text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ interface Props {
|
||||||
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>
|
||||||
|
|
|
||||||
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
|
||||||
|
|
@ -17,7 +17,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;
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.terms {
|
.terms {
|
||||||
color: $grey-40;
|
color: $grey-50;
|
||||||
font-size: $font-small;
|
font-size: $font-small;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
margin-top: $unit;
|
margin-top: $unit;
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
& .Label {
|
& .Label {
|
||||||
color: $grey-50;
|
color: $grey-55;
|
||||||
font-size: $font-tiny;
|
font-size: $font-tiny;
|
||||||
font-weight: $medium;
|
font-weight: $medium;
|
||||||
margin-bottom: $unit;
|
margin-bottom: $unit;
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -17,26 +17,39 @@ interface Props {
|
||||||
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 (
|
||||||
|
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 &&
|
||||||
|
props.gridSummon.uncap_level == 5
|
||||||
|
)
|
||||||
suffix = '_02'
|
suffix = '_02'
|
||||||
|
|
||||||
// Generate the correct source for the summon
|
// Generate the correct source for the summon
|
||||||
|
|
@ -48,18 +61,21 @@ const SummonHovercard = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HoverCard.Root>
|
<HoverCard.Root>
|
||||||
<HoverCard.Trigger>
|
<HoverCard.Trigger>{props.children}</HoverCard.Trigger>
|
||||||
{ props.children }
|
|
||||||
</HoverCard.Trigger>
|
|
||||||
<HoverCard.Content className="Weapon Hovercard">
|
<HoverCard.Content className="Weapon Hovercard">
|
||||||
<div className="top">
|
<div className="top">
|
||||||
<div className="title">
|
<div className="title">
|
||||||
<h4>{props.gridSummon.object.name[locale]}</h4>
|
<h4>{props.gridSummon.object.name[locale]}</h4>
|
||||||
<img alt={props.gridSummon.object.name[locale]} src={summonImage()} />
|
<img
|
||||||
|
alt={props.gridSummon.object.name[locale]}
|
||||||
|
src={summonImage()}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="subInfo">
|
<div className="subInfo">
|
||||||
<div className="icons">
|
<div className="icons">
|
||||||
<WeaponLabelIcon labelType={Element[props.gridSummon.object.element]}/>
|
<WeaponLabelIcon
|
||||||
|
labelType={Element[props.gridSummon.object.element]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<UncapIndicator
|
<UncapIndicator
|
||||||
type="summon"
|
type="summon"
|
||||||
|
|
@ -69,7 +85,9 @@ const SummonHovercard = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">{t('buttons.wiki')}</a>
|
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">
|
||||||
|
{t('buttons.wiki')}
|
||||||
|
</a>
|
||||||
<HoverCard.Arrow />
|
<HoverCard.Arrow />
|
||||||
</HoverCard.Content>
|
</HoverCard.Content>
|
||||||
</HoverCard.Root>
|
</HoverCard.Root>
|
||||||
|
|
@ -77,4 +95,3 @@ const SummonHovercard = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SummonHovercard
|
export default SummonHovercard
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,12 @@
|
||||||
padding: $unit * 1.5;
|
padding: $unit * 1.5;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $grey-90;
|
background: var(--button-contained-bg);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
.Info h5 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
|
@ -21,10 +25,10 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
gap: calc($unit / 2);
|
gap: $unit-half;
|
||||||
|
|
||||||
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;
|
||||||
|
|
@ -37,11 +41,11 @@
|
||||||
|
|
||||||
.stars {
|
.stars {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: #FFA15E;
|
color: #ffa15e;
|
||||||
font-size: $font-xlarge;
|
font-size: $font-xlarge;
|
||||||
|
|
||||||
& > span {
|
& > span {
|
||||||
color: #65DAFF;
|
color: #65daff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,17 @@ 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
|
||||||
|
alt={summon.name[locale]}
|
||||||
|
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}.jpg`}
|
||||||
|
/>
|
||||||
<div className="Info">
|
<div className="Info">
|
||||||
<h5>{summon.name[locale]}</h5>
|
<h5>{summon.name[locale]}</h5>
|
||||||
<UncapIndicator
|
<UncapIndicator
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@ const SummonSearchFilterBar = (props: Props) => {
|
||||||
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) {
|
||||||
|
|
@ -52,12 +53,16 @@ const SummonSearchFilterBar = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendFilters() {
|
function sendFilters() {
|
||||||
const checkedRarityFilters = Object.values(rarityState).filter(x => x.checked).map((x, i) => x.id)
|
const checkedRarityFilters = Object.values(rarityState)
|
||||||
const checkedElementFilters = Object.values(elementState).filter(x => x.checked).map((x, i) => x.id)
|
.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 = {
|
const filters = {
|
||||||
rarity: checkedRarityFilters,
|
rarity: checkedRarityFilters,
|
||||||
element: checkedElementFilters
|
element: checkedElementFilters,
|
||||||
}
|
}
|
||||||
|
|
||||||
props.sendFilters(filters)
|
props.sendFilters(filters)
|
||||||
|
|
@ -69,34 +74,58 @@ const SummonSearchFilterBar = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="SearchFilterBar">
|
<div className="SearchFilterBar">
|
||||||
<SearchFilter label={t('filters.labels.rarity')} numSelected={Object.values(rarityState).map(x => x.checked).filter(Boolean).length} open={rarityMenu} onOpenChange={rarityMenuOpened}>
|
<SearchFilter
|
||||||
<DropdownMenu.Label className="Label">{t('filters.labels.rarity')}</DropdownMenu.Label>
|
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) => {
|
{Array.from(Array(rarities.length)).map((x, i) => {
|
||||||
return (
|
return (
|
||||||
<SearchFilterCheckboxItem
|
<SearchFilterCheckboxItem
|
||||||
key={rarities[i]}
|
key={rarities[i]}
|
||||||
onCheckedChange={handleRarityChange}
|
onCheckedChange={handleRarityChange}
|
||||||
checked={rarityState[rarities[i]].checked}
|
checked={rarityState[rarities[i]].checked}
|
||||||
valueKey={rarities[i]}>
|
valueKey={rarities[i]}
|
||||||
|
>
|
||||||
{t(`rarities.${rarities[i]}`)}
|
{t(`rarities.${rarities[i]}`)}
|
||||||
</SearchFilterCheckboxItem>
|
</SearchFilterCheckboxItem>
|
||||||
)}
|
)
|
||||||
) }
|
})}
|
||||||
</SearchFilter>
|
</SearchFilter>
|
||||||
|
|
||||||
<SearchFilter label={t('filters.labels.element')} numSelected={Object.values(elementState).map(x => x.checked).filter(Boolean).length} open={elementMenu} onOpenChange={elementMenuOpened}>
|
<SearchFilter
|
||||||
<DropdownMenu.Label className="Label">{t('filters.labels.element')}</DropdownMenu.Label>
|
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) => {
|
{Array.from(Array(elements.length)).map((x, i) => {
|
||||||
return (
|
return (
|
||||||
<SearchFilterCheckboxItem
|
<SearchFilterCheckboxItem
|
||||||
key={elements[i]}
|
key={elements[i]}
|
||||||
onCheckedChange={handleElementChange}
|
onCheckedChange={handleElementChange}
|
||||||
checked={elementState[elements[i]].checked}
|
checked={elementState[elements[i]].checked}
|
||||||
valueKey={elements[i]}>
|
valueKey={elements[i]}
|
||||||
|
>
|
||||||
{t(`elements.${elements[i]}`)}
|
{t(`elements.${elements[i]}`)}
|
||||||
</SearchFilterCheckboxItem>
|
</SearchFilterCheckboxItem>
|
||||||
)}
|
)
|
||||||
) }
|
})}
|
||||||
</SearchFilter>
|
</SearchFilter>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.SummonImage {
|
.SummonImage {
|
||||||
background: white;
|
background: var(--card-bg);
|
||||||
border: 1px solid rgba(0, 0, 0, 0);
|
border: 1px solid rgba(0, 0, 0, 0);
|
||||||
border-radius: $unit;
|
border-radius: $unit;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -62,7 +62,7 @@
|
||||||
transition: all 0.18s ease-in-out;
|
transition: all 0.18s ease-in-out;
|
||||||
|
|
||||||
&:hover .icon svg {
|
&:hover .icon svg {
|
||||||
fill: $grey-40;
|
fill: var(--icon-secondary-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
fill: $grey-70;
|
fill: var(--icon-secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -85,12 +85,13 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3, ul {
|
h3,
|
||||||
|
ul {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
color: #333;
|
color: var(--text-primary);
|
||||||
font-size: $font-regular;
|
font-size: $font-regular;
|
||||||
font-weight: $normal;
|
font-weight: $normal;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
|
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;
|
line-height: 21px;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
padding: $unit-2x calc($unit-2x - $offset);
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
border: $offset solid $blue;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,7 +10,8 @@ interface Props {
|
||||||
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
|
||||||
|
|
@ -22,12 +23,10 @@ const TextFieldset = React.forwardRef<HTMLTextAreaElement, Props>(function field
|
||||||
onChange={props.onChange}
|
onChange={props.onChange}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
/>
|
/>
|
||||||
{
|
{props.error.length > 0 && <p className="InputError">{props.error}</p>}
|
||||||
props.error.length > 0 &&
|
|
||||||
<p className='InputError'>{props.error}</p>
|
|
||||||
}
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export default TextFieldset
|
export default TextFieldset
|
||||||
|
|
@ -1,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 {
|
||||||
|
|
|
||||||
|
|
@ -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,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 {
|
||||||
|
|
|
||||||
|
|
@ -15,28 +15,25 @@ interface Props {
|
||||||
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() {
|
function clicked() {
|
||||||
props.onClick(props.index, props.empty)
|
props.onClick(props.index, props.empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <li className={classes} onClick={clicked}></li>
|
||||||
<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
|
||||||
|
|
@ -12,7 +12,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#MainGrid, #ExtraGrid {
|
#MainGrid,
|
||||||
|
#ExtraGrid {
|
||||||
.grid_weapons > * {
|
.grid_weapons > * {
|
||||||
margin-bottom: $unit * 3;
|
margin-bottom: $unit * 3;
|
||||||
margin-right: $unit * 3;
|
margin-right: $unit * 3;
|
||||||
|
|
@ -39,4 +40,3 @@
|
||||||
#ExtraWeapons #grid_weapons > * {
|
#ExtraWeapons #grid_weapons > * {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 { AxiosResponse } from "axios"
|
import { AxiosResponse } from 'axios'
|
||||||
import debounce from "lodash.debounce"
|
import debounce from 'lodash.debounce'
|
||||||
|
|
||||||
import WeaponUnit from "~components/WeaponUnit"
|
import WeaponUnit from '~components/WeaponUnit'
|
||||||
import ExtraWeapons from "~components/ExtraWeapons"
|
import ExtraWeapons from '~components/ExtraWeapons'
|
||||||
|
|
||||||
import api from "~utils/api"
|
import api from '~utils/api'
|
||||||
import { appState } from "~utils/appState"
|
import { appState } from '~utils/appState'
|
||||||
|
|
||||||
import type { SearchableObject } from "~types"
|
import type { SearchableObject } from '~types'
|
||||||
|
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -29,7 +29,7 @@ const WeaponGrid = (props: Props) => {
|
||||||
const numWeapons: number = 9
|
const numWeapons: number = 9
|
||||||
|
|
||||||
// Cookies
|
// Cookies
|
||||||
const cookie = getCookie("account")
|
const cookie = getCookie('account')
|
||||||
const accountData: AccountCookie = cookie
|
const accountData: AccountCookie = cookie
|
||||||
? JSON.parse(cookie as string)
|
? JSON.parse(cookie as string)
|
||||||
: null
|
: null
|
||||||
|
|
@ -132,7 +132,7 @@ const WeaponGrid = (props: Props) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (uncapLevel != previousUncapValues[position])
|
if (uncapLevel != previousUncapValues[position])
|
||||||
await api.updateUncap("weapon", id, uncapLevel).then((response) => {
|
await api.updateUncap('weapon', id, uncapLevel).then((response) => {
|
||||||
storeGridWeapon(response.data.grid_weapon)
|
storeGridWeapon(response.data.grid_weapon)
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -250,7 +250,7 @@ const WeaponGrid = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(() => {
|
{(() => {
|
||||||
return party.extra ? extraGridElement : ""
|
return party.extra ? extraGridElement : ''
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ interface Props {
|
||||||
interface KeyNames {
|
interface KeyNames {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
en: string,
|
en: string
|
||||||
ja: string
|
ja: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -27,39 +27,56 @@ interface KeyNames {
|
||||||
const WeaponHovercard = (props: Props) => {
|
const WeaponHovercard = (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 WeaponKeyNames: KeyNames = {
|
const WeaponKeyNames: KeyNames = {
|
||||||
'2': {
|
'2': {
|
||||||
en: 'Pendulum',
|
en: 'Pendulum',
|
||||||
ja: 'ペンデュラム'
|
ja: 'ペンデュラム',
|
||||||
},
|
},
|
||||||
'3': {
|
'3': {
|
||||||
en: 'Teluma',
|
en: 'Teluma',
|
||||||
ja: 'テルマ'
|
ja: 'テルマ',
|
||||||
},
|
},
|
||||||
'17': {
|
'17': {
|
||||||
en: 'Gauph Key',
|
en: 'Gauph Key',
|
||||||
ja: 'ガフスキー'
|
ja: 'ガフスキー',
|
||||||
},
|
},
|
||||||
'22': {
|
'22': {
|
||||||
en: 'Emblem',
|
en: 'Emblem',
|
||||||
ja: 'エンブレム'
|
ja: 'エンブレム',
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const tintElement = (props.gridWeapon.object.element == 0 && props.gridWeapon.element) ? Element[props.gridWeapon.element] : Element[props.gridWeapon.object.element]
|
const tintElement =
|
||||||
const wikiUrl = `https://gbf.wiki/${props.gridWeapon.object.name.en.replaceAll(' ', '_')}`
|
props.gridWeapon.object.element == 0 && props.gridWeapon.element
|
||||||
|
? Element[props.gridWeapon.element]
|
||||||
|
: Element[props.gridWeapon.object.element]
|
||||||
|
const wikiUrl = `https://gbf.wiki/${props.gridWeapon.object.name.en.replaceAll(
|
||||||
|
' ',
|
||||||
|
'_'
|
||||||
|
)}`
|
||||||
|
|
||||||
const hovercardSide = () => {
|
const hovercardSide = () => {
|
||||||
if (props.gridWeapon.position == -1)
|
if (props.gridWeapon.position == -1) return 'right'
|
||||||
return "right"
|
|
||||||
else if ([6, 7, 8, 9, 10, 11].includes(props.gridWeapon.position))
|
else if ([6, 7, 8, 9, 10, 11].includes(props.gridWeapon.position))
|
||||||
return "top"
|
return 'top'
|
||||||
else
|
else return 'bottom'
|
||||||
return "bottom"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const createPrimaryAxSkillString = () => {
|
const createPrimaryAxSkillString = () => {
|
||||||
|
|
@ -67,9 +84,13 @@ const WeaponHovercard = (props: Props) => {
|
||||||
|
|
||||||
if (props.gridWeapon.ax) {
|
if (props.gridWeapon.ax) {
|
||||||
const simpleAxSkill = props.gridWeapon.ax[0]
|
const simpleAxSkill = props.gridWeapon.ax[0]
|
||||||
const axSkill = primaryAxSkills.find(skill => skill.id == simpleAxSkill.modifier)
|
const axSkill = primaryAxSkills.find(
|
||||||
|
(skill) => skill.id == simpleAxSkill.modifier
|
||||||
|
)
|
||||||
|
|
||||||
return `${axSkill?.name[locale]} +${simpleAxSkill.strength}${ (axSkill?.suffix) ? axSkill.suffix : '' }`
|
return `${axSkill?.name[locale]} +${simpleAxSkill.strength}${
|
||||||
|
axSkill?.suffix ? axSkill.suffix : ''
|
||||||
|
}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return ''
|
return ''
|
||||||
|
|
@ -82,11 +103,17 @@ const WeaponHovercard = (props: Props) => {
|
||||||
const primarySimpleAxSkill = props.gridWeapon.ax[0]
|
const primarySimpleAxSkill = props.gridWeapon.ax[0]
|
||||||
const secondarySimpleAxSkill = props.gridWeapon.ax[1]
|
const secondarySimpleAxSkill = props.gridWeapon.ax[1]
|
||||||
|
|
||||||
const primaryAxSkill = primaryAxSkills.find(skill => skill.id == primarySimpleAxSkill.modifier)
|
const primaryAxSkill = primaryAxSkills.find(
|
||||||
|
(skill) => skill.id == primarySimpleAxSkill.modifier
|
||||||
|
)
|
||||||
|
|
||||||
if (primaryAxSkill && primaryAxSkill.secondary) {
|
if (primaryAxSkill && primaryAxSkill.secondary) {
|
||||||
const secondaryAxSkill = primaryAxSkill.secondary.find(skill => skill.id == secondarySimpleAxSkill.modifier)
|
const secondaryAxSkill = primaryAxSkill.secondary.find(
|
||||||
return `${secondaryAxSkill?.name[locale]} +${secondarySimpleAxSkill.strength}${ (secondaryAxSkill?.suffix) ? secondaryAxSkill.suffix : '' }`
|
(skill) => skill.id == secondarySimpleAxSkill.modifier
|
||||||
|
)
|
||||||
|
return `${secondaryAxSkill?.name[locale]} +${
|
||||||
|
secondarySimpleAxSkill.strength
|
||||||
|
}${secondaryAxSkill?.suffix ? secondaryAxSkill.suffix : ''}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,18 +131,27 @@ const WeaponHovercard = (props: Props) => {
|
||||||
|
|
||||||
const keysSection = (
|
const keysSection = (
|
||||||
<section className="weaponKeys">
|
<section className="weaponKeys">
|
||||||
{ (WeaponKeyNames[props.gridWeapon.object.series]) ?
|
{WeaponKeyNames[props.gridWeapon.object.series] ? (
|
||||||
<h5 className={tintElement}>{ WeaponKeyNames[props.gridWeapon.object.series][locale] }{ (locale === 'en') ? 's' : '' }</h5> : ''
|
<h5 className={tintElement}>
|
||||||
}
|
{WeaponKeyNames[props.gridWeapon.object.series][locale]}
|
||||||
|
{locale === 'en' ? 's' : ''}
|
||||||
|
</h5>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
|
||||||
{ (props.gridWeapon.weapon_keys) ?
|
{props.gridWeapon.weapon_keys
|
||||||
Array.from(Array(props.gridWeapon.weapon_keys.length)).map((x, i) => {
|
? Array.from(Array(props.gridWeapon.weapon_keys.length)).map((x, i) => {
|
||||||
return (
|
return (
|
||||||
<div className="weaponKey" key={props.gridWeapon.weapon_keys![i].id}>
|
<div
|
||||||
|
className="weaponKey"
|
||||||
|
key={props.gridWeapon.weapon_keys![i].id}
|
||||||
|
>
|
||||||
<span>{props.gridWeapon.weapon_keys![i].name[locale]}</span>
|
<span>{props.gridWeapon.weapon_keys![i].name[locale]}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}) : '' }
|
})
|
||||||
|
: ''}
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -124,36 +160,65 @@ const WeaponHovercard = (props: Props) => {
|
||||||
<h5 className={tintElement}>{t('modals.weapon.subtitles.ax_skills')}</h5>
|
<h5 className={tintElement}>{t('modals.weapon.subtitles.ax_skills')}</h5>
|
||||||
<div className="skills">
|
<div className="skills">
|
||||||
<div className="primary axSkill">
|
<div className="primary axSkill">
|
||||||
<img alt="AX1" src={`/icons/ax/primary_${ (props.gridWeapon.ax) ? props.gridWeapon.ax[0].modifier : '' }.png`} />
|
<img
|
||||||
|
alt="AX1"
|
||||||
|
src={`/icons/ax/primary_${
|
||||||
|
props.gridWeapon.ax ? props.gridWeapon.ax[0].modifier : ''
|
||||||
|
}.png`}
|
||||||
|
/>
|
||||||
<span>{createPrimaryAxSkillString()}</span>
|
<span>{createPrimaryAxSkillString()}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ (props.gridWeapon.ax && props.gridWeapon.ax[1].modifier && props.gridWeapon.ax[1].strength) ?
|
{props.gridWeapon.ax &&
|
||||||
|
props.gridWeapon.ax[1].modifier &&
|
||||||
|
props.gridWeapon.ax[1].strength ? (
|
||||||
<div className="secondary axSkill">
|
<div className="secondary axSkill">
|
||||||
<img alt="AX2" src={`/icons/ax/secondary_${ (props.gridWeapon.ax) ? props.gridWeapon.ax[1].modifier : '' }.png`} />
|
<img
|
||||||
|
alt="AX2"
|
||||||
|
src={`/icons/ax/secondary_${
|
||||||
|
props.gridWeapon.ax ? props.gridWeapon.ax[1].modifier : ''
|
||||||
|
}.png`}
|
||||||
|
/>
|
||||||
<span>{createSecondaryAxSkillString()}</span>
|
<span>{createSecondaryAxSkillString()}</span>
|
||||||
</div> : ''}
|
</div>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HoverCard.Root>
|
<HoverCard.Root>
|
||||||
<HoverCard.Trigger>
|
<HoverCard.Trigger>{props.children}</HoverCard.Trigger>
|
||||||
{ props.children }
|
|
||||||
</HoverCard.Trigger>
|
|
||||||
<HoverCard.Content className="Weapon Hovercard" side={hovercardSide()}>
|
<HoverCard.Content className="Weapon Hovercard" side={hovercardSide()}>
|
||||||
<div className="top">
|
<div className="top">
|
||||||
<div className="title">
|
<div className="title">
|
||||||
<h4>{props.gridWeapon.object.name[locale]}</h4>
|
<h4>{props.gridWeapon.object.name[locale]}</h4>
|
||||||
<img alt={props.gridWeapon.object.name[locale]} src={weaponImage()} />
|
<img
|
||||||
|
alt={props.gridWeapon.object.name[locale]}
|
||||||
|
src={weaponImage()}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="subInfo">
|
<div className="subInfo">
|
||||||
<div className="icons">
|
<div className="icons">
|
||||||
{ (props.gridWeapon.object.element !== 0 || (props.gridWeapon.object.element === 0 && props.gridWeapon.element != null)) ?
|
{props.gridWeapon.object.element !== 0 ||
|
||||||
<WeaponLabelIcon labelType={ (props.gridWeapon.object.element === 0 && props.gridWeapon.element !== 0) ? Element[props.gridWeapon.element] : Element[props.gridWeapon.object.element] } />
|
(props.gridWeapon.object.element === 0 &&
|
||||||
: '' }
|
props.gridWeapon.element != null) ? (
|
||||||
<WeaponLabelIcon labelType={ Proficiency[props.gridWeapon.object.proficiency] } />
|
<WeaponLabelIcon
|
||||||
|
labelType={
|
||||||
|
props.gridWeapon.object.element === 0 &&
|
||||||
|
props.gridWeapon.element !== 0
|
||||||
|
? Element[props.gridWeapon.element]
|
||||||
|
: Element[props.gridWeapon.object.element]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
<WeaponLabelIcon
|
||||||
|
labelType={Proficiency[props.gridWeapon.object.proficiency]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<UncapIndicator
|
<UncapIndicator
|
||||||
type="weapon"
|
type="weapon"
|
||||||
|
|
@ -164,9 +229,18 @@ const WeaponHovercard = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ (props.gridWeapon.object.ax > 0 && props.gridWeapon.ax && props.gridWeapon.ax[0].modifier && props.gridWeapon.ax[0].strength ) ? axSection : '' }
|
{props.gridWeapon.object.ax > 0 &&
|
||||||
{ (props.gridWeapon.weapon_keys && props.gridWeapon.weapon_keys.length > 0) ? keysSection : '' }
|
props.gridWeapon.ax &&
|
||||||
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">{t('buttons.wiki')}</a>
|
props.gridWeapon.ax[0].modifier &&
|
||||||
|
props.gridWeapon.ax[0].strength
|
||||||
|
? axSection
|
||||||
|
: ''}
|
||||||
|
{props.gridWeapon.weapon_keys && props.gridWeapon.weapon_keys.length > 0
|
||||||
|
? keysSection
|
||||||
|
: ''}
|
||||||
|
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">
|
||||||
|
{t('buttons.wiki')}
|
||||||
|
</a>
|
||||||
<HoverCard.Arrow />
|
<HoverCard.Arrow />
|
||||||
</HoverCard.Content>
|
</HoverCard.Content>
|
||||||
</HoverCard.Root>
|
</HoverCard.Root>
|
||||||
|
|
@ -174,4 +248,3 @@ const WeaponHovercard = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WeaponHovercard
|
export default WeaponHovercard
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,14 @@ interface Props {
|
||||||
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const WeaponKeyDropdown = React.forwardRef<HTMLSelectElement, Props>(function useFieldSet(props, ref) {
|
const WeaponKeyDropdown = React.forwardRef<HTMLSelectElement, Props>(
|
||||||
|
function useFieldSet(props, ref) {
|
||||||
const [keys, setKeys] = useState<WeaponKey[][]>([])
|
const [keys, setKeys] = useState<WeaponKey[][]>([])
|
||||||
const [currentKey, setCurrentKey] = useState('')
|
const [currentKey, setCurrentKey] = useState('')
|
||||||
|
|
||||||
const pendulumNames = [
|
const pendulumNames = [
|
||||||
{ en: 'Pendulum', jp: '' },
|
{ en: 'Pendulum', jp: '' },
|
||||||
{ en: 'Chain', jp: '' }
|
{ en: 'Chain', jp: '' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const telumaNames = [{ en: 'Teluma', jp: '' }]
|
const telumaNames = [{ en: 'Teluma', jp: '' }]
|
||||||
|
|
@ -27,35 +28,36 @@ const WeaponKeyDropdown = React.forwardRef<HTMLSelectElement, Props>(function us
|
||||||
const gauphNames = [
|
const gauphNames = [
|
||||||
{ en: 'Gauph Key', jp: '' },
|
{ en: 'Gauph Key', jp: '' },
|
||||||
{ en: 'Ultima Key', jp: '' },
|
{ en: 'Ultima Key', jp: '' },
|
||||||
{ en: 'Gate of Omnipotence', jp: '' }
|
{ en: 'Gate of Omnipotence', jp: '' },
|
||||||
]
|
]
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.currentValue)
|
if (props.currentValue) setCurrentKey(props.currentValue.id)
|
||||||
setCurrentKey(props.currentValue.id)
|
|
||||||
}, [props.currentValue])
|
}, [props.currentValue])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const filterParams = {
|
const filterParams = {
|
||||||
params: {
|
params: {
|
||||||
series: props.series,
|
series: props.series,
|
||||||
slot: props.slot
|
slot: props.slot,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function organizeWeaponKeys(weaponKeys: WeaponKey[]) {
|
function organizeWeaponKeys(weaponKeys: WeaponKey[]) {
|
||||||
const numGroups = Math.max.apply(Math, weaponKeys.map(key => key.group))
|
const numGroups = Math.max.apply(
|
||||||
|
Math,
|
||||||
|
weaponKeys.map((key) => key.group)
|
||||||
|
)
|
||||||
let groupedKeys = []
|
let groupedKeys = []
|
||||||
for (let i = 0; i <= numGroups; i++) {
|
for (let i = 0; i <= numGroups; i++) {
|
||||||
groupedKeys[i] = weaponKeys.filter(key => key.group == i)
|
groupedKeys[i] = weaponKeys.filter((key) => key.group == i)
|
||||||
}
|
}
|
||||||
|
|
||||||
setKeys(groupedKeys)
|
setKeys(groupedKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchWeaponKeys() {
|
function fetchWeaponKeys() {
|
||||||
api.endpoints.weapon_keys.getAll(filterParams)
|
api.endpoints.weapon_keys.getAll(filterParams).then((response) => {
|
||||||
.then((response) => {
|
|
||||||
const keys = response.data.map((k: any) => k.weapon_key)
|
const keys = response.data.map((k: any) => k.weapon_key)
|
||||||
organizeWeaponKeys(keys)
|
organizeWeaponKeys(keys)
|
||||||
})
|
})
|
||||||
|
|
@ -65,53 +67,55 @@ const WeaponKeyDropdown = React.forwardRef<HTMLSelectElement, Props>(function us
|
||||||
}, [props.series, props.slot])
|
}, [props.series, props.slot])
|
||||||
|
|
||||||
function weaponKeyGroup(index: number) {
|
function weaponKeyGroup(index: number) {
|
||||||
['α','β','γ','Δ'].sort((a, b) => a.localeCompare(b, 'el'))
|
;['α', 'β', 'γ', 'Δ'].sort((a, b) => a.localeCompare(b, 'el'))
|
||||||
|
|
||||||
const sortByOrder = (a: WeaponKey, b: WeaponKey) => a.order > b.order ? 1 : -1
|
const sortByOrder = (a: WeaponKey, b: WeaponKey) =>
|
||||||
|
a.order > b.order ? 1 : -1
|
||||||
|
|
||||||
const options = keys && keys.length > 0 && keys[index].length > 0 &&
|
const options =
|
||||||
|
keys &&
|
||||||
|
keys.length > 0 &&
|
||||||
|
keys[index].length > 0 &&
|
||||||
keys[index].sort(sortByOrder).map((item, i) => {
|
keys[index].sort(sortByOrder).map((item, i) => {
|
||||||
return (
|
return (
|
||||||
<option key={i} value={item.id}>{item.name.en}</option>
|
<option key={i} value={item.id}>
|
||||||
|
{item.name.en}
|
||||||
|
</option>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
let name: { [key: string]: string } = {}
|
let name: { [key: string]: string } = {}
|
||||||
if (props.series == 2 && index == 0)
|
if (props.series == 2 && index == 0) name = pendulumNames[0]
|
||||||
name = pendulumNames[0]
|
|
||||||
else if (props.series == 2 && props.slot == 1 && index == 1)
|
else if (props.series == 2 && props.slot == 1 && index == 1)
|
||||||
name = pendulumNames[1]
|
name = pendulumNames[1]
|
||||||
else if (props.series == 3)
|
else if (props.series == 3) name = telumaNames[index]
|
||||||
name = telumaNames[index]
|
else if (props.series == 17) name = gauphNames[props.slot]
|
||||||
else if (props.series == 17)
|
else if (props.series == 22) name = emblemNames[index]
|
||||||
name = gauphNames[props.slot]
|
|
||||||
else if (props.series == 22)
|
|
||||||
name = emblemNames[index]
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<optgroup key={index} label={ (props.series == 17 && props.slot == 2) ? name.en : `${name.en}s`}>
|
<optgroup
|
||||||
|
key={index}
|
||||||
|
label={
|
||||||
|
props.series == 17 && props.slot == 2 ? name.en : `${name.en}s`
|
||||||
|
}
|
||||||
|
>
|
||||||
{options}
|
{options}
|
||||||
</optgroup>
|
</optgroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleChange(event: React.ChangeEvent<HTMLSelectElement>) {
|
function handleChange(event: React.ChangeEvent<HTMLSelectElement>) {
|
||||||
if (props.onChange)
|
if (props.onChange) props.onChange(event)
|
||||||
props.onChange(event)
|
|
||||||
|
|
||||||
setCurrentKey(event.currentTarget.value)
|
setCurrentKey(event.currentTarget.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptyOption = () => {
|
const emptyOption = () => {
|
||||||
let name = ''
|
let name = ''
|
||||||
if (props.series == 2)
|
if (props.series == 2) name = pendulumNames[0].en
|
||||||
name = pendulumNames[0].en
|
else if (props.series == 3) name = telumaNames[0].en
|
||||||
else if (props.series == 3)
|
else if (props.series == 17) name = gauphNames[props.slot].en
|
||||||
name = telumaNames[0].en
|
else if (props.series == 22) name = emblemNames[0].en
|
||||||
else if (props.series == 17)
|
|
||||||
name = gauphNames[props.slot].en
|
|
||||||
else if (props.series == 22)
|
|
||||||
name = emblemNames[0].en
|
|
||||||
|
|
||||||
return `No ${name}`
|
return `No ${name}`
|
||||||
}
|
}
|
||||||
|
|
@ -122,13 +126,17 @@ const WeaponKeyDropdown = React.forwardRef<HTMLSelectElement, Props>(function us
|
||||||
value={currentKey}
|
value={currentKey}
|
||||||
onBlur={props.onBlur}
|
onBlur={props.onBlur}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
ref={ref}>
|
ref={ref}
|
||||||
<option key="-1" value="-1">{ emptyOption() }</option>
|
>
|
||||||
|
<option key="-1" value="-1">
|
||||||
|
{emptyOption()}
|
||||||
|
</option>
|
||||||
{Array.from(Array(keys?.length)).map((x, i) => {
|
{Array.from(Array(keys?.length)).map((x, i) => {
|
||||||
return weaponKeyGroup(i)
|
return weaponKeyGroup(i)
|
||||||
})}
|
})}
|
||||||
</select>
|
</select>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export default WeaponKeyDropdown
|
export default WeaponKeyDropdown
|
||||||
|
|
|
||||||
|
|
@ -7,141 +7,140 @@
|
||||||
/* Elements */
|
/* Elements */
|
||||||
|
|
||||||
&.fire.en {
|
&.fire.en {
|
||||||
background-image: url('/labels/element/fire_en.png')
|
background-image: url('/labels/element/fire_en.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.fire.ja {
|
&.fire.ja {
|
||||||
background-image: url('/labels/element/fire_ja.png')
|
background-image: url('/labels/element/fire_ja.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.water.en {
|
&.water.en {
|
||||||
background-image: url('/labels/element/water_en.png')
|
background-image: url('/labels/element/water_en.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.water.ja {
|
&.water.ja {
|
||||||
background-image: url('/labels/element/water_ja.png')
|
background-image: url('/labels/element/water_ja.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.earth.en {
|
&.earth.en {
|
||||||
background-image: url('/labels/element/earth_en.png')
|
background-image: url('/labels/element/earth_en.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.earth.ja {
|
&.earth.ja {
|
||||||
background-image: url('/labels/element/earth_ja.png')
|
background-image: url('/labels/element/earth_ja.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.wind.en {
|
&.wind.en {
|
||||||
background-image: url('/labels/element/wind_en.png')
|
background-image: url('/labels/element/wind_en.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.wind.ja {
|
&.wind.ja {
|
||||||
background-image: url('/labels/element/wind_ja.png')
|
background-image: url('/labels/element/wind_ja.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dark.en {
|
&.dark.en {
|
||||||
background-image: url('/labels/element/dark_en.png')
|
background-image: url('/labels/element/dark_en.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dark.ja {
|
&.dark.ja {
|
||||||
background-image: url('/labels/element/dark_ja.png')
|
background-image: url('/labels/element/dark_ja.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.light.en {
|
&.light.en {
|
||||||
background-image: url('/labels/element/light_en.png')
|
background-image: url('/labels/element/light_en.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.light.ja {
|
&.light.ja {
|
||||||
background-image: url('/labels/element/light_ja.png')
|
background-image: url('/labels/element/light_ja.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.null.en {
|
&.null.en {
|
||||||
background-image: url('/labels/element/any_en.png')
|
background-image: url('/labels/element/any_en.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.null.ja {
|
&.null.ja {
|
||||||
background-image: url('/labels/element/any_ja.png')
|
background-image: url('/labels/element/any_ja.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Proficiencies */
|
/* Proficiencies */
|
||||||
|
|
||||||
&.sword.en {
|
&.sword.en {
|
||||||
background-image: url('/labels/proficiency/sabre_en.png')
|
background-image: url('/labels/proficiency/sabre_en.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.sword.ja {
|
&.sword.ja {
|
||||||
background-image: url('/labels/proficiency/sabre_ja.png')
|
background-image: url('/labels/proficiency/sabre_ja.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dagger.en {
|
&.dagger.en {
|
||||||
background-image: url('/labels/proficiency/dagger_en.png')
|
background-image: url('/labels/proficiency/dagger_en.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dagger.ja {
|
&.dagger.ja {
|
||||||
background-image: url('/labels/proficiency/dagger_ja.png')
|
background-image: url('/labels/proficiency/dagger_ja.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.axe.en {
|
&.axe.en {
|
||||||
background-image: url('/labels/proficiency/axe_en.png')
|
background-image: url('/labels/proficiency/axe_en.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.axe.ja {
|
&.axe.ja {
|
||||||
background-image: url('/labels/proficiency/axe_ja.png')
|
background-image: url('/labels/proficiency/axe_ja.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.spear.en {
|
&.spear.en {
|
||||||
background-image: url('/labels/proficiency/spear_en.png')
|
background-image: url('/labels/proficiency/spear_en.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.spear.ja {
|
&.spear.ja {
|
||||||
background-image: url('/labels/proficiency/spear_ja.png')
|
background-image: url('/labels/proficiency/spear_ja.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.staff.en {
|
&.staff.en {
|
||||||
background-image: url('/labels/proficiency/staff_en.png')
|
background-image: url('/labels/proficiency/staff_en.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.staff.ja {
|
&.staff.ja {
|
||||||
background-image: url('/labels/proficiency/staff_ja.png')
|
background-image: url('/labels/proficiency/staff_ja.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.fist.en {
|
&.fist.en {
|
||||||
background-image: url('/labels/proficiency/melee_en.png')
|
background-image: url('/labels/proficiency/melee_en.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.fist.ja {
|
&.fist.ja {
|
||||||
background-image: url('/labels/proficiency/melee_ja.png')
|
background-image: url('/labels/proficiency/melee_ja.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.harp.en {
|
&.harp.en {
|
||||||
background-image: url('/labels/proficiency/harp_en.png')
|
background-image: url('/labels/proficiency/harp_en.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.harp.ja {
|
&.harp.ja {
|
||||||
background-image: url('/labels/proficiency/harp_ja.png')
|
background-image: url('/labels/proficiency/harp_ja.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.gun.en {
|
&.gun.en {
|
||||||
background-image: url('/labels/proficiency/gun_en.png')
|
background-image: url('/labels/proficiency/gun_en.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.gun.ja {
|
&.gun.ja {
|
||||||
background-image: url('/labels/proficiency/gun_ja.png')
|
background-image: url('/labels/proficiency/gun_ja.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bow.en {
|
&.bow.en {
|
||||||
background-image: url('/labels/proficiency/bow_en.png')
|
background-image: url('/labels/proficiency/bow_en.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bow.ja {
|
&.bow.ja {
|
||||||
background-image: url('/labels/proficiency/bow_ja.png')
|
background-image: url('/labels/proficiency/bow_ja.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.katana.en {
|
&.katana.en {
|
||||||
background-image: url('/labels/proficiency/katana_en.png')
|
background-image: url('/labels/proficiency/katana_en.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.katana.ja {
|
&.katana.ja {
|
||||||
background-image: url('/labels/proficiency/katana_ja.png')
|
background-image: url('/labels/proficiency/katana_ja.png');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,7 @@ interface Props {
|
||||||
const WeaponLabelIcon = (props: Props) => {
|
const WeaponLabelIcon = (props: Props) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
return (
|
return <i className={`WeaponLabelIcon ${props.labelType} ${router.locale}`} />
|
||||||
<i className={`WeaponLabelIcon ${props.labelType} ${router.locale}`} />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WeaponLabelIcon
|
export default WeaponLabelIcon
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
gap: calc($unit / 2);
|
gap: calc($unit / 2);
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
color: $grey-50;
|
color: $grey-55;
|
||||||
font-size: $font-small;
|
font-size: $font-small;
|
||||||
margin-bottom: $unit;
|
margin-bottom: $unit;
|
||||||
}
|
}
|
||||||
|
|
@ -33,7 +33,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,21 +1,21 @@
|
||||||
import React, { useState } from "react"
|
import React, { useState } from 'react'
|
||||||
import { getCookie } from "cookies-next"
|
import { getCookie } from 'cookies-next'
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from 'next/router'
|
||||||
import { useTranslation } from "next-i18next"
|
import { useTranslation } from 'next-i18next'
|
||||||
import { AxiosResponse } from "axios"
|
import { AxiosResponse } from 'axios'
|
||||||
|
|
||||||
import * as Dialog from "@radix-ui/react-dialog"
|
import * as Dialog from '@radix-ui/react-dialog'
|
||||||
|
|
||||||
import AXSelect from "~components/AxSelect"
|
import AXSelect from '~components/AxSelect'
|
||||||
import ElementToggle from "~components/ElementToggle"
|
import ElementToggle from '~components/ElementToggle'
|
||||||
import WeaponKeyDropdown from "~components/WeaponKeyDropdown"
|
import WeaponKeyDropdown from '~components/WeaponKeyDropdown'
|
||||||
import Button from "~components/Button"
|
import Button from '~components/Button'
|
||||||
|
|
||||||
import api from "~utils/api"
|
import api from '~utils/api'
|
||||||
import { appState } from "~utils/appState"
|
import { appState } from '~utils/appState'
|
||||||
|
|
||||||
import CrossIcon from "~public/icons/Cross.svg"
|
import CrossIcon from '~public/icons/Cross.svg'
|
||||||
import "./index.scss"
|
import './index.scss'
|
||||||
|
|
||||||
interface GridWeaponObject {
|
interface GridWeaponObject {
|
||||||
weapon: {
|
weapon: {
|
||||||
|
|
@ -38,11 +38,11 @@ interface Props {
|
||||||
const WeaponModal = (props: Props) => {
|
const WeaponModal = (props: Props) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale =
|
const locale =
|
||||||
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
// Cookies
|
// Cookies
|
||||||
const cookie = getCookie("account")
|
const cookie = getCookie('account')
|
||||||
const accountData: AccountCookie = cookie
|
const accountData: AccountCookie = cookie
|
||||||
? JSON.parse(cookie as string)
|
? JSON.parse(cookie as string)
|
||||||
: null
|
: null
|
||||||
|
|
@ -134,7 +134,7 @@ const WeaponModal = (props: Props) => {
|
||||||
const elementSelect = () => {
|
const elementSelect = () => {
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<h3>{t("modals.weapon.subtitles.element")}</h3>
|
<h3>{t('modals.weapon.subtitles.element')}</h3>
|
||||||
<ElementToggle
|
<ElementToggle
|
||||||
currentElement={props.gridWeapon.element}
|
currentElement={props.gridWeapon.element}
|
||||||
sendValue={receiveElementValue}
|
sendValue={receiveElementValue}
|
||||||
|
|
@ -146,7 +146,7 @@ const WeaponModal = (props: Props) => {
|
||||||
const keySelect = () => {
|
const keySelect = () => {
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<h3>{t("modals.weapon.subtitles.weapon_keys")}</h3>
|
<h3>{t('modals.weapon.subtitles.weapon_keys')}</h3>
|
||||||
{[2, 3, 17, 22].includes(props.gridWeapon.object.series) ? (
|
{[2, 3, 17, 22].includes(props.gridWeapon.object.series) ? (
|
||||||
<WeaponKeyDropdown
|
<WeaponKeyDropdown
|
||||||
currentValue={
|
currentValue={
|
||||||
|
|
@ -159,7 +159,7 @@ const WeaponModal = (props: Props) => {
|
||||||
ref={weaponKey1Select}
|
ref={weaponKey1Select}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{[2, 3, 17].includes(props.gridWeapon.object.series) ? (
|
{[2, 3, 17].includes(props.gridWeapon.object.series) ? (
|
||||||
|
|
@ -174,7 +174,7 @@ const WeaponModal = (props: Props) => {
|
||||||
ref={weaponKey2Select}
|
ref={weaponKey2Select}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{props.gridWeapon.object.series == 17 ? (
|
{props.gridWeapon.object.series == 17 ? (
|
||||||
|
|
@ -189,7 +189,7 @@ const WeaponModal = (props: Props) => {
|
||||||
ref={weaponKey3Select}
|
ref={weaponKey3Select}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|
@ -198,7 +198,7 @@ const WeaponModal = (props: Props) => {
|
||||||
const axSelect = () => {
|
const axSelect = () => {
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<h3>{t("modals.weapon.subtitles.ax_skills")}</h3>
|
<h3>{t('modals.weapon.subtitles.ax_skills')}</h3>
|
||||||
<AXSelect
|
<AXSelect
|
||||||
axType={props.gridWeapon.object.ax}
|
axType={props.gridWeapon.object.ax}
|
||||||
currentSkills={props.gridWeapon.ax}
|
currentSkills={props.gridWeapon.ax}
|
||||||
|
|
@ -225,7 +225,7 @@ const WeaponModal = (props: Props) => {
|
||||||
<div className="DialogHeader">
|
<div className="DialogHeader">
|
||||||
<div className="DialogTop">
|
<div className="DialogTop">
|
||||||
<Dialog.Title className="SubTitle">
|
<Dialog.Title className="SubTitle">
|
||||||
{t("modals.weapon.title")}
|
{t('modals.weapon.title')}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<Dialog.Title className="DialogTitle">
|
<Dialog.Title className="DialogTitle">
|
||||||
{props.gridWeapon.object.name[locale]}
|
{props.gridWeapon.object.name[locale]}
|
||||||
|
|
@ -239,16 +239,16 @@ const WeaponModal = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mods">
|
<div className="mods">
|
||||||
{props.gridWeapon.object.element == 0 ? elementSelect() : ""}
|
{props.gridWeapon.object.element == 0 ? elementSelect() : ''}
|
||||||
{[2, 3, 17, 24].includes(props.gridWeapon.object.series)
|
{[2, 3, 17, 24].includes(props.gridWeapon.object.series)
|
||||||
? keySelect()
|
? keySelect()
|
||||||
: ""}
|
: ''}
|
||||||
{props.gridWeapon.object.ax > 0 ? axSelect() : ""}
|
{props.gridWeapon.object.ax > 0 ? axSelect() : ''}
|
||||||
<Button
|
<Button
|
||||||
onClick={updateWeapon}
|
onClick={updateWeapon}
|
||||||
disabled={props.gridWeapon.object.ax > 0 && !formValid}
|
disabled={props.gridWeapon.object.ax > 0 && !formValid}
|
||||||
>
|
>
|
||||||
{t("modals.weapon.buttons.confirm")}
|
{t('modals.weapon.buttons.confirm')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,16 @@
|
||||||
padding: $unit * 1.5;
|
padding: $unit * 1.5;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $grey-90;
|
background: var(--button-contained-bg);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
.Info h5 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
background: $grey-80;
|
background: var(--card-bg);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 72px;
|
height: 72px;
|
||||||
|
|
@ -21,10 +25,10 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
gap: calc($unit / 2);
|
gap: $unit-half;
|
||||||
|
|
||||||
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;
|
||||||
|
|
@ -37,11 +41,11 @@
|
||||||
|
|
||||||
.stars {
|
.stars {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: #FFA15E;
|
color: #ffa15e;
|
||||||
font-size: $font-xlarge;
|
font-size: $font-xlarge;
|
||||||
|
|
||||||
& > span {
|
& > span {
|
||||||
color: #65DAFF;
|
color: #65daff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue