hensei-web/components/party/PartyDropdown/index.tsx
Justin Edmund 9e6c9a2108
Implement party visibility (#369)
Parties can now be set to be private or unlisted. Private parties cannot
be shared with anyone while Unlisted parties can be seen by those with
the link.

We implemented a dialog to change visibility, notices to let users know
if a party isn't public, and icons on the GridRep so users can see at a
glance which of their parties has different visibility on their profile.

![CleanShot 2023-08-25 at 15 50
10@2x](https://github.com/jedmund/hensei-web/assets/383021/488b7fe2-497a-48f3-982a-d603c0a34539)

![CleanShot 2023-08-25 at 15 49
45@2x](https://github.com/jedmund/hensei-web/assets/383021/675523f6-d158-4019-8c1a-cf87b48501f9)

![CleanShot 2023-08-25 at 15 50
49@2x](https://github.com/jedmund/hensei-web/assets/383021/419a3b06-f083-4c9e-b4fb-ea70669513fd)
2023-08-25 15:51:28 -07:00

199 lines
5 KiB
TypeScript

// Libraries
import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { useSnapshot } from 'valtio'
import { useTranslation } from 'next-i18next'
// Dependencies: Common
import Button from '~components/common/Button'
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
} from '~components/common/DropdownMenuContent'
import DropdownMenuGroup from '~components/common/DropdownMenuGroup'
import DropdownMenuItem from '~components/common/DropdownMenuItem'
// Dependencies: Toasts
import RemixedToast from '~components/toasts/RemixedToast'
import UrlCopiedToast from '~components/toasts/UrlCopiedToast'
// Dependencies: Alerts
import DeleteTeamAlert from '~components/dialogs/DeleteTeamAlert'
import RemixTeamAlert from '~components/dialogs/RemixTeamAlert'
// Dependencies: Utils
import { appState } from '~utils/appState'
// Dependencies: Icons
import EllipsisIcon from '~public/icons/Ellipsis.svg'
// Dependencies: Props
interface Props {
editable: boolean
deleteTeamCallback: () => void
remixTeamCallback: () => void
teamVisibilityCallback: () => void
}
const PartyDropdown = ({
editable,
deleteTeamCallback,
remixTeamCallback,
teamVisibilityCallback,
}: Props) => {
// Localization
const { t } = useTranslation('common')
// Router
const router = useRouter()
const [open, setOpen] = useState(false)
const [deleteAlertOpen, setDeleteAlertOpen] = useState(false)
const [remixAlertOpen, setRemixAlertOpen] = useState(false)
const [copyToastOpen, setCopyToastOpen] = useState(false)
const [remixToastOpen, setRemixToastOpen] = useState(false)
// Snapshots
const { party: partySnapshot } = useSnapshot(appState)
// Methods: Event handlers (Buttons)
function handleButtonClicked() {
setOpen(!open)
}
// Methods: Event handlers (Menus)
function handleOpenChange(open: boolean) {
setOpen(open)
}
function closeMenu() {
setOpen(false)
}
// Method: Actions
function copyToClipboard() {
if (router.asPath.split('/')[1] === 'p') {
navigator.clipboard.writeText(window.location.href)
setCopyToastOpen(true)
}
}
// Methods: Event handlers
// Dialogs / Visibility
function visibilityCallback() {
teamVisibilityCallback()
}
// Alerts / Delete team
function openDeleteTeamAlert() {
setDeleteAlertOpen(true)
}
function handleDeleteTeamAlertChange(open: boolean) {
setDeleteAlertOpen(open)
}
// Alerts / Remix team
function openRemixTeamAlert() {
setRemixAlertOpen(true)
}
function handleRemixTeamAlertChange(open: boolean) {
setRemixAlertOpen(open)
}
// Toasts / Copy URL
function handleCopyToastOpenChanged(open: boolean) {
setCopyToastOpen(!open)
}
function handleCopyToastCloseClicked() {
setCopyToastOpen(false)
}
// Toasts: Remix team
function handleRemixToastOpenChanged(open: boolean) {
setRemixToastOpen(!open)
}
function handleRemixToastCloseClicked() {
setRemixToastOpen(false)
}
function remixCallback() {
setRemixToastOpen(true)
remixTeamCallback()
}
const items = (
<>
<DropdownMenuGroup>
<DropdownMenuItem onClick={visibilityCallback}>
<span>{t('dropdown.party.visibility')}</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={copyToClipboard}>
<span>{t('dropdown.party.copy')}</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={openRemixTeamAlert}>
<span>{t('dropdown.party.remix')}</span>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuGroup>
<DropdownMenuItem destructive={true} onClick={openDeleteTeamAlert}>
<span>{t('dropdown.party.delete')}</span>
</DropdownMenuItem>
</DropdownMenuGroup>
</>
)
return (
<>
<div className="dropdownWrapper">
<DropdownMenu open={open} onOpenChange={handleOpenChange}>
<DropdownMenuTrigger asChild>
<Button
active={open}
blended={true}
leftAccessoryIcon={<EllipsisIcon />}
onClick={handleButtonClicked}
/>
</DropdownMenuTrigger>
<DropdownMenuContent>{items}</DropdownMenuContent>
</DropdownMenu>
</div>
<DeleteTeamAlert
open={deleteAlertOpen}
onOpenChange={handleDeleteTeamAlertChange}
deleteCallback={deleteTeamCallback}
/>
<RemixTeamAlert
creator={editable}
name={partySnapshot.name || t('no_title')}
open={remixAlertOpen}
onOpenChange={handleRemixTeamAlertChange}
remixCallback={remixCallback}
/>
<RemixedToast
open={remixToastOpen}
partyName={partySnapshot.name || t('no_title')}
onOpenChange={handleRemixToastOpenChanged}
onCloseClick={handleRemixToastCloseClicked}
/>
<UrlCopiedToast
open={copyToastOpen}
onOpenChange={handleCopyToastOpenChanged}
onCloseClick={handleCopyToastCloseClicked}
/>
</>
)
}
export default PartyDropdown