From 32f864baa654d96d758887ddb6191259ba733f7f Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Thu, 24 Feb 2022 18:19:15 -0800 Subject: [PATCH] Implement read-only and editable views for party details --- components/BottomHeader/index.tsx | 19 ++++- components/Party/index.tsx | 50 +++++++++++++- components/PartyDetails/index.scss | 87 +++++++++++++++++++++++ components/PartyDetails/index.tsx | 107 +++++++++++++++++++++++++++++ 4 files changed, 258 insertions(+), 5 deletions(-) create mode 100644 components/PartyDetails/index.scss create mode 100644 components/PartyDetails/index.tsx diff --git a/components/BottomHeader/index.tsx b/components/BottomHeader/index.tsx index ef9b4272..31f6cab4 100644 --- a/components/BottomHeader/index.tsx +++ b/components/BottomHeader/index.tsx @@ -3,6 +3,7 @@ import { useRouter } from 'next/router' import { useCookies } from 'react-cookie' import { useSnapshot } from 'valtio' import clonedeep from 'lodash.clonedeep' +import * as Scroll from 'react-scroll' import * as AlertDialog from '@radix-ui/react-alert-dialog' @@ -21,6 +22,7 @@ const BottomHeader = () => { const app = useSnapshot(appState) const router = useRouter() + const scroll = Scroll.animateScroll; // Cookies const [cookies] = useCookies(['user']) @@ -30,6 +32,15 @@ const BottomHeader = () => { } } : {} + function toggleDetails() { + appState.party.detailsVisible = !appState.party.detailsVisible + + if (appState.party.detailsVisible) + scroll.scrollToBottom() + else + scroll.scrollToTop() + } + function deleteTeam(event: React.MouseEvent) { if (appState.party.editable && appState.party.id) { api.endpoints.parties.destroy(appState.party.id, headers) @@ -53,9 +64,11 @@ const BottomHeader = () => { } const leftNav = () => { - return ( - - ) + if (app.party.detailsVisible) { + return () + } else { + return () + } } const rightNav = () => { diff --git a/components/Party/index.tsx b/components/Party/index.tsx index 5da6cb26..63ac7bee 100644 --- a/components/Party/index.tsx +++ b/components/Party/index.tsx @@ -2,8 +2,8 @@ import React, { useEffect, useState } from 'react' import { useSnapshot } from 'valtio' import { useCookies } from 'react-cookie' - import PartySegmentedControl from '~components/PartySegmentedControl' +import PartyDetails from '~components/PartyDetails' import WeaponGrid from '~components/WeaponGrid' import SummonGrid from '~components/SummonGrid' import CharacterGrid from '~components/CharacterGrid' @@ -13,6 +13,7 @@ import { appState } from '~utils/appState' import { GridType, TeamElement } from '~utils/enums' import './index.scss' +import { AxiosResponse } from 'axios' // Props interface Props { @@ -33,6 +34,13 @@ const Party = (props: Props) => { const { party } = useSnapshot(appState) const [currentTab, setCurrentTab] = useState(GridType.Weapon) + // Fetch data from the server + useEffect(() => { + const shortcode = (props.slug) ? props.slug : undefined + if (shortcode) fetchDetails(shortcode) + else appState.party.editable = true + }, [props.slug]) + // Methods: Creating a new party async function createParty(extra: boolean = false) { let body = { @@ -76,6 +84,39 @@ const Party = (props: Props) => { } } + // Methods: Fetch party details + function fetchDetails(shortcode: string) { + return api.endpoints.parties.getOne({ id: shortcode }) + .then(response => processResult(response)) + .catch(error => processError(error)) + } + + function processResult(response: AxiosResponse) { + // Store the response + const party = response.data.party + + // Store the party's user-generated details + if (party.name) + appState.party.name = party.name + + if (party.description) + appState.party.description = party.description + + if (party.raid) + appState.party.raid = party.raid + } + + function processError(error: any) { + if (error.response != null) { + if (error.response.status == 404) { + // setFound(false) + // setLoading(false) + } + } else { + console.error(error) + } + } + // Render: JSX components const navigation = ( { return (
{ navigation } - { currentGrid() } +
+ { currentGrid() } +
+ { }
) } diff --git a/components/PartyDetails/index.scss b/components/PartyDetails/index.scss new file mode 100644 index 00000000..abc7bed8 --- /dev/null +++ b/components/PartyDetails/index.scss @@ -0,0 +1,87 @@ +.Details { + display: none; // This breaks transition, find a workaround + opacity: 0; + margin: 0 auto; + max-width: $unit * 95; + position: relative; + + &.Editable { + top: $unit; + height: 0; + z-index: 2; + transition: opacity 0.2s ease-in-out, + top 0.2s ease-in-out; + + + &.Visible { + display: block; + height: auto; + margin-bottom: 40vh; + opacity: 1; + top: 0; + } + + fieldset { + display: block; + width: 100%; + + textarea { + min-height: $unit * 20; + width: 100%; + } + } + + select { + appearance: none; + background-image: url('/icons/Arrow.svg'); + background-repeat: no-repeat; + background-position-y: center; + background-position-x: 98%; + background-size: $unit * 1.5; + border: none; + border-radius: 6px; + color: $grey-00; + margin-bottom: $unit; + font-size: $font-regular; + padding: 0 ($unit * 2); + height: $unit * 6; + width: 100%; + + &:hover { + cursor: pointer; + } + } + } + + &.ReadOnly { + top: $unit * -1; + transition: opacity 0.2s ease-in-out, + top 0.2s ease-in-out; + + &.Visible { + display: block; + height: auto; + opacity: 1; + top: 0; + } + + h1 { + font-size: $font-xlarge; + font-weight: $normal; + text-align: left; + margin-bottom: $unit; + } + + .Raid { + color: $grey-50; + font-size: $font-regular; + font-weight: $medium; + margin-bottom: $unit * 2; + } + + p { + font-size: $font-regular; + line-height: $font-regular * 1.2; + } + } +} \ No newline at end of file diff --git a/components/PartyDetails/index.tsx b/components/PartyDetails/index.tsx new file mode 100644 index 00000000..5ff4347b --- /dev/null +++ b/components/PartyDetails/index.tsx @@ -0,0 +1,107 @@ +import React, { useState } from 'react' +import { useSnapshot } from 'valtio' +import classNames from 'classnames' + +import CharLimitedFieldset from '~components/CharLimitedFieldset' +import TextFieldset from '~components/TextFieldset' + +import { appState } from '~utils/appState' + +import './index.scss' +import RaidDropdown from '~components/RaidDropdown' + +// Props +interface Props { + editable: boolean +} + +const PartyDetails = (props: Props) => { + const appSnapshot = useSnapshot(appState) + + const nameInput = React.createRef() + const descriptionInput = React.createRef() + + const readOnlyClasses = classNames({ + 'Details': true, + 'ReadOnly': true, + 'Visible': !appSnapshot.party.detailsVisible + }) + + const editableClasses = classNames({ + 'Details': true, + 'Editable': true, + 'Visible': appSnapshot.party.detailsVisible + }) + + const [errors, setErrors] = useState<{ [key: string]: string }>({ + name: '', + description: '' + }) + + function handleInputChange(event: React.ChangeEvent) { + console.log(event) + + event.preventDefault() + + const { name, value } = event.target + let newErrors = errors + + setErrors(newErrors) + } + + function handleTextAreaChange(event: React.ChangeEvent) { + event.preventDefault() + + const { name, value } = event.target + let newErrors = errors + + setErrors(newErrors) + } + + const editable = ( +
+ {} } + onChange={handleInputChange} + error={errors.name} + ref={nameInput} + /> + + + {} } + onChange={handleTextAreaChange} + error={errors.description} + ref={descriptionInput} + /> +
+ ) + + const readOnly = ( +
+

{ (appSnapshot.party.name) ? appSnapshot.party.name : 'No title' }

+ { (appSnapshot.party.raid) ?
{appSnapshot.party.raid.name.en}
: '' } +

{ (appSnapshot.party.description) ? appSnapshot.party.description : '' }

+
+ ) + + return ( +
+ {readOnly} + {editable} +
+ ) +} + +export default PartyDetails