diff --git a/components/WeaponConflictModal/index.tsx b/components/WeaponConflictModal/index.tsx new file mode 100644 index 00000000..84bd3541 --- /dev/null +++ b/components/WeaponConflictModal/index.tsx @@ -0,0 +1,108 @@ +import React, { useEffect, useState } from 'react' +import { useRouter } from 'next/router' +import { Trans, useTranslation } from 'react-i18next' + +import * as Dialog from '@radix-ui/react-dialog' +import Button from '~components/Button' + +import mapWeaponSeries from '~utils/mapWeaponSeries' + +import './index.scss' + +interface Props { + open: boolean + incomingWeapon: Weapon + conflictingWeapons: GridWeapon[] + desiredPosition: number + resolveConflict: () => void + resetConflict: () => void +} + +const WeaponConflictModal = (props: Props) => { + // Localization + const router = useRouter() + const { t } = useTranslation('common') + const locale = + router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en' + + // States + const [open, setOpen] = useState(false) + + useEffect(() => { + setOpen(props.open) + }, [setOpen, props.open]) + + function imageUrl(weapon?: Weapon) { + return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-square/${weapon?.granblue_id}.jpg` + } + + function openChange(open: boolean) { + setOpen(open) + props.resetConflict() + } + + function close() { + setOpen(false) + } + + const infoString = () => { + const series = props.incomingWeapon.series + const seriesSlug = t(`series.${mapWeaponSeries(series)}`) + + return [2, 3].includes(series) ? ( + + ) : ( + + Only one weapon from the + {{ series: seriesSlug }} Series can be included in each + party. Do you want to change your weapons? + + ) + } + + return ( + + + event.preventDefault()} + > +

{infoString()}

+
+
    + {props.conflictingWeapons?.map((weapon, i) => ( +
  • + {weapon.object.name[locale]} + {weapon.object.name[locale]} +
  • + ))} +
+ +
+
+ {props.incomingWeapon?.name[locale]} + {props.incomingWeapon?.name[locale]} +
+
+
+
+
+
+ +
+
+ ) +} + +export default WeaponConflictModal diff --git a/components/WeaponGrid/index.tsx b/components/WeaponGrid/index.tsx index ee296e71..85f696c8 100644 --- a/components/WeaponGrid/index.tsx +++ b/components/WeaponGrid/index.tsx @@ -15,6 +15,7 @@ import { appState } from '~utils/appState' import type { SearchableObject } from '~types' import './index.scss' +import WeaponConflictModal from '~components/WeaponConflictModal' // Props interface Props { @@ -40,6 +41,12 @@ const WeaponGrid = (props: Props) => { // Set up state for view management const { party, grid } = useSnapshot(appState) const [slug, setSlug] = useState() + const [modalOpen, setModalOpen] = useState(false) + + // Set up state for conflict management + const [incoming, setIncoming] = useState() + const [conflicts, setConflicts] = useState([]) + const [position, setPosition] = useState(0) // Create a temporary state to store previous weapon uncap values const [previousUncapValues, setPreviousUncapValues] = useState<{ @@ -90,9 +97,33 @@ const WeaponGrid = (props: Props) => { ) }) } else { - saveWeapon(party.id, weapon, position).then((response) => - storeGridWeapon(response.data) - ) + if (party.editable) + saveWeapon(party.id, weapon, position) + .then((response) => handleWeaponResponse(response.data)) + .catch((error) => console.error(error)) + } + } + + async function handleWeaponResponse(data: any) { + if (data.hasOwnProperty('conflicts')) { + if (data.incoming) setIncoming(data.incoming) + if (data.conflicts) setConflicts(data.conflicts) + if (data.position) setPosition(data.position) + setModalOpen(true) + } else { + storeGridWeapon(data.grid_weapon) + + // If we replaced an existing weapon, remove it from the grid + if (data.hasOwnProperty('meta') && data.meta['replaced'] !== undefined) { + const position = data.meta['replaced'] + + if (position == -1) { + appState.grid.weapons.mainWeapon = undefined + appState.party.element = 0 + } else { + appState.grid.weapons.allWeapons[position] = undefined + } + } } } @@ -125,6 +156,44 @@ const WeaponGrid = (props: Props) => { } } + async function resolveConflict() { + if (incoming && conflicts.length > 0) { + await api + .resolveConflict({ + object: 'weapons', + incoming: incoming.id, + conflicting: conflicts.map((c) => c.id), + position: position, + }) + .then((response) => { + // Store new character in state + storeGridWeapon(response.data) + + // Remove conflicting characters from state + conflicts.forEach((c) => { + if (appState.grid.weapons.mainWeapon?.object.id === c.id) { + appState.grid.weapons.mainWeapon = undefined + appState.party.element = 0 + } else { + appState.grid.weapons.allWeapons[c.position] = undefined + } + }) + + // Reset conflict + resetConflict() + + // Close modal + setModalOpen(false) + }) + } + } + + function resetConflict() { + setPosition(-1) + setConflicts([]) + setIncoming(undefined) + } + // Methods: Updating uncap level // Note: Saves, but debouncing is not working properly async function saveUncap(id: string, position: number, uncapLevel: number) { @@ -242,8 +311,24 @@ const WeaponGrid = (props: Props) => { /> ) + const conflictModal = () => { + return incoming && conflicts ? ( + + ) : ( + '' + ) + } + return (
+ {conflicts ? conflictModal() : ''}
{mainhandElement}
    {weaponGridElement}