Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/locales/lib/human/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@
"exclude_lure": "Exclude Lure",
"timer": "Timer",
"hide": "Hide",
"hidden_for_hour": "Hidden for an hour",
"clean_hidden": "Clean Hidden",
Comment on lines +141 to +142
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The translations for 'hidden_for_hour' and 'clean_hidden' are only added to the English and Polish locale files. This creates an inconsistent user experience for users of other supported languages (de, es, fr, it, ja, nl, pt-br, ru, sv, th, tr, zh-tw). When these users trigger the hide action or access the settings, they will see untranslated keys instead of proper messages. All locale files in packages/locales/lib/human/ should include these new translation keys.

Copilot uses AI. Check for mistakes.
"tier": "Tier",
"slots": "Slots",
"mega": "Mega",
Expand Down
2 changes: 2 additions & 0 deletions packages/locales/lib/human/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@
"has_quest_indicator": "Alternatywny kolor dla Pokéstopów z zadaniami",
"help": "Pomoc",
"hide": "Ukryj",
"hidden_for_hour": "Schowano na godzinę",
"clean_hidden": "Wyczyść schowane",
"hide_editor": "Ukryj edytor",
"historic_rarity": "Rzadkość historyczna",
"hisuian": "Hisuian",
Expand Down
11 changes: 11 additions & 0 deletions src/features/drawer/settings/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import InsightsIcon from '@mui/icons-material/Insights'
import NotificationsActiveIcon from '@mui/icons-material/NotificationsActive'
import NotificationsOffIcon from '@mui/icons-material/NotificationsOff'
import LogoDevIcon from '@mui/icons-material/LogoDev'
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'
import { useTranslation } from 'react-i18next'

import { useMemory } from '@store/useMemory'
Expand All @@ -22,6 +23,7 @@ import { LocaleSelection } from '@components/inputs/LocaleSelection'
import { DividerWithMargin } from '@components/StyledDivider'
import { BoolToggle } from '@components/inputs/BoolToggle'
import { BasicListButton } from '@components/inputs/BasicListButton'
import { clearHiddenEntities } from '@utils/pokemon/hiddenPokemon'

import { DrawerActions } from '../components/Actions'
import { GeneralSetting } from './General'
Expand Down Expand Up @@ -70,6 +72,15 @@ export function Settings() {
</BasicListButton>
)}
<HolidaySetting />
<BasicListButton
onClick={() => {
clearHiddenEntities()
useMemory.setState({ hideList: new Set() })
}}
label="clean_hidden"
>
<VisibilityOffIcon />
</BasicListButton>
<DividerWithMargin />
<UAssetSetting asset="icons" />
<UAssetSetting asset="audio" />
Expand Down
4 changes: 3 additions & 1 deletion src/features/gym/GymPopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { getTimeUntil } from '@utils/getTimeUntil'
import { formatInterval } from '@utils/formatInterval'
import { usePokemonBackgroundVisuals } from '@hooks/usePokemonBackgroundVisuals'
import { getFormDisplay } from '@utils/getFormDisplay'
import { addHiddenEntity, showHideSnackbar } from '@utils/pokemon/hiddenPokemon'

import { useWebhook } from './useWebhook'

