Redesign read only state and add save button

This commit is contained in:
Justin Edmund 2022-03-14 16:47:13 -07:00
parent 9291e5501a
commit bda2639d88
2 changed files with 280 additions and 28 deletions

View file

@ -31,6 +31,22 @@
width: 100%; width: 100%;
} }
} }
.bottom {
display: flex;
flex-direction: row;
gap: $unit;
.left {
flex-grow: 1;
}
.right {
display: flex;
flex-direction: row;
gap: $unit;
}
}
} }
&.ReadOnly { &.ReadOnly {
@ -45,26 +61,8 @@
top: 0; top: 0;
} }
h1 { a:hover {
font-size: $font-xlarge; text-decoration: underline;
font-weight: $normal;
text-align: left;
margin-bottom: $unit;
}
a {
color: $blue;
&:hover {
text-decoration: underline;
}
}
.Raid {
color: $grey-50;
font-size: $font-regular;
font-weight: $medium;
margin-bottom: $unit * 2;
} }
p { p {
@ -72,5 +70,84 @@
line-height: $font-regular * 1.2; line-height: $font-regular * 1.2;
white-space: pre-line; white-space: pre-line;
} }
h1 {
font-size: $font-xlarge;
font-weight: $normal;
text-align: left;
margin-bottom: $unit;
}
.info {
align-items: center;
display: flex;
flex-direction: row;
gap: $unit;
margin-bottom: $unit * 2;
.left {
flex-grow: 1;
}
}
.attribution {
align-items: center;
display: flex;
flex-direction: row;
& > div {
align-items: center;
display: inline-flex;
font-size: $font-small;
height: 26px;
}
time {
font-size: $font-small;
}
& > *:not(:last-child):after {
content: " · ";
margin: 0 calc($unit / 2);
}
}
.user {
align-items: center;
display: inline-flex;
gap: calc($unit / 2);
margin-top: 1px;
img, .no-user {
$diameter: 24px;
border-radius: calc($diameter / 2);
height: $diameter;
width: $diameter;
}
img.gran {
background-color: #CEE7FE;
}
img.djeeta {
background-color: #FFE1FE;
}
.no-user {
background: $grey-80;
}
}
}
}
.EmptyDetails {
display: none;
justify-content: center;
margin-bottom: $unit * 10;
&.Visible {
display: flex;
} }
} }

View file

