Compare commits

...

3 Commits

Author SHA1 Message Date
Renovate Bot
dca232dc99 chore(deps): update dependency typescript-eslint to v8.52.0 2026-01-12 08:04:47 +00:00
d560b6db6f add extra filters 2026-01-12 08:57:42 +01:00
500be84f39 fix price error 2026-01-11 22:28:53 +01:00
7 changed files with 297 additions and 87 deletions

159
frontend/pnpm-lock.yaml generated
View File

@ -59,7 +59,7 @@ importers:
version: 5.9.3 version: 5.9.3
typescript-eslint: typescript-eslint:
specifier: ^8.48.1 specifier: ^8.48.1
version: 8.48.1(eslint@9.39.1)(typescript@5.9.3) version: 8.52.0(eslint@9.39.1)(typescript@5.9.3)
vite: vite:
specifier: npm:rolldown-vite@7.2.5 specifier: npm:rolldown-vite@7.2.5
version: rolldown-vite@7.2.5(@types/node@24.10.1) version: rolldown-vite@7.2.5(@types/node@24.10.1)
@ -167,6 +167,12 @@ packages:
peerDependencies: peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
'@eslint-community/eslint-utils@4.9.1':
resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
'@eslint-community/regexpp@4.12.2': '@eslint-community/regexpp@4.12.2':
resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
@ -362,70 +368,70 @@ packages:
'@types/react@19.2.7': '@types/react@19.2.7':
resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==}
'@typescript-eslint/eslint-plugin@8.48.1': '@typescript-eslint/eslint-plugin@8.52.0':
resolution: {integrity: sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==} resolution: {integrity: sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies: peerDependencies:
'@typescript-eslint/parser': ^8.48.1 '@typescript-eslint/parser': ^8.52.0
eslint: ^8.57.0 || ^9.0.0 eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0' typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/parser@8.48.1': '@typescript-eslint/parser@8.52.0':
resolution: {integrity: sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==} resolution: {integrity: sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0' typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/project-service@8.48.1': '@typescript-eslint/project-service@8.52.0':
resolution: {integrity: sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==} resolution: {integrity: sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies: peerDependencies:
typescript: '>=4.8.4 <6.0.0' typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/scope-manager@8.48.1': '@typescript-eslint/scope-manager@8.52.0':
resolution: {integrity: sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==} resolution: {integrity: sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/tsconfig-utils@8.48.1': '@typescript-eslint/tsconfig-utils@8.52.0':
resolution: {integrity: sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==} resolution: {integrity: sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies: peerDependencies:
typescript: '>=4.8.4 <6.0.0' typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/type-utils@8.48.1': '@typescript-eslint/type-utils@8.52.0':
resolution: {integrity: sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==} resolution: {integrity: sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0' typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/types@8.48.1': '@typescript-eslint/types@8.52.0':
resolution: {integrity: sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==} resolution: {integrity: sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/typescript-estree@8.48.1': '@typescript-eslint/typescript-estree@8.52.0':
resolution: {integrity: sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==} resolution: {integrity: sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies: peerDependencies:
typescript: '>=4.8.4 <6.0.0' typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/utils@8.48.1': '@typescript-eslint/utils@8.52.0':
resolution: {integrity: sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==} resolution: {integrity: sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0' typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/visitor-keys@8.48.1': '@typescript-eslint/visitor-keys@8.52.0':
resolution: {integrity: sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==} resolution: {integrity: sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@vitejs/plugin-react@5.1.2': '@vitejs/plugin-react@5.1.2':
resolution: {integrity: sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==} resolution: {integrity: sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
peerDependencies: peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 vite: npm:rolldown-vite@7.2.5
acorn-jsx@5.3.2: acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
@ -646,9 +652,6 @@ packages:
resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
has-flag@4.0.0: has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -981,8 +984,8 @@ packages:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
ts-api-utils@2.1.0: ts-api-utils@2.4.0:
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==}
engines: {node: '>=18.12'} engines: {node: '>=18.12'}
peerDependencies: peerDependencies:
typescript: '>=4.8.4' typescript: '>=4.8.4'
@ -1004,8 +1007,8 @@ packages:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
typescript-eslint@8.48.1: typescript-eslint@8.52.0:
resolution: {integrity: sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A==} resolution: {integrity: sha512-atlQQJ2YkO4pfTVQmQ+wvYQwexPDOIgo+RaVcD7gHgzy/IQA+XTyuxNM9M9TVXvttkF7koBHmcwisKdOAf2EcA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 eslint: ^8.57.0 || ^9.0.0
@ -1190,6 +1193,11 @@ snapshots:
eslint: 9.39.1 eslint: 9.39.1
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.1)':
dependencies:
eslint: 9.39.1
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {} '@eslint-community/regexpp@4.12.2': {}
'@eslint/config-array@0.21.1': '@eslint/config-array@0.21.1':
@ -1362,96 +1370,95 @@ snapshots:
dependencies: dependencies:
csstype: 3.2.3 csstype: 3.2.3
'@typescript-eslint/eslint-plugin@8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': '@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)':
dependencies: dependencies:
'@eslint-community/regexpp': 4.12.2 '@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) '@typescript-eslint/parser': 8.52.0(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.48.1 '@typescript-eslint/scope-manager': 8.52.0
'@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) '@typescript-eslint/type-utils': 8.52.0(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) '@typescript-eslint/utils': 8.52.0(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.48.1 '@typescript-eslint/visitor-keys': 8.52.0
eslint: 9.39.1 eslint: 9.39.1
graphemer: 1.4.0
ignore: 7.0.5 ignore: 7.0.5
natural-compare: 1.4.0 natural-compare: 1.4.0
ts-api-utils: 2.1.0(typescript@5.9.3) ts-api-utils: 2.4.0(typescript@5.9.3)
typescript: 5.9.3 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3)': '@typescript-eslint/parser@8.52.0(eslint@9.39.1)(typescript@5.9.3)':
dependencies: dependencies:
'@typescript-eslint/scope-manager': 8.48.1 '@typescript-eslint/scope-manager': 8.52.0
'@typescript-eslint/types': 8.48.1 '@typescript-eslint/types': 8.52.0
'@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.48.1 '@typescript-eslint/visitor-keys': 8.52.0
debug: 4.4.3 debug: 4.4.3
eslint: 9.39.1 eslint: 9.39.1
typescript: 5.9.3 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/project-service@8.48.1(typescript@5.9.3)': '@typescript-eslint/project-service@8.52.0(typescript@5.9.3)':
dependencies: dependencies:
'@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) '@typescript-eslint/tsconfig-utils': 8.52.0(typescript@5.9.3)
'@typescript-eslint/types': 8.48.1 '@typescript-eslint/types': 8.52.0
debug: 4.4.3 debug: 4.4.3
typescript: 5.9.3 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/scope-manager@8.48.1': '@typescript-eslint/scope-manager@8.52.0':
dependencies: dependencies:
'@typescript-eslint/types': 8.48.1 '@typescript-eslint/types': 8.52.0
'@typescript-eslint/visitor-keys': 8.48.1 '@typescript-eslint/visitor-keys': 8.52.0
'@typescript-eslint/tsconfig-utils@8.48.1(typescript@5.9.3)': '@typescript-eslint/tsconfig-utils@8.52.0(typescript@5.9.3)':
dependencies: dependencies:
typescript: 5.9.3 typescript: 5.9.3
'@typescript-eslint/type-utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': '@typescript-eslint/type-utils@8.52.0(eslint@9.39.1)(typescript@5.9.3)':
dependencies: dependencies:
'@typescript-eslint/types': 8.48.1 '@typescript-eslint/types': 8.52.0
'@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3)
'@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) '@typescript-eslint/utils': 8.52.0(eslint@9.39.1)(typescript@5.9.3)
debug: 4.4.3 debug: 4.4.3
eslint: 9.39.1 eslint: 9.39.1
ts-api-utils: 2.1.0(typescript@5.9.3) ts-api-utils: 2.4.0(typescript@5.9.3)
typescript: 5.9.3 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/types@8.48.1': {} '@typescript-eslint/types@8.52.0': {}
'@typescript-eslint/typescript-estree@8.48.1(typescript@5.9.3)': '@typescript-eslint/typescript-estree@8.52.0(typescript@5.9.3)':
dependencies: dependencies:
'@typescript-eslint/project-service': 8.48.1(typescript@5.9.3) '@typescript-eslint/project-service': 8.52.0(typescript@5.9.3)
'@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) '@typescript-eslint/tsconfig-utils': 8.52.0(typescript@5.9.3)
'@typescript-eslint/types': 8.48.1 '@typescript-eslint/types': 8.52.0
'@typescript-eslint/visitor-keys': 8.48.1 '@typescript-eslint/visitor-keys': 8.52.0
debug: 4.4.3 debug: 4.4.3
minimatch: 9.0.5 minimatch: 9.0.5
semver: 7.7.3 semver: 7.7.3
tinyglobby: 0.2.15 tinyglobby: 0.2.15
ts-api-utils: 2.1.0(typescript@5.9.3) ts-api-utils: 2.4.0(typescript@5.9.3)
typescript: 5.9.3 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': '@typescript-eslint/utils@8.52.0(eslint@9.39.1)(typescript@5.9.3)':
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.1)
'@typescript-eslint/scope-manager': 8.48.1 '@typescript-eslint/scope-manager': 8.52.0
'@typescript-eslint/types': 8.48.1 '@typescript-eslint/types': 8.52.0
'@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3)
eslint: 9.39.1 eslint: 9.39.1
typescript: 5.9.3 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/visitor-keys@8.48.1': '@typescript-eslint/visitor-keys@8.52.0':
dependencies: dependencies:
'@typescript-eslint/types': 8.48.1 '@typescript-eslint/types': 8.52.0
eslint-visitor-keys: 4.2.1 eslint-visitor-keys: 4.2.1
'@vitejs/plugin-react@5.1.2(rolldown-vite@7.2.5(@types/node@24.10.1))': '@vitejs/plugin-react@5.1.2(rolldown-vite@7.2.5(@types/node@24.10.1))':
@ -1677,8 +1684,6 @@ snapshots:
globals@16.5.0: {} globals@16.5.0: {}
graphemer@1.4.0: {}
has-flag@4.0.0: {} has-flag@4.0.0: {}
hermes-estree@0.25.1: {} hermes-estree@0.25.1: {}
@ -1930,7 +1935,7 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.3) fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3 picomatch: 4.0.3
ts-api-utils@2.1.0(typescript@5.9.3): ts-api-utils@2.4.0(typescript@5.9.3):
dependencies: dependencies:
typescript: 5.9.3 typescript: 5.9.3
@ -1956,12 +1961,12 @@ snapshots:
dependencies: dependencies:
prelude-ls: 1.2.1 prelude-ls: 1.2.1
typescript-eslint@8.48.1(eslint@9.39.1)(typescript@5.9.3): typescript-eslint@8.52.0(eslint@9.39.1)(typescript@5.9.3):
dependencies: dependencies:
'@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) '@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) '@typescript-eslint/parser': 8.52.0(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3)
'@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) '@typescript-eslint/utils': 8.52.0(eslint@9.39.1)(typescript@5.9.3)
eslint: 9.39.1 eslint: 9.39.1
typescript: 5.9.3 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:

View File

@ -333,7 +333,7 @@ export function EditGame({ onShowToast }: Props) {
<input <input
type="number" type="number"
value={price} value={price}
onChange={(e) => setPrice(Number(e.target.value))} onChange={(e) => setPrice(Number(e.target.value.replace(',', '.')))}
min="0" min="0"
step="0.01" step="0.01"
style={inputStyles} style={inputStyles}

View File

@ -12,3 +12,81 @@
.gamefilter-entry:hover { .gamefilter-entry:hover {
background-color: var(--primary-bg); background-color: var(--primary-bg);
} }
.filter-controls {
background-color: var(--secondary-alt-bg);
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 2rem;
border: 1px solid var(--border-color);
}
.filter-controls h3 {
margin: 0 0 1rem 0;
font-size: 1.1rem;
color: var(--text-color);
}
.filter-groups {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
}
.filter-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.filter-group label {
font-size: 0.9rem;
color: var(--text-color);
font-weight: 500;
}
.checkbox-wrapper {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
}
.checkbox-wrapper input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
accent-color: var(--accent-color);
}
.checkbox-wrapper span {
font-size: 0.9rem;
color: var(--text-color);
}
.price-input {
padding: 0.5rem;
border: 1px solid var(--border-color);
border-radius: 4px;
background-color: var(--primary-bg);
color: var(--text-color);
width: 120px;
font-size: 0.9rem;
}
.price-input:focus {
outline: none;
border-color: var(--accent-color);
}
.tooltip-icon {
display: inline-block;
margin-left: 0.3rem;
cursor: help;
font-size: 0.9rem;
opacity: 0.6;
}
.tooltip-icon:hover {
opacity: 1;
}

View File

@ -11,10 +11,16 @@ export function GameFilter() {
const [people, setPeople] = useState<Person[]>([]); const [people, setPeople] = useState<Person[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [selectedPeople, setSelectedPeople] = useState<Set<string>>(new Set()); const [selectedPeople, setSelectedPeople] = useState<Set<string>>(new Set());
const [freeGamesOnly, setFreeGamesOnly] = useState(false);
const [maxPrice, setMaxPrice] = useState<number | null>(null);
const [ownershipMode, setOwnershipMode] = useState(false);
const { filteredGames, gameToPositive } = useGameFilter( const { filteredGames, gameToPositive, games } = useGameFilter(
people, people,
selectedPeople selectedPeople,
freeGamesOnly,
maxPrice,
ownershipMode
); );
useEffect(() => { useEffect(() => {
@ -57,10 +63,62 @@ export function GameFilter() {
onTogglePerson={togglePerson} onTogglePerson={togglePerson}
/> />
<div className="filter-controls">
<h3>Additional Filters</h3>
<div className="filter-groups">
<div className="filter-group">
<label className="checkbox-wrapper">
<input
type="checkbox"
checked={freeGamesOnly}
onChange={(e) => setFreeGamesOnly(e.target.checked)}
/>
<span>Free games only</span>
</label>
</div>
<div className="filter-group">
<label htmlFor="max-price">Maximum price ($)</label>
<input
id="max-price"
type="number"
className="price-input"
min="0"
placeholder="No limit"
value={maxPrice ?? ""}
onChange={(e) => {
const value = e.target.value;
setMaxPrice(value === "" ? null : parseInt(value, 10));
}}
/>
</div>
<div className="filter-group">
<label className="checkbox-wrapper">
<input
type="checkbox"
checked={ownershipMode}
onChange={(e) => setOwnershipMode(e.target.checked)}
/>
<span>
Ownership mode
<span
className="tooltip-icon"
title="For paid games, only show if ALL selected people have marked it as wouldPlay"
>
?
</span>
</span>
</label>
</div>
</div>
</div>
<FilteredGamesList <FilteredGamesList
filteredGames={filteredGames} filteredGames={filteredGames}
gameToPositive={gameToPositive} gameToPositive={gameToPositive}
selectedPeopleCount={selectedPeople.size} selectedPeopleCount={selectedPeople.size}
games={games}
/> />
</div> </div>
); );

View File

@ -463,7 +463,7 @@ export function GameList({ onShowToast }: Props) {
<input <input
type="number" type="number"
value={price} value={price}
onChange={(e) => setPrice(Number(e.target.value))} onChange={(e) => setPrice(Number(e.target.value.replace(',', '.')))}
min="0" min="0"
step="0.01" step="0.01"
style={inputStyles} style={inputStyles}

View File

@ -1,17 +1,20 @@
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { GameImage } from "../GameImage"; import { GameImage } from "../GameImage";
import { EmptyState } from "./EmptyState"; import { EmptyState } from "./EmptyState";
import { Game as GameProto } from "../../items";
interface FilteredGamesListProps { interface FilteredGamesListProps {
filteredGames: string[]; filteredGames: string[];
gameToPositive: Map<string, Set<string>>; gameToPositive: Map<string, Set<string>>;
selectedPeopleCount: number; selectedPeopleCount: number;
games: Map<string, GameProto>;
} }
export function FilteredGamesList({ export function FilteredGamesList({
filteredGames, filteredGames,
gameToPositive, gameToPositive,
selectedPeopleCount, selectedPeopleCount,
games,
}: FilteredGamesListProps) { }: FilteredGamesListProps) {
if (selectedPeopleCount === 0) { if (selectedPeopleCount === 0) {
return ( return (
@ -31,6 +34,8 @@ export function FilteredGamesList({
{filteredGames.map((game) => { {filteredGames.map((game) => {
const positiveCount = gameToPositive.get(game)?.size || 0; const positiveCount = gameToPositive.get(game)?.size || 0;
const neutralCount = selectedPeopleCount - positiveCount; const neutralCount = selectedPeopleCount - positiveCount;
const gameData = games.get(game);
const price = gameData?.price ?? 0;
return ( return (
<Link <Link
@ -68,6 +73,21 @@ export function FilteredGamesList({
{neutralCount > 1 ? "are" : "is"} neutral {neutralCount > 1 ? "are" : "is"} neutral
</div> </div>
)} )}
<div
className="price-badge"
style={{
fontSize: "0.85em",
marginTop: "0.5rem",
padding: "0.2rem 0.6rem",
borderRadius: "4px",
display: "inline-block",
backgroundColor: price === 0 ? "rgba(76, 175, 80, 0.2)" : "rgba(255, 152, 0, 0.2)",
color: price === 0 ? "#4caf50" : "#ff9800",
fontWeight: 600,
}}
>
${price === 0 ? "0 (Free)" : price}
</div>
</div> </div>
<GameImage game={game} /> <GameImage game={game} />
</Link> </Link>

View File

@ -7,7 +7,13 @@ import {
} from "../../items"; } from "../../items";
import { apiFetch } from "../api"; import { apiFetch } from "../api";
export function useGameFilter(people: Person[], selectedPeople: Set<string>) { export function useGameFilter(
people: Person[],
selectedPeople: Set<string>,
freeGamesOnly: boolean,
maxPrice: number | null,
ownershipMode: boolean
) {
const [fetchedTitles, setFetchedTitles] = useState<string[]>([]); const [fetchedTitles, setFetchedTitles] = useState<string[]>([]);
const metaDataRef = useRef<{ [key: string]: GameProto }>({}); const metaDataRef = useRef<{ [key: string]: GameProto }>({});
@ -79,20 +85,63 @@ export function useGameFilter(people: Person[], selectedPeople: Set<string>) {
.filter((title) => metaDataRef.current[title]) .filter((title) => metaDataRef.current[title])
.map((title) => metaDataRef.current[title]); .map((title) => metaDataRef.current[title]);
return filterByPlayerCount(games, selectedPeople.size); return filterGames(
games,
selectedPeople.size,
freeGamesOnly,
maxPrice,
ownershipMode,
selectedPeople,
people
);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [titlesEveryoneWouldPlay, selectedPeople.size, fetchedTitles]); }, [
titlesEveryoneWouldPlay,
selectedPeople.size,
fetchedTitles,
freeGamesOnly,
maxPrice,
ownershipMode,
people,
selectedPeople,
]);
return { filteredGames, gameToPositive: gameToPositiveOpinion }; const gamesMap = useMemo(() => {
return new Map(Object.entries(metaDataRef.current));
}, [fetchedTitles]);
return { filteredGames, gameToPositive: gameToPositiveOpinion, games: gamesMap };
} }
function filterByPlayerCount( function filterGames(
games: GameProto[], games: GameProto[],
playerCount: number playerCount: number,
freeGamesOnly: boolean,
maxPrice: number | null,
ownershipMode: boolean,
selectedPeople: Set<string>,
people: Person[]
): string[] { ): string[] {
const selectedPersons = people.filter((p) => selectedPeople.has(p.name));
return games return games
.filter( .filter(
(game) => game.maxPlayers >= playerCount && game.minPlayers <= playerCount (game) => game.maxPlayers >= playerCount && game.minPlayers <= playerCount
) )
.filter((game) => {
if (freeGamesOnly) return game.price === 0;
if (maxPrice !== null) return game.price <= maxPrice;
return true;
})
.filter((game) => {
if (!ownershipMode) return true;
if (game.price === 0) return true;
return selectedPersons.every((person) =>
person.opinion.some(
(op) => op.title === game.title && op.wouldPlay
)
);
})
.map((game) => game.title); .map((game) => game.title);
} }