refactor(other): update eslint to v9 with the flat configuration (#405)

This commit is contained in:
mahula 2025-12-16 21:21:28 +01:00 committed by GitHub
parent b9bab44274
commit 279110f011
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
122 changed files with 2337 additions and 1377 deletions

2
.gitignore vendored
View File

@ -1,4 +1,6 @@
.claude/
app/node_modules/
lib/node_modules/
data/
node_modules/
cypress/node_modules/

View File

@ -1,3 +0,0 @@
node_modules/
dist/
data/

View File

@ -1,223 +0,0 @@
// eslint-disable-next-line import/no-commonjs
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
'standard',
'eslint:recommended',
'plugin:@eslint-community/eslint-comments/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
// 'plugin:promise/recommended',
'plugin:security/recommended-legacy',
'plugin:react/recommended',
],
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
plugins: [
'@typescript-eslint',
'import',
'promise',
'security',
'no-catch-all',
'react',
'react-hooks',
'react-refresh',
],
// TODO also parse this
ignorePatterns: ['vite.config.ts'],
settings: {
'import/resolver': {
typescript: true,
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
react: {
version: '18.2.0',
},
},
rules: {
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
'react/react-in-jsx-scope': 'off', // Disable requirement for React import
'no-catch-all/no-catch-all': 'error',
'no-console': 'error',
'no-debugger': 'error',
camelcase: 'error',
indent: ['error', 2],
'linebreak-style': ['error', 'unix'],
semi: ['error', 'never'],
// Optional eslint-comments rule
'@eslint-community/eslint-comments/no-unused-disable': 'error',
'@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
// import
'import/export': 'error',
'import/no-deprecated': 'error',
'import/no-empty-named-blocks': 'error',
'import/no-extraneous-dependencies': 'error',
'import/no-mutable-exports': 'error',
'import/no-unused-modules': 'error',
'import/no-named-as-default': 'error',
'import/no-named-as-default-member': 'error',
'import/no-amd': 'error',
'import/no-commonjs': 'error',
'import/no-import-module-exports': 'error',
'import/no-nodejs-modules': 'off',
'import/unambiguous': 'off', // not compatible with scriptless vue files
'import/default': 'error',
'import/named': 'error',
'import/namespace': 'error',
'import/no-absolute-path': 'error',
'import/no-cycle': 'error',
'import/no-dynamic-require': 'error',
'import/no-internal-modules': 'off',
'import/no-relative-packages': 'error',
'import/no-relative-parent-imports': [
'error',
{
ignore: ['#[src,types,root,components,utils,assets]/*', '@/config/*'],
},
],
'import/no-self-import': 'error',
'import/no-unresolved': [
'error',
{
ignore: ['react'],
},
],
'import/no-useless-path-segments': 'error',
'import/no-webpack-loader-syntax': 'error',
'import/consistent-type-specifier-style': 'error',
'import/exports-last': 'off',
'import/extensions': [
'error',
'never',
{
json: 'always',
},
],
'import/first': 'error',
'import/group-exports': 'off',
'import/newline-after-import': 'error',
'import/no-anonymous-default-export': 'off', // todo - consider to enable again
'import/no-default-export': 'off', // incompatible with vite & vike
'import/no-duplicates': 'error',
'import/no-named-default': 'error',
'import/no-namespace': 'error',
'import/no-unassigned-import': [
'error',
{
allow: ['**/*.css'],
},
],
'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
'newlines-between': 'always',
alphabetize: {
order: 'asc', // sort in ascending order. Options: ["ignore", "asc", "desc"]
caseInsensitive: true, // ignore case. Options: [true, false]
},
distinctGroup: true,
},
],
'import/prefer-default-export': 'off',
// promise
'promise/catch-or-return': 'error',
'promise/no-return-wrap': 'error',
'promise/param-names': 'error',
'promise/always-return': 'error',
'promise/no-native': 'off',
'promise/no-nesting': 'warn',
'promise/no-promise-in-callback': 'warn',
'promise/no-callback-in-promise': 'warn',
'promise/avoid-new': 'warn',
'promise/no-new-statics': 'error',
'promise/no-return-in-finally': 'warn',
'promise/valid-params': 'warn',
'promise/prefer-await-to-callbacks': 'error',
'promise/no-multiple-resolved': 'error',
},
overrides: [
{
files: ['*.ts', '*.tsx'],
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json', '**/tsconfig.json'],
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
plugins: ['@typescript-eslint'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:@typescript-eslint/strict',
],
rules: {
'@typescript-eslint/consistent-type-imports': 'error',
// allow explicitly defined dangling promises
'@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
'no-void': ['error', { allowAsStatement: true }],
},
},
{
files: ['!*.json'],
plugins: ['prettier'],
extends: ['plugin:prettier/recommended'],
rules: {
'prettier/prettier': 'error',
},
},
{
files: ['*.json'],
plugins: ['json'],
extends: ['plugin:json/recommended-with-comments'],
},
// {
// files: ['*.{test,spec}.[tj]s'],
// plugins: ['vitest'],
// extends: ['plugin:vitest/all'],
// rules: {
// 'vitest/prefer-lowercase-title': 'off',
// 'vitest/no-hooks': 'off',
// 'vitest/consistent-test-filename': 'off',
// 'vitest/prefer-expect-assertions': [
// 'off',
// {
// onlyFunctionsWithExpectInLoop: true,
// onlyFunctionsWithExpectInCallback: true,
// },
// ],
// 'vitest/prefer-strict-equal': 'off',
// 'vitest/prefer-to-be-falsy': 'off',
// 'vitest/prefer-to-be-truthy': 'off',
// 'vitest/require-hook': [
// 'error',
// {
// allowedFunctionCalls: [
// 'mockClient.setRequestHandler',
// 'setActivePinia',
// 'provideApolloClient',
// ],
// },
// ],
// },
// },
{
files: ['*.yaml', '*.yml'],
parser: 'yaml-eslint-parser',
plugins: ['yml'],
extends: ['plugin:yml/prettier'],
},
],
}

View File

@ -11,4 +11,4 @@
"bracketSameLine": false,
"arrowParens": "always",
"endOfLine": "auto"
}
}

260
app/eslint.config.js Normal file
View File

