Component cookie fixes

This commit is contained in:
Justin Edmund 2022-11-16 05:26:18 -08:00
parent 94c885513d
commit 61a762b29b
8 changed files with 1435 additions and 1290 deletions

View file

@ -1,211 +1,255 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from "react"
import { useCookies } from 'react-cookie' 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 = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en' const locale =
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
// Cookies // Cookies
const [cookies, setCookies] = useCookies() const cookie = getCookie("account")
const headers = (cookies.account != null) ? { const headers = {}
headers: { // cookies.account != null
'Authorization': `Bearer ${cookies.account.access_token}` // ? {
} // headers: {
} : {} // Authorization: `Bearer ${cookies.account.access_token}`,
// },
// State // }
const [open, setOpen] = useState(false) // : {}
const [picture, setPicture] = useState('')
const [language, setLanguage] = useState('')
const [gender, setGender] = useState(0)
const [privateProfile, setPrivateProfile] = useState(false)
// Refs // State
const pictureSelect = React.createRef<HTMLSelectElement>() const [open, setOpen] = useState(false)
const languageSelect = React.createRef<HTMLSelectElement>() const [picture, setPicture] = useState("")
const genderSelect = React.createRef<HTMLSelectElement>() const [language, setLanguage] = useState("")
const privateSelect = React.createRef<HTMLInputElement>() const [gender, setGender] = useState(0)
const [privateProfile, setPrivateProfile] = useState(false)
useEffect(() => { // Refs
if (cookies.user) setPicture(cookies.user.picture) const pictureSelect = React.createRef<HTMLSelectElement>()
if (cookies.user) setLanguage(cookies.user.language) const languageSelect = React.createRef<HTMLSelectElement>()
if (cookies.user) setGender(cookies.user.gender) const genderSelect = React.createRef<HTMLSelectElement>()
}, [cookies]) const privateSelect = React.createRef<HTMLInputElement>()
const pictureOptions = ( // useEffect(() => {
pictureData.sort((a, b) => (a.name.en > b.name.en) ? 1 : -1).map((item, i) => { // if (cookies.user) setPicture(cookies.user.picture)
return ( // if (cookies.user) setLanguage(cookies.user.language)
<option key={`picture-${i}`} value={item.filename}>{item.name[locale]}</option> // if (cookies.user) setGender(cookies.user.gender)
) // }, [cookies])
})
)
function handlePictureChange(event: React.ChangeEvent<HTMLSelectElement>) { const pictureOptions = pictureData
if (pictureSelect.current) .sort((a, b) => (a.name.en > b.name.en ? 1 : -1))
setPicture(pictureSelect.current.value) .map((item, i) => {
return (
<option key={`picture-${i}`} value={item.filename}>
{item.name[locale]}
</option>
)
})
function handlePictureChange(event: React.ChangeEvent<HTMLSelectElement>) {
if (pictureSelect.current) setPicture(pictureSelect.current.value)
}
function handleLanguageChange(event: React.ChangeEvent<HTMLSelectElement>) {
if (languageSelect.current) setLanguage(languageSelect.current.value)
}
function handleGenderChange(event: React.ChangeEvent<HTMLSelectElement>) {
if (genderSelect.current) setGender(parseInt(genderSelect.current.value))
}
function handlePrivateChange(checked: boolean) {
setPrivateProfile(checked)
}
function update(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault()
const object = {
user: {
picture: picture,
element: pictureData.find((i) => i.filename === picture)?.element,
language: language,
gender: gender,
private: privateProfile,
},
} }
function handleLanguageChange(event: React.ChangeEvent<HTMLSelectElement>) { // api.endpoints.users
if (languageSelect.current) // .update(cookies.account.user_id, object, headers)
setLanguage(languageSelect.current.value) // .then((response) => {
} // const user = response.data.user
function handleGenderChange(event: React.ChangeEvent<HTMLSelectElement>) { // const cookieObj = {
if (genderSelect.current) // picture: user.picture.picture,
setGender(parseInt(genderSelect.current.value)) // element: user.picture.element,
} // gender: user.gender,
// language: user.language,
// }
function handlePrivateChange(checked: boolean) { // setCookies("user", cookieObj, { path: "/" })
setPrivateProfile(checked)
}
function update(event: React.FormEvent<HTMLFormElement>) { // accountState.account.user = {
event.preventDefault() // id: user.id,
// username: user.username,
// picture: user.picture.picture,
// element: user.picture.element,
// gender: user.gender,
// }
const object = { // setOpen(false)
user: { // changeLanguage(user.language)
picture: picture, // })
element: pictureData.find(i => i.filename === picture)?.element, }
language: language,
gender: gender,
private: privateProfile
}
}
api.endpoints.users.update(cookies.account.user_id, object, headers) function changeLanguage(newLanguage: string) {
.then(response => { // if (newLanguage !== router.locale) {
const user = response.data.user // setCookies("NEXT_LOCALE", newLanguage, { path: "/" })
// router.push(router.asPath, undefined, { locale: newLanguage })
// }
}
const cookieObj = { function openChange(open: boolean) {
picture: user.picture.picture, setOpen(open)
element: user.picture.element, }
gender: user.gender,
language: user.language
}
setCookies('user', cookieObj, { path: '/'})
accountState.account.user = { return (
id: user.id, <Dialog.Root open={open} onOpenChange={openChange}>
username: user.username, <Dialog.Trigger asChild>
picture: user.picture.picture, <li className="MenuItem">
element: user.picture.element, <span>{t("menu.settings")}</span>
gender: user.gender </li>
} </Dialog.Trigger>
<Dialog.Portal>
<Dialog.Content
className="Account Dialog"
onOpenAutoFocus={(event) => event.preventDefault()}
>
<div className="DialogHeader">
<div className="DialogTop">
<Dialog.Title className="SubTitle">
{t("modals.settings.title")}
</Dialog.Title>
<Dialog.Title className="DialogTitle">
@{account.user?.username}
</Dialog.Title>
</div>
<Dialog.Close className="DialogClose" asChild>
<span>
<CrossIcon />
</span>
</Dialog.Close>
</div>
setOpen(false) <form onSubmit={update}>
changeLanguage(user.language) <div className="field">
}) <div className="left">
} <label>{t("modals.settings.labels.picture")}</label>
</div>
function changeLanguage(newLanguage: string) { <div
if (newLanguage !== router.locale) { className={`preview ${
setCookies('NEXT_LOCALE', newLanguage, { path: '/'}) pictureData.find((i) => i.filename === picture)?.element
router.push(router.asPath, undefined, { locale: newLanguage }) }`}
} >
} <img
alt="Profile preview"
function openChange(open: boolean) { srcSet={`/profile/${picture}.png,
setOpen(open)
}
return (
<Dialog.Root open={open} onOpenChange={openChange}>
<Dialog.Trigger asChild>
<li className="MenuItem">
<span>{t('menu.settings')}</span>
</li>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Content className="Account Dialog" onOpenAutoFocus={ (event) => event.preventDefault() }>
<div className="DialogHeader">
<div className="DialogTop">
<Dialog.Title className="SubTitle">{t('modals.settings.title')}</Dialog.Title>
<Dialog.Title className="DialogTitle">@{account.user?.username}</Dialog.Title>
</div>
<Dialog.Close className="DialogClose" asChild>
<span>
<CrossIcon />
</span>
</Dialog.Close>
</div>
<form onSubmit={update}>
<div className="field">
<div className="left">
<label>{t('modals.settings.labels.picture')}</label>
</div>
<div className={`preview ${pictureData.find(i => i.filename === picture)?.element}`}>
<img
alt="Profile preview"
srcSet={`/profile/${picture}.png,
/profile/${picture}@2x.png 2x`} /profile/${picture}@2x.png 2x`}
src={`/profile/${picture}.png`} src={`/profile/${picture}.png`}
/> />
</div> </div>
<select name="picture" onChange={handlePictureChange} value={picture} ref={pictureSelect}> <select
{pictureOptions} name="picture"
</select> onChange={handlePictureChange}
</div> value={picture}
<div className="field"> ref={pictureSelect}
<div className="left"> >
<label>{t('modals.settings.labels.gender')}</label> {pictureOptions}
</div> </select>
</div>
<div className="field">
<div className="left">
<label>{t("modals.settings.labels.gender")}</label>
</div>
<select name="gender" onChange={handleGenderChange} value={gender} ref={genderSelect}> <select
<option key="gran" value="0">{t('modals.settings.gender.gran')}</option> name="gender"
<option key="djeeta" value="1">{t('modals.settings.gender.djeeta')}</option> onChange={handleGenderChange}
</select> value={gender}
</div> ref={genderSelect}
<div className="field"> >
<div className="left"> <option key="gran" value="0">
<label>{t('modals.settings.labels.language')}</label> {t("modals.settings.gender.gran")}
</div> </option>
<option key="djeeta" value="1">
{t("modals.settings.gender.djeeta")}
</option>
</select>
</div>
<div className="field">
<div className="left">
<label>{t("modals.settings.labels.language")}</label>
</div>
<select name="language" onChange={handleLanguageChange} value={language} ref={languageSelect}> <select
<option key="en" value="en">{t('modals.settings.language.english')}</option> name="language"
<option key="jp" value="ja">{t('modals.settings.language.japanese')}</option> onChange={handleLanguageChange}
</select> value={language}
</div> ref={languageSelect}
<div className="field"> >
<div className="left"> <option key="en" value="en">
<label>{t('modals.settings.labels.private')}</label> {t("modals.settings.language.english")}
<p className={locale}>{t('modals.settings.descriptions.private')}</p> </option>
</div> <option key="jp" value="ja">
{t("modals.settings.language.japanese")}
</option>
</select>
</div>
<div className="field">
<div className="left">
<label>{t("modals.settings.labels.private")}</label>
<p className={locale}>
{t("modals.settings.descriptions.private")}
</p>
</div>
<Switch.Root className="Switch" onCheckedChange={handlePrivateChange} checked={privateProfile}> <Switch.Root
<Switch.Thumb className="Thumb" /> className="Switch"
</Switch.Root> onCheckedChange={handlePrivateChange}
</div> checked={privateProfile}
>
<Switch.Thumb className="Thumb" />
</Switch.Root>
</div>
<Button>{t('modals.settings.buttons.confirm')}</Button> <Button>{t("modals.settings.buttons.confirm")}</Button>
</form> </form>
</Dialog.Content> </Dialog.Content>
<Dialog.Overlay className="Overlay" /> <Dialog.Overlay className="Overlay" />
</Dialog.Portal> </Dialog.Portal>
</Dialog.Root> </Dialog.Root>
) )
} }
export default AccountModal export default AccountModal

