## Summary
- Fixed translation key format compatibility with next-intl
- Fixed pluralization format from i18next to next-intl format
- Fixed dynamic translation key error handling
- Updated server components to match API response structure
- Fixed useSearchParams import location
## Changes
- Changed pluralization from `{{count}} items` to `{count} items` format
- Added proper error handling for missing translation keys
- Fixed import paths for next-intl hooks
- Fixed PartyPageClient trying to set non-existent appState.parties
## Test plan
- [x] Verified translations render correctly
- [x] Tested pluralization works with different counts
- [x] Confirmed no console errors about missing translations
- [x] Tested party page functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
114 lines
2.6 KiB
TypeScript
114 lines
2.6 KiB
TypeScript
'use client'
|
|
import React, { useState } from 'react'
|
|
import { getCookie } from 'cookies-next'
|
|
import classNames from 'classnames'
|
|
import Button from '~components/common/Button'
|
|
import JobAccessoryPopover from '~components/job/JobAccessoryPopover'
|
|
|
|
import ShieldIcon from '~public/icons/Shield.svg'
|
|
import ManaturaIcon from '~public/icons/Manatura.svg'
|
|
|
|
import styles from './index.module.scss'
|
|
|
|
interface Props {
|
|
job?: Job
|
|
currentAccessory?: JobAccessory
|
|
accessories?: JobAccessory[]
|
|
editable: boolean
|
|
user?: User
|
|
onAccessorySelected: (value: string) => void
|
|
}
|
|
|
|
const JobImage = ({
|
|
job,
|
|
currentAccessory,
|
|
editable,
|
|
accessories,
|
|
user,
|
|
onAccessorySelected,
|
|
}: Props) => {
|
|
// Localization
|
|
const locale = (getCookie('NEXT_LOCALE') as string) || 'en'
|
|
|
|
// Component state
|
|
const [open, setOpen] = useState(false)
|
|
|
|
// Refs
|
|
const buttonRef = React.createRef<HTMLButtonElement>()
|
|
|
|
// Static variables
|
|
const imageUrl = () => {
|
|
let source = ''
|
|
|
|
if (job) {
|
|
const slug = job.name.en.replaceAll(' ', '-').toLowerCase()
|
|
const gender = user && user.gender == 1 ? 'b' : 'a'
|
|
source = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/jobs/${slug}_${gender}.png`
|
|
}
|
|
|
|
return source
|
|
}
|
|
|
|
const hasAccessory = job && job.accessory
|
|
const image = <img alt={job?.name[locale]} src={imageUrl()} />
|
|
|
|
const classes = classNames({
|
|
jobAccessory: true,
|
|
selected: open,
|
|
})
|
|
|
|
function handleAccessoryButtonClicked() {
|
|
setOpen(!open)
|
|
}
|
|
|
|
function handlePopoverOpenChanged(open: boolean) {
|
|
setOpen(open)
|
|
}
|
|
|
|
// Elements
|
|
const accessoryButton = () => {
|
|
let icon
|
|
|
|
if (job && job.accessory_type === 1) icon = <ShieldIcon />
|
|
else if (job && job.accessory_type === 2) icon = <ManaturaIcon />
|
|
|
|
return (
|
|
<Button
|
|
leftAccessoryIcon={icon}
|
|
className={classes}
|
|
onClick={handleAccessoryButtonClicked}
|
|
size="icon"
|
|
ref={buttonRef}
|
|
/>
|
|
)
|
|
}
|
|
|
|
const accessoryPopover = () => {
|
|
return job && accessories ? (
|
|
<JobAccessoryPopover
|
|
buttonref={buttonRef}
|
|
currentAccessory={currentAccessory}
|
|
accessories={accessories}
|
|
editable={editable}
|
|
open={open}
|
|
job={job}
|
|
key={`accessory-${open}`}
|
|
onAccessorySelected={onAccessorySelected}
|
|
onOpenChange={handlePopoverOpenChanged}
|
|
>
|
|
{accessoryButton()}
|
|
</JobAccessoryPopover>
|
|
) : (
|
|
''
|
|
)
|
|
}
|
|
return (
|
|
<div className={styles.image}>
|
|
{hasAccessory ? accessoryPopover() : ''}
|
|
{job && job.id !== '-1' ? image : ''}
|
|
<div className={styles.overlay} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default JobImage
|