Expand Down Expand Up @@ -747,7 +748,8 @@ const DropdownOptions = ({

const handleHide = () => {
handleClose()
useMemory.setState((prev) => ({ hideList: new Set(prev.hideList).add(id) }))
useMemory.setState({ hideList: addHiddenEntity(id) })
showHideSnackbar(t('hidden_for_hour'))
}

const handleExclude = (key) => {
Expand Down
4 changes: 3 additions & 1 deletion src/features/nest/NestPopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { setDeepStore } from '@store/useStorage'
import { getTimeUntil } from '@utils/getTimeUntil'
import { useAnalytics } from '@hooks/useAnalytics'
import { Navigation } from '@components/popups/Navigation'
import { addHiddenEntity, showHideSnackbar } from '@utils/pokemon/hiddenPokemon'

/** @param {number} timeSince */
const getColor = (timeSince) => {
Expand Down Expand Up @@ -62,7 +63,8 @@ export function NestPopup({
const handleClose = () => setAnchorEl(null)
const handleHide = () => {
setAnchorEl(null)
useMemory.setState((prev) => ({ hideList: new Set(prev.hideList).add(id) }))
useMemory.setState({ hideList: addHiddenEntity(id) })
showHideSnackbar(t('hidden_for_hour'))
}

const handleExclude = () => {
Expand Down
3 changes: 2 additions & 1 deletion src/features/pokemon/PokemonPopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { GET_TAPPABLE_BY_ID } from '@services/queries/tappable'
import { usePokemonBackgroundVisual } from '@hooks/usePokemonBackgroundVisuals'
import { BackgroundCard } from '@components/popups/BackgroundCard'
import { getFormDisplay } from '@utils/getFormDisplay'
import { addHiddenEntity } from '@utils/pokemon/hiddenPokemon'
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The showHideSnackbar import is missing from this file. The handleHide function calls showHideSnackbar on line 113 (in TappablePopup.jsx), but PokemonPopup.jsx only imports addHiddenEntity. To be consistent with other popups and provide user feedback, this file needs to import showHideSnackbar as well.

Suggested change
import { addHiddenEntity } from '@utils/pokemon/hiddenPokemon'
import { addHiddenEntity } from '@utils/pokemon/hiddenPokemon'
import { showHideSnackbar } from '@utils/showHideSnackbar'

Copilot uses AI. Check for mistakes.

const rowClass = { width: 30, fontWeight: 'bold' }

Expand Down Expand Up @@ -357,7 +358,7 @@ const Header = ({ pokemon, metaData, iconUrl, userSettings, isTutorial }) => {

const handleHide = () => {
setAnchorEl(null)
useMemory.setState((prev) => ({ hideList: new Set(prev.hideList).add(id) }))
useMemory.setState({ hideList: addHiddenEntity(id) })
}
Comment on lines 359 to 362
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PokemonPopup component is missing the showHideSnackbar call when hiding an entity. All other popup components (TappablePopup, StationPopup, PokestopPopup, NestPopup, GymPopup) call showHideSnackbar after adding a hidden entity to provide user feedback, but PokemonPopup only calls addHiddenEntity. This creates an inconsistent user experience where users don't receive feedback when hiding Pokémon.

Copilot uses AI. Check for mistakes.

const handleExclude = () => {
Expand Down
4 changes: 3 additions & 1 deletion src/features/pokestop/PokestopPopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { useGetAvailable } from '@hooks/useGetAvailable'
import { parseQuestConditions } from '@utils/parseConditions'
import { Img } from '@components/Img'
import { readableProbability } from '@utils/readableProbability'
import { addHiddenEntity, showHideSnackbar } from '@utils/pokemon/hiddenPokemon'
import {
usePokemonBackgroundVisuals,
usePokemonBackgroundVisual,
Expand Down Expand Up @@ -341,7 +342,8 @@ const MenuActions = ({

const handleHide = () => {
setAnchorEl(null)
useMemory.setState((prev) => ({ hideList: new Set(prev.hideList).add(id) }))
useMemory.setState({ hideList: addHiddenEntity(id) })
showHideSnackbar(t('hidden_for_hour'))
}

/** @param {string} key */
Expand Down
9 changes: 5 additions & 4 deletions src/features/station/StationPopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { Img, PokemonImg } from '@components/Img'
import { useFormatStore } from '@store/useFormatStore'
import { useRelativeTimer } from '@hooks/useRelativeTime'
import { useAnalytics } from '@hooks/useAnalytics'
import { addHiddenEntity, showHideSnackbar } from '@utils/pokemon/hiddenPokemon'
import { BackgroundCard } from '@components/popups/BackgroundCard'
import { Title } from '@components/popups/Title'
import {
Expand Down Expand Up @@ -169,10 +170,10 @@ function StationMenu({
() => [
{
name: 'hide',
action: () =>
useMemory.setState((prev) => ({
hideList: new Set(prev.hideList).add(id),
})),
action: () => {
useMemory.setState({ hideList: addHiddenEntity(id) })
showHideSnackbar(t('hidden_for_hour'))
},
},
{
name: 'exclude_battle',
Expand Down
9 changes: 5 additions & 4 deletions src/features/tappable/TappablePopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { StatusIcon } from '@components/StatusIcon'
import { Title } from '@components/popups/Title'

import { getTimeUntil } from '@utils/getTimeUntil'
import { addHiddenEntity, showHideSnackbar } from '@utils/pokemon/hiddenPokemon'

import { getTappableDisplaySettings } from './displayRules'

/**
Expand Down Expand Up @@ -107,10 +109,9 @@ export function TappablePopup({ tappable, rewardIcon }) {
const handleHide = React.useCallback(() => {
setMenuAnchorEl(null)
if (tappable.id === undefined || tappable.id === null) return
useMemory.setState((prev) => ({
hideList: new Set(prev.hideList).add(tappable.id),
}))
}, [tappable.id])
useMemory.setState({ hideList: addHiddenEntity(tappable.id) })
showHideSnackbar(t('hidden_for_hour'))
}, [tappable.id, t])

const handleExclude = React.useCallback(() => {
setMenuAnchorEl(null)
Expand Down
4 changes: 3 additions & 1 deletion src/store/useMemory.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import { create } from 'zustand'

import { getHiddenEntitySet } from '@utils/pokemon/hiddenPokemon'

/**
* TODO: Finish this
* @typedef {{
Expand Down Expand Up @@ -151,7 +153,7 @@ export const useMemory = create(() => ({
locationCards: {},
routeTypes: {},
},
hideList: new Set(),
hideList: getHiddenEntitySet(),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce hide expiration during active sessions

Hidden entries are supposed to reappear after an hour, but the hide set is only initialized once at startup via getHiddenEntitySet() and never refreshed; the map filter just checks hideList.has(...) without reapplying the one‑hour TTL. In a session that stays open longer than an hour, anything you hide remains in hideList indefinitely, so markers never return unless the page is reloaded or “Clean Hidden” is clicked, breaking the advertised 1‑hour persistence window.

Useful? React with 👍 / 👎.

timerList: [],
timeOfDay: 'day',
extraUserFields: [],
Expand Down
155 changes: 155 additions & 0 deletions src/utils/pokemon/hiddenPokemon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// @ts-check

const STORAGE_KEY = 'pokemon-hide-list'
const SNACKBAR_COUNT_KEY = 'pokemon-hide-snackbar-count'
const MAX_AGE_MS = 60 * 60 * 1000 // 1 hour
const MAX_SNACKBAR_SHOWS = 3

/**
* @typedef {{ id: string | number, ts: number }} HiddenEntry
*/

/**
* Load hidden Pokemon entries from localStorage
* @returns {HiddenEntry[]}
*/
function loadEntries() {
try {
const raw = localStorage.getItem(STORAGE_KEY)
if (!raw) return []
const parsed = JSON.parse(raw)
return Array.isArray(parsed) ? parsed : []
} catch {
return []
}
}

/**
* Save hidden Pokemon entries to localStorage
* @param {HiddenEntry[]} entries
*/
function saveEntries(entries) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(entries))
} catch {
// localStorage may be full or disabled
}
}

/**
* Clean entries older than 1 hour
* @param {HiddenEntry[]} entries
* @returns {HiddenEntry[]}
*/
function cleanOldEntries(entries) {
const now = Date.now()
return entries.filter((e) => now - e.ts < MAX_AGE_MS)
}

/**
* Add an entity ID to the hidden list with timestamp, cleaning old entries
* @param {string | number} id
* @returns {Set<string | number>} Updated hideList Set
*/
export function addHiddenEntity(id) {
const entries = cleanOldEntries(loadEntries())
if (!entries.some((e) => e.id === id)) {
entries.push({ id, ts: Date.now() })
}
saveEntries(entries)
return new Set(entries.map((e) => e.id))
}

/**
* Get the current hidden entity Set from localStorage (cleaned)
* @returns {Set<string | number>}
*/
export function getHiddenEntitySet() {
const entries = cleanOldEntries(loadEntries())
saveEntries(entries) // persist cleaned list
return new Set(entries.map((e) => e.id))
}

/** @type {{ current: number | null }} */
const snackbarTimer = { current: null }

/** @type {{ current: HTMLDivElement | null }} */
const snackbarRef = { current: null }

/**
* Get snackbar show count from localStorage
* @returns {number}
*/
function getSnackbarCount() {
try {
return parseInt(localStorage.getItem(SNACKBAR_COUNT_KEY) || '0', 10)
} catch {
return 0
}
}

/**
* Increment snackbar show count in localStorage
*/
function incrementSnackbarCount() {
try {
const count = getSnackbarCount() + 1
localStorage.setItem(SNACKBAR_COUNT_KEY, String(count))
} catch {
// localStorage may be full or disabled
}
}

/**
* Show a temporary snackbar message for 2 seconds (max 3 times total)
* @param {string} message
*/
export function showHideSnackbar(message) {
if (getSnackbarCount() >= MAX_SNACKBAR_SHOWS) {
return
}

if (snackbarTimer.current) {
clearTimeout(snackbarTimer.current)
}
if (snackbarRef.current) {
snackbarRef.current.remove()
}

incrementSnackbarCount()

const snackbar = document.createElement('div')
snackbar.textContent = message
snackbar.style.cssText = `
position: fixed;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
background: rgba(50, 50, 50, 0.95);
color: white;
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
z-index: 10000;
pointer-events: none;
`
document.body.appendChild(snackbar)
snackbarRef.current = snackbar

snackbarTimer.current = window.setTimeout(() => {
snackbar.remove()
snackbarRef.current = null
snackbarTimer.current = null
}, 2000)
}

/**
* Clear all hidden entities from localStorage
*/
export function clearHiddenEntities() {
try {
localStorage.removeItem(STORAGE_KEY)
} catch {
// localStorage may be disabled
}
}
Loading