View file

@ -1,187 +1,201 @@
import React, { useEffect, useState } from "react"
import { useRouter } from "next/router"
import { useSnapshot } from "valtio"
import { useTranslation } from "next-i18next"
import classNames from "classnames"
import React, { useEffect, useState } from 'react' import { accountState } from "~utils/accountState"
import { useRouter } from 'next/router' import { formatTimeAgo } from "~utils/timeAgo"
import { useSnapshot } from 'valtio'
import { useTranslation } from 'next-i18next'
import classNames from 'classnames'
import { accountState } from '~utils/accountState' import Button from "~components/Button"
import { formatTimeAgo } from '~utils/timeAgo' import { ButtonType } from "~utils/enums"
import Button from '~components/Button' import "./index.scss"
import { ButtonType } from '~utils/enums'
import './index.scss'
interface Props { interface Props {
shortcode: string shortcode: string
id: string id: string
name: string name: string
raid: Raid raid: Raid
grid: GridWeapon[] grid: GridWeapon[]
user?: User user?: User
favorited: boolean favorited: boolean
createdAt: Date createdAt: Date
displayUser?: boolean | false displayUser?: boolean | false
onClick: (shortcode: string) => void onClick: (shortcode: string) => void
onSave?: (partyId: string, favorited: boolean) => void onSave?: (partyId: string, favorited: boolean) => void
} }
const GridRep = (props: Props) => { const GridRep = (props: Props) => {
const numWeapons: number = 9 const numWeapons: number = 9
const { account } = useSnapshot(accountState) const { account } = useSnapshot(accountState)
const router = useRouter() const router = useRouter()
const { t } = useTranslation('common') const { t } = useTranslation("common")
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en' const locale =
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>>({})
const titleClass = classNames({ const titleClass = classNames({
'empty': !props.name empty: !props.name,
}) })
const raidClass = classNames({ const raidClass = classNames({
'raid': true, raid: true,
'empty': !props.raid empty: !props.raid,
}) })
const userClass = classNames({ const userClass = classNames({
'user': true, user: true,
'empty': !props.user empty: !props.user,
}) })
useEffect(() => { useEffect(() => {
const newWeapons = Array(numWeapons) const newWeapons = Array(numWeapons)
for (const [key, value] of Object.entries(props.grid)) { for (const [key, value] of Object.entries(props.grid)) {
if (value.position == -1) if (value.position == -1) setMainhand(value.object)
setMainhand(value.object) else if (!value.mainhand && value.position != null)
else if (!value.mainhand && value.position != null) newWeapons[value.position] = value.object
newWeapons[value.position] = value.object
}
setWeapons(newWeapons)
}, [props.grid])
function navigate() {
props.onClick(props.shortcode)
} }
function generateMainhandImage() { setWeapons(newWeapons)
let url = '' }, [props.grid])
if (mainhand) { function navigate() {
if (mainhand.element == 0 && props.grid[0].element) { props.onClick(props.shortcode)
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.granblue_id}_${props.grid[0].element}.jpg` }
} else {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.granblue_id}.jpg`
}
}
return (mainhand) ? function generateMainhandImage() {
<img alt={mainhand.name[locale]} src={url} /> : '' let url = ""
if (mainhand) {
if (mainhand.element == 0 && props.grid[0].element) {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.granblue_id}_${props.grid[0].element}.jpg`
} else {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.granblue_id}.jpg`
}
} }
function generateGridImage(position: number) { return mainhand && props.grid[0] ? (
let url = '' <img alt={mainhand.name[locale]} src={url} />
) : (
""
)
}
if (weapons[position]) { function generateGridImage(position: number) {
if (weapons[position].element == 0 && props.grid[position].element) { let url = ""
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapons[position]?.granblue_id}_${props.grid[position].element}.jpg`
} else {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapons[position]?.granblue_id}.jpg`
}
}
return (weapons[position]) ? if (weapons[position]) {
<img alt={weapons[position].name[locale]} src={url} /> : '' if (weapons[position].element == 0 && props.grid[position].element) {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapons[position]?.granblue_id}_${props.grid[position].element}.jpg`
} else {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapons[position]?.granblue_id}.jpg`
}
} }
function sendSaveData() { return weapons[position] ? (
if (props.onSave) <img alt={weapons[position].name[locale]} src={url} />
props.onSave(props.id, props.favorited) ) : (
} ""
)
}
const userImage = () => { function sendSaveData() {
if (props.user) if (props.onSave) props.onSave(props.id, props.favorited)
return ( }
<img
alt={props.user.picture.picture} const userImage = () => {
className={`profile ${props.user.picture.element}`} if (props.user)
srcSet={`/profile/${props.user.picture.picture}.png, return (
<img
alt={props.user.picture.picture}
className={`profile ${props.user.picture.element}`}
srcSet={`/profile/${props.user.picture.picture}.png,
/profile/${props.user.picture.picture}@2x.png 2x`} /profile/${props.user.picture.picture}@2x.png 2x`}
src={`/profile/${props.user.picture.picture}.png`} src={`/profile/${props.user.picture.picture}.png`}
/> />
)
else return <div className="no-user" />
}
const details = (
<div className="Details">
<h2 className={titleClass} onClick={navigate}>
{props.name ? props.name : t("no_title")}
</h2>
<div className="bottom">
<div className={raidClass}>
{props.raid ? props.raid.name[locale] : t("no_raid")}
</div>
<time className="last-updated" dateTime={props.createdAt.toISOString()}>
{formatTimeAgo(props.createdAt, locale)}
</time>
</div>
</div>
)
const detailsWithUsername = (
<div className="Details">
<div className="top">
<div className="info">
<h2 className={titleClass} onClick={navigate}>
{props.name ? props.name : t("no_title")}
</h2>
<div className={raidClass}>
{props.raid ? props.raid.name[locale] : t("no_raid")}
</div>
</div>
{account.authorized &&
((props.user && account.user && account.user.id !== props.user.id) ||
!props.user) ? (
<Button
active={props.favorited}
icon="save"
type={ButtonType.IconOnly}
onClick={sendSaveData}
/>
) : (
""
)}
</div>
<div className="bottom">
<div className={userClass}>
{userImage()}
{props.user ? props.user.username : t("no_user")}
</div>
<time className="last-updated" dateTime={props.createdAt.toISOString()}>
{formatTimeAgo(props.createdAt, locale)}
</time>
</div>
</div>
)
return (
<div className="GridRep">
{props.displayUser ? detailsWithUsername : details}
<div className="Grid" onClick={navigate}>
<div className="weapon grid_mainhand">{generateMainhandImage()}</div>
<ul className="grid_weapons">
{Array.from(Array(numWeapons)).map((x, i) => {
return (
<li
key={`${props.shortcode}-${i}`}
className="weapon grid_weapon"
>
{generateGridImage(i)}
</li>
) )
else })}
return (<div className="no-user" />) </ul>
} </div>
</div>
const details = ( )
<div className="Details">
<h2 className={titleClass} onClick={navigate}>{ (props.name) ? props.name : t('no_title') }</h2>
<div className="bottom">
<div className={raidClass}>{ (props.raid) ? props.raid.name[locale] : t('no_raid') }</div>
<time className="last-updated" dateTime={props.createdAt.toISOString()}>{formatTimeAgo(props.createdAt, locale)}</time>
</div>
</div>
)
const detailsWithUsername = (
<div className="Details">
<div className="top">
<div className="info">
<h2 className={titleClass} onClick={navigate}>{ (props.name) ? props.name : t('no_title') }</h2>
<div className={raidClass}>{ (props.raid) ? props.raid.name[locale] : t('no_raid') }</div>
</div>
{
(account.authorized && (
(props.user && account.user && account.user.id !== props.user.id)
|| (!props.user)
)) ?
<Button
active={props.favorited}
icon="save"
type={ButtonType.IconOnly}
onClick={sendSaveData} />
: ''
}
</div>
<div className="bottom">
<div className={userClass}>
{ userImage() }
{ (props.user) ? props.user.username : t('no_user') }
</div>
<time className="last-updated" dateTime={props.createdAt.toISOString()}>{formatTimeAgo(props.createdAt, locale)}</time>
</div>
</div>
)
return (
<div className="GridRep">
{ (props.displayUser) ? detailsWithUsername : details}
<div className="Grid" onClick={navigate}>
<div className="weapon grid_mainhand">
{generateMainhandImage()}
</div>
<ul className="grid_weapons">
{
Array.from(Array(numWeapons)).map((x, i) => {
return (
<li key={`${props.shortcode}-${i}`} className="weapon grid_weapon">
{generateGridImage(i)}
</li>
)
})
}
</ul>
</div>
</div>
)
} }
export default GridRep export default GridRep

