Implement read-only and editable views for party details
This commit is contained in:
parent
7df3da2d34
commit
32f864baa6
4 changed files with 258 additions and 5 deletions
|
|
@ -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<HTMLButtonElement, 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 (
|
||||
<Button icon="edit" click={() => {}}>Add more info</Button>
|
||||
)
|
||||
if (app.party.detailsVisible) {
|
||||
return (<Button icon="edit" active={true} click={toggleDetails}>Hide info</Button>)
|
||||
} else {
|
||||
return (<Button icon="edit" click={toggleDetails}>Edit info</Button>)
|
||||
}
|
||||
}
|
||||
|
||||
const rightNav = () => {
|
||||
|
|
|
|||
|
|
@ -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>(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 = (
|
||||
<PartySegmentedControl
|
||||
|
|
@ -123,7 +164,12 @@ const Party = (props: Props) => {
|
|||
return (
|
||||
<div>
|
||||
{ navigation }
|
||||
{ currentGrid() }
|
||||
<section id="Party">
|
||||
{ currentGrid() }
|
||||
</section>
|
||||
{ <PartyDetails
|
||||
editable={party.editable}
|
||||
/>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
87
components/PartyDetails/index.scss
Normal file
87
components/PartyDetails/index.scss
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
107
components/PartyDetails/index.tsx
Normal file
107
components/PartyDetails/index.tsx
Normal file
|
|
@ -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<HTMLInputElement>()
|
||||
const descriptionInput = React.createRef<HTMLTextAreaElement>()
|
||||
|
||||
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<HTMLInputElement>) {
|
||||
console.log(event)
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
const { name, value } = event.target
|
||||
let newErrors = errors
|
||||
|
||||
setErrors(newErrors)
|
||||
}
|
||||
|
||||
function handleTextAreaChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
|
||||
event.preventDefault()
|
||||
|
||||
const { name, value } = event.target
|
||||
let newErrors = errors
|
||||
|
||||
setErrors(newErrors)
|
||||
}
|
||||
|
||||
const editable = (
|
||||
<section className={editableClasses}>
|
||||
<CharLimitedFieldset
|
||||
fieldName="name"
|
||||
placeholder="Name your team"
|
||||
value={appSnapshot.party.name}
|
||||
limit={50}
|
||||
onBlur={ () => {} }
|
||||
onChange={handleInputChange}
|
||||
error={errors.name}
|
||||
ref={nameInput}
|
||||
/>
|
||||
<RaidDropdown />
|
||||
<select>
|
||||
<option>Belial</option>
|
||||
<option>Beelzebub</option>
|
||||
<option>Lucifer (Hard)</option>
|
||||
</select>
|
||||
<TextFieldset
|
||||
fieldName="name"
|
||||
placeholder={"Write your notes here\n\n\nWatch out for the 50% trigger!\nMake sure to click Fediel’s 1 first\nGood luck with RNG!"}
|
||||
value={appSnapshot.party.description}
|
||||
onBlur={ () => {} }
|
||||
onChange={handleTextAreaChange}
|
||||
error={errors.description}
|
||||
ref={descriptionInput}
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
|
||||
const readOnly = (
|
||||
<section className={readOnlyClasses}>
|
||||
<h1>{ (appSnapshot.party.name) ? appSnapshot.party.name : 'No title' }</h1>
|
||||
{ (appSnapshot.party.raid) ? <div className="Raid">{appSnapshot.party.raid.name.en}</div> : '' }
|
||||
<p>{ (appSnapshot.party.description) ? appSnapshot.party.description : '' }</p>
|
||||
</section>
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{readOnly}
|
||||
{editable}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PartyDetails
|
||||
Loading…
Reference in a new issue