diff --git a/components/CharacterModal/index.scss b/components/CharacterModal/index.scss new file mode 100644 index 00000000..4a4ea5ee --- /dev/null +++ b/components/CharacterModal/index.scss @@ -0,0 +1,41 @@ +.Character.Dialog { + min-width: 480px; + + @include breakpoint(phone) { + min-width: inherit; + } + + .mods { + display: flex; + flex-direction: column; + gap: $unit-4x; + + section { + display: flex; + flex-direction: column; + gap: $unit-half; + + h3 { + color: $grey-55; + font-size: $font-small; + margin-bottom: $unit; + } + + select { + background-color: $grey-90; + } + } + + .Button { + font-size: $font-regular; + padding: ($unit * 1.5) ($unit-2x); + width: 100%; + + &.btn-disabled { + background: $grey-90; + color: $grey-70; + cursor: not-allowed; + } + } + } +} diff --git a/components/CharacterModal/index.tsx b/components/CharacterModal/index.tsx new file mode 100644 index 00000000..36ca31f6 --- /dev/null +++ b/components/CharacterModal/index.tsx @@ -0,0 +1,310 @@ +// Core dependencies +import React, { useEffect, useState } from 'react' +import { getCookie } from 'cookies-next' +import { useRouter } from 'next/router' +import { useTranslation } from 'next-i18next' +import { AxiosResponse } from 'axios' + +// UI dependencies +import { + Dialog, + DialogClose, + DialogContent, + DialogTitle, + DialogTrigger, +} from '~components/Dialog' +import Button from '~components/Button' +import AwakeningSelect from '~components/AwakeningSelect' + +// Utilities +import api from '~utils/api' +import { appState } from '~utils/appState' +import { retrieveCookies } from '~utils/retrieveCookies' + +// Styles and icons +import CrossIcon from '~public/icons/Cross.svg' +import './index.scss' + +// Types +interface GridCharacterObject { + character: { + ring_modifier1: number + ring_modifier2: number + ring_modifier3: number + ring_modifier4: number + ring_strength1: number + ring_strength2: number + ring_strength3: number + ring_strength4: number + awakening_type: number + awakening_level: number + transcendence_step: number + } +} + +interface Props { + gridCharacter: GridCharacter + children: React.ReactNode +} + +const CharacterModal = ({ gridCharacter, children }: Props) => { + const router = useRouter() + const locale = + router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en' + const { t } = useTranslation('common') + + // Cookies + const cookies = retrieveCookies() + + // UI state + const [open, setOpen] = useState(false) + const [formValid, setFormValid] = useState(false) + + const [ring1Open, setRing1Open] = useState(false) + const [ring2Open, setRing2Open] = useState(false) + const [ring3Open, setRing3Open] = useState(false) + const [ring4Open, setRing4Open] = useState(false) + const [earringOpen, setEarringOpen] = useState(false) + const [awakeningOpen, setAwakeningOpen] = useState(false) + + // Character properties: Ring + const [ringModifier1, setRingModifier1] = useState(0) + const [ringModifier2, setRingModifier2] = useState(0) + const [ringModifier3, setRingModifier3] = useState(0) + const [ringModifier4, setRingModifier4] = useState(0) + + const [ringStrength1, setRingStrength1] = useState(0) + const [ringStrength2, setRingStrength2] = useState(0) + const [ringStrength3, setRingStrength3] = useState(0) + const [ringStrength4, setRingStrength4] = useState(0) + + // Character properties: Earrings + const [earringModifier, setEarringModifier] = useState(0) + const [earringStrength, setEarringStrength] = useState(0) + + // Character properties: Awakening + const [awakeningType, setAwakeningType] = useState(0) + const [awakeningLevel, setAwakeningLevel] = useState(0) + + // Character properties: Transcendence + const [transcendenceStep, setTranscendenceStep] = useState(0) + + // Hooks + useEffect(() => {}, []) + + // Methods: UI state management + function openChange(open: boolean) { + setOpen(open) + } + + function openSelect( + name: 'ring1' | 'ring2' | 'ring3' | 'ring4' | 'earring' | 'awakening' + ) { + setRing1Open(name === 'ring1' ? !ring1Open : false) + setRing2Open(name === 'ring2' ? !ring2Open : false) + setRing3Open(name === 'ring3' ? !ring3Open : false) + setRing4Open(name === 'ring4' ? !ring4Open : false) + setEarringOpen(name === 'earring' ? !earringOpen : false) + setAwakeningOpen(name === 'awakening' ? !awakeningOpen : false) + } + + const anySelectOpen = + ring1Open || + ring2Open || + ring3Open || + ring4Open || + earringOpen || + awakeningOpen + + function onEscapeKeyDown(event: KeyboardEvent) { + if (anySelectOpen) { + return event.preventDefault() + } else { + setOpen(false) + } + } + + function receiveRingOpen(index: 1 | 2 | 3 | 4, isOpen: boolean) { + if (index === 1) setRing1Open(isOpen) + if (index === 2) setRing2Open(isOpen) + if (index === 3) setRing3Open(isOpen) + if (index === 4) setRing4Open(isOpen) + } + + function receiveEarringOpen(isOpen: boolean) { + setEarringOpen(isOpen) + } + + function receiveAwakeningOpen(isOpen: boolean) { + setAwakeningOpen(isOpen) + } + + // Methods: Receive data from components + function receiveRingValues( + ringModifier1: number, + ringModifier2: number, + ringModifier3: number, + ringModifier4: number, + ringStrength1: number, + ringStrength2: number, + ringStrength3: number, + ringStrength4: number + ) { + setRingModifier1(ringModifier1) + setRingModifier2(ringModifier2) + setRingModifier3(ringModifier3) + setRingModifier4(ringModifier4) + + setRingStrength1(ringStrength1) + setRingStrength2(ringStrength2) + setRingStrength3(ringStrength3) + setRingStrength4(ringStrength4) + } + + function receiveEarringValues( + earringModifier: number, + earringStrength: number + ) { + setEarringModifier(earringModifier) + setEarringStrength(earringStrength) + } + + function receiveAwakeningValues(type: number, level: number) { + setAwakeningType(type) + setAwakeningLevel(level) + } + + function receiveValidity(isValid: boolean) { + setFormValid(isValid) + } + + // Methods: Data syncing + + // Prepare the GridWeaponObject to send to the server + function prepareObject() { + let object: GridCharacterObject = { + character: { + ring_modifier1: ringModifier1, + ring_modifier2: ringModifier2, + ring_modifier3: ringModifier3, + ring_modifier4: ringModifier4, + ring_strength1: ringStrength1, + ring_strength2: ringStrength2, + ring_strength3: ringStrength3, + ring_strength4: ringStrength4, + awakening_type: awakeningType, + awakening_level: awakeningLevel, + transcendence_step: transcendenceStep, + }, + } + + return object + } + + // Send the GridWeaponObject to the server + async function updateCharacter() { + const updateObject = prepareObject() + return await api.endpoints.grid_characters + .update(gridCharacter.id, updateObject) + .then((response) => processResult(response)) + .catch((error) => processError(error)) + } + + // Save the server's response to state + function processResult(response: AxiosResponse) { + const gridCharacter: GridCharacter = response.data + appState.grid.characters[gridCharacter.position] = gridCharacter + + setOpen(false) + } + + function processError(error: any) { + console.error(error) + } + + const ringSelect = () => { + return ( +
+

{t('modals.characters.subtitles.ring')}

+ +
+ ) + } + + const earringSelect = () => { + return ( +
+

{t('modals.characters.subtitles.earring')}

+ +
+ ) + } + + const awakeningSelect = () => { + return ( +
+

{t('modals.weapon.subtitles.awakening')}

+ +
+ ) + } + + return ( + // TODO: Refactor into Dialog component + + {children} + event.preventDefault()} + onEscapeKeyDown={onEscapeKeyDown} + > +
+
+ + {t('modals.character.title')} + + + {gridCharacter.object.name[locale]} + +
+ + + + + +
+ +
+ {ringSelect()} + {earringSelect()} + {awakeningSelect()} +
+
+
+ ) +} + +export default CharacterModal