hensei-web/components/party/PartyDropdown/index.tsx
Justin Edmund e4b7f0c356
Fix TypeScript errors for production build (#436)
## Summary
Fixed multiple TypeScript errors that were preventing the production
build from completing on Railway.

## Changes Made

### Nullable Type Fixes
- Fixed `searchParams.toString()` calls with optional chaining (`?.`)
and fallback values
- Fixed `pathname` nullable access in UpdateToastClient
- Added fallbacks for undefined values in translation interpolations

### Type Consistency Fixes
- Fixed recency parameter handling (string from URL, converted to number
internally)
- Removed duplicate local interface definitions for Party and User types
- Fixed Party type mismatches by using global type definitions

### API Route Error Handling
- Fixed error type checking in catch blocks for login/signup routes
- Added proper type guards for axios error objects

### Component Props Fixes
- Fixed RadixSelect.Trigger by removing invalid placeholder prop
- Fixed Toast and Tooltip components by using Omit to exclude
conflicting content type
- Added missing onAdvancedFilter prop to FilterBar components
- Fixed PartyFooter props with required parameters

## Test Plan
- [x] Fixed all TypeScript compilation errors locally
- [ ] Production build should complete successfully on Railway
- [ ] All affected components should function correctly

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-04 01:47:10 -07:00

202 lines
5.1 KiB
TypeScript

'use client'
// Libraries
import React, { useEffect, useState } from 'react'
import { useRouter, usePathname } from 'next/navigation'
import { useSnapshot } from 'valtio'
import { useTranslations } from 'next-intl'
// 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 = useTranslations('common')
// Router
const router = useRouter()
const pathname = usePathname()
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 (pathname?.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