Redesign read only state and add save button
This commit is contained in:
parent
9291e5501a
commit
bda2639d88
2 changed files with 280 additions and 28 deletions
|
|
@ -31,6 +31,22 @@
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: $unit;
|
||||
|
||||
.left {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: $unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.ReadOnly {
|
||||
|
|
@ -45,26 +61,8 @@
|
|||
top: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: $font-xlarge;
|
||||
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;
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
p {
|
||||
|
|
@ -72,5 +70,84 @@
|
|||
line-height: $font-regular * 1.2;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +1,53 @@
|
|||
import React, { useState } from 'react'
|
||||
import Head from 'next/head'
|
||||
import Router, { useRouter } from 'next/router'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
import Linkify from 'react-linkify'
|
||||
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 RaidDropdown from '~components/RaidDropdown'
|
||||
import TextFieldset from '~components/TextFieldset'
|
||||
|
||||
import { accountState } from '~utils/accountState'
|
||||
import { appState } from '~utils/appState'
|
||||
|
||||
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
|
||||
interface Props {
|
||||
editable: boolean
|
||||
updateCallback: (name?: string, description?: string, raid?: Raid) => void
|
||||
deleteCallback: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
|
||||
}
|
||||
|
||||
const PartyDetails = (props: Props) => {
|
||||
const { party, raids } = useSnapshot(appState)
|
||||
const { account } = useSnapshot(accountState)
|
||||
|
||||
const { t } = useTranslation('common')
|
||||
const router = useRouter()
|
||||
const locale = router.locale || 'en'
|
||||
|
||||
const nameInput = React.createRef<HTMLInputElement>()
|
||||
const descriptionInput = React.createRef<HTMLTextAreaElement>()
|
||||
|
|
@ -39,6 +65,25 @@ const PartyDetails = (props: Props) => {
|
|||
'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 }>({
|
||||
name: '',
|
||||
description: ''
|
||||
|
|
@ -62,12 +107,100 @@ const PartyDetails = (props: Props) => {
|
|||
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 descriptionValue = descriptionInput.current?.value
|
||||
const raid = raids.find(raid => raid.slug === raidSelect.current?.value)
|
||||
|
||||
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 = (
|
||||
|
|
@ -77,7 +210,6 @@ const PartyDetails = (props: Props) => {
|
|||
placeholder="Name your team"
|
||||
value={party.name}
|
||||
limit={50}
|
||||
onBlur={updateDetails}
|
||||
onChange={handleInputChange}
|
||||
error={errors.name}
|
||||
ref={nameInput}
|
||||
|
|
@ -85,29 +217,72 @@ const PartyDetails = (props: Props) => {
|
|||
<RaidDropdown
|
||||
showAllRaidsOption={false}
|
||||
currentRaid={party.raid?.slug || ''}
|
||||
onBlur={updateDetails}
|
||||
ref={raidSelect}
|
||||
/>
|
||||
<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={party.description}
|
||||
onBlur={updateDetails}
|
||||
onChange={handleTextAreaChange}
|
||||
error={errors.description}
|
||||
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>
|
||||
)
|
||||
|
||||
const readOnly = (
|
||||
<section className={readOnlyClasses}>
|
||||
{ (party.name) ? <h1>{party.name}</h1> : '' }
|
||||
{ (party.raid) ? <div className="Raid">{party.raid.name.en}</div> : '' }
|
||||
<div className="info">
|
||||
<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> : '' }
|
||||
</section>
|
||||
)
|
||||
|
||||
const emptyDetails = (
|
||||
<div className={emptyClasses}>
|
||||
<Button active={true} icon="edit" onClick={toggleDetails}>{t('buttons.show_info')}</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
const generateTitle = () => {
|
||||
let title = ''
|
||||
|
||||
|
|
@ -138,7 +313,7 @@ const PartyDetails = (props: Props) => {
|
|||
<meta name="twitter:title" content={generateTitle()} />
|
||||
<meta name="twitter:description" content={ (party.description) ? party.description : '' } />
|
||||
</Head>
|
||||
{readOnly}
|
||||
{ (editable && (party.name || party.description || party.raid)) ? readOnly : emptyDetails}
|
||||
{editable}
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue