mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2026-01-16 01:44:43 +00:00
Merge branch 'main' into invites-2
This commit is contained in:
commit
c1da299732
2
.github/workflows/deploy.docs.yml
vendored
2
.github/workflows/deploy.docs.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
|
||||
with:
|
||||
node-version-file: '.tool-versions'
|
||||
- name: Install Dependencies & Build Library
|
||||
|
||||
2
.github/workflows/test.backend.seed.yml
vendored
2
.github/workflows/test.backend.seed.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
node-version-file: '.tool-versions'
|
||||
|
||||
|
||||
4
.github/workflows/test.build.lib.yml
vendored
4
.github/workflows/test.build.lib.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
|
||||
with:
|
||||
node-version-file: '.tool-versions'
|
||||
- name: Install Dependencies & Build Library
|
||||
@ -45,7 +45,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
|
||||
with:
|
||||
node-version-file: '.tool-versions'
|
||||
- name: Link Utopia-UI in Example App
|
||||
|
||||
4
.github/workflows/test.docs.lib.yml
vendored
4
.github/workflows/test.docs.lib.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
|
||||
# - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
|
||||
# - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
|
||||
# with:
|
||||
# node-version-file: './.tool-versions'
|
||||
# - name: Frontend | Build
|
||||
@ -42,7 +42,7 @@ jobs:
|
||||
COVERAGE_REQUIRED: 0
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
|
||||
with:
|
||||
node-version-file: '.tool-versions'
|
||||
- name: Docs
|
||||
|
||||
4
.github/workflows/test.e2e.yml
vendored
4
.github/workflows/test.e2e.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5.0.0
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v5.0.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5.0.0
|
||||
with:
|
||||
node-version-file: ./.tool-versions
|
||||
cache: 'npm'
|
||||
@ -159,7 +159,7 @@ jobs:
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5.0.0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v5.0.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5.0.0
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
2
.github/workflows/test.lint.cypress.yml
vendored
2
.github/workflows/test.lint.cypress.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5.0.0
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v5.0.0
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5.0.0
|
||||
with:
|
||||
node-version-file: '.tool-versions'
|
||||
- name: Lint
|
||||
|
||||
2
.github/workflows/test.lint.frontend.yml
vendored
2
.github/workflows/test.lint.frontend.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
|
||||
with:
|
||||
node-version-file: '.tool-versions'
|
||||
- name: Lint
|
||||
|
||||
10
.github/workflows/test.lint.lib.examples.yml
vendored
10
.github/workflows/test.lint.lib.examples.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
|
||||
# - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
|
||||
# - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
|
||||
# with:
|
||||
# node-version-file: './.tool-versions'
|
||||
# - name: Frontend | Build
|
||||
@ -39,7 +39,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
|
||||
with:
|
||||
node-version-file: '.tool-versions'
|
||||
- name: Lint
|
||||
@ -53,7 +53,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
|
||||
with:
|
||||
node-version-file: '.tool-versions'
|
||||
- name: Lint
|
||||
@ -67,7 +67,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
|
||||
with:
|
||||
node-version-file: '.tool-versions'
|
||||
- name: Lint
|
||||
@ -81,7 +81,7 @@ jobs:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
|
||||
# - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
|
||||
# - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
|
||||
# with:
|
||||
# node-version-file: './.tool-versions'
|
||||
# - name: Frontend | Unit
|
||||
|
||||
2
.github/workflows/test.lint.lib.yml
vendored
2
.github/workflows/test.lint.lib.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
|
||||
with:
|
||||
node-version-file: '.tool-versions'
|
||||
- name: Lint
|
||||
|
||||
2
.github/workflows/test.lint.pr.yml
vendored
2
.github/workflows/test.lint.pr.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.actor != 'dependabot[bot]' }}
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
- uses: amannn/action-semantic-pull-request@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
2
.github/workflows/test.unit.lib.yml
vendored
2
.github/workflows/test.unit.lib.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
|
||||
with:
|
||||
node-version-file: '.tool-versions'
|
||||
- name: Unit
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,7 +1,10 @@
|
||||
.claude/
|
||||
app/node_modules/
|
||||
lib/node_modules/
|
||||
data/
|
||||
node_modules/
|
||||
cypress/node_modules/
|
||||
cypress/results/
|
||||
cypress/runner-results/
|
||||
cypress/screenshots/
|
||||
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
node_modules/
|
||||
dist/
|
||||
data/
|
||||
@ -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'],
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -11,4 +11,4 @@
|
||||
"bracketSameLine": false,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
}
|
||||
|
||||
260
app/eslint.config.js
Normal file
260
app/eslint.config.js
Normal 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,
|
||||
)
|
||||
@ -9,50 +9,47 @@
|
||||
"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": {
|
||||
"@directus/sdk": "^17.0.2",
|
||||
"@heroicons/react": "^2.1.1",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@types/geojson": "^7946.0.10",
|
||||
"axios": "^1.13.2",
|
||||
"react": "^18.2.0",
|
||||
"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",
|
||||
"@types/node": "^24.10.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",
|
||||
"daisyui": "^5.5.14",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-json": "^3.1.0",
|
||||
"eslint-plugin-n": "^17.23.1",
|
||||
"eslint-plugin-import-x": "^4.16.1",
|
||||
"eslint-plugin-json": "^4.0.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",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.2.4",
|
||||
"vite-plugin-pwa": "^1.1.0"
|
||||
"typescript-eslint": "^8.9.0",
|
||||
"vite": "^7.3.0",
|
||||
"vite-plugin-pwa": "^1.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* @eslint-disable-next-line import/no-relative-parent-imports */
|
||||
import { config } from '@/config'
|
||||
|
||||
import type { UserApi } from 'utopia-ui'
|
||||
|
||||
@ -20,8 +20,8 @@ export class itemsApi<T> implements FullItemsApi<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 FullItemsApi<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
|
||||
|
||||
@ -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'],
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable camelcase */
|
||||
import type { Item, Tag } from 'utopia-ui'
|
||||
|
||||
export const tags: Tag[] = [
|
||||
|
||||
@ -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"
|
||||
|
||||
99
backend/prepare-seed.js
Normal file
99
backend/prepare-seed.js
Normal file
@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Prepares seed data by updating event dates relative to the current date.
|
||||
*
|
||||
* This script modifies items.json in-place, updating all items with
|
||||
* layer "layer-events" to have realistic start/end dates that ensure
|
||||
* events are visible in the app (end dates in the future).
|
||||
*
|
||||
* Date strategy:
|
||||
* - Event 1 (item-event-1): Long-running, started 30 days ago, ends in 365 days
|
||||
* - Event 2 (item-event-2): Upcoming single-day event in 7 days
|
||||
* - Event 3 (item-event-3): Ongoing event, started yesterday, ends tomorrow
|
||||
* - Event 4 (item-event-4): Upcoming multi-day conference in 30 days
|
||||
*/
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const seedPath = path.join(__dirname, 'directus-config/development/seed/items.json')
|
||||
|
||||
function addDays(date, days) {
|
||||
const result = new Date(date)
|
||||
result.setDate(result.getDate() + days)
|
||||
return result
|
||||
}
|
||||
|
||||
function addHours(date, hours) {
|
||||
const result = new Date(date)
|
||||
result.setHours(result.getHours() + hours)
|
||||
return result
|
||||
}
|
||||
|
||||
function formatDateTime(date) {
|
||||
return date.toISOString().slice(0, 19)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate dynamic dates for each event based on current time
|
||||
*/
|
||||
function getEventDates(syncId, now) {
|
||||
const dateConfigs = {
|
||||
// "Some Event" - long-running, started in past, ends far in future
|
||||
'item-event-1': {
|
||||
start: addDays(now, -30),
|
||||
end: addDays(now, 365)
|
||||
},
|
||||
// "Tech Meetup Munich" - upcoming single-day event (the one used in search tests)
|
||||
'item-event-2': {
|
||||
start: addHours(addDays(now, 7), 18),
|
||||
end: addHours(addDays(now, 7), 21)
|
||||
},
|
||||
// "Sustainability Workshop NYC" - ongoing event (started yesterday, ends tomorrow)
|
||||
'item-event-3': {
|
||||
start: addHours(addDays(now, -1), 14),
|
||||
end: addHours(addDays(now, 1), 17)
|
||||
},
|
||||
// "Open Source Conference" - upcoming multi-day conference
|
||||
'item-event-4': {
|
||||
start: addHours(addDays(now, 30), 9),
|
||||
end: addHours(addDays(now, 32), 18)
|
||||
}
|
||||
}
|
||||
|
||||
return dateConfigs[syncId] || null
|
||||
}
|
||||
|
||||
function prepareSeedData() {
|
||||
// Read the current items.json
|
||||
const content = fs.readFileSync(seedPath, 'utf8')
|
||||
const seedData = JSON.parse(content)
|
||||
|
||||
const now = new Date()
|
||||
let updatedCount = 0
|
||||
|
||||
// Update event items with dynamic dates
|
||||
for (const item of seedData.data) {
|
||||
if (item.layer === 'layer-events' && item._sync_id) {
|
||||
const dates = getEventDates(item._sync_id, now)
|
||||
if (dates) {
|
||||
item.start = formatDateTime(dates.start)
|
||||
item.end = formatDateTime(dates.end)
|
||||
console.log(` ${item._sync_id} (${item.name}):`)
|
||||
console.log(` start: ${item.start}`)
|
||||
console.log(` end: ${item.end}`)
|
||||
updatedCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write back to items.json
|
||||
fs.writeFileSync(seedPath, JSON.stringify(seedData, null, 4))
|
||||
|
||||
console.log(`\nUpdated ${updatedCount} event(s) with dynamic dates.`)
|
||||
}
|
||||
|
||||
console.log('Preparing seed data with dynamic dates...\n')
|
||||
prepareSeedData()
|
||||
|
||||
@ -15,6 +15,9 @@ PGDATABASE="${PGDATABASE:-'directus'}"
|
||||
PROJECT_NAME="${PROJECT:-development}"
|
||||
PROJECT_FOLDER=$SCRIPT_DIR/directus-config/$PROJECT_NAME
|
||||
|
||||
echo "Preparing seed data with dynamic dates"
|
||||
node $SCRIPT_DIR/prepare-seed.js || exit 1
|
||||
|
||||
echo "Seed data"
|
||||
npx directus-sync@3.4.0 seed push \
|
||||
--seed-path $PROJECT_FOLDER/seed \
|
||||
|
||||
@ -74,7 +74,7 @@ describe('Utopia Map Search', () => {
|
||||
cy.contains('Wat Arun').first().click()
|
||||
})
|
||||
|
||||
cy.get('.leaflet-popup').should('be.visible')
|
||||
cy.get('.leaflet-popup', { timeout: 15000 }).should('exist')
|
||||
cy.get('.leaflet-popup-content').should('contain', 'Wat Arun')
|
||||
})
|
||||
|
||||
|
||||
@ -76,6 +76,12 @@ Cypress.Commands.add('searchFor', (query: string) => {
|
||||
Cypress.Commands.add('waitForMapReady', () => {
|
||||
cy.get('[data-cy="search-input"]', { timeout: 10000 }).should('be.visible')
|
||||
cy.get('.leaflet-container', { timeout: 10000 }).should('be.visible')
|
||||
cy.wait('@getLayers', { timeout: 15000 }).then((interception) => {
|
||||
const layerCount = interception.response?.body?.data?.length || 3
|
||||
for (let i = 0; i < layerCount; i++) {
|
||||
cy.wait('@getLayerItems', { timeout: 15000 })
|
||||
}
|
||||
})
|
||||
cy.get('.leaflet-marker-icon', { timeout: 15000 }).should('have.length.at.least', 1)
|
||||
})
|
||||
|
||||
|
||||
@ -5,6 +5,71 @@ import './commands'
|
||||
// for screenshot embedding
|
||||
import addContext from 'mochawesome/addContext'
|
||||
|
||||
const photonMockData: Record<string, object> = {
|
||||
berlin: {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
osm_type: 'R',
|
||||
osm_id: 62422,
|
||||
osm_key: 'place',
|
||||
osm_value: 'city',
|
||||
type: 'city',
|
||||
countrycode: 'DE',
|
||||
name: 'Berlin',
|
||||
country: 'Germany',
|
||||
state: 'Berlin',
|
||||
extent: [13.088345, 52.6755087, 13.7611609, 52.3382448],
|
||||
},
|
||||
geometry: { type: 'Point', coordinates: [13.3951309, 52.5173885] },
|
||||
},
|
||||
],
|
||||
},
|
||||
'wat arun': {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
osm_type: 'W',
|
||||
osm_id: 25867629,
|
||||
osm_key: 'tourism',
|
||||
osm_value: 'attraction',
|
||||
type: 'attraction',
|
||||
countrycode: 'TH',
|
||||
name: 'Wat Arun',
|
||||
country: 'Thailand',
|
||||
city: 'Bangkok',
|
||||
extent: [100.4882, 13.7437, 100.4912, 13.7407],
|
||||
},
|
||||
geometry: { type: 'Point', coordinates: [100.4897, 13.7437] },
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
cy.intercept('GET', 'https://photon.komoot.io/api/*', (req) => {
|
||||
const url = new URL(req.url)
|
||||
const query = (url.searchParams.get('q') || '').toLowerCase()
|
||||
|
||||
const mockKey = Object.keys(photonMockData).find((key) =>
|
||||
query.includes(key.toLowerCase()),
|
||||
)
|
||||
|
||||
if (mockKey) {
|
||||
req.reply(photonMockData[mockKey])
|
||||
} else {
|
||||
req.reply({ type: 'FeatureCollection', features: [] })
|
||||
}
|
||||
}).as('photonApi')
|
||||
|
||||
cy.intercept('GET', '**/items/layers*').as('getLayers')
|
||||
cy.intercept('GET', '**/items/items*').as('getLayerItems')
|
||||
})
|
||||
|
||||
// Global exception handler
|
||||
Cypress.on('uncaught:exception', (err) => {
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
node_modules/
|
||||
dist/
|
||||
examples/
|
||||
docs/
|
||||
coverage/
|
||||
@ -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'],
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -11,4 +11,4 @@
|
||||
"bracketSameLine": false,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
260
lib/eslint.config.js
Normal 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,
|
||||
)
|
||||
@ -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",
|
||||
"daisyui": "^5.5.14",
|
||||
"eslint": "^9.39.2",
|
||||
"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-json": "^3.1.0",
|
||||
"eslint-plugin-n": "^17.23.1",
|
||||
"eslint-plugin-import-x": "^4.16.1",
|
||||
"eslint-plugin-json": "^4.0.1",
|
||||
"eslint-plugin-no-catch-all": "^1.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-promise": "^7.2.1",
|
||||
@ -76,22 +73,23 @@
|
||||
"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.3",
|
||||
"prettier": "^3.7.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"rollup": "^4.53.3",
|
||||
"rollup-plugin-dts": "^6.1.1",
|
||||
"rollup-plugin-dts": "^6.3.0",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"rollup-plugin-svg": "^2.0.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typedoc": "^0.27.6",
|
||||
"typedoc-plugin-coverage": "^3.4.1",
|
||||
"typedoc-plugin-missing-exports": "^3.1.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.2.4",
|
||||
"typescript-eslint": "^8.9.0",
|
||||
"vite": "^7.3.0",
|
||||
"vite-plugin-svgr": "^4.3.0",
|
||||
"vitest": "^3.0.5"
|
||||
},
|
||||
@ -103,16 +101,16 @@
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.0.17",
|
||||
"@maplibre/maplibre-gl-leaflet": "^0.1.3",
|
||||
"@tiptap/core": "^3.11.0",
|
||||
"@tiptap/extension-bubble-menu": "^3.11.0",
|
||||
"@tiptap/extension-color": "^3.11.0",
|
||||
"@tiptap/extension-image": "^3.11.0",
|
||||
"@tiptap/extension-link": "^3.11.0",
|
||||
"@tiptap/extension-placeholder": "^3.11.0",
|
||||
"@tiptap/extension-youtube": "^3.11.0",
|
||||
"@tiptap/core": "^3.13.0",
|
||||
"@tiptap/extension-bubble-menu": "^3.13.0",
|
||||
"@tiptap/extension-color": "^3.13.0",
|
||||
"@tiptap/extension-image": "^3.13.0",
|
||||
"@tiptap/extension-link": "^3.13.0",
|
||||
"@tiptap/extension-placeholder": "^3.13.0",
|
||||
"@tiptap/extension-youtube": "^3.13.0",
|
||||
"@tiptap/pm": "^3.6.5",
|
||||
"@tiptap/react": "^3.11.0",
|
||||
"@tiptap/starter-kit": "^3.11.0",
|
||||
"@tiptap/react": "^3.13.0",
|
||||
"@tiptap/starter-kit": "^3.13.0",
|
||||
"axios": "^1.13.2",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"classnames": "^2.5.1",
|
||||
@ -128,12 +126,12 @@
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-leaflet-cluster": "^3.1.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-photo-album": "^3.2.1",
|
||||
"react-photo-album": "^3.3.0",
|
||||
"react-qr-code": "^2.0.16",
|
||||
"react-toastify": "^9.1.3",
|
||||
"remark-breaks": "^4.0.0",
|
||||
"tiptap-markdown": "^0.9.0",
|
||||
"yet-another-react-lightbox": "^3.21.7"
|
||||
"yet-another-react-lightbox": "^3.26.0"
|
||||
},
|
||||
"imports": {
|
||||
"#assets/*": "./src/assets/*",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// eslint-disable-next-line import/no-commonjs
|
||||
// eslint-disable-next-line import-x/no-commonjs
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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={
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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'>
|
||||
|
||||
@ -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'>
|
||||
|
||||
@ -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'>
|
||||
|
||||
@ -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'>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
import { useCallback, useState, createContext, useContext } from 'react'
|
||||
|
||||
type UseQuestManagerResult = ReturnType<typeof useQuestsManager>
|
||||
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
@ -16,14 +16,14 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
|
||||
selector: (ctx) => {
|
||||
return {
|
||||
isBold: ctx.editor.isActive('bold'),
|
||||
canBold: ctx.editor.can().chain().focus().toggleBold().run(),
|
||||
canBold: ctx.editor.can().toggleBold(),
|
||||
isItalic: ctx.editor.isActive('italic'),
|
||||
canItalic: ctx.editor.can().chain().focus().toggleItalic().run(),
|
||||
canItalic: ctx.editor.can().toggleItalic(),
|
||||
isStrike: ctx.editor.isActive('strike'),
|
||||
canStrike: ctx.editor.can().chain().focus().toggleStrike().run(),
|
||||
canStrike: ctx.editor.can().toggleStrike(),
|
||||
isCode: ctx.editor.isActive('code'),
|
||||
canCode: ctx.editor.can().chain().focus().toggleCode().run(),
|
||||
canClearMarks: ctx.editor.can().chain().focus().unsetAllMarks().run(),
|
||||
canCode: ctx.editor.can().toggleCode(),
|
||||
canClearMarks: ctx.editor.can().unsetAllMarks(),
|
||||
isParagraph: ctx.editor.isActive('paragraph'),
|
||||
isHeading1: ctx.editor.isActive('heading', { level: 1 }),
|
||||
isHeading2: ctx.editor.isActive('heading', { level: 2 }),
|
||||
@ -36,8 +36,8 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
|
||||
isOrderedList: ctx.editor.isActive('orderedList'),
|
||||
isCodeBlock: ctx.editor.isActive('codeBlock'),
|
||||
isBlockquote: ctx.editor.isActive('blockquote'),
|
||||
canUndo: ctx.editor.can().chain().focus().undo().run(),
|
||||
canRedo: ctx.editor.can().chain().focus().redo().run(),
|
||||
canUndo: ctx.editor.can().undo(),
|
||||
canRedo: ctx.editor.can().redo(),
|
||||
}
|
||||
},
|
||||
})
|
||||
@ -77,7 +77,7 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div className='tw:w-[1px] tw:p-0 tw:mx-1 tw:bg-base-content/10 tw:my-1' />
|
||||
<div className='tw:w-px tw:p-0 tw:mx-1 tw:bg-base-content/10 tw:my-1' />
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
@ -98,7 +98,7 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div className='tw:w-[1px] tw:p-0 tw:mx-1 tw:bg-base-content/10 tw:my-1' />
|
||||
<div className='tw:w-px tw:p-0 tw:mx-1 tw:bg-base-content/10 tw:my-1' />
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
@ -119,7 +119,7 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div className='tw:w-[1px] tw:p-0 tw:mx-1 tw:bg-base-content/10 tw:my-1' />
|
||||
<div className='tw:w-px tw:p-0 tw:mx-1 tw:bg-base-content/10 tw:my-1' />
|
||||
</li>
|
||||
{/* <li>
|
||||
<div className='tw:@sm:tooltip tw:px-1.5 tw:mx-0.5' data-tip='Image' onClick={addImage}>
|
||||
@ -135,7 +135,7 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
|
||||
<MdHorizontalRule className='tw:w-5 tw:h-5' />
|
||||
</div>
|
||||
</li>
|
||||
<div className='tw:flex-grow'></div>
|
||||
<div className='tw:grow'></div>
|
||||
<li>
|
||||
<div
|
||||
className={`tw:@sm:tooltip tw:px-1.5 tw:mx-0.5 tw:hidden tw:@sm:block ${editorState.canUndo ? '' : 'tw:opacity-50'}`}
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)}
|
||||
|
||||
@ -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)}
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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'}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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' />}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { MapPinIcon } from '@heroicons/react/24/solid'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
|
||||
@ -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'>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
@ -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)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
@ -16,7 +16,7 @@ export interface HeaderViewProps {
|
||||
|
||||
export interface PlatformConfig {
|
||||
shareUrl: string
|
||||
icon: JSX.Element
|
||||
icon: React.JSX.Element
|
||||
label: string
|
||||
bgColor: string
|
||||
}
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||
import { TextAreaInput } from '#components/Input'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||
import { TextInput } from '#components/Input'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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 })
|
||||
}
|
||||
},
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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 */
|
||||
|
||||
|
||||
@ -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: () => {},
|
||||
})
|
||||
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
interface GeocodeResult {
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -17,7 +17,9 @@ export default function useWindowDimensions() {
|
||||
}
|
||||
|
||||
window.addEventListener('resize', handleResize)
|
||||
return () => window.removeEventListener('resize', handleResize)
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return windowDimensions
|
||||
|
||||
@ -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)
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user