hensei-web/components/party/PartyHeader/index.tsx
Justin Edmund 702566e2ed
(Hotfix) (Temporary) nuclear option for raid population (#339)
* Another attempt to fix RaidCombobox loading

* Final nuclear option of getting raids to populate

No matter what I do, raids won't populate from state specifically in production. I will have to investigate this more, but for now we are going with the nuclear option of passing raids down from the context object we get from SSR through all components into RaidCombobox
2023-07-04 02:20:48 -07:00

433 lines
12 KiB
TypeScript

import React, { useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useSnapshot } from 'valtio'
import { useTranslation } from 'next-i18next'
import classNames from 'classnames'
import Button from '~components/common/Button'
import Tooltip from '~components/common/Tooltip'
import Token from '~components/common/Token'
import EditPartyModal from '~components/party/EditPartyModal'
import PartyDropdown from '~components/party/PartyDropdown'
import api from '~utils/api'
import { accountState } from '~utils/accountState'
import { appState, initialAppState } from '~utils/appState'
import { formatTimeAgo } from '~utils/timeAgo'
import RemixTeamAlert from '~components/dialogs/RemixTeamAlert'
import RemixedToast from '~components/toasts/RemixedToast'
import EditIcon from '~public/icons/Edit.svg'
import RemixIcon from '~public/icons/Remix.svg'
import SaveIcon from '~public/icons/Save.svg'
import type { DetailsObject } from 'types'
import styles from './index.module.scss'
// Props
interface Props {
party?: Party
new: boolean
editable: boolean
raidGroups: RaidGroup[]
deleteCallback: () => void
remixCallback: () => void
updateCallback: (details: DetailsObject) => Promise<any>
}
const PartyHeader = (props: Props) => {
const { party } = useSnapshot(appState)
const { t } = useTranslation('common')
const router = useRouter()
const locale = router.locale || 'en'
const { party: partySnapshot } = useSnapshot(appState)
// State: Component
const [detailsOpen, setDetailsOpen] = useState(false)
const [remixAlertOpen, setRemixAlertOpen] = useState(false)
const [remixToastOpen, setRemixToastOpen] = useState(false)
const userClass = classNames({
[styles.user]: true,
[styles.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,
})
// Actions: Favorites
function toggleFavorite() {
if (appState.party.favorited) unsaveFavorite()
else saveFavorite()
}
function saveFavorite() {
if (appState.party.id)
api.saveTeam({ id: appState.party.id }).then((response) => {
if (response.status == 201) appState.party.favorited = true
})
else console.error('Failed to save team: No party ID')
}
function unsaveFavorite() {
if (appState.party.id)
api.unsaveTeam({ id: appState.party.id }).then((response) => {
if (response.status == 200) appState.party.favorited = false
})
else console.error('Failed to unsave team: No party ID')
}
// Methods: Navigation
function goTo(shortcode?: string) {
if (shortcode) router.push(`/p/${shortcode}`)
}
const userImage = (picture?: string, element?: string) => {
if (picture && element)
return (
<img
alt={picture}
className={`profile ${element}`}
srcSet={`/profile/${picture}.png,
/profile/${picture}@2x.png 2x`}
src={`/profile/${picture}.png`}
/>
)
else
return (
<img
alt={t('no_user')}
className={`profile anonymous`}
srcSet={`/profile/npc.png,
/profile/npc@2x.png 2x`}
src={`/profile/npc.png`}
/>
)
}
// Actions: Edit info
function handleDetailsOpenChange(open: boolean) {
setDetailsOpen(open)
}
// Actions: Remix team
function remixTeamCallback() {
setRemixToastOpen(true)
props.remixCallback()
}
// Alerts: Remix team
function openRemixTeamAlert() {
setRemixAlertOpen(true)
}
function handleRemixTeamAlertChange(open: boolean) {
setRemixAlertOpen(open)
}
// Toasts: Remix team
function handleRemixToastOpenChanged(open: boolean) {
setRemixToastOpen(!open)
}
function handleRemixToastCloseClicked() {
setRemixToastOpen(false)
}
// Rendering
const userBlock = (username?: string, picture?: string, element?: string) => {
return (
<div className={userClass}>
{userImage(picture, element)}
{username ? username : t('no_user')}
</div>
)
}
const renderUserBlock = () => {
let username, picture, element
if (accountState.account.authorized && props.new) {
username = accountState.account.user?.username
picture = accountState.account.user?.avatar.picture
element = accountState.account.user?.avatar.element
} else if (party.user && !props.new) {
username = party.user.username
picture = party.user.avatar.picture
element = party.user.avatar.element
}
if (username && picture && element) {
return linkedUserBlock(username, picture, element)
} else if (!props.new) {
return userBlock()
}
}
const linkedUserBlock = (
username?: string,
picture?: string,
element?: string
) => {
return (
<div>
<Link href={`/${username}`} passHref>
<a className={linkClass}>{userBlock(username, picture, element)}</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>
)
}
// Render: Tokens
const chargeAttackToken = (
<Token active={party.chargeAttack} className="chargeAttack">
{`${t('party.details.labels.charge_attack')} ${
party.chargeAttack ? 'On' : 'Off'
}`}
</Token>
)
const fullAutoToken = (
<Token active={party.fullAuto} className="fullAuto">
{`${t('party.details.labels.full_auto')} ${
party.fullAuto ? 'On' : 'Off'
}`}
</Token>
)
const autoGuardToken = (
<Token active={party.autoGuard} className="autoGuard">
{`${t('party.details.labels.auto_guard')} ${
party.autoGuard ? 'On' : 'Off'
}`}
</Token>
)
const autoSummonToken = (
<Token active={party.autoSummon} className="autoSummon">
{`${t('party.details.labels.auto_summon')} ${
party.autoSummon ? 'On' : 'Off'
}`}
</Token>
)
const turnCountToken = (
<Token>
{t('party.details.turns.with_count', {
count: party.turnCount,
})}
</Token>
)
const buttonChainToken = () => {
if (party.buttonCount !== undefined || party.chainCount !== undefined) {
let string = ''
if (party.buttonCount !== undefined) {
string += `${party.buttonCount}b`
}
if (party.buttonCount === undefined && party.chainCount !== undefined) {
string += `0${t('party.details.suffix.buttons')}${party.chainCount}${t(
'party.details.suffix.chains'
)}`
} else if (
party.buttonCount !== undefined &&
party.chainCount !== undefined
) {
string += `${party.chainCount}${t('party.details.suffix.chains')}`
} else if (
party.buttonCount !== undefined &&
party.chainCount === undefined
) {
string += `0${t('party.details.suffix.chains')}`
}
return <Token>{string}</Token>
}
}
const clearTimeToken = () => {
const minutes = Math.floor(party.clearTime / 60)
const seconds = party.clearTime - minutes * 60
let string = ''
if (minutes > 0)
string = `${minutes}${t('party.details.suffix.minutes')} ${seconds}${t(
'party.details.suffix.seconds'
)}`
else string = `${seconds}${t('party.details.suffix.seconds')}`
return <Token>{string}</Token>
}
function renderTokens() {
return (
<>
{' '}
{chargeAttackToken}
{fullAutoToken}
{autoSummonToken}
{autoGuardToken}
{party.turnCount !== undefined && turnCountToken}
{party.clearTime > 0 && clearTimeToken()}
{buttonChainToken()}
</>
)
}
// Render: Buttons
const saveButton = () => {
return (
<Tooltip content={t('tooltips.save')}>
<Button
leftAccessoryIcon={<SaveIcon />}
className={classNames({
save: true,
grow: true,
saved: partySnapshot.favorited,
})}
text={
appState.party.favorited ? t('buttons.saved') : t('buttons.save')
}
onClick={toggleFavorite}
/>
</Tooltip>
)
}
const remixButton = () => {
return (
<Tooltip content={t('tooltips.remix.create')}>
<Button
leftAccessoryIcon={<RemixIcon />}
className="grow"
text={t('buttons.remix')}
onClick={openRemixTeamAlert}
/>
</Tooltip>
)
}
const remixedButton = () => {
const tooltipString =
party.remix && party.sourceParty
? t('tooltips.remix.source')
: t('tooltips.remix.deleted')
const buttonAction =
party.sourceParty && (() => goTo(party.sourceParty?.shortcode))
return (
<Tooltip content={tooltipString}>
<Button
blended={true}
className="remixed"
leftAccessoryIcon={<RemixIcon />}
text={t('tokens.remix')}
size="small"
disabled={!party.sourceParty}
onClick={buttonAction}
/>
</Tooltip>
)
}
return (
<>
<header className={styles.wrapper}>
<section className={styles.info}>
<div className={styles.left}>
<div className={styles.header}>
<h1 className={party.name ? '' : styles.empty}>
{party.name ? party.name : t('no_title')}
</h1>
{party.remix && remixedButton()}
</div>
<div className={styles.attribution}>
{renderUserBlock()}
{appState.party.raid && linkedRaidBlock(appState.party.raid)}
{party.created_at != '' && (
<time
className={styles.lastUpdated}
dateTime={new Date(party.created_at).toString()}
>
{formatTimeAgo(new Date(party.created_at), locale)}
</time>
)}
</div>
</div>
{props.editable ? (
<div className={styles.right}>
<EditPartyModal
open={detailsOpen}
party={props.party}
raidGroups={props.raidGroups}
onOpenChange={handleDetailsOpenChange}
updateParty={props.updateCallback}
>
<Button
className="full"
leftAccessoryIcon={<EditIcon />}
text={t('buttons.show_info')}
/>
</EditPartyModal>
{!props.new && (
<PartyDropdown
editable={props.editable}
deleteTeamCallback={props.deleteCallback}
remixTeamCallback={props.remixCallback}
/>
)}
</div>
) : (
<div className={styles.right}>
{saveButton()}
{remixButton()}
</div>
)}
</section>
<section className={styles.tokens}>{renderTokens()}</section>
</header>
<RemixTeamAlert
creator={props.editable}
name={partySnapshot.name ? partySnapshot.name : t('no_title')}
open={remixAlertOpen}
onOpenChange={handleRemixTeamAlertChange}
remixCallback={remixTeamCallback}
/>
<RemixedToast
open={remixToastOpen}
partyName={props.party?.name || t('no_title')}
onOpenChange={handleRemixToastOpenChanged}
onCloseClick={handleRemixToastCloseClicked}
/>
</>
)
}
export default PartyHeader