View file

@ -1,15 +1,10 @@
.GridRepCollection { .GridRepCollection {
display: grid; display: grid;
grid-template-columns: auto auto auto; grid-template-columns: auto auto auto;
margin: 0 auto; margin: 0 auto;
opacity: 0; padding: 0;
padding: 0; width: fit-content;
width: fit-content; transition: opacity 0.14s ease-in-out;
transition: opacity 0.14s ease-in-out; // width: fit-content;
// width: fit-content; max-width: 996px;
max-width: 996px; }
&.visible {
opacity: 1;
}
}

View file

@ -1,24 +1,18 @@
import classNames from 'classnames' import classNames from "classnames"
import React from 'react' import React from "react"
import './index.scss' import "./index.scss"
interface Props { interface Props {
loading: boolean children: React.ReactNode
children: React.ReactNode
} }
const GridRepCollection = (props: Props) => { const GridRepCollection = (props: Props) => {
const classes = classNames({ const classes = classNames({
'GridRepCollection': true, GridRepCollection: true,
'visible': !props.loading })
})
return <div className={classes}>{props.children}</div>
return (
<div className={classes}>
{props.children}
</div>
)
} }
export default GridRepCollection export default GridRepCollection

View file

@ -1,213 +1,216 @@
import React, { useState } from 'react' import React, { useState } from "react"
import { useCookies } from 'react-cookie' import { setCookie } from "cookies-next"
import Router, { useRouter } from 'next/router' import Router, { useRouter } from "next/router"
import { useTranslation } from 'react-i18next' import { useTranslation } from "react-i18next"
import { AxiosResponse } from 'axios' import { AxiosResponse } from "axios"
import * as Dialog from '@radix-ui/react-dialog' import * as Dialog from "@radix-ui/react-dialog"
import api from '~utils/api' import api from "~utils/api"
import { accountState } from '~utils/accountState' import { accountState } from "~utils/accountState"
import Button from '~components/Button' import Button from "~components/Button"
import Fieldset from '~components/Fieldset' import Fieldset from "~components/Fieldset"
import CrossIcon from '~public/icons/Cross.svg' import CrossIcon from "~public/icons/Cross.svg"
import './index.scss' import "./index.scss"
interface Props {} interface Props {}
interface ErrorMap { interface ErrorMap {
[index: string]: string [index: string]: string
email: string email: string
password: string password: string
} }
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ const emailRegex =
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
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: "",
}) })
// Cookies // States
const [cookies, setCookies] = useCookies() const [open, setOpen] = useState(false)
// States // Set up form refs
const [open, setOpen] = useState(false) const emailInput: React.RefObject<HTMLInputElement> = React.createRef()
const passwordInput: React.RefObject<HTMLInputElement> = React.createRef()
const form: React.RefObject<HTMLInputElement>[] = [emailInput, passwordInput]
// Set up form refs function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
const emailInput: React.RefObject<HTMLInputElement> = React.createRef() const { name, value } = event.target
const passwordInput: React.RefObject<HTMLInputElement> = React.createRef() let newErrors = { ...errors }
const form: React.RefObject<HTMLInputElement>[] = [emailInput, passwordInput]
function handleChange(event: React.ChangeEvent<HTMLInputElement>) { switch (name) {
const { name, value } = event.target case "email":
let newErrors = {...errors} if (value.length == 0)
newErrors.email = t("modals.login.errors.empty_email")
else if (!emailRegex.test(value))
newErrors.email = t("modals.login.errors.invalid_email")
else newErrors.email = ""
break
switch(name) { case "password":
case 'email': newErrors.password =
if (value.length == 0) value.length == 0 ? t("modals.login.errors.empty_password") : ""
newErrors.email = t('modals.login.errors.empty_email') break
else if (!emailRegex.test(value))
newErrors.email = t('modals.login.errors.invalid_email')
else
newErrors.email = ''
break
case 'password': default:
newErrors.password = value.length == 0 break
? t('modals.login.errors.empty_password')
: ''
break
default:
break
}
setErrors(newErrors)
setFormValid(validateForm(newErrors))
} }
function validateForm(errors: ErrorMap) { setErrors(newErrors)
let valid = true setFormValid(validateForm(newErrors))
}
Object.values(form).forEach( function validateForm(errors: ErrorMap) {
(input) => input.current?.value.length == 0 && (valid = false) let valid = true
)
Object.values(errors).forEach( Object.values(form).forEach(
(error) => error.length > 0 && (valid = false) (input) => input.current?.value.length == 0 && (valid = false)
)
return valid
}
function login(event: React.FormEvent) {
event.preventDefault()
const body = {
email: emailInput.current?.value,
password: passwordInput.current?.value,
grant_type: 'password'
}
if (formValid) {
api.login(body)
.then(response => {
storeCookieInfo(response)
return response.data.user.id
})
.then(id => fetchUserInfo(id))
.then(infoResponse => storeUserInfo(infoResponse))
}
}
function fetchUserInfo(id: string) {
return api.userInfo(id)
}
function storeCookieInfo(response: AxiosResponse) {
const user = response.data.user
const cookieObj = {
user_id: user.id,
username: user.username,
access_token: response.data.access_token
}
setCookies('account', cookieObj, { path: '/' })
}
function storeUserInfo(response: AxiosResponse) {
const user = response.data.user
const cookieObj = {
picture: user.picture.picture,
element: user.picture.element,
language: user.language,
gender: user.gender
}
setCookies('user', cookieObj, { path: '/' })
accountState.account.user = {
id: user.id,
username: user.username,
picture: user.picture.picture,
element: user.picture.element,
gender: user.gender
}
accountState.account.authorized = true
setOpen(false)
changeLanguage(user.language)
}
function changeLanguage(newLanguage: string) {
if (newLanguage !== router.locale) {
setCookies('NEXT_LOCALE', newLanguage, { path: '/'})
router.push(router.asPath, undefined, { locale: newLanguage })
}
}
function openChange(open: boolean) {
setOpen(open)
setErrors({
email: '',
password: ''
})
}
return (
<Dialog.Root open={open} onOpenChange={openChange}>
<Dialog.Trigger asChild>
<li className="MenuItem">
<span>{t('menu.login')}</span>
</li>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Content className="Login Dialog" onOpenAutoFocus={ (event) => event.preventDefault() }>
<div className="DialogHeader">
<Dialog.Title className="DialogTitle">{t('modals.login.title')}</Dialog.Title>
<Dialog.Close className="DialogClose" asChild>
<span>
<CrossIcon />
</span>
</Dialog.Close>
</div>
<form className="form" onSubmit={login}>
<Fieldset
fieldName="email"
placeholder={t('modals.login.placeholders.email')}
onChange={handleChange}
error={errors.email}
ref={emailInput}
/>
<Fieldset
fieldName="password"
placeholder={t('modals.login.placeholders.password')}
onChange={handleChange}
error={errors.password}
ref={passwordInput}
/>
<Button>{t('modals.login.buttons.confirm')}</Button>
</form>
</Dialog.Content>
<Dialog.Overlay className="Overlay" />
</Dialog.Portal>
</Dialog.Root>
) )
Object.values(errors).forEach(
(error) => error.length > 0 && (valid = false)
)
return valid
}
function login(event: React.FormEvent) {
event.preventDefault()
const body = {
email: emailInput.current?.value,
password: passwordInput.current?.value,
grant_type: "password",
}
if (formValid) {
api
.login(body)
.then((response) => {
storeCookieInfo(response)
return response.data.user.id
})
.then((id) => fetchUserInfo(id))
.then((infoResponse) => storeUserInfo(infoResponse))
}
}
function fetchUserInfo(id: string) {
return api.userInfo(id)
}
function storeCookieInfo(response: AxiosResponse) {
const user = response.data.user
const cookieObj: AccountCookie = {
userId: user.id,
username: user.username,
token: response.data.access_token,
}
setCookie("account", cookieObj, { path: "/" })
}
function storeUserInfo(response: AxiosResponse) {
const user = response.data.user
const cookieObj: UserCookie = {
picture: user.picture.picture,
element: user.picture.element,
language: user.language,
gender: user.gender,
}
setCookie("user", cookieObj, { path: "/" })
accountState.account.user = {
id: user.id,
username: user.username,
picture: user.picture.picture,
element: user.picture.element,
gender: user.gender,
}
console.log("Authorizing account...")
accountState.account.authorized = true
setOpen(false)
changeLanguage(user.language)
}
function changeLanguage(newLanguage: string) {
if (newLanguage !== router.locale) {
setCookie("NEXT_LOCALE", newLanguage, { path: "/" })
router.push(router.asPath, undefined, { locale: newLanguage })
}
}
function openChange(open: boolean) {
setOpen(open)
setErrors({
email: "",
password: "",
})
}
return (
<Dialog.Root open={open} onOpenChange={openChange}>
<Dialog.Trigger asChild>
<li className="MenuItem">
<span>{t("menu.login")}</span>
</li>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Content
className="Login Dialog"
onOpenAutoFocus={(event) => event.preventDefault()}
>
<div className="DialogHeader">
<Dialog.Title className="DialogTitle">
{t("modals.login.title")}
</Dialog.Title>
<Dialog.Close className="DialogClose" asChild>
<span>
<CrossIcon />
</span>
</Dialog.Close>
</div>
<form className="form" onSubmit={login}>
<Fieldset
fieldName="email"
placeholder={t("modals.login.placeholders.email")}
onChange={handleChange}
error={errors.email}
ref={emailInput}
/>
<Fieldset
fieldName="password"
placeholder={t("modals.login.placeholders.password")}
onChange={handleChange}
error={errors.password}
ref={passwordInput}
/>
<Button>{t("modals.login.buttons.confirm")}</Button>
</form>
</Dialog.Content>
<Dialog.Overlay className="Overlay" />
</Dialog.Portal>
</Dialog.Root>
)
} }
export default LoginModal export default LoginModal

