* 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
310 lines
8.7 KiB
TypeScript
310 lines
8.7 KiB
TypeScript
import React, { useEffect, useState } from 'react'
|
|
import { useRouter } from 'next/router'
|
|
import { useSnapshot } from 'valtio'
|
|
import { useTranslation } from 'next-i18next'
|
|
import classNames from 'classnames'
|
|
import clonedeep from 'lodash.clonedeep'
|
|
|
|
import Linkify from 'react-linkify'
|
|
import LiteYouTubeEmbed from 'react-lite-youtube-embed'
|
|
import reactStringReplace from 'react-string-replace'
|
|
|
|
import Button from '~components/common/Button'
|
|
import SegmentedControl from '~components/common/SegmentedControl'
|
|
import Segment from '~components/common/Segment'
|
|
import GridRepCollection from '~components/GridRepCollection'
|
|
import GridRep from '~components/GridRep'
|
|
import RemixTeamAlert from '~components/dialogs/RemixTeamAlert'
|
|
import RemixedToast from '~components/toasts/RemixedToast'
|
|
import EditPartyModal from '../EditPartyModal'
|
|
|
|
import api from '~utils/api'
|
|
import { appState } from '~utils/appState'
|
|
import { youtube } from '~utils/youtube'
|
|
|
|
import type { DetailsObject } from 'types'
|
|
|
|
import RemixIcon from '~public/icons/Remix.svg'
|
|
import EditIcon from '~public/icons/Edit.svg'
|
|
import styles from './index.module.scss'
|
|
|
|
// Props
|
|
interface Props {
|
|
party?: Party
|
|
new: boolean
|
|
editable: boolean
|
|
raidGroups: RaidGroup[]
|
|
remixCallback: () => void
|
|
updateCallback: (details: DetailsObject) => Promise<any>
|
|
}
|
|
|
|
const PartyFooter = (props: Props) => {
|
|
const { t } = useTranslation('common')
|
|
const router = useRouter()
|
|
|
|
const { party: partySnapshot } = useSnapshot(appState)
|
|
|
|
const youtubeUrlRegex =
|
|
/(?:https:\/\/www\.youtube\.com\/watch\?v=|https:\/\/youtu\.be\/)([\w-]+)/g
|
|
|
|
// State: Component
|
|
const [currentSegment, setCurrentSegment] = useState(0)
|
|
const [detailsOpen, setDetailsOpen] = useState(false)
|
|
const [remixAlertOpen, setRemixAlertOpen] = useState(false)
|
|
const [remixToastOpen, setRemixToastOpen] = useState(false)
|
|
|
|
// State: Data
|
|
const [remixes, setRemixes] = useState<Party[]>([])
|
|
const [embeddedDescription, setEmbeddedDescription] =
|
|
useState<React.ReactNode>()
|
|
|
|
useEffect(() => {
|
|
// Extract the video IDs from the description
|
|
if (partySnapshot.description) {
|
|
const videoIds = extractYoutubeVideoIds(partySnapshot.description)
|
|
|
|
// Fetch the video titles for each ID
|
|
const fetchPromises = videoIds.map(({ id }) => fetchYoutubeData(id))
|
|
|
|
// Wait for all the video titles to be fetched
|
|
Promise.all(fetchPromises).then((videoTitles) => {
|
|
// Replace the video URLs in the description with LiteYoutubeEmbed elements
|
|
const newDescription = reactStringReplace(
|
|
partySnapshot.description,
|
|
youtubeUrlRegex,
|
|
(match, i) => (
|
|
<LiteYouTubeEmbed
|
|
key={`${match}-${i}`}
|
|
id={match}
|
|
title={videoTitles[i]}
|
|
wrapperClass={styles.youtube}
|
|
playerClass={styles.playerButton}
|
|
/>
|
|
)
|
|
)
|
|
|
|
// Update the state with the new description
|
|
setEmbeddedDescription(newDescription)
|
|
})
|
|
} else {
|
|
setEmbeddedDescription('')
|
|
}
|
|
}, [partySnapshot.description])
|
|
|
|
async function fetchYoutubeData(videoId: string) {
|
|
return await youtube
|
|
.getVideoById(videoId, { maxResults: 1 })
|
|
.then((data) => data.items[0].snippet.localized.title)
|
|
}
|
|
|
|
// Methods: Navigation
|
|
function goTo(shortcode?: string) {
|
|
if (shortcode) router.push(`/p/${shortcode}`)
|
|
}
|
|
|
|
function extractYoutubeVideoIds(text: string) {
|
|
// Initialize an array to store the video IDs
|
|
const videoIds = []
|
|
|
|
// Use the regular expression to find all the Youtube URLs in the text
|
|
let match
|
|
while ((match = youtubeUrlRegex.exec(text)) !== null) {
|
|
// Extract the video ID from the URL
|
|
const videoId = match[1]
|
|
|
|
// Add the video ID to the array, along with the character position of the URL
|
|
videoIds.push({
|
|
id: videoId,
|
|
url: match[0],
|
|
position: match.index,
|
|
})
|
|
}
|
|
|
|
// Return the array of video IDs
|
|
return videoIds
|
|
}
|
|
|
|
// Methods: Favorites
|
|
function toggleFavorite(teamId: string, favorited: boolean) {
|
|
if (favorited) unsaveFavorite(teamId)
|
|
else saveFavorite(teamId)
|
|
}
|
|
|
|
function saveFavorite(teamId: string) {
|
|
api.saveTeam({ id: teamId }).then((response) => {
|
|
if (response.status == 201) {
|
|
const index = remixes.findIndex((p) => p.id === teamId)
|
|
const party = remixes[index]
|
|
|
|
party.favorited = true
|
|
|
|
let clonedParties = clonedeep(remixes)
|
|
clonedParties[index] = party
|
|
|
|
setRemixes(clonedParties)
|
|
}
|
|
})
|
|
}
|
|
|
|
function unsaveFavorite(teamId: string) {
|
|
api.unsaveTeam({ id: teamId }).then((response) => {
|
|
if (response.status == 200) {
|
|
const index = remixes.findIndex((p) => p.id === teamId)
|
|
const party = remixes[index]
|
|
|
|
party.favorited = false
|
|
|
|
let clonedParties = clonedeep(remixes)
|
|
clonedParties[index] = party
|
|
|
|
setRemixes(clonedParties)
|
|
}
|
|
})
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
const segmentedControl = (
|
|
<SegmentedControl className="background">
|
|
<Segment
|
|
name="description"
|
|
groupName="footer"
|
|
selected={currentSegment === 0}
|
|
onClick={() => setCurrentSegment(0)}
|
|
>
|
|
{t('footer.description.label')}
|
|
</Segment>
|
|
<Segment
|
|
name="remixes"
|
|
groupName="footer"
|
|
selected={currentSegment === 1}
|
|
onClick={() => setCurrentSegment(1)}
|
|
>
|
|
{t('footer.remixes.label', { count: partySnapshot?.remixes?.length })}
|
|
</Segment>
|
|
</SegmentedControl>
|
|
)
|
|
|
|
const descriptionSection = (
|
|
<section className={styles.description}>
|
|
{partySnapshot &&
|
|
partySnapshot.description &&
|
|
partySnapshot.description.length > 0 && (
|
|
<Linkify>{embeddedDescription}</Linkify>
|
|
)}
|
|
{(!partySnapshot || !partySnapshot.description) && (
|
|
<div className={styles.noDescription}>
|
|
<h3>{t('footer.description.empty')}</h3>
|
|
{props.editable && (
|
|
<EditPartyModal
|
|
open={detailsOpen}
|
|
party={props.party}
|
|
raidGroups={props.raidGroups}
|
|
onOpenChange={handleDetailsOpenChange}
|
|
updateParty={props.updateCallback}
|
|
>
|
|
<Button
|
|
leftAccessoryIcon={<EditIcon />}
|
|
text={t('buttons.show_info')}
|
|
/>
|
|
</EditPartyModal>
|
|
)}
|
|
</div>
|
|
)}
|
|
</section>
|
|
)
|
|
|
|
const remixesSection = (
|
|
<section className={styles.remixes}>
|
|
{partySnapshot?.remixes?.length > 0 && (
|
|
<GridRepCollection>{renderRemixes()}</GridRepCollection>
|
|
)}
|
|
{partySnapshot?.remixes?.length === 0 && (
|
|
<div className={styles.noRemixes}>
|
|
<h3>{t('footer.remixes.empty')}</h3>
|
|
<Button
|
|
leftAccessoryIcon={<RemixIcon />}
|
|
text={t('buttons.remix')}
|
|
onClick={openRemixTeamAlert}
|
|
/>
|
|
</div>
|
|
)}
|
|
</section>
|
|
)
|
|
|
|
function renderRemixes() {
|
|
return partySnapshot?.remixes.map((party, i) => {
|
|
return (
|
|
<GridRep
|
|
id={party.id}
|
|
shortcode={party.shortcode}
|
|
name={party.name}
|
|
createdAt={new Date(party.created_at)}
|
|
raid={party.raid}
|
|
grid={party.weapons}
|
|
user={party.user}
|
|
favorited={party.favorited}
|
|
fullAuto={party.full_auto}
|
|
autoGuard={party.auto_guard}
|
|
key={`party-${i}`}
|
|
onClick={goTo}
|
|
onSave={toggleFavorite}
|
|
/>
|
|
)
|
|
})
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className={styles.wrapper}>
|
|
{segmentedControl}
|
|
{currentSegment === 0 && descriptionSection}
|
|
{currentSegment === 1 && remixesSection}
|
|
</div>
|
|
|
|
<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 PartyFooter
|