The values a user entered in the Edit team modal would persist even if the user hit cancel to close the modal. They wouldn't save to the server, but very confusing nonetheless. Now fixed.
488 lines
12 KiB
TypeScript
488 lines
12 KiB
TypeScript
import React, { useEffect, useRef, useState } from 'react'
|
||
import { useRouter } from 'next/router'
|
||
import { useSnapshot } from 'valtio'
|
||
import { useTranslation } from 'react-i18next'
|
||
|
||
import {
|
||
Dialog,
|
||
DialogTrigger,
|
||
DialogClose,
|
||
DialogTitle,
|
||
} from '~components/common/Dialog'
|
||
import DialogContent from '~components/common/DialogContent'
|
||
import Button from '~components/common/Button'
|
||
import CharLimitedFieldset from '~components/common/CharLimitedFieldset'
|
||
import DurationInput from '~components/common/DurationInput'
|
||
import InputTableField from '~components/common/InputTableField'
|
||
import RaidCombobox from '~components/raids/RaidCombobox'
|
||
import SegmentedControl from '~components/common/SegmentedControl'
|
||
import Segment from '~components/common/Segment'
|
||
import SwitchTableField from '~components/common/SwitchTableField'
|
||
import TableField from '~components/common/TableField'
|
||
|
||
import type { DetailsObject } from 'types'
|
||
import type { DialogProps } from '@radix-ui/react-dialog'
|
||
|
||
import { appState } from '~utils/appState'
|
||
|
||
import CheckIcon from '~public/icons/Check.svg'
|
||
import CrossIcon from '~public/icons/Cross.svg'
|
||
import './index.scss'
|
||
|
||
interface Props extends DialogProps {
|
||
party?: Party
|
||
updateCallback: (details: DetailsObject) => void
|
||
}
|
||
|
||
const EditPartyModal = ({ updateCallback, ...props }: Props) => {
|
||
// Set up router
|
||
const router = useRouter()
|
||
|
||
// Set up translation
|
||
const { t } = useTranslation('common')
|
||
|
||
// Set up reactive state
|
||
const { party } = useSnapshot(appState)
|
||
|
||
// Refs
|
||
const headerRef = React.createRef<HTMLDivElement>()
|
||
const footerRef = React.createRef<HTMLDivElement>()
|
||
const descriptionInput = useRef<HTMLTextAreaElement>(null)
|
||
|
||
// States: Component
|
||
const [open, setOpen] = useState(false)
|
||
const [errors, setErrors] = useState<{ [key: string]: string }>({
|
||
name: '',
|
||
description: '',
|
||
})
|
||
const [currentSegment, setCurrentSegment] = useState(0)
|
||
|
||
// States: Data
|
||
const [name, setName] = useState('')
|
||
const [description, setDescription] = useState('')
|
||
const [raid, setRaid] = useState<Raid>()
|
||
const [extra, setExtra] = useState(false)
|
||
const [chargeAttack, setChargeAttack] = useState(true)
|
||
const [fullAuto, setFullAuto] = useState(false)
|
||
const [autoGuard, setAutoGuard] = useState(false)
|
||
const [autoSummon, setAutoSummon] = useState(false)
|
||
|
||
const [buttonCount, setButtonCount] = useState<number | undefined>(undefined)
|
||
const [chainCount, setChainCount] = useState<number | undefined>(undefined)
|
||
const [turnCount, setTurnCount] = useState<number | undefined>(undefined)
|
||
const [clearTime, setClearTime] = useState(0)
|
||
|
||
// Hooks
|
||
useEffect(() => {
|
||
persistFromState()
|
||
}, [party])
|
||
|
||
// Methods: Event handlers (Dialog)
|
||
function openChange() {
|
||
if (open) {
|
||
setOpen(false)
|
||
setCurrentSegment(0)
|
||
persistFromState()
|
||
if (props.onOpenChange) props.onOpenChange(false)
|
||
} else {
|
||
setOpen(true)
|
||
if (props.onOpenChange) props.onOpenChange(true)
|
||
}
|
||
}
|
||
|
||
function onEscapeKeyDown(event: KeyboardEvent) {
|
||
event.preventDefault()
|
||
openChange()
|
||
}
|
||
|
||
function onOpenAutoFocus(event: Event) {
|
||
event.preventDefault()
|
||
}
|
||
|
||
// Methods: Event handlers (Fields)
|
||
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||
event.preventDefault()
|
||
|
||
const { name, value } = event.target
|
||
setName(value)
|
||
|
||
let newErrors = errors
|
||
setErrors(newErrors)
|
||
}
|
||
|
||
function handleChargeAttackChanged(checked: boolean) {
|
||
setChargeAttack(checked)
|
||
}
|
||
|
||
function handleFullAutoChanged(checked: boolean) {
|
||
setFullAuto(checked)
|
||
}
|
||
|
||
function handleAutoGuardChanged(checked: boolean) {
|
||
setAutoGuard(checked)
|
||
}
|
||
|
||
function handleAutoSummonChanged(checked: boolean) {
|
||
setAutoSummon(checked)
|
||
}
|
||
|
||
function handleExtraChanged(checked: boolean) {
|
||
setExtra(checked)
|
||
}
|
||
|
||
function handleClearTimeChanged(value: number) {
|
||
if (!isNaN(value)) setClearTime(value)
|
||
}
|
||
|
||
function handleTurnCountChanged(value?: string) {
|
||
if (!value) return
|
||
const numericalValue = parseInt(value)
|
||
if (!isNaN(numericalValue)) setTurnCount(numericalValue)
|
||
}
|
||
|
||
function handleButtonCountChanged(value?: string) {
|
||
if (!value) return
|
||
const numericalValue = parseInt(value)
|
||
if (!isNaN(numericalValue)) setButtonCount(numericalValue)
|
||
}
|
||
|
||
function handleChainCountChanged(value?: string) {
|
||
if (!value) return
|
||
const numericalValue = parseInt(value)
|
||
if (!isNaN(numericalValue)) setChainCount(numericalValue)
|
||
}
|
||
|
||
function handleTextAreaChanged(
|
||
event: React.ChangeEvent<HTMLTextAreaElement>
|
||
) {
|
||
event.preventDefault()
|
||
|
||
const { name, value } = event.target
|
||
let newErrors = errors
|
||
|
||
setErrors(newErrors)
|
||
}
|
||
|
||
function receiveRaid(raid?: Raid) {
|
||
if (raid) {
|
||
setRaid(raid)
|
||
|
||
if (raid.group.extra) setExtra(true)
|
||
else setExtra(false)
|
||
}
|
||
}
|
||
|
||
// Methods: Data methods
|
||
function persistFromState() {
|
||
if (!party) return
|
||
setName(party.name ? party.name : '')
|
||
setDescription(party.description ? party.description : '')
|
||
setRaid(party.raid)
|
||
setAutoGuard(party.autoGuard)
|
||
setAutoSummon(party.autoSummon)
|
||
setFullAuto(party.fullAuto)
|
||
setChargeAttack(party.chargeAttack)
|
||
setClearTime(party.clearTime)
|
||
if (party.turnCount) setTurnCount(party.turnCount)
|
||
if (party.buttonCount) setButtonCount(party.buttonCount)
|
||
if (party.chainCount) setChainCount(party.chainCount)
|
||
}
|
||
|
||
function updateDetails(event: React.MouseEvent) {
|
||
const descriptionValue = descriptionInput.current?.value
|
||
const details: DetailsObject = {
|
||
fullAuto: fullAuto,
|
||
autoGuard: autoGuard,
|
||
autoSummon: autoSummon,
|
||
chargeAttack: chargeAttack,
|
||
clearTime: clearTime,
|
||
buttonCount: buttonCount,
|
||
turnCount: turnCount,
|
||
chainCount: chainCount,
|
||
name: name,
|
||
description: descriptionValue,
|
||
raid: raid,
|
||
extra: extra,
|
||
}
|
||
|
||
updateCallback(details)
|
||
openChange()
|
||
}
|
||
|
||
// Methods: Rendering methods
|
||
const segmentedControl = () => {
|
||
return (
|
||
<SegmentedControl blended={true}>
|
||
<Segment
|
||
groupName="edit_nav"
|
||
name="core"
|
||
selected={currentSegment === 0}
|
||
tabIndex={0}
|
||
onClick={() => setCurrentSegment(0)}
|
||
>
|
||
{t('modals.edit_team.segments.basic_info')}
|
||
</Segment>
|
||
<Segment
|
||
groupName="edit_nav"
|
||
name="properties"
|
||
selected={currentSegment === 1}
|
||
tabIndex={0}
|
||
onClick={() => setCurrentSegment(1)}
|
||
>
|
||
{t('modals.edit_team.segments.properties')}
|
||
</Segment>
|
||
</SegmentedControl>
|
||
)
|
||
}
|
||
|
||
const nameField = () => {
|
||
return (
|
||
<CharLimitedFieldset
|
||
className="Bound"
|
||
fieldName="name"
|
||
placeholder="Name your team"
|
||
value={name}
|
||
limit={50}
|
||
onChange={handleInputChange}
|
||
error={errors.name}
|
||
/>
|
||
)
|
||
}
|
||
|
||
const raidField = () => {
|
||
return (
|
||
<RaidCombobox
|
||
showAllRaidsOption={false}
|
||
currentRaid={raid}
|
||
onChange={receiveRaid}
|
||
/>
|
||
)
|
||
}
|
||
|
||
const extraNotice = () => {
|
||
if (extra) {
|
||
return (
|
||
<div className="ExtraNotice">
|
||
<span className="ExtraNoticeText">
|
||
{raid && raid.group.guidebooks
|
||
? t('modals.edit_team.extra_notice_guidebooks')
|
||
: t('modals.edit_team.extra_notice')}
|
||
</span>
|
||
</div>
|
||
)
|
||
}
|
||
}
|
||
|
||
const descriptionField = () => {
|
||
return (
|
||
<div className="DescriptionField">
|
||
<textarea
|
||
className="Input Bound"
|
||
name="description"
|
||
placeholder={
|
||
'Write your notes here\n\n\nWatch out for the 50% trigger!\nMake sure to click Fediel’s 3 first\nGood luck with RNG!'
|
||
}
|
||
onChange={handleTextAreaChanged}
|
||
ref={descriptionInput}
|
||
defaultValue={description}
|
||
/>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
const chargeAttackField = () => {
|
||
return (
|
||
<SwitchTableField
|
||
name="charge_attack"
|
||
label={t('modals.edit_team.labels.charge_attack')}
|
||
value={chargeAttack}
|
||
onValueChange={handleChargeAttackChanged}
|
||
/>
|
||
)
|
||
}
|
||
|
||
const fullAutoField = () => {
|
||
return (
|
||
<SwitchTableField
|
||
name="full_auto"
|
||
label={t('modals.edit_team.labels.full_auto')}
|
||
value={fullAuto}
|
||
onValueChange={handleFullAutoChanged}
|
||
/>
|
||
)
|
||
}
|
||
|
||
const autoGuardField = () => {
|
||
return (
|
||
<SwitchTableField
|
||
name="auto_guard"
|
||
label={t('modals.edit_team.labels.auto_guard')}
|
||
value={autoGuard}
|
||
onValueChange={handleAutoGuardChanged}
|
||
/>
|
||
)
|
||
}
|
||
|
||
const autoSummonField = () => {
|
||
return (
|
||
<SwitchTableField
|
||
name="auto_summon"
|
||
label={t('modals.edit_team.labels.auto_summon')}
|
||
value={autoSummon}
|
||
onValueChange={handleAutoSummonChanged}
|
||
/>
|
||
)
|
||
}
|
||
|
||
const extraField = () => {
|
||
return (
|
||
<SwitchTableField
|
||
name="extra"
|
||
className="Extra"
|
||
label={t('modals.edit_team.labels.extra')}
|
||
description={t('modals.edit_team.descriptions.extra')}
|
||
value={extra}
|
||
disabled={true}
|
||
onValueChange={handleExtraChanged}
|
||
/>
|
||
)
|
||
}
|
||
|
||
const clearTimeField = () => {
|
||
return (
|
||
<TableField
|
||
className="Numeric"
|
||
name="clear_time"
|
||
label={t('modals.edit_team.labels.clear_time')}
|
||
>
|
||
<DurationInput
|
||
name="clear_time"
|
||
className="Bound"
|
||
value={clearTime}
|
||
onValueChange={(value: number) => handleClearTimeChanged(value)}
|
||
/>
|
||
</TableField>
|
||
)
|
||
}
|
||
|
||
const turnCountField = () => {
|
||
return (
|
||
<InputTableField
|
||
name="turn_count"
|
||
className="Numeric"
|
||
label={t('modals.edit_team.labels.turn_count')}
|
||
placeholder="0"
|
||
type="number"
|
||
value={turnCount}
|
||
onValueChange={handleTurnCountChanged}
|
||
/>
|
||
)
|
||
}
|
||
|
||
const buttonCountField = () => {
|
||
return (
|
||
<InputTableField
|
||
name="button_count"
|
||
className="Numeric"
|
||
label={t('modals.edit_team.labels.button_count')}
|
||
placeholder="0"
|
||
type="number"
|
||
value={buttonCount}
|
||
onValueChange={handleButtonCountChanged}
|
||
/>
|
||
)
|
||
}
|
||
|
||
const chainCountField = () => {
|
||
return (
|
||
<InputTableField
|
||
name="chain_count"
|
||
className="Numeric"
|
||
label={t('modals.edit_team.labels.chain_count')}
|
||
placeholder="0"
|
||
type="number"
|
||
value={chainCount}
|
||
onValueChange={handleChainCountChanged}
|
||
/>
|
||
)
|
||
}
|
||
|
||
const infoPage = () => {
|
||
return (
|
||
<>
|
||
{nameField()}
|
||
{raidField()}
|
||
{extraNotice()}
|
||
{descriptionField()}
|
||
</>
|
||
)
|
||
}
|
||
|
||
const propertiesPage = () => {
|
||
return (
|
||
<>
|
||
{chargeAttackField()}
|
||
{fullAutoField()}
|
||
{autoSummonField()}
|
||
{autoGuardField()}
|
||
{extraField()}
|
||
{clearTimeField()}
|
||
{turnCountField()}
|
||
{buttonCountField()}
|
||
{chainCountField()}
|
||
</>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<Dialog open={open} onOpenChange={openChange}>
|
||
<DialogTrigger asChild>{props.children}</DialogTrigger>
|
||
<DialogContent
|
||
className="EditTeam"
|
||
headerref={headerRef}
|
||
footerref={footerRef}
|
||
onEscapeKeyDown={onEscapeKeyDown}
|
||
onOpenAutoFocus={onOpenAutoFocus}
|
||
>
|
||
<div className="DialogHeader" ref={headerRef}>
|
||
<div className="DialogTop">
|
||
<DialogTitle className="DialogTitle">
|
||
{t('modals.edit_team.title')}
|
||
</DialogTitle>
|
||
</div>
|
||
<DialogClose className="DialogClose" asChild>
|
||
<span>
|
||
<CrossIcon />
|
||
</span>
|
||
</DialogClose>
|
||
</div>
|
||
|
||
<div className="Content">
|
||
{segmentedControl()}
|
||
<div className="Fields">
|
||
{currentSegment === 0 && infoPage()}
|
||
{currentSegment === 1 && propertiesPage()}
|
||
</div>
|
||
</div>
|
||
<div className="DialogFooter" ref={footerRef}>
|
||
<div className="Left"></div>
|
||
<div className="Right Buttons Spaced">
|
||
<Button
|
||
contained={true}
|
||
text={t('buttons.cancel')}
|
||
onClick={openChange}
|
||
/>
|
||
<Button
|
||
contained={true}
|
||
rightAccessoryIcon={<CheckIcon />}
|
||
text={t('modals.edit_team.buttons.confirm')}
|
||
onClick={updateDetails}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
)
|
||
}
|
||
|
||
export default EditPartyModal
|