View file

@ -1,311 +1,348 @@
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from "react"
import { useCookies } from 'react-cookie' import { getCookie, setCookie } from "cookies-next"
import { useRouter } from 'next/router' import { useRouter } from "next/router"
import { useSnapshot } from 'valtio' import { useSnapshot } from "valtio"
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 { appState } from '~utils/appState' import { appState } from "~utils/appState"
import api from '~utils/api' import api from "~utils/api"
import * as Dialog from '@radix-ui/react-dialog' import * as Dialog from "@radix-ui/react-dialog"
import CharacterSearchFilterBar from '~components/CharacterSearchFilterBar' import CharacterSearchFilterBar from "~components/CharacterSearchFilterBar"
import WeaponSearchFilterBar from '~components/WeaponSearchFilterBar' import WeaponSearchFilterBar from "~components/WeaponSearchFilterBar"
import SummonSearchFilterBar from '~components/SummonSearchFilterBar' import SummonSearchFilterBar from "~components/SummonSearchFilterBar"
import 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 './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: Character | Weapon | Summon, position: number) => any send: (object: Character | Weapon | Summon, position: number) => any
placeholderText: string placeholderText: string
fromPosition: number fromPosition: number
object: 'weapons' | 'characters' | 'summons', object: "weapons" | "characters" | "summons"
children: React.ReactNode children: React.ReactNode
} }
const SearchModal = (props: Props) => { const SearchModal = (props: Props) => {
// Set up snapshot of app state // Set up snapshot of app state
let { grid, search } = useSnapshot(appState) let { grid, search } = useSnapshot(appState)
// Set up router // Set up router
const router = useRouter() const router = useRouter()
const locale = router.locale const locale = router.locale
// Set up translation // Set up translation
const { t } = useTranslation('common') const { t } = useTranslation("common")
// Set up cookies let searchInput = React.createRef<HTMLInputElement>()
const [cookies, setCookies] = useCookies() let scrollContainer = React.createRef<HTMLDivElement>()
let searchInput = React.createRef<HTMLInputElement>() const [firstLoad, setFirstLoad] = useState(true)
let scrollContainer = React.createRef<HTMLDivElement>() const [objects, setObjects] = useState<{
[id: number]: GridCharacter | GridWeapon | GridSummon
}>()
const [filters, setFilters] = useState<{ [key: string]: number[] }>()
const [open, setOpen] = useState(false)
const [query, setQuery] = useState("")
const [results, setResults] = useState<(Weapon | Summon | Character)[]>([])
const [firstLoad, setFirstLoad] = useState(true) // Pagination states
const [objects, setObjects] = useState<{[id: number]: GridCharacter | GridWeapon | GridSummon}>() const [recordCount, setRecordCount] = useState(0)
const [filters, setFilters] = useState<{ [key: string]: number[] }>() const [currentPage, setCurrentPage] = useState(1)
const [open, setOpen] = useState(false) const [totalPages, setTotalPages] = useState(1)
const [query, setQuery] = useState('')
const [results, setResults] = useState<(Weapon | Summon | Character)[]>([])
// Pagination states useEffect(() => {
const [recordCount, setRecordCount] = useState(0) setObjects(grid[props.object])
const [currentPage, setCurrentPage] = useState(1) }, [grid, props.object])
const [totalPages, setTotalPages] = useState(1)
useEffect(() => { useEffect(() => {
setObjects(grid[props.object]) if (searchInput.current) searchInput.current.focus()
}, [grid, props.object]) }, [searchInput])
useEffect(() => { function inputChanged(event: React.ChangeEvent<HTMLInputElement>) {
if (searchInput.current) const text = event.target.value
searchInput.current.focus() if (text.length) {
}, [searchInput]) setQuery(text)
} else {
setQuery("")
}
}
function inputChanged(event: React.ChangeEvent<HTMLInputElement>) { function fetchResults({ replace = false }: { replace?: boolean }) {
const text = event.target.value api
if (text.length) { .search({
setQuery(text) object: props.object,
query: query,
filters: filters,
locale: locale,
page: currentPage,
})
.then((response) => {
setTotalPages(response.data.total_pages)
setRecordCount(response.data.count)
if (replace) {
replaceResults(response.data.count, response.data.results)
} else { } else {
setQuery('') appendResults(response.data.results)
} }
} })
.catch((error) => {
function fetchResults({ replace = false }: { replace?: boolean }) { console.error(error)
api.search({ })
object: props.object, }
query: query,
filters: filters,
locale: locale,
page: currentPage
}).then(response => {
setTotalPages(response.data.total_pages)
setRecordCount(response.data.count)
if (replace) { function replaceResults(
replaceResults(response.data.count, response.data.results) count: number,
} else { list: Weapon[] | Summon[] | Character[]
appendResults(response.data.results) ) {
} if (count > 0) {
}).catch(error => { setResults(list)
console.error(error) } else {
}) setResults([])
}
}
function appendResults(list: Weapon[] | Summon[] | Character[]) {
setResults([...results, ...list])
}
function storeRecentResult(result: Character | Weapon | Summon) {
const key = `recent_${props.object}`
const cookie = getCookie(key)
const cookieObj: Character[] | Weapon[] | Summon[] = cookie
? JSON.parse(cookie as string)
: []
let recents: Character[] | Weapon[] | Summon[] = []
if (props.object === "weapons") {
recents = cloneDeep(cookieObj as Weapon[]) || []
if (!recents.find((item) => item.granblue_id === result.granblue_id)) {
recents.unshift(result as Weapon)
}
} else if (props.object === "summons") {
recents = cloneDeep(cookieObj as Summon[]) || []
if (!recents.find((item) => item.granblue_id === result.granblue_id)) {
recents.unshift(result as Summon)
}
} }
function replaceResults(count: number, list: Weapon[] | Summon[] | Character[]) { if (recents && recents.length > 5) recents.pop()
if (count > 0) { setCookie(`recent_${props.object}`, recents, { path: "/" })
setResults(list) sendData(result)
} else { }
setResults([])
} function sendData(result: Character | Weapon | Summon) {
props.send(result, props.fromPosition)
openChange()
}
function receiveFilters(filters: { [key: string]: number[] }) {
setCurrentPage(1)
setResults([])
setFilters(filters)
}
useEffect(() => {
// Current page changed
if (open && currentPage > 1) {
fetchResults({ replace: false })
} else if (open && currentPage == 1) {
fetchResults({ replace: true })
} }
}, [currentPage])
function appendResults(list: Weapon[] | Summon[] | Character[]) { useEffect(() => {
setResults([...results, ...list]) // Filters changed
} const key = `recent_${props.object}`
const cookie = getCookie(key)
const cookieObj: Weapon[] | Summon[] | Character[] = cookie
? JSON.parse(cookie as string)
: []
function storeRecentResult(result: Character | Weapon | Summon) { if (open) {
const key = `recent_${props.object}` if (firstLoad && cookieObj && cookieObj.length > 0) {
let recents: Character[] | Weapon[] | Summon[] = [] setResults(cookieObj)
setRecordCount(cookieObj.length)
if (props.object === "weapons") { setFirstLoad(false)
recents = cloneDeep(cookies[key] as Weapon[]) || [] } else {
if (!recents.find(item => item.granblue_id === result.granblue_id)) {
recents.unshift(result as Weapon)
}
} else if (props.object === "summons") {
recents = cloneDeep(cookies[key] as Summon[]) || []
if (!recents.find(item => item.granblue_id === result.granblue_id)) {
recents.unshift(result as Summon)
}
}
if (recents && recents.length > 5) recents.pop()
setCookies(`recent_${props.object}`, recents, { path: '/' })
sendData(result)
}
function sendData(result: Character | Weapon | Summon) {
props.send(result, props.fromPosition)
openChange()
}
function receiveFilters(filters: { [key: string]: number[] }) {
setCurrentPage(1) setCurrentPage(1)
setResults([]) fetchResults({ replace: true })
setFilters(filters) }
} }
}, [filters])
useEffect(() => { useEffect(() => {
// Current page changed // Query changed
if (open && currentPage > 1) { if (open && query.length != 1) {
fetchResults({ replace: false }) setCurrentPage(1)
} else if (open && currentPage == 1) { fetchResults({ replace: true })
fetchResults({ replace: true })
}
}, [currentPage])
useEffect(() => {
// Filters changed
const key = `recent_${props.object}`
if (open) {
if (firstLoad && cookies[key] && cookies[key].length > 0) {
setResults(cookies[key])
setRecordCount(cookies[key].length)
setFirstLoad(false)
} else {
setCurrentPage(1)
fetchResults({ replace: true })
}
}
}, [filters])
useEffect(() => {
// Query changed
if (open && query.length != 1) {
setCurrentPage(1)
fetchResults({ replace: true })
}
}, [query])
function renderResults() {
let jsx
switch(props.object) {
case 'weapons':
jsx = renderWeaponSearchResults()
break
case 'summons':
jsx = renderSummonSearchResults(results)
break
case 'characters':
jsx = renderCharacterSearchResults(results)
break
}
return (
<InfiniteScroll
dataLength={ (results && results.length > 0) ? results.length : 0}
next={ () => setCurrentPage(currentPage + 1) }
hasMore={totalPages > currentPage}
scrollableTarget="Results"
loader={<div className="footer">Loading...</div>}>
{jsx}
</InfiniteScroll>
)
} }
}, [query])
function renderWeaponSearchResults() { function renderResults() {
let jsx: React.ReactNode let jsx
const castResults: Weapon[] = results as Weapon[]
if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: Weapon) => {
return <WeaponResult
key={result.id}
data={result}
onClick={() => { storeRecentResult(result) }}
/>
})
}
return jsx switch (props.object) {
} case "weapons":
jsx = renderWeaponSearchResults()
function renderSummonSearchResults(results: { [key: string]: any }) { break
let jsx: React.ReactNode case "summons":
jsx = renderSummonSearchResults(results)
const castResults: Summon[] = results as Summon[] break
if (castResults && Object.keys(castResults).length > 0) { case "characters":
jsx = castResults.map((result: Summon) => { jsx = renderCharacterSearchResults(results)
return <SummonResult break
key={result.id}
data={result}
onClick={() => { storeRecentResult(result) }}
/>
})
}
return jsx
}
function renderCharacterSearchResults(results: { [key: string]: any }) {
let jsx: React.ReactNode
const castResults: Character[] = results as Character[]
if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: Character) => {
return <CharacterResult
key={result.id}
data={result}
onClick={() => { storeRecentResult(result) }}
/>
})
}
return jsx
}
function openChange() {
if (open) {
setQuery('')
setFirstLoad(true)
setResults([])
setRecordCount(0)
setCurrentPage(1)
setOpen(false)
} else {
setOpen(true)
}
} }
return ( return (
<Dialog.Root open={open} onOpenChange={openChange}> <InfiniteScroll
<Dialog.Trigger asChild> dataLength={results && results.length > 0 ? results.length : 0}
{props.children} next={() => setCurrentPage(currentPage + 1)}
</Dialog.Trigger> hasMore={totalPages > currentPage}
<Dialog.Portal> scrollableTarget="Results"
<Dialog.Content className="Search Dialog"> loader={<div className="footer">Loading...</div>}
<div id="Header"> >
<div id="Bar"> {jsx}
<label className="search_label" htmlFor="search_input"> </InfiniteScroll>
<input
autoComplete="off"
type="text"
name="query"
className="Input"
id="search_input"
ref={searchInput}
value={query}
placeholder={props.placeholderText}
onChange={inputChanged}
/>
</label>
<Dialog.Close className="DialogClose" onClick={openChange}>
<CrossIcon />
</Dialog.Close>
</div>
{ (props.object === 'characters') ? <CharacterSearchFilterBar sendFilters={receiveFilters} /> : '' }
{ (props.object === 'weapons') ? <WeaponSearchFilterBar sendFilters={receiveFilters} /> : '' }
{ (props.object === 'summons') ? <SummonSearchFilterBar sendFilters={receiveFilters} /> : '' }
</div>
<div id="Results" ref={scrollContainer}>
<h5 className="total">{t('search.result_count', { "record_count": recordCount })}</h5>
{ (open) ? renderResults() : ''}
</div>
</Dialog.Content>
<Dialog.Overlay className="Overlay" />
</Dialog.Portal>
</Dialog.Root>
) )
}
function renderWeaponSearchResults() {
let jsx: React.ReactNode
const castResults: Weapon[] = results as Weapon[]
if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: Weapon) => {
return (
<WeaponResult
key={result.id}
data={result}
onClick={() => {
storeRecentResult(result)
}}
/>
)
})
}
return jsx
}
function renderSummonSearchResults(results: { [key: string]: any }) {
let jsx: React.ReactNode
const castResults: Summon[] = results as Summon[]
if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: Summon) => {
return (
<SummonResult
key={result.id}
data={result}
onClick={() => {
storeRecentResult(result)
}}
/>
)
})
}
return jsx
}
function renderCharacterSearchResults(results: { [key: string]: any }) {
let jsx: React.ReactNode
const castResults: Character[] = results as Character[]
if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: Character) => {
return (
<CharacterResult
key={result.id}
data={result}
onClick={() => {
storeRecentResult(result)
}}
/>
)
})
}
return jsx
}
function openChange() {
if (open) {
setQuery("")
setFirstLoad(true)
setResults([])
setRecordCount(0)
setCurrentPage(1)
setOpen(false)
} else {
setOpen(true)
}
}
return (
<Dialog.Root open={open} onOpenChange={openChange}>
<Dialog.Trigger asChild>{props.children}</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Content className="Search Dialog">
<div id="Header">
<div id="Bar">
<label className="search_label" htmlFor="search_input">
<input
autoComplete="off"
type="text"
name="query"
className="Input"
id="search_input"
ref={searchInput}
value={query}
placeholder={props.placeholderText}
onChange={inputChanged}
/>
</label>
<Dialog.Close className="DialogClose" onClick={openChange}>
<CrossIcon />
</Dialog.Close>
</div>
{props.object === "characters" ? (
<CharacterSearchFilterBar sendFilters={receiveFilters} />
) : (
""
)}
{props.object === "weapons" ? (
<WeaponSearchFilterBar sendFilters={receiveFilters} />
) : (
""
)}
{props.object === "summons" ? (
<SummonSearchFilterBar sendFilters={receiveFilters} />
) : (
""
)}
</div>
<div id="Results" ref={scrollContainer}>
<h5 className="total">
{t("search.result_count", { record_count: recordCount })}
</h5>
{open ? renderResults() : ""}
</div>
</Dialog.Content>
<Dialog.Overlay className="Overlay" />
</Dialog.Portal>
</Dialog.Root>
)
} }
export default SearchModal export default SearchModal

