Add confirmation alert to Edit Party modal

This commit is contained in:
Justin Edmund 2023-07-03 20:12:57 -07:00
parent 102ac3e1d1
commit 6ff0258f60
3 changed files with 132 additions and 60 deletions

View file

@ -1,10 +1,11 @@
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useSnapshot } from 'valtio' import { useSnapshot } from 'valtio'
import { useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import classNames from 'classnames' import classNames from 'classnames'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import Alert from '~components/common/Alert'
import { Dialog, DialogTrigger } from '~components/common/Dialog' import { Dialog, DialogTrigger } from '~components/common/Dialog'
import DialogHeader from '~components/common/DialogHeader' import DialogHeader from '~components/common/DialogHeader'
import DialogFooter from '~components/common/DialogFooter' import DialogFooter from '~components/common/DialogFooter'
@ -29,14 +30,18 @@ import styles from './index.module.scss'
import Input from '~components/common/Input' import Input from '~components/common/Input'
interface Props extends DialogProps { interface Props extends DialogProps {
open: boolean
party?: Party party?: Party
updateCallback: (details: DetailsObject) => void onOpenChange?: (open: boolean) => void
updateParty: (details: DetailsObject) => Promise<any>
} }
const EditPartyModal = ({ updateCallback, ...props }: Props) => { const EditPartyModal = ({
// Set up router open,
const router = useRouter() updateParty,
onOpenChange,
...props
}: Props) => {
// Set up translation // Set up translation
const { t } = useTranslation('common') const { t } = useTranslation('common')
@ -50,7 +55,7 @@ const EditPartyModal = ({ updateCallback, ...props }: Props) => {
const descriptionInput = useRef<HTMLDivElement>(null) const descriptionInput = useRef<HTMLDivElement>(null)
// States: Component // States: Component
const [open, setOpen] = useState(false) const [alertOpen, setAlertOpen] = useState(false)
const [errors, setErrors] = useState<{ [key: string]: string }>({ const [errors, setErrors] = useState<{ [key: string]: string }>({
name: '', name: '',
description: '', description: '',
@ -84,21 +89,26 @@ const EditPartyModal = ({ updateCallback, ...props }: Props) => {
}, [party]) }, [party])
// Methods: Event handlers (Dialog) // Methods: Event handlers (Dialog)
function openChange() { function handleOpenChange() {
if (open) { if (hasBeenModified() && open) {
setOpen(false) setAlertOpen(true)
setCurrentSegment(0) } else if (!hasBeenModified() && open) {
persistFromState() close()
if (props.onOpenChange) props.onOpenChange(false)
} else { } else {
setOpen(true) if (onOpenChange) onOpenChange(true)
if (props.onOpenChange) props.onOpenChange(true)
} }
} }
function close() {
setAlertOpen(false)
setCurrentSegment(0)
persistFromState()
if (onOpenChange) onOpenChange(false)
}
function onEscapeKeyDown(event: KeyboardEvent) { function onEscapeKeyDown(event: KeyboardEvent) {
event.preventDefault() event.preventDefault()
openChange() handleOpenChange()
} }
function onOpenAutoFocus(event: Event) { function onOpenAutoFocus(event: Event) {
@ -268,6 +278,36 @@ const EditPartyModal = ({ updateCallback, ...props }: Props) => {
return low2 + ((high2 - low2) * (value - low1)) / (high1 - low1) return low2 + ((high2 - low2) * (value - low1)) / (high1 - low1)
} }
// Methods: Modification checking
function hasBeenModified() {
const nameChanged = name !== party.name
const descriptionChanged =
descriptionInput.current?.innerHTML !== party.description
const raidChanged = raid !== party.raid
const chargeAttackChanged = chargeAttack !== party.chargeAttack
const fullAutoChanged = fullAuto !== party.fullAuto
const autoGuardChanged = autoGuard !== party.autoGuard
const autoSummonChanged = autoSummon !== party.autoSummon
const clearTimeChanged = clearTime !== party.clearTime
const turnCountChanged = turnCount !== party.turnCount
const buttonCountChanged = buttonCount !== party.buttonCount
const chainCountChanged = chainCount !== party.chainCount
return (
nameChanged ||
descriptionChanged ||
raidChanged ||
chargeAttackChanged ||
fullAutoChanged ||
autoGuardChanged ||
autoSummonChanged ||
clearTimeChanged ||
turnCountChanged ||
buttonCountChanged ||
chainCountChanged
)
}
// Methods: Data methods // Methods: Data methods
function persistFromState() { function persistFromState() {
if (!party) return if (!party) return
@ -284,7 +324,7 @@ const EditPartyModal = ({ updateCallback, ...props }: Props) => {
if (party.chainCount) setChainCount(party.chainCount) if (party.chainCount) setChainCount(party.chainCount)
} }
function updateDetails(event: React.MouseEvent) { async function updateDetails(event: React.MouseEvent) {
const descriptionValue = descriptionInput.current?.innerHTML const descriptionValue = descriptionInput.current?.innerHTML
const details: DetailsObject = { const details: DetailsObject = {
fullAuto: fullAuto, fullAuto: fullAuto,
@ -301,11 +341,32 @@ const EditPartyModal = ({ updateCallback, ...props }: Props) => {
extra: extra, extra: extra,
} }
updateCallback(details) await updateParty(details)
openChange() if (onOpenChange) onOpenChange(false)
} }
// Methods: Rendering methods // Methods: Rendering methods
const confirmationAlert = (
<Alert
message={
<span>
<Trans i18nKey="alerts.unsaved_changes.party">
You will lose all changes to your party{' '}
<strong>{{ objectName: party.name }}</strong> if you continue.
<br />
<br />
Are you sure you want to continue without saving?
</Trans>
</span>
}
open={alertOpen}
primaryActionText="Close"
primaryAction={close}
cancelActionText="Nevermind"
cancelAction={() => setAlertOpen(false)}
/>
)
const segmentedControl = ( const segmentedControl = (
<nav className={styles.segmentedControlWrapper} ref={topContainerRef}> <nav className={styles.segmentedControlWrapper} ref={topContainerRef}>
<SegmentedControl blended={true}> <SegmentedControl blended={true}>
@ -499,45 +560,48 @@ const EditPartyModal = ({ updateCallback, ...props }: Props) => {
) )
return ( return (
<Dialog open={open} onOpenChange={openChange}> <>
<DialogTrigger asChild>{props.children}</DialogTrigger> {confirmationAlert}
<DialogContent <Dialog open={open} onOpenChange={handleOpenChange}>
className="editParty" <DialogTrigger asChild>{props.children}</DialogTrigger>
headerref={topContainerRef} <DialogContent
footerref={footerRef} className="editParty"
onEscapeKeyDown={onEscapeKeyDown} headerref={topContainerRef}
onOpenAutoFocus={onOpenAutoFocus} footerref={footerRef}
> onEscapeKeyDown={onEscapeKeyDown}
<DialogHeader title={t('modals.edit_team.title')} ref={headerRef} /> onOpenAutoFocus={onOpenAutoFocus}
>
<DialogHeader title={t('modals.edit_team.title')} ref={headerRef} />
<div className={styles.content}> <div className={styles.content}>
{segmentedControl} {segmentedControl}
<div className={fieldsClasses} onScroll={handleScroll}> <div className={fieldsClasses} onScroll={handleScroll}>
{currentSegment === 0 && infoPage} {currentSegment === 0 && infoPage}
{currentSegment === 1 && propertiesPage} {currentSegment === 1 && propertiesPage}
</div>
</div> </div>
</div>
<DialogFooter <DialogFooter
ref={footerRef} ref={footerRef}
rightElements={[ rightElements={[
<Button <Button
bound={true} bound={true}
key="cancel" key="cancel"
text={t('buttons.cancel')} text={t('buttons.cancel')}
onClick={openChange} onClick={handleOpenChange}
/>, />,
<Button <Button
bound={true} bound={true}
key="confirm" key="confirm"
rightAccessoryIcon={<CheckIcon />} rightAccessoryIcon={<CheckIcon />}
text={t('modals.edit_team.buttons.confirm')} text={t('modals.edit_team.buttons.confirm')}
onClick={updateDetails} onClick={updateDetails}
/>, />,
]} ]}
/> />
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</>
) )
} }

View file

@ -132,7 +132,7 @@ const Party = (props: Props) => {
// Methods: Updating the party's details // Methods: Updating the party's details
async function updateDetails(details: DetailsObject) { async function updateDetails(details: DetailsObject) {
if (!props.team) return await createParty(details) if (!props.team) return await createParty(details)
else updateParty(details) else return await updateParty(details)
} }
function formatDetailsObject(details: DetailsObject) { function formatDetailsObject(details: DetailsObject) {
@ -254,7 +254,7 @@ const Party = (props: Props) => {
const storeParty = function (team: any) { const storeParty = function (team: any) {
// Store the important party and state-keeping values in global state // Store the important party and state-keeping values in global state
appState.party.name = team.name appState.party.name = team.name
appState.party.description = team.description appState.party.description = team.description ? team.description : ''
appState.party.raid = team.raid appState.party.raid = team.raid
appState.party.updated_at = team.updated_at appState.party.updated_at = team.updated_at
appState.party.job = team.job appState.party.job = team.job

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react' import React, { useState } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useSnapshot } from 'valtio' import { useSnapshot } from 'valtio'
@ -35,7 +35,7 @@ interface Props {
editable: boolean editable: boolean
deleteCallback: () => void deleteCallback: () => void
remixCallback: () => void remixCallback: () => void
updateCallback: (details: DetailsObject) => void updateCallback: (details: DetailsObject) => Promise<any>
} }
const PartyHeader = (props: Props) => { const PartyHeader = (props: Props) => {
@ -48,6 +48,7 @@ const PartyHeader = (props: Props) => {
const { party: partySnapshot } = useSnapshot(appState) const { party: partySnapshot } = useSnapshot(appState)
// State: Component // State: Component
const [detailsOpen, setDetailsOpen] = useState(false)
const [remixAlertOpen, setRemixAlertOpen] = useState(false) const [remixAlertOpen, setRemixAlertOpen] = useState(false)
const [remixToastOpen, setRemixToastOpen] = useState(false) const [remixToastOpen, setRemixToastOpen] = useState(false)
@ -115,6 +116,11 @@ const PartyHeader = (props: Props) => {
) )
} }
// Actions: Edit info
function handleDetailsOpenChange(open: boolean) {
setDetailsOpen(open)
}
// Actions: Remix team // Actions: Remix team
function remixTeamCallback() { function remixTeamCallback() {
setRemixToastOpen(true) setRemixToastOpen(true)
@ -375,8 +381,10 @@ const PartyHeader = (props: Props) => {
{props.editable ? ( {props.editable ? (
<div className={styles.right}> <div className={styles.right}>
<EditPartyModal <EditPartyModal
open={detailsOpen}
party={props.party} party={props.party}
updateCallback={props.updateCallback} onOpenChange={handleDetailsOpenChange}
updateParty={props.updateCallback}
> >
<Button <Button
className="full" className="full"