@ -0,0 +1,260 @@
// ESLint v9 flat config for Utopia Map App
import js from '@eslint/js'
import eslintCommentsConfigs from '@eslint-community/eslint-plugin-eslint-comments/configs'
import importXPlugin from 'eslint-plugin-import-x'
import jsonPlugin from 'eslint-plugin-json'
import noCatchAllPlugin from 'eslint-plugin-no-catch-all'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
import promisePlugin from 'eslint-plugin-promise'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import securityPlugin from 'eslint-plugin-security'
import globals from 'globals'
import tseslint from 'typescript-eslint'
export default tseslint.config(
// Ignore patterns
{
ignores: ['dist/**', 'node_modules/**', 'data/**', 'vite.config.ts'],
},
// Report unused eslint-disable directives (catches stale comments after rule renames)
{
linterOptions: {
reportUnusedDisableDirectives: 'error',
},
},
// Base ESLint recommended config
js.configs.recommended,
// ESLint comments recommended config
eslintCommentsConfigs.recommended,
// Security recommended config
securityPlugin.configs.recommended,
// React recommended configs
react.configs.flat.recommended,
react.configs.flat['jsx-runtime'],
// Main configuration for JavaScript/TypeScript files
{
files: ['**/*.{js,jsx,ts,tsx,cjs,mjs}'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
...globals.browser,
...globals.es2021,
},
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
plugins: {
react: react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
'import-x': importXPlugin,
promise: promisePlugin,
'no-catch-all': noCatchAllPlugin,
},
settings: {
react: {
version: '18.2.0',
},
'import-x/resolver': {
typescript: true,
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
},
rules: {
// ESLint comments rules - allow whole-file disables without eslint-enable
'@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
// Basic rules
'no-console': 'error',
'no-debugger': 'error',
camelcase: 'error',
// Standard JS rules (replacing eslint-config-standard)
semi: ['error', 'never'],
quotes: ['error', 'single', { avoidEscape: true }],
'comma-dangle': ['error', 'always-multiline'],
// Disabled: conflicts with common TypeScript/React patterns
// 'space-before-function-paren': ['error', 'always'],
'keyword-spacing': ['error', { before: true, after: true }],
'space-infix-ops': 'error',
'eol-last': ['error', 'always'],
'no-trailing-spaces': 'error',
'object-curly-spacing': ['error', 'always'],
'array-bracket-spacing': ['error', 'never'],
'computed-property-spacing': ['error', 'never'],
'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }],
// Disable indent rule due to known issues with TypeScript/JSX
// 'indent': ['error', 2],
'linebreak-style': ['error', 'unix'],
// Additional standard rules that were missing
eqeqeq: ['error', 'always', { null: 'ignore' }],
'new-cap': ['error', { newIsCap: true, capIsNew: false, properties: true }],
'array-callback-return': ['error', { allowImplicit: false, checkForEach: false }],
// React rules
'react/react-in-jsx-scope': 'off',
'react/no-unescaped-entities': 'error',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
// Import rules
'import-x/export': 'error',
'import-x/no-deprecated': 'error',
'import-x/no-empty-named-blocks': 'error',
'import-x/no-extraneous-dependencies': 'error',
'import-x/no-mutable-exports': 'error',
'import-x/no-named-as-default': 'error',
'import-x/no-named-as-default-member': 'error',
'import-x/no-amd': 'error',
'import-x/no-commonjs': 'error',
'import-x/no-nodejs-modules': 'off',
'import-x/default': 'error',
'import-x/named': 'error',
'import-x/namespace': 'error',
'import-x/no-absolute-path': 'error',
'import-x/no-cycle': 'error',
'import-x/no-dynamic-require': 'error',
'import-x/no-internal-modules': 'off',
'import-x/no-relative-packages': 'error',
'import-x/no-self-import': 'error',
'import-x/no-unresolved': ['error', { ignore: ['react'] }],
'import-x/no-useless-path-segments': 'error',
'import-x/no-webpack-loader-syntax': 'error',
'import-x/consistent-type-specifier-style': 'error',
'import-x/exports-last': 'off',
'import-x/extensions': ['error', 'never', { json: 'always' }],
'import-x/first': 'error',
'import-x/group-exports': 'off',
'import-x/newline-after-import': 'error',
'import-x/no-anonymous-default-export': 'off',
'import-x/no-default-export': 'off',
'import-x/no-duplicates': 'error',
'import-x/no-named-default': 'error',
'import-x/no-namespace': 'error',
'import-x/no-unassigned-import': ['error', { allow: ['**/*.css'] }],
'import-x/order': [
'error',
{
groups: [
'builtin',
'external',
'internal',
'parent',
'sibling',
'index',
'object',
'type',
],
'newlines-between': 'always',
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
distinctGroup: true,
},
],
'import-x/prefer-default-export': 'off',
// Promise rules
'promise/catch-or-return': 'error',
'promise/no-return-wrap': 'error',
'promise/param-names': 'error',
'promise/always-return': 'error',
'promise/no-native': 'off',
'promise/no-nesting': 'warn',
'promise/no-promise-in-callback': 'warn',
'promise/no-callback-in-promise': 'warn',
'promise/avoid-new': 'warn',
'promise/no-new-statics': 'error',
'promise/no-return-in-finally': 'warn',
'promise/valid-params': 'warn',
'promise/prefer-await-to-callbacks': 'error',
'promise/no-multiple-resolved': 'error',
// Security and other rules
'no-catch-all/no-catch-all': 'error',
// Additional import rules
'import-x/no-unused-modules': 'error',
'import-x/no-import-module-exports': 'error',
'import-x/unambiguous': 'off',
'import-x/no-relative-parent-imports': [
'error',
{
ignore: ['#[src,types,root,components,utils,assets]/*', '@/config/*'],
},
],
},
},
// TypeScript configs (applied after main config)
...tseslint.configs.recommended,
...tseslint.configs.strict,
...tseslint.configs.stylistic,
// TypeScript type-checking configuration
{
files: ['**/*.{ts,tsx}'],
extends: [
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.strictTypeChecked,
...tseslint.configs.stylisticTypeChecked,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.json'],
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
'no-void': ['error', { allowAsStatement: true }],
// Disable empty function rule - legitimate use in React contexts and empty constructors
'@typescript-eslint/no-empty-function': 'off',
// Configure no-unused-expressions to allow logical AND and ternary patterns
'@typescript-eslint/no-unused-expressions': [
'error',
{
allowShortCircuit: true,
allowTernary: true,
},
],
},
},
// JSON files configuration
{
files: ['**/*.json'],
plugins: {
json: jsonPlugin,
},
rules: {
// Disable TypeScript-specific rules for JSON files
'@typescript-eslint/no-unused-expressions': 'off',
// JSON-specific rules
'json/*': 'error',
},
},
// Prettier recommended config (should be last to override other formatting rules)
eslintPluginPrettierRecommended,
)

View File

@ -9,7 +9,7 @@
"scripts": {
"dev": "vite --host",
"build": "tsc && vite build",
"test:lint:eslint": "eslint --ext .ts,.tsx,.js,.jsx,.cjs,.mjs,.json,.yml,.yaml --max-warnings 0 .",
"test:lint:eslint": "eslint --max-warnings 0 .",
"preview": "vite preview"
},
"dependencies": {
@ -22,36 +22,33 @@
"react-dom": "^18.2.0",
"react-rnd": "^10.4.1",
"react-router-dom": "^6.23.0",
"vite-tsconfig-paths": "^5.1.4",
"utopia-ui": "^3.0.111"
"utopia-ui": "^3.0.111",
"vite-tsconfig-paths": "^5.1.4"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.1",
"@eslint/js": "^9.36.0",
"@types/node": "^24.10.2",
"@types/react": "^18.2.79",
"@types/react-dom": "^18.2.25",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-react": "^4.0.0",
"daisyui": "^5.5.5",
"eslint": "^8.24.0",
"eslint-config-prettier": "^10.1.8",
"eslint-config-standard": "^17.1.0",
"eslint": "^9.36.0",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-import-x": "^4.16.1",
"eslint-plugin-json": "^3.1.0",
"eslint-plugin-n": "^17.23.1",
"eslint-plugin-no-catch-all": "^1.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-react": "^7.31.8",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"eslint-plugin-security": "^3.0.1",
"eslint-plugin-yml": "^1.14.0",
"globals": "^16.3.0",
"postcss": "^8.4.30",
"tailwindcss": "^4.1.17",
"typescript": "^5.9.3",
"typescript-eslint": "^8.9.0",
"vite": "^7.2.7",
"vite-plugin-pwa": "^1.2.0"
}

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable import/order */
/* eslint-disable import-x/order */
/* eslint-disable eqeqeq */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable react-hooks/exhaustive-deps */
@ -8,6 +8,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable new-cap */
/* eslint-disable @typescript-eslint/prefer-optional-chain */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */

View File

@ -2,6 +2,7 @@
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import { useEffect, useState } from 'react'
import { TextView } from 'utopia-ui'
@ -22,7 +23,9 @@ export function Welcome1({ clickAction1, map }: ChapterProps) {
<div className='tw:grid'>
<label
className='tw:btn tw:btn-primary tw:place-self-end tw:mt-4'
onClick={() => clickAction1()}
onClick={() => {
clickAction1()
}}
>
Close
</label>
@ -45,7 +48,9 @@ export function Welcome1({ clickAction1, map }: ChapterProps) {
<div className='tw:grid'>
<label
className='tw:btn tw:btn-primary tw:place-self-end tw:mt-4'
onClick={() => clickAction1()}
onClick={() => {
clickAction1()
}}
>
Close
</label>

View File

@ -4,7 +4,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { createDirectus, rest, authentication } from '@directus/sdk'
// eslint-disable-next-line import/no-relative-parent-imports
// eslint-disable-next-line import-x/no-relative-parent-imports
import { config } from '../config'
import type { AuthenticationData, AuthenticationStorage } from '@directus/sdk'
@ -86,9 +86,10 @@ export const authLocalStorage = (mainKey = 'directus_storage') =>
// implementation of set, here set the value at mainKey in localStorage, or remove it if value is null
set: async (value: AuthenticationData | null) => {
if (!value) {
return window.localStorage.removeItem(mainKey)
window.localStorage.removeItem(mainKey)
return
}
return window.localStorage.setItem(mainKey, JSON.stringify(value))
window.localStorage.setItem(mainKey, JSON.stringify(value))
},
}) as AuthenticationStorage

View File

@ -1,4 +1,3 @@
/* @eslint-disable-next-line import/no-relative-parent-imports */
import { config } from '@/config'
import type { UserApi } from 'utopia-ui'

View File

@ -20,8 +20,8 @@ export class itemsApi<T> implements ItemsApi<T> {
constructor(
collectionName: keyof MyCollections,
layerId?: string | undefined,
mapId?: string | undefined,
layerId?: string,
mapId?: string,
filter?: any,
customParameter?: any,
) {
@ -111,8 +111,8 @@ export class itemsApi<T> implements ItemsApi<T> {
async deleteItem(id: string): Promise<boolean> {
try {
const result = await directusClient.request(deleteItem(this.collectionName, id))
return result as unknown as boolean
await directusClient.request(deleteItem(this.collectionName, id))
return true
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message

View File

@ -21,6 +21,7 @@ export class layersApi {
{ itemType: ['*.*', { profileTemplate: ['*', 'item.*.*.*.*'] }] },
{ markerIcon: ['*'] } as any,
],
// eslint-disable-next-line camelcase
filter: { maps: { maps_id: { id: { _eq: this.mapId } } } },
limit: 500,
sort: ['sort'],

View File

@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-useless-constructor */
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
import { readPermissions } from '@directus/sdk'

View File

@ -1,7 +1,7 @@
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import axios from 'axios'
import type { ItemsApi } from 'utopia-ui'

View File

@ -3,7 +3,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { createUser, passwordRequest, passwordReset, readMe, updateMe } from '@directus/sdk'
import { directusClient } from './directus'
@ -54,7 +53,8 @@ export class UserApi {
async logout(): Promise<any> {
try {
return await directusClient.logout()
await directusClient.logout()
return
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
@ -98,7 +98,8 @@ export class UserApi {
async requestPasswordReset(email: string, reset_url?: string): Promise<any> {
try {
return await directusClient.request(passwordRequest(email, reset_url))
await directusClient.request(passwordRequest(email, reset_url))
return
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
@ -108,7 +109,8 @@ export class UserApi {
async passwordReset(reset_token: string, new_password: string): Promise<any> {
try {
return await directusClient.request(passwordReset(reset_token, new_password))
await directusClient.request(passwordReset(reset_token, new_password))
return
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message

View File

@ -1,5 +1,4 @@
/* eslint-disable import/extensions */
/* eslint-disable import/no-named-as-default-member */
/* eslint-disable import-x/extensions */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import React from 'react'
import ReactDOM from 'react-dom/client'

View File

@ -1,7 +1,7 @@
/* eslint-disable react/no-unescaped-entities */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable import/no-relative-parent-imports */
/* eslint-disable import-x/no-relative-parent-imports */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable new-cap */

View File

@ -1,9 +1,9 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable import/no-relative-parent-imports */
/* eslint-disable import-x/no-relative-parent-imports */
/* eslint-disable new-cap */
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { useEffect, useState } from 'react'
import {

View File

@ -1,3 +1,4 @@
/* eslint-disable camelcase */
import type { Item, Tag } from 'utopia-ui'
export const tags: Tag[] = [

View File

@ -12,35 +12,17 @@
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
],
"utopia-ui": [
"../lib/src"
],
"#components/*": [
"../lib/src/Components/*"
],
"#utils/*": [
"../lib/src/Utils/*"
],
"#types/*": [
"../lib/src/types/*"
],
"#assets/*": [
"../lib/src/assets/*"
],
"#src/*": [
"../lib/src/*"
],
"#root/*": [
"../lib/*"
]
"@/*": ["src/*"],
"utopia-ui": ["../lib/src"],
"#components/*": ["../lib/src/Components/*"],
"#utils/*": ["../lib/src/Utils/*"],
"#types/*": ["../lib/src/types/*"],
"#assets/*": ["../lib/src/assets/*"],
"#src/*": ["../lib/src/*"],
"#root/*": ["../lib/*"]
}
},
"include": [
"src"
],
"include": ["src"],
"references": [
{
"path": "./tsconfig.node.json"

View File

@ -1,5 +0,0 @@
node_modules/
dist/
examples/
docs/
coverage/

View File

@ -1,221 +0,0 @@
// eslint-disable-next-line import/no-commonjs
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
'standard',
'eslint:recommended',
'plugin:@eslint-community/eslint-comments/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
// 'plugin:promise/recommended',
'plugin:security/recommended-legacy',
'plugin:react/recommended',
],
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
plugins: [
'@typescript-eslint',
'import',
'promise',
'security',
'no-catch-all',
'react',
'react-hooks',
'react-refresh',
],
settings: {
'import/resolver': {
typescript: true,
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
react: {
version: '18.2.0',
},
},
rules: {
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
'react/react-in-jsx-scope': 'off', // Disable requirement for React import
'no-catch-all/no-catch-all': 'error',
'no-console': 'error',
'no-debugger': 'error',
camelcase: 'error',
indent: ['error', 2],
'linebreak-style': ['error', 'unix'],
semi: ['error', 'never'],
// Optional eslint-comments rule
'@eslint-community/eslint-comments/no-unused-disable': 'error',
'@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
// import
'import/export': 'error',
'import/no-deprecated': 'error',
'import/no-empty-named-blocks': 'error',
'import/no-extraneous-dependencies': 'error',
'import/no-mutable-exports': 'error',
'import/no-unused-modules': 'error',
'import/no-named-as-default': 'error',
'import/no-named-as-default-member': 'error',
'import/no-amd': 'error',
'import/no-commonjs': 'error',
'import/no-import-module-exports': 'error',
'import/no-nodejs-modules': 'off',
'import/unambiguous': 'off', // not compatible with scriptless vue files
'import/default': 'error',
'import/named': 'error',
'import/namespace': 'error',
'import/no-absolute-path': 'error',
'import/no-cycle': 'error',
'import/no-dynamic-require': 'error',
'import/no-internal-modules': 'off',
'import/no-relative-packages': 'error',
'import/no-relative-parent-imports': [
'error',
{
ignore: ['#[src,types,root,components,utils,assets]/*'],
},
],
'import/no-self-import': 'error',
'import/no-unresolved': [
'error',
{
ignore: ['react'],
},
],
'import/no-useless-path-segments': 'error',
'import/no-webpack-loader-syntax': 'error',
'import/consistent-type-specifier-style': 'error',
'import/exports-last': 'off',
'import/extensions': [
'error',
'never',
{
json: 'always',
},
],
'import/first': 'error',
'import/group-exports': 'off',
'import/newline-after-import': 'error',
'import/no-anonymous-default-export': 'off', // todo - consider to enable again
'import/no-default-export': 'off', // incompatible with vite & vike
'import/no-duplicates': 'error',
'import/no-named-default': 'error',
'import/no-namespace': 'error',
'import/no-unassigned-import': [
'error',
{
allow: ['**/*.css'],
},
],
'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
'newlines-between': 'always',
alphabetize: {
order: 'asc', // sort in ascending order. Options: ["ignore", "asc", "desc"]
caseInsensitive: true, // ignore case. Options: [true, false]
},
distinctGroup: true,
},
],
'import/prefer-default-export': 'off',
// promise
'promise/catch-or-return': 'error',
'promise/no-return-wrap': 'error',
'promise/param-names': 'error',
'promise/always-return': 'error',
'promise/no-native': 'off',
'promise/no-nesting': 'warn',
'promise/no-promise-in-callback': 'warn',
'promise/no-callback-in-promise': 'warn',
'promise/avoid-new': 'warn',
'promise/no-new-statics': 'error',
'promise/no-return-in-finally': 'warn',
'promise/valid-params': 'warn',
'promise/prefer-await-to-callbacks': 'error',
'promise/no-multiple-resolved': 'error',
},
overrides: [
{
files: ['*.ts', '*.tsx'],
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json', '**/tsconfig.json'],
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
plugins: ['@typescript-eslint'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:@typescript-eslint/strict',
],
rules: {
'@typescript-eslint/consistent-type-imports': 'error',
// allow explicitly defined dangling promises
'@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
'no-void': ['error', { allowAsStatement: true }],
},
},
{
files: ['!*.json'],
plugins: ['prettier'],
extends: ['plugin:prettier/recommended'],
rules: {
'prettier/prettier': 'error',
},
},
{
files: ['*.json'],
plugins: ['json'],
extends: ['plugin:json/recommended-with-comments'],
},
// {
// files: ['*.{test,spec}.[tj]s'],
// plugins: ['vitest'],
// extends: ['plugin:vitest/all'],
// rules: {
// 'vitest/prefer-lowercase-title': 'off',
// 'vitest/no-hooks': 'off',
// 'vitest/consistent-test-filename': 'off',
// 'vitest/prefer-expect-assertions': [
// 'off',
// {
// onlyFunctionsWithExpectInLoop: true,
// onlyFunctionsWithExpectInCallback: true,
// },
// ],
// 'vitest/prefer-strict-equal': 'off',
// 'vitest/prefer-to-be-falsy': 'off',
// 'vitest/prefer-to-be-truthy': 'off',
// 'vitest/require-hook': [
// 'error',
// {
// allowedFunctionCalls: [
// 'mockClient.setRequestHandler',
// 'setActivePinia',
// 'provideApolloClient',
// ],
// },
// ],
// },
// },
{
files: ['*.yaml', '*.yml'],
parser: 'yaml-eslint-parser',
plugins: ['yml'],
extends: ['plugin:yml/prettier'],
},
],
}

View File

@ -11,4 +11,4 @@
"bracketSameLine": false,
"arrowParens": "always",
"endOfLine": "auto"
}
}

View File

@ -14,7 +14,7 @@
// ***********************************************************
// Import commands.js using ES2015 syntax:
// eslint-disable-next-line import/no-unassigned-import
// eslint-disable-next-line import-x/no-unassigned-import
import './commands'
import { mount } from 'cypress/react'

260
lib/eslint.config.js Normal file
View File

@ -0,0 +1,260 @@
// ESLint v9 flat config for Utopia UI Library
import js from '@eslint/js'
import eslintCommentsConfigs from '@eslint-community/eslint-plugin-eslint-comments/configs'
import importXPlugin from 'eslint-plugin-import-x'
import jsonPlugin from 'eslint-plugin-json'
import noCatchAllPlugin from 'eslint-plugin-no-catch-all'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
import promisePlugin from 'eslint-plugin-promise'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import securityPlugin from 'eslint-plugin-security'
import globals from 'globals'
import tseslint from 'typescript-eslint'
export default tseslint.config(
// Ignore patterns
{
ignores: ['dist/**', 'node_modules/**', 'coverage/**', 'docs/**', 'examples/**'],
},
// Report unused eslint-disable directives (catches stale comments after rule renames)
{
linterOptions: {
reportUnusedDisableDirectives: 'error',
},
},
// Base ESLint recommended config
js.configs.recommended,
// ESLint comments recommended config
eslintCommentsConfigs.recommended,
// Security recommended config
securityPlugin.configs.recommended,
// React recommended configs
react.configs.flat.recommended,
react.configs.flat['jsx-runtime'],
// Main configuration for JavaScript/TypeScript files
{
files: ['**/*.{js,jsx,ts,tsx,cjs,mjs}'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
...globals.browser,
...globals.es2021,
},
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
plugins: {
react: react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
'import-x': importXPlugin,
promise: promisePlugin,
'no-catch-all': noCatchAllPlugin,
},
settings: {
react: {
version: '18.2.0',
},
'import-x/resolver': {
typescript: true,
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
},
rules: {
// ESLint comments rules - allow whole-file disables without eslint-enable
'@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
// Basic rules
'no-console': 'error',
'no-debugger': 'error',
camelcase: 'error',
// Standard JS rules
semi: ['error', 'never'],
quotes: ['error', 'single', { avoidEscape: true }],
'comma-dangle': ['error', 'always-multiline'],
'keyword-spacing': ['error', { before: true, after: true }],
'space-infix-ops': 'error',
'eol-last': ['error', 'always'],
'no-trailing-spaces': 'error',
'object-curly-spacing': ['error', 'always'],
'array-bracket-spacing': ['error', 'never'],
'computed-property-spacing': ['error', 'never'],
'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }],
indent: ['error', 2],
'linebreak-style': ['error', 'unix'],
eqeqeq: ['error', 'always', { null: 'ignore' }],
'new-cap': ['error', { newIsCap: true, capIsNew: false, properties: true }],
'array-callback-return': ['error', { allowImplicit: false, checkForEach: false }],
// React rules
'react/react-in-jsx-scope': 'off',
'react/no-unescaped-entities': 'error',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
// Import rules
'import-x/export': 'error',
'import-x/no-deprecated': 'error',
'import-x/no-empty-named-blocks': 'error',
'import-x/no-extraneous-dependencies': 'error',
'import-x/no-mutable-exports': 'error',
'import-x/no-named-as-default': 'error',
'import-x/no-named-as-default-member': 'error',
'import-x/no-amd': 'error',
'import-x/no-commonjs': 'error',
'import-x/no-nodejs-modules': 'off',
'import-x/default': 'error',
'import-x/named': 'error',
'import-x/namespace': 'error',
'import-x/no-absolute-path': 'error',
'import-x/no-cycle': 'error',
'import-x/no-dynamic-require': 'error',
'import-x/no-internal-modules': 'off',
'import-x/no-relative-packages': 'error',
'import-x/no-self-import': 'error',
'import-x/no-unresolved': ['error', { ignore: ['react'] }],
'import-x/no-useless-path-segments': 'error',
'import-x/no-webpack-loader-syntax': 'error',
'import-x/consistent-type-specifier-style': 'error',
'import-x/exports-last': 'off',
'import-x/extensions': ['error', 'never', { json: 'always' }],
'import-x/first': 'error',
'import-x/group-exports': 'off',
'import-x/newline-after-import': 'error',
'import-x/no-anonymous-default-export': 'off',
'import-x/no-default-export': 'off',
'import-x/no-duplicates': 'error',
'import-x/no-named-default': 'error',
'import-x/no-namespace': 'error',
'import-x/no-unassigned-import': ['error', { allow: ['**/*.css'] }],
'import-x/order': [
'error',
{
groups: [
'builtin',
'external',
'internal',
'parent',
'sibling',
'index',
'object',
'type',
],
'newlines-between': 'always',
alphabetize: { order: 'asc', caseInsensitive: true },
distinctGroup: true,
},
],
'import-x/prefer-default-export': 'off',
'import-x/no-unused-modules': 'error',
'import-x/no-import-module-exports': 'error',
'import-x/unambiguous': 'off',
'import-x/no-relative-parent-imports': [
'error',
{ ignore: ['#[src,types,root,components,utils,assets]/*'] },
],
// Promise rules
'promise/catch-or-return': 'error',
'promise/no-return-wrap': 'error',
'promise/param-names': 'error',
'promise/always-return': 'error',
'promise/no-native': 'off',
'promise/no-nesting': 'warn',
'promise/no-promise-in-callback': 'warn',
'promise/no-callback-in-promise': 'warn',
'promise/avoid-new': 'warn',
'promise/no-new-statics': 'error',
'promise/no-return-in-finally': 'warn',
'promise/valid-params': 'warn',
'promise/prefer-await-to-callbacks': 'error',
'promise/no-multiple-resolved': 'error',
// Security and other rules
'no-catch-all/no-catch-all': 'error',
},
},
// TypeScript configs (applied after main config)
...tseslint.configs.recommended,
...tseslint.configs.strict,
...tseslint.configs.stylistic,
// TypeScript type-checking configuration
{
files: ['**/*.{ts,tsx}'],
extends: [
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.strictTypeChecked,
...tseslint.configs.stylisticTypeChecked,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.json'],
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
'no-void': ['error', { allowAsStatement: true }],
// Disable empty function rule - legitimate use in React contexts
'@typescript-eslint/no-empty-function': 'off',
// Configure no-unused-expressions to allow logical AND and ternary patterns
'@typescript-eslint/no-unused-expressions': [
'error',
{
allowShortCircuit: true,
allowTernary: true,
},
],
},
},
// JSON files configuration
{
files: ['**/*.json'],
plugins: {
json: jsonPlugin,
},
rules: {
// Disable TypeScript-specific rules for JSON files
'@typescript-eslint/no-unused-expressions': 'off',
// JSON-specific rules
'json/*': 'error',
},
},
// CommonJS configuration files (e.g., postcss.config.cjs)
// Provides Node.js globals (module, require, etc.) that were previously
// included via eslint-config-standard in ESLint v8
{
files: ['**/*.cjs'],
languageOptions: {
globals: {
...globals.node,
},
sourceType: 'commonjs',
},
},
// Prettier recommended config (should be last to override other formatting rules)
eslintPluginPrettierRecommended,
)

View File

@ -26,7 +26,7 @@
"scripts": {
"build": "rollup -c",
"start": "rollup -c -w",
"test:lint:eslint": "eslint --ext .ts,.tsx,.js,.jsx,.cjs,.mjs,.json,.yml,.yaml --max-warnings 0 .",
"test:lint:eslint": "eslint --max-warnings 0 .",
"lint": "npm run test:lint:eslint",
"lintfix": "npm run test:lint:eslint -- --fix",
"test:component": "cypress run --component --browser electron",
@ -44,6 +44,7 @@
"license": "GPL-3.0-only",
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.1",
"@eslint/js": "^9.36.0",
"@rollup/plugin-alias": "^6.0.0",
"@rollup/plugin-commonjs": "^29.0.0",
"@rollup/plugin-node-resolve": "^16.0.3",
@ -56,19 +57,15 @@
"@types/leaflet.markercluster": "^1.5.5",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.0.5",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^3.0.5",
"cypress": "^15.7.1",
"daisyui": "^5.5.5",
"eslint": "^8.24.0",
"eslint": "^9.36.0",
"eslint-config-prettier": "^10.1.8",
"eslint-config-standard": "^17.1.0",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-import-x": "^4.16.1",
"eslint-plugin-json": "^3.1.0",
"eslint-plugin-n": "^17.23.1",
"eslint-plugin-no-catch-all": "^1.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^7.2.1",
@ -76,7 +73,7 @@
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"eslint-plugin-security": "^3.0.1",
"eslint-plugin-yml": "^1.14.0",
"globals": "^16.3.0",
"happy-dom": "^20.0.11",
"postcss": "^8.4.21",
"prettier": "^3.7.4",
@ -91,6 +88,7 @@
"typedoc-plugin-coverage": "^3.4.1",
"typedoc-plugin-missing-exports": "^3.1.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.9.0",
"vite": "^7.2.7",
"vite-plugin-svgr": "^4.3.0",
"vitest": "^3.0.5"

View File

@ -1,4 +1,4 @@
// eslint-disable-next-line import/no-commonjs
// eslint-disable-next-line import-x/no-commonjs
module.exports = {
plugins: {
'@tailwindcss/postcss': {},

View File

@ -1,2 +1,2 @@
// eslint-disable-next-line import/no-unassigned-import
// eslint-disable-next-line import-x/no-unassigned-import
import '@testing-library/jest-dom'

View File

@ -31,7 +31,9 @@ export default function NavBar({ appName }: { appName: string }) {
className='tw:btn tw:btn-square tw:btn-ghost tw:ml-3'
aria-controls='#sidenav'
aria-haspopup='true'
onClick={() => toggleSidebar()}
onClick={() => {
toggleSidebar()
}}
>
<Bars3Icon className='tw:inline-block tw:w-5 tw:h-5' />
</button>
@ -50,7 +52,9 @@ export default function NavBar({ appName }: { appName: string }) {
</Link>
<button
className='tw:btn tw:px-2 tw:btn-ghost'
onClick={() => window.my_modal_3.showModal()}
onClick={() => {
window.my_modal_3.showModal()
}}
>
<QuestionMarkIcon className='tw:h-5 tw:w-5' />
</button>

View File

@ -6,7 +6,7 @@ import SidebarSubmenu from './SidebarSubmenu'
export interface Route {
path: string
icon: JSX.Element
icon: React.JSX.Element
name: string
submenu?: Route[]
blank?: boolean
@ -57,7 +57,9 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
to={`${route.path}${params && '?' + params.toString()}`}
className={({ isActive }) =>
`${isActive ? 'tw:font-semibold tw:bg-base-200 tw:rounded-none!' : 'tw:font-normal tw:rounded-none!'}`
isActive
? 'tw:font-semibold tw:bg-base-200 tw:rounded-none!'
: 'tw:font-normal tw:rounded-none!'
}
onClick={() => {
if (screen.width < 640 && !appState.sideBarSlim) toggleSidebarOpen()
@ -70,7 +72,7 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
{route.icon}
</div>
<span
className={`${appState.sideBarSlim ? 'tw:hidden' : ''}`}
className={appState.sideBarSlim ? 'tw:hidden' : ''}
data-te-sidenav-slim='false'
>
{route.name}
@ -119,7 +121,7 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
>
{route.icon}
<span
className={`${appState.sideBarSlim ? 'tw:hidden' : ''}`}
className={appState.sideBarSlim ? 'tw:hidden' : ''}
data-te-sidenav-slim='false'
>
{route.name}
@ -143,7 +145,9 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
'tw:w-5 tw:h-5 tw:mb-4 tw:mr-5 tw:mt-2 tw:cursor-pointer tw:float-right tw:delay-400 tw:duration-500 tw:transition-all ' +
(!appState.sideBarSlim ? 'tw:rotate-180' : '')
}
onClick={() => toggleSidebarSlim()}
onClick={() => {
toggleSidebarSlim()
}}
/>
</div>
</div>

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/prefer-find */
import ChevronDownIcon from '@heroicons/react/24/outline/ChevronDownIcon'
import { useEffect, useState } from 'react'
import { Link, useLocation } from 'react-router-dom'
@ -10,7 +11,7 @@ function SidebarSubmenu({
icon,
}: {
path: string
icon: JSX.Element
icon: React.JSX.Element
name: string
submenu?: Route[]
}) {
@ -31,7 +32,12 @@ function SidebarSubmenu({
return (
<div className='flex-col'>
{/** Route header */}
<div className='w-full' onClick={() => setIsExpanded(!isExpanded)}>
<div
className='w-full'
onClick={() => {
setIsExpanded(!isExpanded)
}}
>
{icon} <span>{name} </span>
<ChevronDownIcon
className={

View File

@ -1,5 +1,4 @@
/* eslint-disable react/prop-types */
/* eslint-disable @typescript-eslint/no-empty-function */
import { useCallback, useState, createContext, useContext } from 'react'
import type { AssetsApi } from '#types/AssetsApi'

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import { useCallback, useEffect, useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { toast } from 'react-toastify'
@ -68,7 +69,7 @@ export function LoginPage({ inviteApi, showRequestPassword }: Props) {
},
error: {
render({ data }) {
return `${data as string}`
return data as string
},
autoClose: 10000,
},
@ -97,13 +98,17 @@ export function LoginPage({ inviteApi, showRequestPassword }: Props) {
type='email'
placeholder='E-Mail'
value={email}
onChange={(e) => setEmail(e.target.value)}
onChange={(e) => {
setEmail(e.target.value)
}}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
<input
type='password'
placeholder='Password'
onChange={(e) => setPassword(e.target.value)}
onChange={(e) => {
setPassword(e.target.value)
}}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
<div className='tw:text-right tw:text-primary'>

View File

@ -28,7 +28,7 @@ export function RequestPasswordPage({ resetUrl }: { resetUrl: string }) {
},
error: {
render({ data }) {
return `${data as string}`
return data as string
},
},
pending: 'sending email ...',
@ -42,7 +42,9 @@ export function RequestPasswordPage({ resetUrl }: { resetUrl: string }) {
type='email'
placeholder='E-Mail'
value={email}
onChange={(e) => setEmail(e.target.value)}
onChange={(e) => {
setEmail(e.target.value)
}}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
<div className='tw:card-actions tw:mt-4'>

View File

@ -28,7 +28,7 @@ export function SetNewPasswordPage() {
},
error: {
render({ data }) {
return `${data as string}`
return data as string
},
},
pending: 'setting password ...',
@ -41,7 +41,9 @@ export function SetNewPasswordPage() {
<input
type='password'
placeholder='Password'
onChange={(e) => setPassword(e.target.value)}
onChange={(e) => {
setPassword(e.target.value)
}}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
<div className='tw:card-actions tw:mt-4'>

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { toast } from 'react-toastify'
@ -32,7 +33,7 @@ export function SignupPage() {
},
error: {
render({ data }) {
return `${data as string}`
return data as string
},
autoClose: 10000,
},
@ -61,20 +62,26 @@ export function SignupPage() {
type='text'
placeholder='Name'
value={userName}
onChange={(e) => setUserName(e.target.value)}
onChange={(e) => {
setUserName(e.target.value)
}}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
<input
type='email'
placeholder='E-Mail'
value={email}
onChange={(e) => setEmail(e.target.value)}
onChange={(e) => {
setEmail(e.target.value)
}}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
<input
type='password'
placeholder='Password'
onChange={(e) => setPassword(e.target.value)}
onChange={(e) => {
setPassword(e.target.value)
}}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
<div className='tw:card-actions tw:mt-4'>

View File

@ -67,7 +67,7 @@ export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
return undefined
}
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
} catch {
setLoading(false)
return undefined
} finally {
@ -135,7 +135,8 @@ export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
setLoading(true)
try {
await userApi.requestPasswordReset(email, resetUrl)
return setLoading(false)
setLoading(false)
return
} catch (error) {
setLoading(false)
throw error
@ -146,7 +147,8 @@ export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
setLoading(true)
try {
await userApi.passwordReset(token, newPassword)
return setLoading(false)
setLoading(false)
return
} catch (error) {
setLoading(false)
throw error

View File

@ -44,7 +44,9 @@ export function Quests() {
<div className='tw:card-actions tw:justify-end'>
<label
className='tw:btn tw:btn-sm tw:btn-circle tw:btn-ghost tw:absolute tw:right-1 tw:top-1'
onClick={() => setQuestsOpen(false)}
onClick={() => {
setQuestsOpen(false)
}}
>
</label>

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import { useCallback, useState, createContext, useContext } from 'react'
type UseQuestManagerResult = ReturnType<typeof useQuestsManager>

View File

@ -85,7 +85,9 @@ export const Autocomplete = ({
ref={inputRef}
{...inputProps}
type='text'
onChange={(e) => handleChange(e)}
onChange={(e) => {
handleChange(e)
}}
tabIndex='-1'
onKeyDown={handleKeyDown}
className='tw:border-none tw:focus:outline-none tw:focus:ring-0 tw:mt-5'
@ -94,7 +96,12 @@ export const Autocomplete = ({
className={`tw:absolute tw:z-4000 ${filteredSuggestions.length > 0 && 'tw:bg-base-100 tw:rounded-xl tw:p-2'}`}
>
{filteredSuggestions.map((suggestion, index) => (
<li key={index} onClick={() => handleSuggestionClick(suggestion)}>
<li
key={index}
onClick={() => {
handleSuggestionClick(suggestion)
}}
>
<TagView heighlight={index === heighlightedSuggestion} tag={suggestion}></TagView>
</li>
))}

View File

@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/restrict-plus-operands */
import { useContext, useMemo, useState } from 'react'
import { Marker, Tooltip } from 'react-leaflet'

View File

@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import { MapPinIcon } from '@heroicons/react/24/solid'
import { useState } from 'react'
import SVG from 'react-inlinesvg'
@ -89,7 +90,9 @@ export default function AddButton({
tabIndex={0}
className='tw:z-500 tw:border-0 tw:p-0 tw:w-10 tw:h-10 tw:cursor-pointer tw:rounded-full tw:mouse tw:drop-shadow-md tw:transition tw:ease-in tw:duration-200 tw:focus:outline-hidden tw:flex tw:items-center tw:justify-center'
style={{ backgroundColor: layer.menuColor || '#777' }}
onClick={() => handleLayerClick(layer)}
onClick={() => {
handleLayerClick(layer)
}}
onTouchEnd={(e) => {
handleLayerClick(layer)
e.preventDefault()

View File

@ -1,3 +1,4 @@
/* eslint-disable array-callback-return */
import FunnelIcon from '@heroicons/react/24/outline/FunnelIcon'
import { useEffect, useState } from 'react'
@ -18,7 +19,9 @@ export function FilterControl() {
]
useEffect(() => {
groupTypes.map((layer) => addVisibleGroupType(layer.value))
groupTypes.map((layer) => {
addVisibleGroupType(layer.value)
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
@ -48,7 +51,9 @@ export function FilterControl() {
>
<input
id={groupType.value}
onChange={() => toggleVisibleGroupType(groupType.value)}
onChange={() => {
toggleVisibleGroupType(groupType.value)
}}
type='checkbox'
className='tw:checkbox tw:checkbox-xs tw:checkbox-success'
checked={isGroupTypeVisible(groupType.value)}

View File

@ -36,7 +36,9 @@ export function LayerControl({ expandLayerControl = false }: { expandLayerContro
>
<input
id={layer.name}
onChange={() => toggleVisibleLayer(layer)}
onChange={() => {
toggleVisibleLayer(layer)
}}
type='checkbox'
className='tw:checkbox tw:checkbox-xs tw:checkbox-success tw:text-white'
checked={isLayerVisible(layer)}

View File

@ -1,3 +1,4 @@
/* eslint-disable camelcase */ // Directus database fields use snake_case
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */

View File

@ -1,3 +1,5 @@
/* eslint-disable camelcase */ // Directus database fields use snake_case
/* eslint-disable promise/always-return */
import { control } from 'leaflet'
import { useCallback, useEffect, useRef, useState } from 'react'
import SVG from 'react-inlinesvg'
@ -15,7 +17,7 @@ import DialogModal from '#components/Templates/DialogModal'
import type { Item } from '#types/Item'
import type { LatLng } from 'leaflet'
// eslint-disable-next-line import/no-unassigned-import
// eslint-disable-next-line import-x/no-unassigned-import
import 'leaflet.locatecontrol'
// Type definitions for leaflet.locatecontrol
@ -31,7 +33,7 @@ declare module 'leaflet' {
* React wrapper for leaflet.locatecontrol that provides user geolocation functionality
* @category Map Controls
*/
export const LocateControl = (): JSX.Element => {
export const LocateControl = (): React.JSX.Element => {
const map = useMap()
const myProfile = useMyProfile()
const updateItem = useUpdateItem()
@ -206,7 +208,9 @@ export const LocateControl = (): JSX.Element => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
if (lc) lc.stop()
// Reset flag after a delay to allow future updates
setTimeout(() => setHasUpdatedPosition(false), 5000)
setTimeout(() => {
setHasUpdatedPosition(false)
}, 5000)
} catch (error: unknown) {
if (error instanceof Error) {
toast.update(toastId, {
@ -278,7 +282,9 @@ export const LocateControl = (): JSX.Element => {
<label
className='tw:btn tw:mt-4 tw:btn-primary'
onClick={() => {
void itemUpdatePosition().then(() => setShowLocationModal(false))
void itemUpdatePosition().then(() => {
setShowLocationModal(false)
})
}}
>
Yes

View File

@ -12,11 +12,15 @@ export function QuestControl() {
) : (
<div
className='tw:card tw:bg-base-100 tw:shadow-xl tw:my-2 tw:w-10'
onClick={(e) => e.stopPropagation()}
onClick={(e) => {
e.stopPropagation()
}}
>
<div
className='tw:card-body tw:hover:bg-slate-300 tw:rounded-2xl tw:p-2 tw:h-10 tw:w-10 tw:transition-all tw:duration-300 tw:hover:cursor-pointer'
onClick={() => setQuestsOpen(true)}
onClick={() => {
setQuestsOpen(true)
}}
>
<img src={FistSVG} alt='Quests' className='tw:h-[2em]' />
</div>

View File

@ -3,6 +3,7 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
@ -115,7 +116,9 @@ export const SearchControl = () => {
className='tw:input tw:input-bordered tw:h-12 tw:grow tw:shadow-xl tw:rounded-box tw:pr-12 tw:w-full'
data-cy='search-input'
ref={searchInput}
onChange={(e) => setValue(e.target.value)}
onChange={(e) => {
setValue(e.target.value)
}}
onFocus={() => {
setHideSuggestions(false)
if (windowDimensions.width < 500) map.closePopup()
@ -126,7 +129,9 @@ export const SearchControl = () => {
<button
className='tw:btn tw:btn-sm tw:btn-circle tw:absolute tw:right-2 tw:top-2'
data-cy='search-clear-button'
onClick={() => setValue('')}
onClick={() => {
setValue('')
}}
>
</button>

View File

@ -13,7 +13,9 @@ export const SidebarControl = () => {
<>
<div
className='tw:card tw:justify-center tw:items-center tw:bg-base-100 tw:flex-none tw:shadow-xl tw:px-0 tw:hover:bg-slate-300 tw:hover:cursor-pointer tw:transition-all tw:duration-300 tw:mr-2 tw:h-12 tw:w-12 '
onClick={() => toggleSidebar()}
onClick={() => {
toggleSidebar()
}}
>
<Bars3Icon className='tw:inline-block tw:w-5 tw:h-5' />
</div>

View File

@ -16,7 +16,9 @@ export const TagsControl = () => {
<div className='tw:card-actions tw:justify-end'>
<label
className='tw:btn tw:btn-xs tw:btn-circle tw:absolute tw:-right-2 tw:-top-2 tw:bg-white tw:text-gray-600'
onClick={() => removeFilterTag(tag.name)}
onClick={() => {
removeFilterTag(tag.name)
}}
>
</label>

View File

@ -1,3 +1,4 @@
/* eslint-disable camelcase */ // Directus database fields use snake_case
/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable no-catch-all/no-catch-all */
@ -235,8 +236,7 @@ export function ItemFormPopup(props: Props) {
}, [popupForm?.position])
return (
popupForm &&
popupForm.layer.name === activeLayerName && (
popupForm?.layer.name === activeLayerName && (
<LeafletPopup
minWidth={275}
maxWidth={275}

View File

@ -35,7 +35,8 @@ export function ConnectionStatus({ item }: ConnectionStatusProps) {
return (
<button
style={{
backgroundColor: `${item.color ?? (tags[0]?.color ? tags[0].color : item.layer.markerDefaultColor || '#000')}`,
backgroundColor:
item.color ?? (tags[0]?.color ? tags[0].color : item.layer.markerDefaultColor || '#000'),
}}
className='tw:btn tw:text-white tw:mr-2 tw:tooltip tw:tooltip-top '
data-tip={'Connect'}

View File

@ -17,7 +17,11 @@ export function DeleteModal({ item, isOpen, onClose, onConfirm }: DeleteModalPro
return (
<DialogModal isOpened={isOpen} title='Are you sure?' showCloseButton={false} onClose={onClose}>
<div onClick={(e) => e.stopPropagation()}>
<div
onClick={(e) => {
e.stopPropagation()
}}
>
<span>
Do you want to delete <b>{item.name}</b>?
</span>

View File

@ -53,7 +53,12 @@ export function EditMenu({
if (!hasDeletePermission && !hasUpdatePermission) return null
return (
<div onClick={(e) => e.stopPropagation()} className={`${big ? 'tw:mt-5' : 'tw:mt-1'}`}>
<div
onClick={(e) => {
e.stopPropagation()
}}
className={big ? 'tw:mt-5' : 'tw:mt-1'}
>
<div className='tw:dropdown tw:dropdown-bottom tw:dropdown-center'>
<label tabIndex={0} className='tw:btn tw:btn-ghost tw:px-2.5'>
<EllipsisVerticalIcon className='tw:h-5 tw:w-5' />
@ -67,13 +72,13 @@ export function EditMenu({
<a
className='tw:text-base-content! tw:tooltip tw:tooltip-top tw:cursor-pointer'
data-tip='Edit'
onClick={(e) =>
onClick={(e) => {
item.layer?.customEditLink
? navigate(
`${item.layer.customEditLink}${item.layer.customEditParameter ? `/${item.id}${params.toString() ? '?' + params.toString() : ''}` : ''}`,
)
: editCallback(e)
}
}}
>
<PencilIcon className='tw:h-5 tw:w-5' />
</a>

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import { QrCodeIcon } from '@heroicons/react/24/solid'
import { useState } from 'react'
@ -53,8 +54,12 @@ export function ItemAvatar({
className='tw:w-full tw:h-full tw:object-cover tw:rounded-full tw:border-white'
src={avatar}
alt={(item.name ?? '') + ' logo'}
onLoad={() => setImageLoaded(true)}
onError={() => setImageLoaded(false)}
onLoad={() => {
setImageLoaded(true)
}}
onError={() => {
setImageLoaded(false)
}}
style={{ display: imageLoaded ? 'block' : 'none' }}
/>
{!imageLoaded && <div className='tw:w-full tw:h-full tw:bg-gray-200 tw:rounded-full' />}

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import { MapPinIcon } from '@heroicons/react/24/solid'
import { useEffect, useRef, useState } from 'react'

View File

@ -24,7 +24,12 @@ export function QRModal({ item, isOpen, onClose }: QRModalProps) {
onClose={onClose}
className='tw:w-[calc(100vw-2rem)] tw:!max-w-96'
>
<div onClick={(e) => e.stopPropagation()} className='tw:text-center tw:p-4'>
<div
onClick={(e) => {
e.stopPropagation()
}}
className='tw:text-center tw:p-4'
>
<p className='tw:text-xl tw:font-bold'>Share your Profile to expand your Network!</p>
<div className='tw:flex tw:flex-col tw:items-center tw:gap-4 tw:my-8'>

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import { toast } from 'react-toastify'
import type { Item } from '#types/Item'

View File

@ -44,7 +44,9 @@ export function HeaderView({
item={item}
big={big}
showQrButton={showQrButton}
onQrClick={() => setQrModalOpen(true)}
onQrClick={() => {
setQrModalOpen(true)
}}
/>
<ItemTitle
item={item}
@ -64,7 +66,9 @@ export function HeaderView({
loading={loading}
hideMenu={hideMenu}
big={big}
onDeleteModalOpen={() => setModalOpen(true)}
onDeleteModalOpen={() => {
setModalOpen(true)
}}
/>
</div>
@ -81,12 +85,20 @@ export function HeaderView({
<DeleteModal
item={item}
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onClose={() => {
setModalOpen(false)
}}
onConfirm={deleteCallback ?? (() => undefined)}
/>
{showQrButton && (
<QRModal item={item} isOpen={qrModalOpen} onClose={() => setQrModalOpen(false)} />
<QRModal
item={item}
isOpen={qrModalOpen}
onClose={() => {
setQrModalOpen(false)
}}
/>
)}
</>
)

View File

@ -16,7 +16,7 @@ export interface HeaderViewProps {
export interface PlatformConfig {
shareUrl: string
icon: JSX.Element
icon: React.JSX.Element
label: string
bgColor: string
}

View File

@ -1,5 +1,8 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-base-to-string */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/prefer-optional-chain */
import { get } from 'radash'
import { Link } from 'react-router-dom'

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import { TextAreaInput } from '#components/Input'
import type { Item } from '#types/Item'

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import { TextInput } from '#components/Input'
import type { Item } from '#types/Item'

View File

@ -114,7 +114,9 @@ export const ItemViewPopup = forwardRef((props: ItemViewPopupProps, ref: any) =>
) : (
<p
className='tw:my-0! tw:min-h-[21px] tw:font-bold tw:cursor-pointer tw:text-gray-500'
onClick={() => setInfoExpanded(true)}
onClick={() => {
setInfoExpanded(true)
}}
>
</p>

View File

@ -1,4 +1,4 @@
/* eslint-disable import/no-unassigned-import */
/* eslint-disable import-x/no-unassigned-import */
import L from 'leaflet'
import { useEffect } from 'react'
import { useMap } from 'react-leaflet'

View File

@ -34,7 +34,9 @@ export const SelectPositionToast = ({
}
window.addEventListener('keydown', handleEscape)
return () => window.removeEventListener('keydown', handleEscape)
return () => {
window.removeEventListener('keydown', handleEscape)
}
}, [selectNewItemPosition, setSelectNewItemPosition])
const toastContent = useMemo(() => {

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
@ -104,7 +103,9 @@ export function UtopiaMapInner({
useTheme(defaultTheme)
useEffect(() => {
layers.forEach((layer) => addVisibleLayer(layer))
layers.forEach((layer) => {
addVisibleLayer(layer)
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [layers])
@ -302,7 +303,7 @@ export function UtopiaMapInner({
<Outlet />
<Control position='topLeft' zIndex='1000' absolute>
<SearchControl />
<div className={`${showZoomControl ? 'tw:pl-14' : ''}`}>
<div className={showZoomControl ? 'tw:pl-14' : ''}>
<TagsControl />
</div>
</Control>
@ -327,7 +328,9 @@ export function UtopiaMapInner({
<MapLibreLayer styleUrl={maplibreStyle} attribution={tileServerAttribution} />
)}
<MarkerClusterGroup
ref={(r) => setClusterRef(r as any)}
ref={(r) => {
setClusterRef(r as any)
}}
showCoverageOnHover
chunkedLoading
maxClusterRadius={50}
@ -342,7 +345,7 @@ export function UtopiaMapInner({
eventHandlers={{
click: (e) => {
if (selectNewItemPosition) {
e.layer.closePopup()
e.propagatedFrom.closePopup()
setMapClicked({ position: e.latlng, setItemFormPopup: setPopupForm })
}
},

View File

@ -1,5 +1,4 @@
/* eslint-disable react/prop-types */
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { createContext, useContext, useState } from 'react'

View File

@ -1,8 +1,6 @@
/* eslint-disable react/prop-types */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable no-case-declarations */
import { useCallback, useReducer, createContext, useContext, useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
@ -154,8 +152,8 @@ function useFilterManager(initialTags: Tag[]): {
params.set('tags', `${urlTags || ''}${urlTags ? ';' : ''}${tag.name}`)
}
if (windowDimensions.width < 786 && location.pathname.split('/').length > 2)
navigate('/' + `${params ? `?${params}` : ''}`)
else navigate(location.pathname + `${params ? `?${params}` : ''}`)
navigate('/' + (params ? `?${params}` : ''))
else navigate(location.pathname + (params ? `?${params}` : ''))
dispatchTags({
type: 'ADD_TAG',
@ -173,16 +171,16 @@ function useFilterManager(initialTags: Tag[]): {
if (tags?.length === 0 && urlTags?.length && urlTags.length > 0) tags[0] = urlTags
tags?.map((urlTag) => {
if (!(urlTag.toLocaleLowerCase() === name.toLocaleLowerCase())) {
newUrlTags = newUrlTags + `${newUrlTags === '' ? urlTag : `;${urlTag}`}`
newUrlTags = newUrlTags + (newUrlTags === '' ? urlTag : `;${urlTag}`)
}
return null
})
if (newUrlTags !== '') {
params.set('tags', `${newUrlTags}`)
navigate(location.pathname + `${params ? `?${params}` : ''}`)
params.set('tags', newUrlTags)
navigate(location.pathname + (params ? `?${params}` : ''))
} else {
params.delete('tags')
navigate(location.pathname + `${params ? `?${params}` : ''}`)
navigate(location.pathname + (params ? `?${params}` : ''))
}
dispatchTags({

View File

@ -1,6 +1,5 @@
/* eslint-disable react/prop-types */
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/restrict-template-expressions */

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
import { useCallback, useReducer, createContext, useContext } from 'react'
import type { LayerProps } from '#types/LayerProps'
@ -11,7 +12,6 @@ type UseItemManagerResult = ReturnType<typeof useLayerManager>
const LayerContext = createContext<UseItemManagerResult>({
layers: [],
// eslint-disable-next-line @typescript-eslint/no-empty-function
addLayer: () => {},
})

View File

@ -1,6 +1,5 @@
/* eslint-disable react/prop-types */
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-empty-object-type */
import { useCallback, useReducer, createContext, useContext } from 'react'
import type { Item } from '#types/Item'

View File

@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import { useCallback, useReducer, createContext, useContext, useState } from 'react'
import { useAuth } from '#components/Auth/useAuth'

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import { useState, useEffect } from 'react'
interface GeocodeResult {

View File

@ -1,7 +1,7 @@
/* eslint-disable camelcase */ // Directus database fields use snake_case
/* eslint-disable react/prop-types */
/* eslint-disable @typescript-eslint/prefer-optional-chain */
/* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-non-null-assertion */

View File

@ -1,6 +1,5 @@
/* eslint-disable react/prop-types */
/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript-eslint/prefer-optional-chain */
/* eslint-disable @typescript-eslint/no-non-null-assertion */

View File

@ -17,7 +17,9 @@ export default function useWindowDimensions() {
}
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
return windowDimensions

View File

@ -1,3 +1,4 @@
/* eslint-disable camelcase */ // Directus database fields use snake_case
/* eslint-disable @typescript-eslint/no-explicit-any */
import { describe, it, expect, vi, beforeEach } from 'vitest'
@ -12,10 +13,16 @@ const toastUpdateMock: (id: number, options: any) => void = vi.fn()
vi.mock('react-toastify', () => ({
toast: {
error: (t: string) => toastErrorMock(t),
success: (t: string) => toastSuccessMock(t),
error: (t: string) => {
toastErrorMock(t)
},
success: (t: string) => {
toastSuccessMock(t)
},
loading: (t: string) => toastLoadingMock(t),
update: (id: number, options: any) => toastUpdateMock(id, options),
update: (id: number, options: any) => {
toastUpdateMock(id, options)
},
},
}))

View File

@ -1,5 +1,7 @@
/* eslint-disable camelcase */ // Directus database fields use snake_case
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/prefer-optional-chain */
import classNames from 'classnames'
import { useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/use-unknown-in-catch-callback-variable */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
@ -186,7 +186,9 @@ export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi<any>
// Error handling is already in handleDelete
})
}}
editCallback={() => navigate('/edit-item/' + item.id)}
editCallback={() => {
navigate('/edit-item/' + item.id)
}}
setPositionCallback={() => {
map.closePopup()
setSelectPosition(item)

View File

@ -2,6 +2,7 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/prefer-optional-chain */
import LinkIcon from '@heroicons/react/24/outline/LinkIcon'
import PlusIcon from '@heroicons/react/24/outline/PlusIcon'
import { useState } from 'react'
@ -89,7 +90,9 @@ export function ActionButton({
<DialogModal
title={'Select'}
isOpened={modalOpen}
onClose={() => setModalOpen(false)}
onClose={() => {
setModalOpen(false)
}}
className='tw:w-xl tw:sm:w-2xl tw:min-h-80 tw:bg-base-200'
>
<TextInput

View File

@ -2,6 +2,7 @@
/* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/prefer-optional-chain */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import ArrowUpTrayIcon from '@heroicons/react/24/outline/ArrowUpTrayIcon'
import { useState, useCallback, useRef } from 'react'
import { ReactCrop, centerCrop, makeAspectCrop } from 'react-image-crop'
@ -201,7 +202,13 @@ export const AvatarWidget: React.FC<AvatarWidgetProps> = ({ avatar, setAvatar, i
}}
closeOnClickOutside={false}
>
<ReactCrop crop={crop} onChange={(c) => setCrop(c)} aspect={1}>
<ReactCrop
crop={crop}
onChange={(c) => {
setCrop(c)
}}
aspect={1}
>
<img src={image} ref={imgRef} onLoad={onImageLoad} />
</ReactCrop>
<button

View File

@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { useCallback, useEffect, useRef, useState } from 'react'
import { HexColorPicker } from 'react-colorful'
@ -12,7 +11,9 @@ export const ColorPicker = ({ color, onChange, className }) => {
const popover = useRef<HTMLDivElement>(null)
const [isOpen, toggle] = useState(false)
const close = useCallback(() => toggle(false), [])
const close = useCallback(() => {
toggle(false)
}, [])
useClickOutside(popover, close)
const colorPickerRef = useRef<HTMLDivElement>(null)
@ -35,11 +36,23 @@ export const ColorPicker = ({ color, onChange, className }) => {
return (
<div ref={colorPickerRef} className={`picker ${className}`}>
<div className='swatch' style={{ backgroundColor: color }} onClick={() => toggle(true)} />
<div
className='swatch'
style={{ backgroundColor: color }}
onClick={() => {
toggle(true)
}}
/>
{isOpen && (
<div className='popover tw:z-10000' ref={popover}>
<HexColorPicker color={color} onChange={onChange} onClick={() => toggle(false)} />
<HexColorPicker
color={color}
onChange={onChange}
onClick={() => {
toggle(false)
}}
/>
</div>
)}
</div>

View File

@ -18,12 +18,12 @@ export const ContactInfoForm = ({
type='email'
required={false}
defaultValue={state.contact}
updateFormValue={(v) =>
updateFormValue={(v) => {
setState((prevState) => ({
...prevState,
contact: v,
}))
}
}}
/>
</div>
@ -35,12 +35,12 @@ export const ContactInfoForm = ({
required={false}
pattern='^\+?[0-9\s\-]{7,15}$'
defaultValue={state.telephone}
updateFormValue={(v) =>
updateFormValue={(v) => {
setState((prevState) => ({
...prevState,
telephone: v,
}))
}
}}
/>
</div>
</div>

View File

@ -18,12 +18,12 @@ export const CrowdfundingForm = ({
type='text'
required={false}
defaultValue={state.openCollectiveSlug}
updateFormValue={(v) =>
updateFormValue={(v) => {
setState((prevState) => ({
...prevState,
openCollectiveSlug: v,
}))
}
}}
/>
</div>
</div>

View File

@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import { TextInput } from '#components/Input'
@ -20,22 +21,22 @@ export const FormHeader = ({ item, state, setState }: Props) => {
<div className='tw:flex'>
<AvatarWidget
avatar={state.image}
setAvatar={(i) =>
setAvatar={(i) => {
setState((prevState) => ({
...prevState,
image: i,
}))
}
}}
item={item}
/>
<ColorPicker
color={state.color}
onChange={(c) =>
onChange={(c) => {
setState((prevState) => ({
...prevState,
color: c,
}))
}
}}
className={'tw:-left-6 tw:top-14 tw:-mr-6'}
/>
<div
@ -44,12 +45,12 @@ export const FormHeader = ({ item, state, setState }: Props) => {
<TextInput
placeholder='Name'
defaultValue={item.name ? item.name : ''}
updateFormValue={(v) =>
updateFormValue={(v) => {
setState((prevState) => ({
...prevState,
name: v,
}))
}
}}
containerStyle='tw:grow tw:px-4'
inputStyle='tw:input-md'
/>
@ -58,12 +59,12 @@ export const FormHeader = ({ item, state, setState }: Props) => {
placeholder={item.layer.itemType.subtitle_label}
required={false}
defaultValue={item.subname ? item.subname : ''}
updateFormValue={(v) =>
updateFormValue={(v) => {
setState((prevState) => ({
...prevState,
subname: v,
}))
}
}}
containerStyle='tw:grow tw:px-4 tw:mt-1'
inputStyle='tw:input-sm'
/>

View File

@ -1,3 +1,5 @@
/* eslint-disable camelcase */ // Directus database fields use snake_case
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import { readFileSync } from 'node:fs'
import path from 'node:path'

View File

@ -1,3 +1,5 @@
/* eslint-disable camelcase */ // Directus database fields use snake_case
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import TrashIcon from '@heroicons/react/24/solid/TrashIcon'
import imageCompression from 'browser-image-compression'
import { useState } from 'react'
@ -28,7 +30,9 @@ export const GalleryForm = ({ state, setState, hideInputLabel = false }: Props)
const [imageSelectedToDelete, setImageSelectedToDelete] = useState<number | null>(null)
const closeModal = () => setImageSelectedToDelete(null)
const closeModal = () => {
setImageSelectedToDelete(null)
}
const upload = async (acceptedFiles: File[]) => {
setState((prevState) => ({
@ -48,7 +52,8 @@ export const GalleryForm = ({ state, setState, hideInputLabel = false }: Props)
}
})
for await (const upload of uploads) {
const results = await Promise.all(uploads)
for (const upload of results) {
setState((prevState) => ({
...prevState,
uploadingImages: prevState.uploadingImages.filter((f) => f.name !== upload.name),
@ -116,7 +121,9 @@ export const GalleryForm = ({ state, setState, hideInputLabel = false }: Props)
{image.state === 'uploaded' && (
<button
className='tw:m-2 tw:bg-red-500 tw:text-white tw:p-2 tw:rounded-full tw:absolute tw:top-0 tw:right-0 tw:hover:bg-red-600 tw:cursor-pointer'
onClick={() => setImageSelectedToDelete(index)}
onClick={() => {
setImageSelectedToDelete(index)
}}
type='button'
>
<TrashIcon className='tw:h-5 tw:w-5' data-testid='trash' />
@ -142,7 +149,11 @@ export const GalleryForm = ({ state, setState, hideInputLabel = false }: Props)
showCloseButton={false}
onClose={closeModal}
>
<div onClick={(e) => e.stopPropagation()}>
<div
onClick={(e) => {
e.stopPropagation()
}}
>
<span>Do you want to delete this image?</span>
<div className='tw:grid'>
<div className='tw:flex tw:justify-between'>

View File

@ -44,10 +44,19 @@ export const GalleryView = ({ item }: { item: Item }) => {
<RowsPhotoAlbum
photos={images}
targetRowHeight={150}
onClick={({ index: current }) => setIndex(current)}
onClick={({ index: current }) => {
setIndex(current)
}}
/>
<ReactLightbox index={index} slides={images} open={index >= 0} close={() => setIndex(-1)} />
<ReactLightbox
index={index}
slides={images}
open={index >= 0}
close={() => {
setIndex(-1)
}}
/>
</div>
)
else return <></>

View File

@ -1,3 +1,4 @@
/* eslint-disable camelcase */ // Directus database fields use snake_case
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import { useEffect } from 'react'
@ -57,12 +58,12 @@ export const GroupSubheaderForm = ({
id='status'
options={groupStates || []}
value={state.status}
onValueChange={(v) =>
onValueChange={(v) => {
setState((prevState) => ({
...prevState,
status: v,
}))
}
}}
/>
</div>
<div>
@ -71,12 +72,12 @@ export const GroupSubheaderForm = ({
id='groupType'
options={groupTypes?.map((gt) => gt.groupTypes_id.name) || []}
value={state.group_type}
onValueChange={(v) =>
onValueChange={(v) => {
setState((prevState) => ({
...prevState,
group_type: v,
}))
}
}}
/>
</div>
</div>

View File

@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable no-constant-binary-expression */
import EllipsisVerticalIcon from '@heroicons/react/16/solid/EllipsisVerticalIcon'
import LinkSlashIcon from '@heroicons/react/24/outline/LinkSlashIcon'
@ -51,7 +51,12 @@ export function LinkedItemsHeaderView({
</div>
</div>
</div>
<div className='tw:col-span-1' onClick={(e) => e.stopPropagation()}>
<div
className='tw:col-span-1'
onClick={(e) => {
e.stopPropagation()
}}
>
{unlinkPermission && (
<div className='tw:dropdown tw:dropdown-bottom'>
<label

View File

@ -6,7 +6,9 @@ export const MarkdownHint = () => {
const [expended, setExpended] = useState<boolean>(false)
return (
<div
onClick={() => setExpended(true)}
onClick={() => {
setExpended(true)
}}
title='Markdown is supported'
className='flex tw:flex-row tw:text-gray-400 tw:cursor-pointer tw:items-center'
>

View File

@ -14,18 +14,18 @@ export const ProfileStartEndForm = ({
<PopupStartEndInput
item={item}
showLabels={false}
updateEndValue={(e) =>
updateEndValue={(e) => {
setState((prevState) => ({
...prevState,
end: e,
}))
}
updateStartValue={(s) =>
}}
updateStartValue={(s) => {
setState((prevState) => ({
...prevState,
start: s,
}))
}
}}
></PopupStartEndInput>
)
}

View File

@ -44,12 +44,12 @@ export const ProfileTextForm = ({
placeholder={'...'}
// eslint-disable-next-line security/detect-object-injection
defaultValue={state[field]}
updateFormValue={(v) =>
updateFormValue={(v) => {
setState((prevState) => ({
...prevState,
[field]: v,
}))
}
}}
showMenu={size === 'full'}
containerStyle={size === 'full' ? 'tw:flex-1' : 'tw:h-24 tw:flex-none'}
/>

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/use-unknown-in-catch-callback-variable */
import { toast } from 'react-toastify'
import ChevronSVG from '#assets/chevron.svg'
@ -41,7 +42,9 @@ const SocialShareBar = ({
color: 'white',
backgroundColor: '#444',
}}
onClick={() => copyLink()}
onClick={() => {
copyLink()
}}
title='share link via email'
>
<img src={ChevronSVG} alt='\/' className='tw:h-4 tw:w-4' />
@ -55,7 +58,9 @@ const SocialShareBar = ({
color: 'white',
backgroundColor: '#888',
}}
onClick={() => copyLink()}
onClick={() => {
copyLink()
}}
title='copy Link'
>
<img src={ClipboardSVG} className='tw:w-5' />

View File

@ -3,6 +3,7 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { useEffect, useState } from 'react'
@ -121,7 +122,9 @@ export const TagsWidget = ({ placeholder, containerStyle, defaultTags, onUpdate
<div className='tw:card-actions tw:justify-end'>
<label
className='tw:btn tw:btn-xs tw:btn-circle tw:absolute tw:-right-2 tw:-top-2 tw:bg-white tw:text-gray-600'
onClick={() => deleteTag(tag)}
onClick={() => {
deleteTag(tag)
}}
>
</label>
@ -134,7 +137,9 @@ export const TagsWidget = ({ placeholder, containerStyle, defaultTags, onUpdate
pushFilteredSuggestions={pushFilteredSuggestions}
setFocus={focusInput}
inputProps={inputProps}
onSelected={(tag) => onSelected(tag)}
onSelected={(tag) => {
onSelected(tag)
}}
/>
</div>
</div>

View File

@ -29,12 +29,12 @@ export const OnepagerForm = ({
<TextAreaInput
placeholder='Beschreibung'
defaultValue={state.text || ''}
updateFormValue={(v) =>
updateFormValue={(v) => {
setState((prevState) => ({
...prevState,
text: v,
}))
}
}}
inputStyle='tw:h-48'
/>
</div>

View File

@ -2,6 +2,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import { TextAreaInput } from '#components/Input'
// eslint-disable-next-line react/prop-types

View File

@ -3,6 +3,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable react/prop-types */
@ -131,7 +132,9 @@ export const TabsForm = ({
<div
key={i.id}
className='tw:cursor-pointer tw:card tw:bg-base-200 tw:border-[1px] tw:border-base-300 tw:card-body tw:shadow-xl tw:text-base-content tw:mx-4 tw:p-6 tw:mb-4'
onClick={() => navigate('/item/' + i.id)}
onClick={() => {
navigate('/item/' + i.id)
}}
>
<LinkedItemsHeaderView
unlinkPermission={updatePermission}

View File

@ -88,9 +88,11 @@ export const TabsView = ({
name='my_tabs_2'
role='tab'
className={'tw:tab tw:font-bold tw:ps-2! tw:pe-2! '}
aria-label={`${item.layer?.itemType.icon_as_labels && activeTab !== 0 ? '📝' : '📝\u00A0Info'}`}
aria-label={item.layer?.itemType.icon_as_labels && activeTab !== 0 ? '📝' : '📝\u00A0Info'}
checked={activeTab === 0 && true}
onChange={() => updateActiveTab(0)}
onChange={() => {
updateActiveTab(0)
}}
/>
<div
role='tabpanel'
@ -112,9 +114,13 @@ export const TabsView = ({
name='my_tabs_2'
role='tab'
className={'tw:tab tw:font-bold tw:ps-2! tw:pe-2!'}
aria-label={`${item.layer.itemType.icon_as_labels && activeTab !== 3 ? '❤️' : '❤️\u00A0Trust'}`}
aria-label={
item.layer.itemType.icon_as_labels && activeTab !== 3 ? '❤️' : '❤️\u00A0Trust'
}
checked={activeTab === 3 && true}
onChange={() => updateActiveTab(3)}
onChange={() => {
updateActiveTab(3)
}}
/>
<div
role='tabpanel'
@ -195,9 +201,15 @@ export const TabsView = ({
name='my_tabs_2'
role='tab'
className={`tw:tab tw:font-bold tw:ps-2! tw:pe-2! ${!(item.layer.itemType.icon_as_labels && activeTab !== 1) && 'tw:min-w-[10.4em]'} `}
aria-label={`${item.layer.itemType.icon_as_labels && activeTab !== 1 ? '♻️' : '♻️\u00A0Offers & Needs'}`}
aria-label={
item.layer.itemType.icon_as_labels && activeTab !== 1
? '♻️'
: '♻️\u00A0Offers & Needs'
}
checked={activeTab === 1 && true}
onChange={() => updateActiveTab(1)}
onChange={() => {
updateActiveTab(1)
}}
/>
<div
role='tabpanel'
@ -228,7 +240,13 @@ export const TabsView = ({
<h3 className='tw:-mb-2 tw:col-span-1'>Needs</h3>
<div className='tw:flex tw:flex-wrap tw:mb-4'>
{needs.map((n) => (
<TagView key={n.id} tag={n} onClick={() => addFilterTag(n)} />
<TagView
key={n.id}
tag={n}
onClick={() => {
addFilterTag(n)
}}
/>
))}
</div>
</div>
@ -248,9 +266,13 @@ export const TabsView = ({
name='my_tabs_2'
role='tab'
className='tw:tab tw:font-bold tw:ps-2! tw:pe-2! '
aria-label={`${item.layer.itemType.icon_as_labels && activeTab !== 2 ? '🔗' : '🔗\u00A0Links'}`}
aria-label={
item.layer.itemType.icon_as_labels && activeTab !== 2 ? '🔗' : '🔗\u00A0Links'
}
checked={activeTab === 2 && true}
onChange={() => updateActiveTab(2)}
onChange={() => {
updateActiveTab(2)
}}
/>
<div
role='tabpanel'
@ -263,7 +285,9 @@ export const TabsView = ({
<div
key={i.id}
className='tw:cursor-pointer tw:card tw:bg-base-200 tw:border-[1px] tw:border-base-300 tw:card-body tw:shadow-xl tw:text-base-content tw:p-6 tw:mr-4 tw:mb-4'
onClick={() => navigate('/item/' + i.id)}
onClick={() => {
navigate('/item/' + i.id)
}}
>
<LinkedItemsHeaderView
unlinkPermission={updatePermission}

Some files were not shown because too many files have changed in this diff Show More