View file

@ -1,306 +1,324 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from "react"
import Link from 'next/link' import Link from "next/link"
import { useCookies } from 'react-cookie' import { setCookie } from "cookies-next"
import { useRouter } from 'next/router' import { useRouter } from "next/router"
import { Trans, useTranslation } from 'next-i18next' import { Trans, useTranslation } from "next-i18next"
import { AxiosResponse } from 'axios' import { AxiosResponse } from "axios"
import * as Dialog from '@radix-ui/react-dialog' import * as Dialog from "@radix-ui/react-dialog"
import api from '~utils/api' import api from "~utils/api"
import { accountState } from '~utils/accountState' import { accountState } from "~utils/accountState"
import Button from '~components/Button' import Button from "~components/Button"
import Fieldset from '~components/Fieldset' import Fieldset from "~components/Fieldset"
import CrossIcon from '~public/icons/Cross.svg' import CrossIcon from "~public/icons/Cross.svg"
import './index.scss' import "./index.scss"
interface Props {} interface Props {}
interface ErrorMap { interface ErrorMap {
[index: string]: string [index: string]: string
username: string username: string
email: string email: string
password: string password: string
passwordConfirmation: string passwordConfirmation: string
} }
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ const emailRegex =
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
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
const [formValid, setFormValid] = useState(false)
const [errors, setErrors] = useState<ErrorMap>({
username: '',
email: '',
password: '',
passwordConfirmation: ''
})
// Cookies // Set up form states and error handling
const [cookies, setCookies] = useCookies() const [formValid, setFormValid] = useState(false)
const [errors, setErrors] = useState<ErrorMap>({
username: "",
email: "",
password: "",
passwordConfirmation: "",
})
// States // States
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
// Set up form refs
const usernameInput = React.createRef<HTMLInputElement>()
const emailInput = React.createRef<HTMLInputElement>()
const passwordInput = React.createRef<HTMLInputElement>()
const passwordConfirmationInput = React.createRef<HTMLInputElement>()
const form = [usernameInput, emailInput, passwordInput, passwordConfirmationInput]
function register(event: React.FormEvent) { // Set up form refs
event.preventDefault() const usernameInput = React.createRef<HTMLInputElement>()
const emailInput = React.createRef<HTMLInputElement>()
const passwordInput = React.createRef<HTMLInputElement>()
const passwordConfirmationInput = React.createRef<HTMLInputElement>()
const form = [
usernameInput,
emailInput,
passwordInput,
passwordConfirmationInput,
]
const body = { function register(event: React.FormEvent) {
user: { event.preventDefault()
username: usernameInput.current?.value,
email: emailInput.current?.value,
password: passwordInput.current?.value,
password_confirmation: passwordConfirmationInput.current?.value,
language: router.locale
}
}
if (formValid) const body = {
api.endpoints.users.create(body) user: {
.then(response => { username: usernameInput.current?.value,
storeCookieInfo(response) email: emailInput.current?.value,
return response.data.user.user_id password: passwordInput.current?.value,
}) password_confirmation: passwordConfirmationInput.current?.value,
.then(id => fetchUserInfo(id)) language: router.locale,
.then(infoResponse => storeUserInfo(infoResponse)) },
} }
function storeCookieInfo(response: AxiosResponse) { if (formValid)
const user = response.data.user api.endpoints.users
.create(body)
const cookieObj = { .then((response) => {
user_id: user.user_id, storeCookieInfo(response)
username: user.username, return response.data.user.user_id
access_token: user.token
}
setCookies('account', cookieObj, { path: '/'})
}
function fetchUserInfo(id: string) {
return api.userInfo(id)
}
function storeUserInfo(response: AxiosResponse) {
const user = response.data.user
const cookieObj = {
picture: user.picture.picture,
element: user.picture.element,
language: user.language,
gender: user.gender
}
// TODO: Set language
setCookies('user', cookieObj, { path: '/'})
accountState.account.user = {
id: user.id,
username: user.username,
picture: user.picture.picture,
element: user.picture.element,
gender: user.gender
}
accountState.account.authorized = true
setOpen(false)
}
function handleNameChange(event: React.ChangeEvent<HTMLInputElement>) {
event.preventDefault()
const fieldName = event.target.name
const value = event.target.value
if (value.length >= 3) {
api.check(fieldName, value)
.then((response) => {
processNameCheck(fieldName, value, response.data.available)
}, (error) => {
console.error(error)
})
} else {
validateName(fieldName, value)
}
}
function processNameCheck(fieldName: string, value: string, available: boolean) {
const newErrors = {...errors}
if (available) {
// Continue checking for errors
newErrors[fieldName] = ''
setErrors(newErrors)
setFormValid(true)
validateName(fieldName, value)
} else {
newErrors[fieldName] = t('modals.signup.errors.field_in_use', { field: fieldName})
setErrors(newErrors)
setFormValid(false)
}
}
function validateName(fieldName: string, value: string) {
let newErrors = {...errors}
switch(fieldName) {
case 'username':
if (value.length < 3)
newErrors.username = t('modals.signup.errors.username_too_short')
else if (value.length > 20)
newErrors.username = t('modals.signup.errors.username_too_long')
else
newErrors.username = ''
break
case 'email':
newErrors.email = emailRegex.test(value)
? ''
: t('modals.signup.errors.invalid_email')
break
default:
break
}
setFormValid(validateForm(newErrors))
}
function handlePasswordChange(event: React.ChangeEvent<HTMLInputElement>) {
event.preventDefault()
const { name, value } = event.target
let newErrors = {...errors}
switch(name) {
case 'password':
newErrors.password = passwordInput.current?.value.includes(usernameInput.current?.value!)
? t('modals.signup.errors.password_contains_username')
: ''
break
case 'password':
newErrors.password = value.length < 8
? t('modals.signup.errors.password_too_short')
: ''
break
case 'confirm_password':
newErrors.passwordConfirmation = passwordInput.current?.value === passwordConfirmationInput.current?.value
? ''
: t('modals.signup.errors.passwords_dont_match')
break
default:
break
}
setFormValid(validateForm(newErrors))
}
function validateForm(errors: ErrorMap) {
let valid = true
Object.values(form).forEach(
(input) => input.current?.value.length == 0 && (valid = false)
)
Object.values(errors).forEach(
(error) => error.length > 0 && (valid = false)
)
return valid
}
function openChange(open: boolean) {
setOpen(open)
setErrors({
username: '',
email: '',
password: '',
passwordConfirmation: ''
}) })
.then((id) => fetchUserInfo(id))
.then((infoResponse) => storeUserInfo(infoResponse))
}
function storeCookieInfo(response: AxiosResponse) {
const user = response.data.user
const cookieObj: AccountCookie = {
userId: user.user_id,
username: user.username,
token: user.token,
} }
return ( setCookie("account", cookieObj, { path: "/" })
<Dialog.Root open={open} onOpenChange={openChange}> }
<Dialog.Trigger asChild>
<li className="MenuItem">
<span>{t('menu.signup')}</span>
</li>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Content className="Signup Dialog" onOpenAutoFocus={ (event) => event.preventDefault() }>
<div className="DialogHeader">
<Dialog.Title className="DialogTitle">{t('modals.signup.title')}</Dialog.Title>
<Dialog.Close className="DialogClose" asChild>
<span>
<CrossIcon />
</span>
</Dialog.Close>
</div>
<form className="form" onSubmit={register}> function fetchUserInfo(id: string) {
<Fieldset return api.userInfo(id)
fieldName="username" }
placeholder={t('modals.signup.placeholders.username')}
onChange={handleNameChange}
error={errors.username}
ref={usernameInput}
/>
<Fieldset function storeUserInfo(response: AxiosResponse) {
fieldName="email" const user = response.data.user
placeholder={t('modals.signup.placeholders.email')}
onChange={handleNameChange}
error={errors.email}
ref={emailInput}
/>
<Fieldset const cookieObj: UserCookie = {
fieldName="password" picture: user.picture.picture,
placeholder={t('modals.signup.placeholders.password')} element: user.picture.element,
onChange={handlePasswordChange} language: user.language,
error={errors.password} gender: user.gender,
ref={passwordInput} }
/>
<Fieldset // TODO: Set language
fieldName="confirm_password" setCookie("user", cookieObj, { path: "/" })
placeholder={t('modals.signup.placeholders.password_confirm')}
onChange={handlePasswordChange}
error={errors.passwordConfirmation}
ref={passwordConfirmationInput}
/>
<Button>{t('modals.signup.buttons.confirm')}</Button> accountState.account.user = {
id: user.id,
username: user.username,
picture: user.picture.picture,
element: user.picture.element,
gender: user.gender,
}
<Dialog.Description className="terms"> accountState.account.authorized = true
{/* <Trans i18nKey="modals.signup.agreement"> setOpen(false)
}
function handleNameChange(event: React.ChangeEvent<HTMLInputElement>) {
event.preventDefault()
const fieldName = event.target.name
const value = event.target.value
if (value.length >= 3) {
api.check(fieldName, value).then(
(response) => {
processNameCheck(fieldName, value, response.data.available)
},
(error) => {
console.error(error)
}
)
} else {
validateName(fieldName, value)
}
}
function processNameCheck(
fieldName: string,
value: string,
available: boolean
) {
const newErrors = { ...errors }
if (available) {
// Continue checking for errors
newErrors[fieldName] = ""
setErrors(newErrors)
setFormValid(true)
validateName(fieldName, value)
} else {
newErrors[fieldName] = t("modals.signup.errors.field_in_use", {
field: fieldName,
})
setErrors(newErrors)
setFormValid(false)
}
}
function validateName(fieldName: string, value: string) {
let newErrors = { ...errors }
switch (fieldName) {
case "username":
if (value.length < 3)
newErrors.username = t("modals.signup.errors.username_too_short")
else if (value.length > 20)
newErrors.username = t("modals.signup.errors.username_too_long")
else newErrors.username = ""
break
case "email":
newErrors.email = emailRegex.test(value)
? ""
: t("modals.signup.errors.invalid_email")
break
default:
break
}
setFormValid(validateForm(newErrors))
}
function handlePasswordChange(event: React.ChangeEvent<HTMLInputElement>) {
event.preventDefault()
const { name, value } = event.target
let newErrors = { ...errors }
switch (name) {
case "password":
newErrors.password = passwordInput.current?.value.includes(
usernameInput.current?.value!
)
? t("modals.signup.errors.password_contains_username")
: ""
break
case "password":
newErrors.password =
value.length < 8 ? t("modals.signup.errors.password_too_short") : ""
break
case "confirm_password":
newErrors.passwordConfirmation =
passwordInput.current?.value ===
passwordConfirmationInput.current?.value
? ""
: t("modals.signup.errors.passwords_dont_match")
break
default:
break
}
setFormValid(validateForm(newErrors))
}
function validateForm(errors: ErrorMap) {
let valid = true
Object.values(form).forEach(
(input) => input.current?.value.length == 0 && (valid = false)
)
Object.values(errors).forEach(
(error) => error.length > 0 && (valid = false)
)
return valid
}
function openChange(open: boolean) {
setOpen(open)
setErrors({
username: "",
email: "",
password: "",
passwordConfirmation: "",
})
}
return (
<Dialog.Root open={open} onOpenChange={openChange}>
<Dialog.Trigger asChild>
<li className="MenuItem">
<span>{t("menu.signup")}</span>
</li>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Content
className="Signup Dialog"
onOpenAutoFocus={(event) => event.preventDefault()}
>
<div className="DialogHeader">
<Dialog.Title className="DialogTitle">
{t("modals.signup.title")}
</Dialog.Title>
<Dialog.Close className="DialogClose" asChild>
<span>
<CrossIcon />
</span>
</Dialog.Close>
</div>
<form className="form" onSubmit={register}>
<Fieldset
fieldName="username"
placeholder={t("modals.signup.placeholders.username")}
onChange={handleNameChange}
error={errors.username}
ref={usernameInput}
/>
<Fieldset
fieldName="email"
placeholder={t("modals.signup.placeholders.email")}
onChange={handleNameChange}
error={errors.email}
ref={emailInput}
/>
<Fieldset
fieldName="password"
placeholder={t("modals.signup.placeholders.password")}
onChange={handlePasswordChange}
error={errors.password}
ref={passwordInput}
/>
<Fieldset
fieldName="confirm_password"
placeholder={t("modals.signup.placeholders.password_confirm")}
onChange={handlePasswordChange}
error={errors.passwordConfirmation}
ref={passwordConfirmationInput}
/>
<Button>{t("modals.signup.buttons.confirm")}</Button>
<Dialog.Description className="terms">
{/* <Trans i18nKey="modals.signup.agreement">
By signing up, I agree to the <Link href="/privacy"><span>Privacy Policy</span></Link><Link href="/usage"><span>Usage Guidelines</span></Link>. By signing up, I agree to the <Link href="/privacy"><span>Privacy Policy</span></Link><Link href="/usage"><span>Usage Guidelines</span></Link>.
</Trans> */} </Trans> */}
</Dialog.Description> </Dialog.Description>
</form> </form>
</Dialog.Content> </Dialog.Content>
<Dialog.Overlay className="Overlay" /> <Dialog.Overlay className="Overlay" />
</Dialog.Portal> </Dialog.Portal>
</Dialog.Root> </Dialog.Root>
) )
} }
export default SignupModal
export default SignupModal