@ -1,27 +1,53 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import Head from 'next/head' import Head from 'next/head'
import Router, { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useSnapshot } from 'valtio' import { useSnapshot } from 'valtio'
import { useTranslation } from 'next-i18next'
import Linkify from 'react-linkify' import Linkify from 'react-linkify'
import classNames from 'classnames' import classNames from 'classnames'
import * as AlertDialog from '@radix-ui/react-alert-dialog'
import CrossIcon from '~public/icons/Cross.svg'
import Button from '~components/Button'
import CharLimitedFieldset from '~components/CharLimitedFieldset' import CharLimitedFieldset from '~components/CharLimitedFieldset'
import RaidDropdown from '~components/RaidDropdown' import RaidDropdown from '~components/RaidDropdown'
import TextFieldset from '~components/TextFieldset' import TextFieldset from '~components/TextFieldset'
import { accountState } from '~utils/accountState'
import { appState } from '~utils/appState' import { appState } from '~utils/appState'
import './index.scss' import './index.scss'
import Link from 'next/link'
import { formatTimeAgo } from '~utils/timeAgo'
const emptyRaid: Raid = {
id: '',
name: {
en: '',
ja: ''
},
slug: '',
level: 0,
group: 0,
element: 0
}
// Props // Props
interface Props { interface Props {
editable: boolean editable: boolean
updateCallback: (name?: string, description?: string, raid?: Raid) => void updateCallback: (name?: string, description?: string, raid?: Raid) => void
deleteCallback: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
} }
const PartyDetails = (props: Props) => { const PartyDetails = (props: Props) => {
const { party, raids } = useSnapshot(appState) const { party, raids } = useSnapshot(appState)
const { account } = useSnapshot(accountState)
const { t } = useTranslation('common')
const router = useRouter() const router = useRouter()
const locale = router.locale || 'en'
const nameInput = React.createRef<HTMLInputElement>() const nameInput = React.createRef<HTMLInputElement>()
const descriptionInput = React.createRef<HTMLTextAreaElement>() const descriptionInput = React.createRef<HTMLTextAreaElement>()
@ -39,6 +65,25 @@ const PartyDetails = (props: Props) => {
'Visible': party.detailsVisible 'Visible': party.detailsVisible
}) })
const emptyClasses = classNames({
'EmptyDetails': true,
'Visible': !party.detailsVisible
})
const userClass = classNames({
'user': true,
'empty': !party.user
})
const linkClass = classNames({
'wind': party && party.element == 1,
'fire': party && party.element == 2,
'water': party && party.element == 3,
'earth': party && party.element == 4,
'dark': party && party.element == 5,
'light': party && party.element == 6
})
const [errors, setErrors] = useState<{ [key: string]: string }>({ const [errors, setErrors] = useState<{ [key: string]: string }>({
name: '', name: '',
description: '' description: ''
@ -62,12 +107,100 @@ const PartyDetails = (props: Props) => {
setErrors(newErrors) setErrors(newErrors)
} }
function updateDetails(event: React.ChangeEvent) { function toggleDetails() {
appState.party.detailsVisible = !appState.party.detailsVisible
// if (appState.party.detailsVisible)
// scroll.scrollToBottom()
// else
// scroll.scrollToTop()
}
function updateDetails(event: React.MouseEvent) {
const nameValue = nameInput.current?.value const nameValue = nameInput.current?.value
const descriptionValue = descriptionInput.current?.value const descriptionValue = descriptionInput.current?.value
const raid = raids.find(raid => raid.slug === raidSelect.current?.value) const raid = raids.find(raid => raid.slug === raidSelect.current?.value)
props.updateCallback(nameValue, descriptionValue, raid) props.updateCallback(nameValue, descriptionValue, raid)
toggleDetails()
}
const userImage = () => {
if (party.user)
return (
<img
alt={party.user.picture.picture}
className={`profile ${party.user.picture.element}`}
srcSet={`/profile/${party.user.picture.picture}.png,
/profile/${party.user.picture.picture}@2x.png 2x`}
src={`/profile/${party.user.picture.picture}.png`}
/>
)
else
return (<div className="no-user" />)
}
const userBlock = () => {
return (
<div className={userClass}>
{ userImage() }
{ (party.user) ? party.user.username : t('no_user') }
</div>
)
}
const linkedUserBlock = (user: User) => {
return (
<div>
<Link href={`/${user.username}`} passHref>
<a className={linkClass}>{userBlock()}</a>
</Link>
</div>
)
}
const linkedRaidBlock = (raid: Raid) => {
return (
<div>
<Link href={`/teams?raid=${raid.slug}`} passHref>
<a className={`Raid ${linkClass}`}>
{raid.name[locale]}
</a>
</Link>
</div>
)
}
const deleteButton = () => {
if (party.editable) {
return (
<AlertDialog.Root>
<AlertDialog.Trigger className="Button destructive">
<span className='icon'>
<CrossIcon />
</span>
<span className="text">{t('buttons.delete')}</span>
</AlertDialog.Trigger>
<AlertDialog.Portal>
<AlertDialog.Overlay className="Overlay" />
<AlertDialog.Content className="Dialog">
<AlertDialog.Title className="DialogTitle">
{t('modals.delete_team.title')}
</AlertDialog.Title>
<AlertDialog.Description className="DialogDescription">
{t('modals.delete_team.description')}
</AlertDialog.Description>
<div className="actions">
<AlertDialog.Cancel className="Button modal">{t('modals.delete_team.buttons.cancel')}</AlertDialog.Cancel>
<AlertDialog.Action className="Button modal destructive" onClick={(e) => props.deleteCallback(e)}>{t('modals.delete_team.buttons.confirm')}</AlertDialog.Action>
</div>
</AlertDialog.Content>
</AlertDialog.Portal>
</AlertDialog.Root>
)
} else {
return ('')
}
} }
const editable = ( const editable = (
@ -77,7 +210,6 @@ const PartyDetails = (props: Props) => {
placeholder="Name your team" placeholder="Name your team"
value={party.name} value={party.name}
limit={50} limit={50}
onBlur={updateDetails}
onChange={handleInputChange} onChange={handleInputChange}
error={errors.name} error={errors.name}
ref={nameInput} ref={nameInput}
@ -85,29 +217,72 @@ const PartyDetails = (props: Props) => {
<RaidDropdown <RaidDropdown
showAllRaidsOption={false} showAllRaidsOption={false}
currentRaid={party.raid?.slug || ''} currentRaid={party.raid?.slug || ''}
onBlur={updateDetails}
ref={raidSelect} ref={raidSelect}
/> />
<TextFieldset <TextFieldset
fieldName="name" fieldName="name"
placeholder={"Write your notes here\n\n\nWatch out for the 50% trigger!\nMake sure to click Fediels 1 first\nGood luck with RNG!"} placeholder={"Write your notes here\n\n\nWatch out for the 50% trigger!\nMake sure to click Fediels 1 first\nGood luck with RNG!"}
value={party.description} value={party.description}
onBlur={updateDetails}
onChange={handleTextAreaChange} onChange={handleTextAreaChange}
error={errors.description} error={errors.description}
ref={descriptionInput} ref={descriptionInput}
/> />
<div className="bottom">
<div className="left">
{ (router.pathname !== '/new') ? deleteButton() : '' }
</div>
<div className="right">
<Button
active={true}
onClick={toggleDetails}>
{t('buttons.cancel')}
</Button>
<Button
active={true}
icon="check"
onClick={updateDetails}>
{t('buttons.save_info')}
</Button>
</div>
</div>
</section> </section>
) )
const readOnly = ( const readOnly = (
<section className={readOnlyClasses}> <section className={readOnlyClasses}>
{ (party.name) ? <h1>{party.name}</h1> : '' } <div className="info">
{ (party.raid) ? <div className="Raid">{party.raid.name.en}</div> : '' } <div className="left">
{ (party.name) ? <h1>{party.name}</h1> : '' }
<div className="attribution">
{ (party.user) ? linkedUserBlock(party.user) : userBlock() }
{ (party.raid) ? linkedRaidBlock(party.raid) : '' }
{ (party.created_at != undefined)
? <time
className="last-updated"
dateTime={new Date(party.created_at).toString()}>
{formatTimeAgo(new Date(party.created_at), locale)}
</time>
: '' }
</div>
</div>
<div className="right">
{ (party.editable)
? <Button active={true} icon="edit" onClick={toggleDetails}>{t('buttons.show_info')}</Button>
: <div /> }
</div>
</div>
{ (party.description) ? <p><Linkify>{party.description}</Linkify></p> : '' } { (party.description) ? <p><Linkify>{party.description}</Linkify></p> : '' }
</section> </section>
) )
const emptyDetails = (
<div className={emptyClasses}>
<Button active={true} icon="edit" onClick={toggleDetails}>{t('buttons.show_info')}</Button>
</div>
)
const generateTitle = () => { const generateTitle = () => {
let title = '' let title = ''
@ -138,7 +313,7 @@ const PartyDetails = (props: Props) => {
<meta name="twitter:title" content={generateTitle()} /> <meta name="twitter:title" content={generateTitle()} />
<meta name="twitter:description" content={ (party.description) ? party.description : '' } /> <meta name="twitter:description" content={ (party.description) ? party.description : '' } />
</Head> </Head>
{readOnly} { (editable && (party.name || party.description || party.raid)) ? readOnly : emptyDetails}
{editable} {editable}
</div> </div>
) )