View file

@ -1,223 +1,263 @@
import React, { useState } from 'react' import React, { useState } from "react"
import { useCookies } from 'react-cookie' // import { useCookies } from 'react-cookie'
import { useRouter } from 'next/router' import { useRouter } from "next/router"
import { useTranslation } from 'next-i18next' import { useTranslation } from "next-i18next"
import { AxiosResponse } from 'axios' import { AxiosResponse } from "axios"
import * as Dialog from '@radix-ui/react-dialog' import * as Dialog from "@radix-ui/react-dialog"
import AXSelect from '~components/AxSelect' import AXSelect from "~components/AxSelect"
import ElementToggle from '~components/ElementToggle' import ElementToggle from "~components/ElementToggle"
import WeaponKeyDropdown from '~components/WeaponKeyDropdown' import WeaponKeyDropdown from "~components/WeaponKeyDropdown"
import Button from '~components/Button' import Button from "~components/Button"
import api from '~utils/api' import api from "~utils/api"
import { appState } from '~utils/appState' import { appState } from "~utils/appState"
import CrossIcon from '~public/icons/Cross.svg' import CrossIcon from "~public/icons/Cross.svg"
import './index.scss' import "./index.scss"
interface GridWeaponObject { interface GridWeaponObject {
weapon: { weapon: {
element?: number element?: number
weapon_key1_id?: string weapon_key1_id?: string
weapon_key2_id?: string weapon_key2_id?: string
weapon_key3_id?: string weapon_key3_id?: string
ax_modifier1?: number ax_modifier1?: number
ax_modifier2?: number ax_modifier2?: number
ax_strength1?: number ax_strength1?: number
ax_strength2?: number ax_strength2?: number
} }
} }
interface Props { interface Props {
gridWeapon: GridWeapon gridWeapon: GridWeapon
children: React.ReactNode children: React.ReactNode
} }
const WeaponModal = (props: Props) => { const WeaponModal = (props: Props) => {
const router = useRouter() const router = useRouter()
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en' const locale =
const { t } = useTranslation('common') router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
const { t } = useTranslation("common")
// Cookies
const [cookies] = useCookies(['account']) // Cookies
const headers = (cookies.account != null) ? { const [cookies] = useCookies(["account"])
headers: { const headers =
'Authorization': `Bearer ${cookies.account.access_token}` cookies.account != null
? {
headers: {
Authorization: `Bearer ${cookies.account.access_token}`,
},
} }
} : {} : {}
// Refs
const weaponKey1Select = React.createRef<HTMLSelectElement>()
const weaponKey2Select = React.createRef<HTMLSelectElement>()
const weaponKey3Select = React.createRef<HTMLSelectElement>()
// State // Refs
const [open, setOpen] = useState(false) const weaponKey1Select = React.createRef<HTMLSelectElement>()
const [formValid, setFormValid] = useState(false) const weaponKey2Select = React.createRef<HTMLSelectElement>()
const weaponKey3Select = React.createRef<HTMLSelectElement>()
const [element, setElement] = useState(-1) // State
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1) const [open, setOpen] = useState(false)
const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1) const [formValid, setFormValid] = useState(false)
const [primaryAxValue, setPrimaryAxValue] = useState(0.0)
const [secondaryAxValue, setSecondaryAxValue] = useState(0.0)
function receiveAxValues(primaryAxModifier: number, primaryAxValue: number, secondaryAxModifier: number, secondaryAxValue: number) {
setPrimaryAxModifier(primaryAxModifier)
setSecondaryAxModifier(secondaryAxModifier)
setPrimaryAxValue(primaryAxValue) const [element, setElement] = useState(-1)
setSecondaryAxValue(secondaryAxValue) const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1)
const [primaryAxValue, setPrimaryAxValue] = useState(0.0)
const [secondaryAxValue, setSecondaryAxValue] = useState(0.0)
function receiveAxValues(
primaryAxModifier: number,
primaryAxValue: number,
secondaryAxModifier: number,
secondaryAxValue: number
) {
setPrimaryAxModifier(primaryAxModifier)
setSecondaryAxModifier(secondaryAxModifier)
setPrimaryAxValue(primaryAxValue)
setSecondaryAxValue(secondaryAxValue)
}
function receiveAxValidity(isValid: boolean) {
setFormValid(isValid)
}
function receiveElementValue(element: string) {
setElement(parseInt(element))
}
function prepareObject() {
let object: GridWeaponObject = { weapon: {} }
if (props.gridWeapon.object.element == 0) object.weapon.element = element
if ([2, 3, 17, 24].includes(props.gridWeapon.object.series))
object.weapon.weapon_key1_id = weaponKey1Select.current?.value
if ([2, 3, 17].includes(props.gridWeapon.object.series))
object.weapon.weapon_key2_id = weaponKey2Select.current?.value
if (props.gridWeapon.object.series == 17)
object.weapon.weapon_key3_id = weaponKey3Select.current?.value
if (props.gridWeapon.object.ax > 0) {
object.weapon.ax_modifier1 = primaryAxModifier
object.weapon.ax_modifier2 = secondaryAxModifier
object.weapon.ax_strength1 = primaryAxValue
object.weapon.ax_strength2 = secondaryAxValue
} }
function receiveAxValidity(isValid: boolean) { return object
setFormValid(isValid) }
}
function receiveElementValue(element: string) { async function updateWeapon() {
setElement(parseInt(element)) const updateObject = prepareObject()
} return await api.endpoints.grid_weapons
.update(props.gridWeapon.id, updateObject, headers)
.then((response) => processResult(response))
.catch((error) => processError(error))
}
function prepareObject() { function processResult(response: AxiosResponse) {
let object: GridWeaponObject = { weapon: {} } const gridWeapon: GridWeapon = response.data.grid_weapon
if (props.gridWeapon.object.element == 0) if (gridWeapon.mainhand) appState.grid.weapons.mainWeapon = gridWeapon
object.weapon.element = element else appState.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon
if ([2, 3, 17, 24].includes(props.gridWeapon.object.series)) setOpen(false)
object.weapon.weapon_key1_id = weaponKey1Select.current?.value }
if ([2, 3, 17].includes(props.gridWeapon.object.series)) function processError(error: any) {
object.weapon.weapon_key2_id = weaponKey2Select.current?.value console.error(error)
}
if (props.gridWeapon.object.series == 17) const elementSelect = () => {
object.weapon.weapon_key3_id = weaponKey3Select.current?.value
if (props.gridWeapon.object.ax > 0) {
object.weapon.ax_modifier1 = primaryAxModifier
object.weapon.ax_modifier2 = secondaryAxModifier
object.weapon.ax_strength1 = primaryAxValue
object.weapon.ax_strength2 = secondaryAxValue
}
return object
}
async function updateWeapon() {
const updateObject = prepareObject()
return await api.endpoints.grid_weapons.update(props.gridWeapon.id, updateObject, headers)
.then(response => processResult(response))
.catch(error => processError(error))
}
function processResult(response: AxiosResponse) {
const gridWeapon: GridWeapon = response.data.grid_weapon
if (gridWeapon.mainhand)
appState.grid.weapons.mainWeapon = gridWeapon
else
appState.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon
setOpen(false)
}
function processError(error: any) {
console.error(error)
}
const elementSelect = () => {
return (
<section>
<h3>{t('modals.weapon.subtitles.element')}</h3>
<ElementToggle
currentElement={props.gridWeapon.element}
sendValue={receiveElementValue}
/>
</section>
)
}
const keySelect = () => {
return (
<section>
<h3>{t('modals.weapon.subtitles.weapon_keys')}</h3>
{ ([2, 3, 17, 22].includes(props.gridWeapon.object.series)) ?
<WeaponKeyDropdown
currentValue={ (props.gridWeapon.weapon_keys) ? props.gridWeapon.weapon_keys[0] : undefined }
series={props.gridWeapon.object.series}
slot={0}
ref={weaponKey1Select} />
: ''}
{ ([2, 3, 17].includes(props.gridWeapon.object.series)) ?
<WeaponKeyDropdown
currentValue={ (props.gridWeapon.weapon_keys) ? props.gridWeapon.weapon_keys[1] : undefined }
series={props.gridWeapon.object.series}
slot={1}
ref={weaponKey2Select} />
: ''}
{ (props.gridWeapon.object.series == 17) ?
<WeaponKeyDropdown
currentValue={ (props.gridWeapon.weapon_keys) ? props.gridWeapon.weapon_keys[2] : undefined }
series={props.gridWeapon.object.series}
slot={2}
ref={weaponKey3Select} />
: ''}
</section>
)
}
const axSelect = () => {
return (
<section>
<h3>{t('modals.weapon.subtitles.ax_skills')}</h3>
<AXSelect
axType={props.gridWeapon.object.ax}
currentSkills={props.gridWeapon.ax}
sendValidity={receiveAxValidity}
sendValues={receiveAxValues}
/>
</section>
)
}
function openChange(open: boolean) {
setFormValid(false)
setOpen(open)
}
return ( return (
<Dialog.Root open={open} onOpenChange={openChange}> <section>
<Dialog.Trigger asChild> <h3>{t("modals.weapon.subtitles.element")}</h3>
{ props.children } <ElementToggle
</Dialog.Trigger> currentElement={props.gridWeapon.element}
<Dialog.Portal> sendValue={receiveElementValue}
<Dialog.Content className="Weapon Dialog" onOpenAutoFocus={ (event) => event.preventDefault() }> />
<div className="DialogHeader"> </section>
<div className="DialogTop">
<Dialog.Title className="SubTitle">{t('modals.weapon.title')}</Dialog.Title>
<Dialog.Title className="DialogTitle">{props.gridWeapon.object.name[locale]}</Dialog.Title>
</div>
<Dialog.Close className="DialogClose" asChild>
<span>
<CrossIcon />
</span>
</Dialog.Close>
</div>
<div className="mods">
{ (props.gridWeapon.object.element == 0) ? elementSelect() : '' }
{ ([2, 3, 17, 24].includes(props.gridWeapon.object.series)) ? keySelect() : '' }
{ (props.gridWeapon.object.ax > 0) ? axSelect() : '' }
<Button onClick={updateWeapon} disabled={props.gridWeapon.object.ax > 0 && !formValid}>{t('modals.weapon.buttons.confirm')}</Button>
</div>
</Dialog.Content>
<Dialog.Overlay className="Overlay" />
</Dialog.Portal>
</Dialog.Root>
) )
}
const keySelect = () => {
return (
<section>
<h3>{t("modals.weapon.subtitles.weapon_keys")}</h3>
{[2, 3, 17, 22].includes(props.gridWeapon.object.series) ? (
<WeaponKeyDropdown
currentValue={
props.gridWeapon.weapon_keys
? props.gridWeapon.weapon_keys[0]
: undefined
}
series={props.gridWeapon.object.series}
slot={0}
ref={weaponKey1Select}
/>
) : (
""
)}
{[2, 3, 17].includes(props.gridWeapon.object.series) ? (
<WeaponKeyDropdown
currentValue={
props.gridWeapon.weapon_keys
? props.gridWeapon.weapon_keys[1]
: undefined
}
series={props.gridWeapon.object.series}
slot={1}
ref={weaponKey2Select}
/>
) : (
""
)}
{props.gridWeapon.object.series == 17 ? (
<WeaponKeyDropdown
currentValue={
props.gridWeapon.weapon_keys
? props.gridWeapon.weapon_keys[2]
: undefined
}
series={props.gridWeapon.object.series}
slot={2}
ref={weaponKey3Select}
/>
) : (
""
)}
</section>
)
}
const axSelect = () => {
return (
<section>
<h3>{t("modals.weapon.subtitles.ax_skills")}</h3>
<AXSelect
axType={props.gridWeapon.object.ax}
currentSkills={props.gridWeapon.ax}
sendValidity={receiveAxValidity}
sendValues={receiveAxValues}
/>
</section>
)
}
function openChange(open: boolean) {
setFormValid(false)
setOpen(open)
}
return (
<Dialog.Root open={open} onOpenChange={openChange}>
<Dialog.Trigger asChild>{props.children}</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Content
className="Weapon Dialog"
onOpenAutoFocus={(event) => event.preventDefault()}
>
<div className="DialogHeader">
<div className="DialogTop">
<Dialog.Title className="SubTitle">
{t("modals.weapon.title")}
</Dialog.Title>
<Dialog.Title className="DialogTitle">
{props.gridWeapon.object.name[locale]}
</Dialog.Title>
</div>
<Dialog.Close className="DialogClose" asChild>
<span>
<CrossIcon />
</span>
</Dialog.Close>
</div>
<div className="mods">
{props.gridWeapon.object.element == 0 ? elementSelect() : ""}
{[2, 3, 17, 24].includes(props.gridWeapon.object.series)
? keySelect()
: ""}
{props.gridWeapon.object.ax > 0 ? axSelect() : ""}
<Button
onClick={updateWeapon}
disabled={props.gridWeapon.object.ax > 0 && !formValid}
>
{t("modals.weapon.buttons.confirm")}
</Button>
</div>
</Dialog.Content>
<Dialog.Overlay className="Overlay" />
</Dialog.Portal>
</Dialog.Root>
)
} }
export default WeaponModal export default WeaponModal