diff --git a/documentation/project-improvement-todo.ru.md b/documentation/project-improvement-todo.ru.md index 3f350b9..e9ade3d 100644 --- a/documentation/project-improvement-todo.ru.md +++ b/documentation/project-improvement-todo.ru.md @@ -35,15 +35,17 @@ Frontend: Цель: включить strict TypeScript как обязательный стандарт для frontend и закрыть legacy errors без сохранения JS/CommonJS как допустимого направления. Новый и изменённый код должен быть strict-compatible. +Статус: выполнено 2026-07-06 для strict TypeScript baseline и frontend lint-warning cleanup. Первичный запуск `strict: true` давал 294 ошибки; после исправлений `frontend/tsconfig.json` использует общий `strict: true`, `npm run typecheck` проходит, `npm run lint` проходит без warnings, `npm run test` проходит (307 tests), `npm run test:e2e` проходит (10 tests), `npm run build` проходит. + TODO: -- Посчитать текущий frontend typecheck baseline: сколько ошибок даёт `strict: true`. -- Включать strictness по шагам: сначала отдельный strict config для selected folders/new code, затем общий `strict: true`, когда baseline закрыт. -- Для новых/изменённых файлов запрещать новый `any` без явной причины. -- Запретить новые type assertions/casts (`as`, angle-bracket assertions, non-null `!`) в feature code. Исключения: validated external boundaries, DOM/library interop, discriminated unions после guard; исключение должно быть локальным и объяснённым. -- Вернуть `no-unused-vars`/unused imports как warning, затем поднять до error после cleanup. -- Добавить frontend `typecheck` script и включить его в minimal checks, когда baseline проходит. -- `react-hooks/exhaustive-deps` включать file-by-file после исправления конкретных hooks. +- [x] Посчитать текущий frontend typecheck baseline: сколько ошибок даёт `strict: true`. +- [x] Включать strictness по шагам: сначала отдельный strict config для selected folders/new code, затем общий `strict: true`, когда baseline закрыт. +- [x] Для новых/изменённых файлов запрещать новый `any` без явной причины через `@typescript-eslint/no-explicit-any` warning. +- [x] Вернуть guardrails для type assertions/casts (`as`, angle-bracket assertions, non-null `!`) как warnings. Исключения: validated external boundaries, DOM/library interop, discriminated unions после guard; исключение должно быть локальным и объяснённым. +- [x] Вернуть `no-unused-vars`/unused imports как warning и очистить текущие warnings. +- [x] Добавить frontend `typecheck` script и включить его в minimal checks, когда baseline проходит. +- [x] Вернуть `react-hooks/exhaustive-deps` как warning и очистить текущие hook dependency warnings. ### Auth storage diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index 7ee9d25..1d3eef4 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -14,10 +14,26 @@ module.exports = { }, rules: { 'react/no-children-prop': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-unused-vars': 'off', // Turned off to reduce noise + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + ignoreRestSiblings: true, + }, + ], + '@typescript-eslint/consistent-type-assertions': [ + 'warn', + { + assertionStyle: 'as', + objectLiteralTypeAssertions: 'allow', + }, + ], + '@typescript-eslint/no-non-null-assertion': 'warn', '@typescript-eslint/ban-types': 'off', - 'react-hooks/exhaustive-deps': 'off', // Turned off to reduce noise + 'react-hooks/exhaustive-deps': 'warn', 'import/named': 'error', 'import/no-duplicates': 'error', 'import/no-unresolved': 'error', diff --git a/frontend/docs/frontend-architecture.md b/frontend/docs/frontend-architecture.md index 8c17a2b..6fe18f4 100644 --- a/frontend/docs/frontend-architecture.md +++ b/frontend/docs/frontend-architecture.md @@ -16,7 +16,7 @@ The frontend is a **Next.js 15** application with **React 19**, **TypeScript**, |------------|---------|---------| | Next.js | 15.3.1 | React framework with Pages Router | | React | 19.0.0 | UI library | -| TypeScript | 5.x | Type safety | +| TypeScript | 5.x | Strict type safety | | Redux Toolkit | 2.1.0 | State management | | MUI X DataGrid | 7.0.0 | Data tables | | Tailwind CSS | 3.4.1 | Utility-first styling | diff --git a/frontend/docs/types-module.md b/frontend/docs/types-module.md index cc4b185..9d97e5d 100644 --- a/frontend/docs/types-module.md +++ b/frontend/docs/types-module.md @@ -6,6 +6,8 @@ The types module provides **TypeScript type definitions** used throughout the fr **Location:** `frontend/src/types/` +**Strictness:** Frontend TypeScript runs with `strict: true` in `frontend/tsconfig.json`. `npm run typecheck` is the strict type gate for changed frontend code. ESLint also reports explicit `any`, type assertions/non-null assertions, unused variables/imports, and React hook dependency issues as warnings; the current frontend lint baseline has no warnings. + **Statistics:** - **20 files** (~2,205 LOC total) - **6 categories**: Domain Entities, Runtime/Presentation, Infrastructure, Forms/Filters, Specialized, Module Declarations diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ec574f4..3ccc27b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -45,7 +45,10 @@ "devDependencies": { "@playwright/test": "^1.61.1", "@tailwindcss/forms": "^0.5.7", + "@types/file-saver": "^2.0.7", + "@types/lodash": "^4.17.24", "@types/node": "18.7.16", + "@types/react-dom": "^19.2.3", "@typescript-eslint/eslint-plugin": "^8.62.1", "@typescript-eslint/parser": "^8.62.1", "autoprefixer": "^10.4.0", @@ -2573,6 +2576,13 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/file-saver": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz", + "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz", @@ -2592,6 +2602,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "18.7.16", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.16.tgz", @@ -2621,6 +2638,16 @@ "csstype": "^3.2.2" } }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.12", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", diff --git a/frontend/package.json b/frontend/package.json index e22135c..d037239 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -57,7 +57,10 @@ "devDependencies": { "@playwright/test": "^1.61.1", "@tailwindcss/forms": "^0.5.7", + "@types/file-saver": "^2.0.7", + "@types/lodash": "^4.17.24", "@types/node": "18.7.16", + "@types/react-dom": "^19.2.3", "@typescript-eslint/eslint-plugin": "^8.62.1", "@typescript-eslint/parser": "^8.62.1", "autoprefixer": "^10.4.0", diff --git a/frontend/src/colors.ts b/frontend/src/colors.ts index 35f0abb..b6e3552 100644 --- a/frontend/src/colors.ts +++ b/frontend/src/colors.ts @@ -105,6 +105,9 @@ export const getButtonColor = ( info: 'border-blue-600 border-blue-600 dark:border-pavitra-blue', }, text: { + white: 'text-black dark:text-white', + whiteDark: 'text-black dark:text-white', + lightDark: 'text-black dark:text-white', contrast: 'dark:text-slate-100', success: 'text-emerald-600 dark:text-pavitra-blue', danger: 'text-red-600 dark:text-red-500', @@ -112,6 +115,9 @@ export const getButtonColor = ( info: 'text-blue-600 dark:text-pavitra-blue', }, outlineHover: { + white: 'hover:bg-gray-100 hover:dark:bg-dark-800', + whiteDark: 'hover:bg-gray-100 hover:dark:bg-dark-800', + lightDark: 'hover:bg-gray-200 hover:dark:bg-slate-700', contrast: 'hover:bg-gray-800 hover:text-gray-100 hover:dark:bg-slate-100 hover:dark:text-black', success: diff --git a/frontend/src/components/Access_logs/CardAccess_logs.tsx b/frontend/src/components/Access_logs/CardAccess_logs.tsx index 90d2420..885eb5a 100644 --- a/frontend/src/components/Access_logs/CardAccess_logs.tsx +++ b/frontend/src/components/Access_logs/CardAccess_logs.tsx @@ -1,12 +1,9 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; -import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; +import dataFormatter from '../../helpers/dataFormatter'; +import { useAppSelector } from '../../stores/hooks'; +import ListActionsPopover from '../ListActionsPopover'; +import LoadingSpinner from '../LoadingSpinner'; +import { Pagination } from '../Pagination'; import { hasPermission } from '../../helpers/userPermissions'; import type { AccessLog } from '../../types/entities'; @@ -47,7 +44,7 @@ const CardAccess_logs = ({ className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > {!loading && - access_logs.map((item, index) => ( + access_logs.map((item) => (
  • state.auth.currentUser); const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ACCESS_LOGS'); - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( <>
    diff --git a/frontend/src/components/Access_logs/TableAccess_logs.tsx b/frontend/src/components/Access_logs/TableAccess_logs.tsx index 3df6dfd..ada1fc3 100644 --- a/frontend/src/components/Access_logs/TableAccess_logs.tsx +++ b/frontend/src/components/Access_logs/TableAccess_logs.tsx @@ -1,13 +1,13 @@ -import { createTableComponent } from '../Factory/createTableComponent'; import { - fetch, - update, deleteItem, - setRefetch, deleteItemsByIds, + fetch, + setRefetch, + update, } from '../../stores/access_logs/access_logsSlice'; -import { loadColumns } from './configureAccess_logsCols'; import type { AccessLog } from '../../types/entities'; +import { createTableComponent } from '../Factory/createTableComponent'; +import { loadColumns } from './configureAccess_logsCols'; const TableAccess_logs = createTableComponent({ entityName: 'access_logs', diff --git a/frontend/src/components/Access_logs/configureAccess_logsCols.tsx b/frontend/src/components/Access_logs/configureAccess_logsCols.tsx index 985be76..3ed5269 100644 --- a/frontend/src/components/Access_logs/configureAccess_logsCols.tsx +++ b/frontend/src/components/Access_logs/configureAccess_logsCols.tsx @@ -1,6 +1,6 @@ import { - createColumnLoader, ColumnMetadata, + createColumnLoader, } from '../DataGrid/configBuilderFactory'; const ACCESS_LOGS_COLUMNS: ColumnMetadata[] = [ diff --git a/frontend/src/components/AsideMenu.tsx b/frontend/src/components/AsideMenu.tsx index 1a1f0fc..78b0573 100644 --- a/frontend/src/components/AsideMenu.tsx +++ b/frontend/src/components/AsideMenu.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { MenuAsideItem } from '../types/menu'; import AsideMenuLayer from './AsideMenuLayer'; import OverlayLayer from './OverlayLayer'; diff --git a/frontend/src/components/AsideMenuItem.tsx b/frontend/src/components/AsideMenuItem.tsx index b8e1644..9b53b24 100644 --- a/frontend/src/components/AsideMenuItem.tsx +++ b/frontend/src/components/AsideMenuItem.tsx @@ -1,12 +1,12 @@ -import React, { useEffect, useState } from 'react'; import { mdiMinus, mdiPlus } from '@mdi/js'; -import BaseIcon from './BaseIcon'; import Link from 'next/link'; -import { getButtonColor } from '../colors'; -import AsideMenuList from './AsideMenuList'; -import { MenuAsideItem } from '../types/menu'; -import { useAppSelector } from '../stores/hooks'; import { useRouter } from 'next/router'; +import { useEffect, useState } from 'react'; +import { getButtonColor } from '../colors'; +import { useAppSelector } from '../stores/hooks'; +import { MenuAsideItem } from '../types/menu'; +import AsideMenuList from './AsideMenuList'; +import BaseIcon from './BaseIcon'; type Props = { item: MenuAsideItem; diff --git a/frontend/src/components/AsideMenuLayer.tsx b/frontend/src/components/AsideMenuLayer.tsx index 0fd0e65..1f1c390 100644 --- a/frontend/src/components/AsideMenuLayer.tsx +++ b/frontend/src/components/AsideMenuLayer.tsx @@ -1,10 +1,9 @@ +import { mdiClose } from '@mdi/js'; import React from 'react'; -import { mdiLogout, mdiClose } from '@mdi/js'; -import BaseIcon from './BaseIcon'; -import AsideMenuList from './AsideMenuList'; -import { MenuAsideItem } from '../types/menu'; import { useAppSelector } from '../stores/hooks'; -import Link from 'next/link'; +import { MenuAsideItem } from '../types/menu'; +import AsideMenuList from './AsideMenuList'; +import BaseIcon from './BaseIcon'; type Props = { menu: MenuAsideItem[]; diff --git a/frontend/src/components/AsideMenuList.tsx b/frontend/src/components/AsideMenuList.tsx index 30cb703..f52ac3b 100644 --- a/frontend/src/components/AsideMenuList.tsx +++ b/frontend/src/components/AsideMenuList.tsx @@ -1,8 +1,7 @@ -import React from 'react'; +import { hasPermission } from '../helpers/userPermissions'; +import { useAppSelector } from '../stores/hooks'; import { MenuAsideItem } from '../types/menu'; import AsideMenuItem from './AsideMenuItem'; -import { useAppSelector } from '../stores/hooks'; -import { hasPermission } from '../helpers/userPermissions'; type Props = { menu: MenuAsideItem[]; @@ -22,7 +21,9 @@ export default function AsideMenuList({ return (
      {menu.map((item, index) => { - if (!hasPermission(currentUser, item.permissions)) return null; + if (item.permissions && !hasPermission(currentUser, item.permissions)) { + return null; + } return (
      diff --git a/frontend/src/components/Asset_variants/CardAsset_variants.tsx b/frontend/src/components/Asset_variants/CardAsset_variants.tsx index 2e40ca4..b87a8be 100644 --- a/frontend/src/components/Asset_variants/CardAsset_variants.tsx +++ b/frontend/src/components/Asset_variants/CardAsset_variants.tsx @@ -1,12 +1,9 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; -import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; +import dataFormatter from '../../helpers/dataFormatter'; +import { useAppSelector } from '../../stores/hooks'; +import ListActionsPopover from '../ListActionsPopover'; +import LoadingSpinner from '../LoadingSpinner'; +import { Pagination } from '../Pagination'; import { hasPermission } from '../../helpers/userPermissions'; import type { AssetVariant } from '../../types/entities'; @@ -50,7 +47,7 @@ const CardAsset_variants = ({ className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > {!loading && - asset_variants.map((item, index) => ( + asset_variants.map((item) => (
    • state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( <>
      diff --git a/frontend/src/components/Asset_variants/TableAsset_variants.tsx b/frontend/src/components/Asset_variants/TableAsset_variants.tsx index eccb8b9..89f684d 100644 --- a/frontend/src/components/Asset_variants/TableAsset_variants.tsx +++ b/frontend/src/components/Asset_variants/TableAsset_variants.tsx @@ -1,13 +1,13 @@ -import { createTableComponent } from '../Factory/createTableComponent'; import { - fetch, - update, deleteItem, - setRefetch, deleteItemsByIds, + fetch, + setRefetch, + update, } from '../../stores/asset_variants/asset_variantsSlice'; -import { loadColumns } from './configureAsset_variantsCols'; import type { AssetVariant } from '../../types/entities'; +import { createTableComponent } from '../Factory/createTableComponent'; +import { loadColumns } from './configureAsset_variantsCols'; const TableAsset_variants = createTableComponent({ entityName: 'asset_variants', diff --git a/frontend/src/components/Asset_variants/configureAsset_variantsCols.tsx b/frontend/src/components/Asset_variants/configureAsset_variantsCols.tsx index e2ddf57..96f5b13 100644 --- a/frontend/src/components/Asset_variants/configureAsset_variantsCols.tsx +++ b/frontend/src/components/Asset_variants/configureAsset_variantsCols.tsx @@ -1,6 +1,6 @@ import { - createColumnLoader, ColumnMetadata, + createColumnLoader, } from '../DataGrid/configBuilderFactory'; const ASSET_VARIANTS_COLUMNS: ColumnMetadata[] = [ diff --git a/frontend/src/components/Assets/AssetSectionCard.tsx b/frontend/src/components/Assets/AssetSectionCard.tsx index 53ecca4..bb75c26 100644 --- a/frontend/src/components/Assets/AssetSectionCard.tsx +++ b/frontend/src/components/Assets/AssetSectionCard.tsx @@ -92,7 +92,7 @@ const AssetSectionCard: React.FC = ({ {!loading && - assets.map((item, index) => ( + assets.map((item) => (
    • state.auth.currentUser); const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ASSETS'); - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( <>
      diff --git a/frontend/src/components/Assets/TableAssets.tsx b/frontend/src/components/Assets/TableAssets.tsx index 98d0d28..1dd6d03 100644 --- a/frontend/src/components/Assets/TableAssets.tsx +++ b/frontend/src/components/Assets/TableAssets.tsx @@ -1,13 +1,13 @@ -import { createTableComponent } from '../Factory/createTableComponent'; import { - fetch, - update, deleteItem, - setRefetch, deleteItemsByIds, + fetch, + setRefetch, + update, } from '../../stores/assets/assetsSlice'; -import { loadColumns } from './configureAssetsCols'; import type { Asset } from '../../types/entities'; +import { createTableComponent } from '../Factory/createTableComponent'; +import { loadColumns } from './configureAssetsCols'; const TableAssets = createTableComponent({ entityName: 'assets', diff --git a/frontend/src/components/Assets/configureAssetsCols.tsx b/frontend/src/components/Assets/configureAssetsCols.tsx index ddc127f..99e9b5e 100644 --- a/frontend/src/components/Assets/configureAssetsCols.tsx +++ b/frontend/src/components/Assets/configureAssetsCols.tsx @@ -1,6 +1,6 @@ import { - createColumnLoader, ColumnMetadata, + createColumnLoader, } from '../DataGrid/configBuilderFactory'; const ASSETS_COLUMNS: ColumnMetadata[] = [ diff --git a/frontend/src/components/Assets/useAssetUploader.ts b/frontend/src/components/Assets/useAssetUploader.ts index ae29819..9aae229 100644 --- a/frontend/src/components/Assets/useAssetUploader.ts +++ b/frontend/src/components/Assets/useAssetUploader.ts @@ -1,15 +1,15 @@ import axios from 'axios'; import { useCallback, useEffect, useRef, useState } from 'react'; import { toast } from 'react-toastify'; +import { logger } from '../../lib/logger'; +import { + isAudioMimeType, + isVideoMimeType, + probeMediaDuration, +} from '../../lib/mediaDuration'; import FileUploader from '../Uploaders/UploadService'; import type { AssetSection } from './AssetSectionCard'; import type { UploadQueueItem } from './UploadProgressList'; -import { logger } from '../../lib/logger'; -import { - probeMediaDuration, - isVideoMimeType, - isAudioMimeType, -} from '../../lib/mediaDuration'; interface UseAssetUploaderOptions { selectedProjectId: string; diff --git a/frontend/src/components/BackdropPortal.tsx b/frontend/src/components/BackdropPortal.tsx index 7f417bc..cce433c 100644 --- a/frontend/src/components/BackdropPortal.tsx +++ b/frontend/src/components/BackdropPortal.tsx @@ -11,11 +11,11 @@ import React, { createContext, + useCallback, useContext, + useMemo, useRef, useState, - useCallback, - useMemo, } from 'react'; interface BackdropItem { diff --git a/frontend/src/components/BaseButton.tsx b/frontend/src/components/BaseButton.tsx index 427dec0..f91a00e 100644 --- a/frontend/src/components/BaseButton.tsx +++ b/frontend/src/components/BaseButton.tsx @@ -1,9 +1,9 @@ -import React from 'react'; import Link from 'next/link'; +import React from 'react'; import { getButtonColor } from '../colors'; -import BaseIcon from './BaseIcon'; -import type { ColorButtonKey } from '../types/ui'; import { useAppSelector } from '../stores/hooks'; +import type { ColorButtonKey } from '../types/ui'; +import BaseIcon from './BaseIcon'; type Props = { label?: string; diff --git a/frontend/src/components/BaseButtons.tsx b/frontend/src/components/BaseButtons.tsx index bff8011..eed306b 100644 --- a/frontend/src/components/BaseButtons.tsx +++ b/frontend/src/components/BaseButtons.tsx @@ -1,5 +1,5 @@ -import { Children, cloneElement, ReactElement } from 'react'; import type { ReactNode } from 'react'; +import { Children, cloneElement, isValidElement } from 'react'; type Props = { type?: string; @@ -24,13 +24,15 @@ const BaseButtons = ({ noWrap ? 'flex-nowrap' : 'flex-wrap' }`} > - {Children.map(children, (child: ReactElement) => - child - ? cloneElement(child as ReactElement<{ className?: string }>, { - className: `${classAddon} ${(child.props as { className?: string }).className || ''}`, - }) - : null, - )} + {Children.map(children, (child) => { + if (!isValidElement<{ className?: string }>(child)) { + return child; + } + + return cloneElement(child, { + className: `${classAddon} ${child.props.className || ''}`, + }); + })}
      ); }; diff --git a/frontend/src/components/BaseDivider.tsx b/frontend/src/components/BaseDivider.tsx index 52e7f29..c14b124 100644 --- a/frontend/src/components/BaseDivider.tsx +++ b/frontend/src/components/BaseDivider.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { useAppSelector } from '../stores/hooks'; type Props = { navBar?: boolean; diff --git a/frontend/src/components/BaseIcon.tsx b/frontend/src/components/BaseIcon.tsx index d26fe1c..bdf19b4 100644 --- a/frontend/src/components/BaseIcon.tsx +++ b/frontend/src/components/BaseIcon.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import { ReactNode } from 'react'; type Props = { path: string; diff --git a/frontend/src/components/CanvasLoadingSpinner.tsx b/frontend/src/components/CanvasLoadingSpinner.tsx index ace5cad..a5c7c17 100644 --- a/frontend/src/components/CanvasLoadingSpinner.tsx +++ b/frontend/src/components/CanvasLoadingSpinner.tsx @@ -6,7 +6,7 @@ * Includes delay to avoid flashing for quick operations. */ -import React, { useState, useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { PRELOAD_CONFIG } from '../config/preload.config'; interface CanvasLoadingSpinnerProps { diff --git a/frontend/src/components/CardBox.tsx b/frontend/src/components/CardBox.tsx index dea7269..b822a9f 100644 --- a/frontend/src/components/CardBox.tsx +++ b/frontend/src/components/CardBox.tsx @@ -1,7 +1,7 @@ import React, { ReactNode } from 'react'; +import { useAppSelector } from '../stores/hooks'; import CardBoxComponentBody from './CardBoxComponentBody'; import CardBoxComponentFooter from './CardBoxComponentFooter'; -import { useAppSelector } from '../stores/hooks'; type Props = { rounded?: string; @@ -20,7 +20,6 @@ type Props = { }; export default function CardBox({ - rounded = 'rounded', flex = 'flex-col', className = '', hasComponentLayout = false, @@ -28,7 +27,6 @@ export default function CardBox({ hasTable = false, isHoverable = false, isList = false, - isModal = false, children, footer, id = '', diff --git a/frontend/src/components/CardBoxComponentBody.tsx b/frontend/src/components/CardBoxComponentBody.tsx index 12448d8..e1c0cd9 100644 --- a/frontend/src/components/CardBoxComponentBody.tsx +++ b/frontend/src/components/CardBoxComponentBody.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import { ReactNode } from 'react'; type Props = { noPadding?: boolean; diff --git a/frontend/src/components/CardBoxComponentEmpty.tsx b/frontend/src/components/CardBoxComponentEmpty.tsx index c9072bb..e6545e4 100644 --- a/frontend/src/components/CardBoxComponentEmpty.tsx +++ b/frontend/src/components/CardBoxComponentEmpty.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - const CardBoxComponentEmpty = () => { return (
      diff --git a/frontend/src/components/CardBoxComponentFooter.tsx b/frontend/src/components/CardBoxComponentFooter.tsx index 184a058..dc8ebc8 100644 --- a/frontend/src/components/CardBoxComponentFooter.tsx +++ b/frontend/src/components/CardBoxComponentFooter.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import { ReactNode } from 'react'; type Props = { className?: string; diff --git a/frontend/src/components/CardBoxComponentTitle.tsx b/frontend/src/components/CardBoxComponentTitle.tsx index 20990e6..d51cea4 100644 --- a/frontend/src/components/CardBoxComponentTitle.tsx +++ b/frontend/src/components/CardBoxComponentTitle.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import { ReactNode } from 'react'; type Props = { title: string; diff --git a/frontend/src/components/CardBoxModal.tsx b/frontend/src/components/CardBoxModal.tsx index f72f320..2e7db2b 100644 --- a/frontend/src/components/CardBoxModal.tsx +++ b/frontend/src/components/CardBoxModal.tsx @@ -1,5 +1,5 @@ import { mdiClose } from '@mdi/js'; -import { ReactNode } from 'react'; +import type { MouseEvent, ReactNode } from 'react'; import type { ColorButtonKey } from '../types/ui'; import BaseButton from './BaseButton'; import BaseButtons from './BaseButtons'; @@ -32,12 +32,20 @@ const CardBoxModal = ({ return null; } + const handleConfirm = (_event: MouseEvent) => { + onConfirm(); + }; + + const handleCancel = (_event: MouseEvent) => { + onCancel?.(); + }; + const footer = ( {!!onCancel && ( @@ -45,7 +53,7 @@ const CardBoxModal = ({ label='Cancel' color={buttonColor} outline - onClick={onCancel} + onClick={handleCancel} /> )} @@ -53,7 +61,7 @@ const CardBoxModal = ({ return ( diff --git a/frontend/src/components/ClickOutside.tsx b/frontend/src/components/ClickOutside.tsx index 4e031ad..88773d8 100644 --- a/frontend/src/components/ClickOutside.tsx +++ b/frontend/src/components/ClickOutside.tsx @@ -1,15 +1,15 @@ -import React, { +import { + MutableRefObject, + ReactNode, useCallback, useEffect, useRef, - ReactNode, - MutableRefObject, } from 'react'; interface ClickOutsideProps { children?: ReactNode; onClickOutside: () => void; - excludedElements: MutableRefObject[]; + excludedElements: MutableRefObject[]; } const ClickOutside = ({ @@ -17,19 +17,21 @@ const ClickOutside = ({ onClickOutside, excludedElements, }: ClickOutsideProps) => { - const wrapperRef = useRef(null); - + const wrapperRef = useRef(null); const handleClickOutside = useCallback( - (event) => { + (event: MouseEvent) => { + const target = event.target; + if (!(target instanceof Node)) return; + if ( wrapperRef.current && - !wrapperRef.current.contains(event.target) && - !excludedElements.some((el) => el.current.contains(event.target)) + !wrapperRef.current.contains(target) && + !excludedElements.some((el) => el.current?.contains(target)) ) { onClickOutside(); } }, - [wrapperRef, onClickOutside, ...excludedElements], + [excludedElements, onClickOutside], ); useEffect(() => { diff --git a/frontend/src/components/Constructor/AssetSelectCompact.tsx b/frontend/src/components/Constructor/AssetSelectCompact.tsx index f552875..75c6b41 100644 --- a/frontend/src/components/Constructor/AssetSelectCompact.tsx +++ b/frontend/src/components/Constructor/AssetSelectCompact.tsx @@ -6,8 +6,8 @@ */ import React from 'react'; -import type { AssetOption } from './types'; import { addFallbackAssetOption } from '../../lib/constructorHelpers'; +import type { AssetOption } from './types'; interface AssetSelectCompactProps { label: string; diff --git a/frontend/src/components/Constructor/BackgroundSettingsEditor.tsx b/frontend/src/components/Constructor/BackgroundSettingsEditor.tsx index 5531109..e0ce040 100644 --- a/frontend/src/components/Constructor/BackgroundSettingsEditor.tsx +++ b/frontend/src/components/Constructor/BackgroundSettingsEditor.tsx @@ -7,12 +7,12 @@ */ import React from 'react'; +import { addFallbackAssetOption } from '../../lib/constructorHelpers'; import type { AssetOption, - VideoPlaybackSettings, AudioPlaybackSettings, + VideoPlaybackSettings, } from './types'; -import { addFallbackAssetOption } from '../../lib/constructorHelpers'; interface BackgroundSettingsEditorProps { type: 'image' | 'video' | 'embed' | 'audio'; diff --git a/frontend/src/components/Constructor/CanvasBackground.tsx b/frontend/src/components/Constructor/CanvasBackground.tsx index dfd0447..c245ec4 100644 --- a/frontend/src/components/Constructor/CanvasBackground.tsx +++ b/frontend/src/components/Constructor/CanvasBackground.tsx @@ -6,21 +6,20 @@ * Supports custom video playback settings (autoplay, loop, muted, start/end time). */ -import React, { useEffect, useState, useMemo, useCallback } from 'react'; -import { useBackgroundVideoPlayback } from '../../hooks/useBackgroundVideoPlayback'; -import { useBackgroundAudioPlayback } from '../../hooks/useBackgroundAudioPlayback'; -import PreviousBackgroundOverlay from '../PreviousBackgroundOverlay'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { baseURLApi } from '../../config'; +import { useBackgroundAudioPlayback } from '../../hooks/useBackgroundAudioPlayback'; +import { useBackgroundVideoPlayback } from '../../hooks/useBackgroundVideoPlayback'; import { buildChromeFreeEmbedUrl } from '../../lib/embedUrl'; -import CanvasBackgroundAudioLayer from './CanvasBackgroundAudioLayer'; -import CanvasBackgroundEmbedLayer from './CanvasBackgroundEmbedLayer'; +import PreviousBackgroundOverlay from '../PreviousBackgroundOverlay'; import { getActiveCanvasVideoUrl, getCanvasVideoSrc, - isBlobUrl, scheduleAfterPaint, shouldUseNativeVideoLoop, } from './CanvasBackground.helpers'; +import CanvasBackgroundAudioLayer from './CanvasBackgroundAudioLayer'; +import CanvasBackgroundEmbedLayer from './CanvasBackgroundEmbedLayer'; import CanvasBackgroundImageLayer from './CanvasBackgroundImageLayer'; import CanvasBackgroundVideoLayer from './CanvasBackgroundVideoLayer'; import { useCanvasBackgroundImageReady } from './useCanvasBackgroundImageReady'; diff --git a/frontend/src/components/Constructor/CanvasBackgroundImageLayer.tsx b/frontend/src/components/Constructor/CanvasBackgroundImageLayer.tsx index 4426ac1..4b8de67 100644 --- a/frontend/src/components/Constructor/CanvasBackgroundImageLayer.tsx +++ b/frontend/src/components/Constructor/CanvasBackgroundImageLayer.tsx @@ -1,5 +1,5 @@ -import React from 'react'; import NextImage from 'next/image'; +import React from 'react'; import { isBlobUrl } from './CanvasBackground.helpers'; interface CanvasBackgroundImageLayerProps { diff --git a/frontend/src/components/Constructor/CanvasElement.tsx b/frontend/src/components/Constructor/CanvasElement.tsx index adec4cf..71d7bc0 100644 --- a/frontend/src/components/Constructor/CanvasElement.tsx +++ b/frontend/src/components/Constructor/CanvasElement.tsx @@ -8,20 +8,20 @@ */ import React, { useCallback } from 'react'; -import UiElementRenderer from '../UiElements/UiElementRenderer'; -import { useElementEffects } from '../../hooks/useElementEffects'; import { useAudioEffects } from '../../hooks/useAudioEffects'; +import { useElementEffects } from '../../hooks/useElementEffects'; +import type { PreloadCacheProvider } from '../../hooks/video'; import { - buildTransitionStyle, buildAppearAnimationStyle, + buildTransitionStyle, hasAnyEffects, type ElementEffectProperties, } from '../../lib/elementEffects'; +import { normalizeZIndexValue } from '../../lib/elementStyles'; +import { isInfoPanelElementType } from '../../lib/elementTypeGuards'; import type { CanvasElement as CanvasElementType } from '../../types/constructor'; import type { ResolvedTransitionSettings } from '../../types/transition'; -import type { PreloadCacheProvider } from '../../hooks/video'; -import { isInfoPanelElementType } from '../../lib/elementTypeGuards'; -import { normalizeZIndexValue } from '../../lib/elementStyles'; +import UiElementRenderer from '../UiElements/UiElementRenderer'; interface CanvasElementProps { element: CanvasElementType; @@ -180,6 +180,7 @@ const CanvasElement: React.FC = ({ // Check if we need the inner wrapper for effects const needsEffectWrapper = !isEditMode && hasAnyEffects(effectProperties); + const { onMouseDown: eventMouseDown, ...outerEventHandlers } = eventHandlers; return (
      = ({ data-constructor-element-id={element.id} className='absolute cursor-pointer' style={positionStyle} - onMouseDown={isEditMode ? onMouseDown : undefined} + onMouseDown={ + isEditMode + ? onMouseDown + : !needsEffectWrapper + ? eventMouseDown + : undefined + } onClick={handleClick} onKeyDown={handleKeyDown} aria-disabled={isDisabled} - {...(!isEditMode && !needsEffectWrapper ? eventHandlers : {})} + {...(!isEditMode && !needsEffectWrapper ? outerEventHandlers : {})} > {needsEffectWrapper ? ( // Inner wrapper handles hover/focus/active effects independently from animation diff --git a/frontend/src/components/Constructor/ConstructorCanvasElementsLayer.tsx b/frontend/src/components/Constructor/ConstructorCanvasElementsLayer.tsx index a04afbb..a5fc259 100644 --- a/frontend/src/components/Constructor/ConstructorCanvasElementsLayer.tsx +++ b/frontend/src/components/Constructor/ConstructorCanvasElementsLayer.tsx @@ -1,15 +1,15 @@ import { mdiPlus } from '@mdi/js'; import type { CSSProperties, MouseEvent } from 'react'; -import BaseButton from '../BaseButton'; -import CanvasElementComponent from './CanvasElement'; +import type { PreloadCacheProvider } from '../../hooks/video'; +import { isElementFlagEnabled } from '../../lib/elementFlags'; +import { + isInfoPanelElementType, + isNavigationElementType, +} from '../../lib/elementTypeGuards'; import type { CanvasElement } from '../../types/constructor'; import type { ResolvedTransitionSettings } from '../../types/transition'; -import type { PreloadCacheProvider } from '../../hooks/video'; -import { - isNavigationElementType, - isInfoPanelElementType, -} from '../../lib/elementTypeGuards'; -import { isElementFlagEnabled } from '../../lib/elementFlags'; +import BaseButton from '../BaseButton'; +import CanvasElementComponent from './CanvasElement'; import { shouldRenderConstructorCanvasElement } from './constructorPage.helpers'; type ConstructorCanvasElementsLayerProps = { @@ -22,7 +22,7 @@ type ConstructorCanvasElementsLayerProps = { letterboxStyles?: CSSProperties; pageTransitionSettings: ResolvedTransitionSettings; preloadCache: PreloadCacheProvider; - resolveUrl: (url: string) => string; + resolveUrl: (url: string | undefined) => string; isElementVisible: (element: CanvasElement) => boolean; isInfoPanelOpen: (elementId: string) => boolean; onCreateFirstPage: () => void; @@ -31,7 +31,7 @@ type ConstructorCanvasElementsLayerProps = { onGalleryCardClick: (element: CanvasElement, cardIndex: number) => void; onCarouselButtonPositionChange: ( elementId: string, - button: 'prev' | 'next' | 'back', + button: 'prev' | 'next', x: number, y: number, ) => void; diff --git a/frontend/src/components/Constructor/ConstructorCanvasStage.helpers.test.ts b/frontend/src/components/Constructor/ConstructorCanvasStage.helpers.test.ts index efb9e84..cdddd2c 100644 --- a/frontend/src/components/Constructor/ConstructorCanvasStage.helpers.test.ts +++ b/frontend/src/components/Constructor/ConstructorCanvasStage.helpers.test.ts @@ -1,11 +1,11 @@ import assert from 'node:assert/strict'; import test from 'node:test'; +import type { TransitionPreviewState } from '../../types/presentation'; import { shouldShowConstructorCanvasElements, shouldShowConstructorCanvasSpinner, } from './ConstructorCanvasStage.helpers'; -import type { TransitionPreviewState } from '../../types/presentation'; const transitionPreview = { videoUrl: 'assets/transition.mp4', diff --git a/frontend/src/components/Constructor/ConstructorCanvasStage.tsx b/frontend/src/components/Constructor/ConstructorCanvasStage.tsx index e889c78..dbe657d 100644 --- a/frontend/src/components/Constructor/ConstructorCanvasStage.tsx +++ b/frontend/src/components/Constructor/ConstructorCanvasStage.tsx @@ -1,14 +1,7 @@ import type { CSSProperties, MouseEvent, PointerEvent, RefObject } from 'react'; -import { BackdropPortalProvider } from '../BackdropPortal'; -import CanvasLoadingSpinner from '../CanvasLoadingSpinner'; -import RuntimeControls from '../Runtime/RuntimeControls'; -import CanvasBackground from './CanvasBackground'; -import ConstructorCanvasElementsLayer from './ConstructorCanvasElementsLayer'; -import ElementEditorPanel from './ElementEditorPanel'; -import TransitionBlackOverlay from '../TransitionBlackOverlay'; -import { isSafari } from '../../lib/browserUtils'; import type { PreloadCacheProvider } from '../../hooks/video'; +import { isSafari } from '../../lib/browserUtils'; import type { CanvasElement } from '../../types/constructor'; import type { TourPage } from '../../types/entities'; import type { TransitionPreviewState } from '../../types/presentation'; @@ -17,10 +10,17 @@ import type { ResolvedUiControlsSettings, SystemUiControlType, } from '../../types/uiControls'; +import { BackdropPortalProvider } from '../BackdropPortal'; +import CanvasLoadingSpinner from '../CanvasLoadingSpinner'; +import RuntimeControls from '../Runtime/RuntimeControls'; +import TransitionBlackOverlay from '../TransitionBlackOverlay'; +import CanvasBackground from './CanvasBackground'; +import ConstructorCanvasElementsLayer from './ConstructorCanvasElementsLayer'; import { shouldShowConstructorCanvasElements, shouldShowConstructorCanvasSpinner, } from './ConstructorCanvasStage.helpers'; +import ElementEditorPanel from './ElementEditorPanel'; type Position = { x: number; @@ -71,11 +71,11 @@ type ConstructorCanvasStageProps = { videoAutoplay: boolean; videoLoop: boolean; videoMuted: boolean; - videoStartTime?: number; - videoEndTime?: number; + videoStartTime?: number | null; + videoEndTime?: number | null; audioLoop: boolean; - audioStartTime?: number; - audioEndTime?: number; + audioStartTime?: number | null; + audioEndTime?: number | null; }; soundControl: { isMuted: boolean; @@ -110,7 +110,7 @@ type ConstructorCanvasStageProps = { onGalleryCardClick: (element: CanvasElement, cardIndex: number) => void; onCarouselButtonPositionChange: ( elementId: string, - button: 'prev' | 'next' | 'back', + button: 'prev' | 'next', x: number, y: number, ) => void; diff --git a/frontend/src/components/Constructor/ConstructorPageModals.tsx b/frontend/src/components/Constructor/ConstructorPageModals.tsx index ea37982..906e4bf 100644 --- a/frontend/src/components/Constructor/ConstructorPageModals.tsx +++ b/frontend/src/components/Constructor/ConstructorPageModals.tsx @@ -1,6 +1,6 @@ import CardBoxModal from '../CardBoxModal'; -import CreatePageModal from './CreatePageModal'; import { getConstructorDeletePageName } from './ConstructorPageModals.helpers'; +import CreatePageModal from './CreatePageModal'; type ConstructorPageModalsProps = { isCreatePageModalActive: boolean; diff --git a/frontend/src/components/Constructor/ConstructorRuntimeOverlays.helpers.test.ts b/frontend/src/components/Constructor/ConstructorRuntimeOverlays.helpers.test.ts index 679e300..28c59cf 100644 --- a/frontend/src/components/Constructor/ConstructorRuntimeOverlays.helpers.test.ts +++ b/frontend/src/components/Constructor/ConstructorRuntimeOverlays.helpers.test.ts @@ -1,11 +1,11 @@ import assert from 'node:assert/strict'; import test from 'node:test'; +import type { InfoPanelImage } from '../../types/infoPanel'; import { getConstructorInfoPanelDetailImage, shouldRenderConstructorImageDetailPanel, } from './ConstructorRuntimeOverlays.helpers'; -import type { InfoPanelImage } from '../../types/infoPanel'; const image = { id: 'image-1', url: 'assets/image.jpg' } as InfoPanelImage; diff --git a/frontend/src/components/Constructor/ConstructorRuntimeOverlays.helpers.ts b/frontend/src/components/Constructor/ConstructorRuntimeOverlays.helpers.ts index 4129039..19201d4 100644 --- a/frontend/src/components/Constructor/ConstructorRuntimeOverlays.helpers.ts +++ b/frontend/src/components/Constructor/ConstructorRuntimeOverlays.helpers.ts @@ -4,7 +4,7 @@ export const getConstructorInfoPanelDetailImage = ({ activeDetailImages, panelId, }: { - activeDetailImages: Record; + activeDetailImages: Record; panelId: string; }) => activeDetailImages[panelId]; @@ -12,6 +12,6 @@ export const shouldRenderConstructorImageDetailPanel = ({ image, isEditMode, }: { - image?: InfoPanelImage; + image?: InfoPanelImage | null; isEditMode: boolean; }) => Boolean(image) || isEditMode; diff --git a/frontend/src/components/Constructor/ConstructorRuntimeOverlays.tsx b/frontend/src/components/Constructor/ConstructorRuntimeOverlays.tsx index 6d92e9d..268fe1a 100644 --- a/frontend/src/components/Constructor/ConstructorRuntimeOverlays.tsx +++ b/frontend/src/components/Constructor/ConstructorRuntimeOverlays.tsx @@ -1,14 +1,14 @@ import { Fragment, type CSSProperties } from 'react'; -import GalleryCarouselOverlay from '../UiElements/GalleryCarouselOverlay'; -import ImageDetailPanel from '../UiElements/ImageDetailPanel'; -import InfoPanelOverlay from '../UiElements/InfoPanelOverlay'; import type { CanvasElement, GalleryCarouselMediaItem, } from '../../types/constructor'; import type { InfoPanelImage } from '../../types/infoPanel'; import type { ResolvedTransitionSettings } from '../../types/transition'; +import GalleryCarouselOverlay from '../UiElements/GalleryCarouselOverlay'; +import ImageDetailPanel from '../UiElements/ImageDetailPanel'; +import InfoPanelOverlay from '../UiElements/InfoPanelOverlay'; import { getConstructorInfoPanelDetailImage, shouldRenderConstructorImageDetailPanel, @@ -32,7 +32,7 @@ type ConstructorRuntimeOverlaysProps = { activeInfoPanelGalleryElement: CanvasElement | null; shouldShowInfoPanelOverlays: boolean; infoPanelElementsToRender: CanvasElement[]; - activeDetailImages: Record; + activeDetailImages: Record; isEditMode: boolean; letterboxStyles: CSSProperties; cssVars: CSSProperties; @@ -42,7 +42,7 @@ type ConstructorRuntimeOverlaysProps = { onCloseInfoPanelGallery: () => void; onCloseAllInfoPanels: () => void; onCloseInfoPanel: (panelId: string) => void; - onSetDetailImage: (panelId: string, image: InfoPanelImage) => void; + onSetDetailImage: (panelId: string, image: InfoPanelImage | null) => void; onCloseDetailImage: (panelId: string) => void; onOpenInfoPanelGallery: ( panelId: string, @@ -220,7 +220,7 @@ const ConstructorRuntimeOverlays = ({ }) && ( onCloseDetailImage(infoPanelElementToRender.id)} resolveUrl={resolveUrl} letterboxStyles={letterboxStyles} diff --git a/frontend/src/components/Constructor/ConstructorToolbar.helpers.test.ts b/frontend/src/components/Constructor/ConstructorToolbar.helpers.test.ts index 0e5be2a..b1e0b7c 100644 --- a/frontend/src/components/Constructor/ConstructorToolbar.helpers.test.ts +++ b/frontend/src/components/Constructor/ConstructorToolbar.helpers.test.ts @@ -1,12 +1,12 @@ import assert from 'node:assert/strict'; import test from 'node:test'; -import type { TourPage } from './types'; import { getCollapsedToolbarPageName, - getConstructorToolbarMaxWidth, getConstructorToolbarActionState, + getConstructorToolbarMaxWidth, sortToolbarPages, } from './ConstructorToolbar.helpers'; +import type { TourPage } from './types'; const makePage = (id: string, name: string, sortOrder?: number): TourPage => ({ diff --git a/frontend/src/components/Constructor/ConstructorToolbar.tsx b/frontend/src/components/Constructor/ConstructorToolbar.tsx index c33d944..3b6ebdd 100644 --- a/frontend/src/components/Constructor/ConstructorToolbar.tsx +++ b/frontend/src/components/Constructor/ConstructorToolbar.tsx @@ -5,19 +5,19 @@ * Glassmorphism styling with draggable positioning. */ -import { useState, useRef, useEffect, forwardRef } from 'react'; import { mdiDotsVertical } from '@mdi/js'; +import { forwardRef, useEffect, useRef, useState } from 'react'; import BaseIcon from '../BaseIcon'; -import InteractionModeToggle from './InteractionModeToggle'; -import ConstructorToolbarCollapsed from './ConstructorToolbarCollapsed'; -import ConstructorToolbarElementActions from './ConstructorToolbarElementActions'; -import ConstructorToolbarPageActions from './ConstructorToolbarPageActions'; -import ConstructorToolbarSaveControls from './ConstructorToolbarSaveControls'; import { getConstructorToolbarActionState, getConstructorToolbarMaxWidth, type ToolbarDropdown, } from './ConstructorToolbar.helpers'; +import ConstructorToolbarCollapsed from './ConstructorToolbarCollapsed'; +import ConstructorToolbarElementActions from './ConstructorToolbarElementActions'; +import ConstructorToolbarPageActions from './ConstructorToolbarPageActions'; +import ConstructorToolbarSaveControls from './ConstructorToolbarSaveControls'; +import InteractionModeToggle from './InteractionModeToggle'; import type { ConstructorToolbarProps } from './types'; const ConstructorToolbar = forwardRef( diff --git a/frontend/src/components/Constructor/ConstructorToolbarCollapsed.tsx b/frontend/src/components/Constructor/ConstructorToolbarCollapsed.tsx index ba100e2..f4e5e72 100644 --- a/frontend/src/components/Constructor/ConstructorToolbarCollapsed.tsx +++ b/frontend/src/components/Constructor/ConstructorToolbarCollapsed.tsx @@ -1,8 +1,8 @@ +import { mdiChevronRight, mdiDotsVertical } from '@mdi/js'; import type { ForwardedRef, MouseEvent } from 'react'; import BaseIcon from '../BaseIcon'; -import { mdiChevronRight, mdiDotsVertical } from '@mdi/js'; -import type { Position, TourPage } from './types'; import { getCollapsedToolbarPageName } from './ConstructorToolbar.helpers'; +import type { Position, TourPage } from './types'; interface Props { position: Position; diff --git a/frontend/src/components/Constructor/ConstructorToolbarElementActions.tsx b/frontend/src/components/Constructor/ConstructorToolbarElementActions.tsx index 067d989..927b0df 100644 --- a/frontend/src/components/Constructor/ConstructorToolbarElementActions.tsx +++ b/frontend/src/components/Constructor/ConstructorToolbarElementActions.tsx @@ -1,4 +1,3 @@ -import type { RefObject } from 'react'; import { mdiChevronDown, mdiContentDuplicate, @@ -12,10 +11,11 @@ import { mdiVideo, mdiViewCarousel, } from '@mdi/js'; +import type { RefObject } from 'react'; +import type { CanvasElementType } from '../../types/constructor'; import BaseIcon from '../BaseIcon'; import ClickOutside from '../ClickOutside'; import MenuActionButton from './MenuActionButton'; -import type { CanvasElementType } from '../../types/constructor'; import type { NavigationElementType } from './types'; interface Props { diff --git a/frontend/src/components/Constructor/ConstructorToolbarLayer.helpers.test.ts b/frontend/src/components/Constructor/ConstructorToolbarLayer.helpers.test.ts index 189f02c..8975e01 100644 --- a/frontend/src/components/Constructor/ConstructorToolbarLayer.helpers.test.ts +++ b/frontend/src/components/Constructor/ConstructorToolbarLayer.helpers.test.ts @@ -1,12 +1,12 @@ import assert from 'node:assert/strict'; import test from 'node:test'; +import type { TourPage } from '../../types/entities'; import { findConstructorToolbarPage, getConstructorExitHref, shouldShowConstructorToolbar, } from './ConstructorToolbarLayer.helpers'; -import type { TourPage } from '../../types/entities'; test('shouldShowConstructorToolbar requires pages and normal constructor mode', () => { assert.equal( diff --git a/frontend/src/components/Constructor/ConstructorToolbarLayer.tsx b/frontend/src/components/Constructor/ConstructorToolbarLayer.tsx index c1ded6a..2a4c072 100644 --- a/frontend/src/components/Constructor/ConstructorToolbarLayer.tsx +++ b/frontend/src/components/Constructor/ConstructorToolbarLayer.tsx @@ -1,17 +1,17 @@ import type { RefObject } from 'react'; +import type { NavigationElementType } from '../../context/ConstructorContext'; +import type { + CanvasElementType, + EditorMenuItem, +} from '../../types/constructor'; +import type { TourPage } from '../../types/entities'; import ConstructorToolbar from './ConstructorToolbar'; import { findConstructorToolbarPage, getConstructorExitHref, shouldShowConstructorToolbar, } from './ConstructorToolbarLayer.helpers'; -import type { - CanvasElementType, - EditorMenuItem, -} from '../../types/constructor'; -import type { NavigationElementType } from '../../context/ConstructorContext'; -import type { TourPage } from '../../types/entities'; import type { ConstructorInteractionMode, Position } from './types'; type ConstructorToolbarLayerProps = { @@ -19,7 +19,7 @@ type ConstructorToolbarLayerProps = { position: Position; isElementEditMode: boolean; pages: TourPage[]; - activePageId: string; + activePageId: string | null; projectId: string; isReorderingPages: boolean; isSaving: boolean; @@ -100,7 +100,7 @@ const ConstructorToolbarLayer = ({ position={position} onDragStart={onDragStart} pages={pages} - activePageId={activePageId} + activePageId={activePageId || ''} onPageChange={(pageId) => { const page = findConstructorToolbarPage(pages, pageId); if (page) onSwitchToPage(page); diff --git a/frontend/src/components/Constructor/ConstructorToolbarPageActions.tsx b/frontend/src/components/Constructor/ConstructorToolbarPageActions.tsx index 20be375..5ed0e7a 100644 --- a/frontend/src/components/Constructor/ConstructorToolbarPageActions.tsx +++ b/frontend/src/components/Constructor/ConstructorToolbarPageActions.tsx @@ -1,4 +1,3 @@ -import type { RefObject } from 'react'; import { mdiChevronDown, mdiChevronUp, @@ -10,11 +9,12 @@ import { mdiPlus, mdiVideo, } from '@mdi/js'; +import type { RefObject } from 'react'; +import type { EditorMenuItem } from '../../types/constructor'; import BaseIcon from '../BaseIcon'; import ClickOutside from '../ClickOutside'; import MenuActionButton from './MenuActionButton'; import PageSelector from './PageSelector'; -import type { EditorMenuItem } from '../../types/constructor'; import type { ConstructorToolbarProps } from './types'; interface Props { diff --git a/frontend/src/components/Constructor/ConstructorToolbarSaveControls.tsx b/frontend/src/components/Constructor/ConstructorToolbarSaveControls.tsx index 061118f..f1e349a 100644 --- a/frontend/src/components/Constructor/ConstructorToolbarSaveControls.tsx +++ b/frontend/src/components/Constructor/ConstructorToolbarSaveControls.tsx @@ -1,7 +1,7 @@ import { mdiChevronLeft, mdiExitToApp } from '@mdi/js'; +import dataFormatter from '../../helpers/dataFormatter'; import BaseButton from '../BaseButton'; import BaseIcon from '../BaseIcon'; -import dataFormatter from '../../helpers/dataFormatter'; interface Props { isSaving: boolean; diff --git a/frontend/src/components/Constructor/CreatePageModal.tsx b/frontend/src/components/Constructor/CreatePageModal.tsx index ad3e686..3a132fb 100644 --- a/frontend/src/components/Constructor/CreatePageModal.tsx +++ b/frontend/src/components/Constructor/CreatePageModal.tsx @@ -5,9 +5,9 @@ * Slug is auto-generated from the page name behind the scenes. */ -import React, { useState, useEffect, useMemo, useCallback } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { buildUniqueSlug, sanitizeSlug } from '../../lib/slugHelpers'; import CardBoxModal from '../CardBoxModal'; -import { sanitizeSlug, buildUniqueSlug } from '../../lib/slugHelpers'; interface CreatePageModalProps { /** Whether the modal is visible */ diff --git a/frontend/src/components/Constructor/ElementEditorCommonCssSection.tsx b/frontend/src/components/Constructor/ElementEditorCommonCssSection.tsx index 7ca18c2..fcd7e17 100644 --- a/frontend/src/components/Constructor/ElementEditorCommonCssSection.tsx +++ b/frontend/src/components/Constructor/ElementEditorCommonCssSection.tsx @@ -1,8 +1,8 @@ +import type { CanvasElement } from '../../types/constructor'; import { StyleSettingsSectionCompact, extractNumericValue, } from '../ElementSettings'; -import type { CanvasElement } from '../../types/constructor'; import { buildElementCssPatch } from './elementEditorPanel.helpers'; interface ElementEditorCommonCssSectionProps { diff --git a/frontend/src/components/Constructor/ElementEditorEffectsTab.tsx b/frontend/src/components/Constructor/ElementEditorEffectsTab.tsx index d0a83a8..5721eac 100644 --- a/frontend/src/components/Constructor/ElementEditorEffectsTab.tsx +++ b/frontend/src/components/Constructor/ElementEditorEffectsTab.tsx @@ -1,5 +1,5 @@ -import { EffectsSettingsSectionCompact } from '../ElementSettings'; import type { AssetOption, CanvasElement } from '../../types/constructor'; +import { EffectsSettingsSectionCompact } from '../ElementSettings'; interface ElementEditorEffectsTabProps { selectedElement: CanvasElement; diff --git a/frontend/src/components/Constructor/ElementEditorGalleryCssSection.tsx b/frontend/src/components/Constructor/ElementEditorGalleryCssSection.tsx index d37937e..0b73f08 100644 --- a/frontend/src/components/Constructor/ElementEditorGalleryCssSection.tsx +++ b/frontend/src/components/Constructor/ElementEditorGalleryCssSection.tsx @@ -1,5 +1,5 @@ -import { GallerySectionStyleInputs } from '../ElementSettings'; import type { CanvasElement } from '../../types/constructor'; +import { GallerySectionStyleInputs } from '../ElementSettings'; interface ElementEditorGalleryCssSectionProps { selectedElement: CanvasElement; diff --git a/frontend/src/components/Constructor/ElementEditorGeneralTab.tsx b/frontend/src/components/Constructor/ElementEditorGeneralTab.tsx index e2fd886..f4bd3b8 100644 --- a/frontend/src/components/Constructor/ElementEditorGeneralTab.tsx +++ b/frontend/src/components/Constructor/ElementEditorGeneralTab.tsx @@ -1,28 +1,3 @@ -import { - CommonSettingsSectionCompact, - DescriptionSettingsSectionCompact, - MediaSettingsSectionCompact, - GallerySettingsSectionCompact, - CarouselSettingsSectionCompact, - GalleryCarouselSettingsSectionCompact, - InfoPanelSettingsSectionCompact, -} from '../ElementSettings'; -import NavigationSettingsSectionCompact from '../ElementSettings/NavigationSettingsSectionCompact'; -import { - normalizeAppearDelaySec, - normalizeAppearDurationSec, -} from '../../lib/elementDefaults'; -import { - isNavigationElementType, - isDescriptionElementType, - isGalleryElementType, - isCarouselElementType, - isMediaElementType, - isVideoPlayerElementType, - isInfoPanelElementType, -} from '../../lib/elementTypeGuards'; -import type { AssetOption, CanvasElement } from '../../types/constructor'; -import type { TourPage } from '../../types/entities'; import type { CarouselSlideOperations, GalleryCardOperations, @@ -30,6 +5,31 @@ import type { InfoPanelSectionOperations, NavigationElementType, } from '../../context/ConstructorContext'; +import { + normalizeAppearDelaySec, + normalizeAppearDurationSec, +} from '../../lib/elementDefaults'; +import { + isCarouselElementType, + isDescriptionElementType, + isGalleryElementType, + isInfoPanelElementType, + isMediaElementType, + isNavigationElementType, + isVideoPlayerElementType, +} from '../../lib/elementTypeGuards'; +import type { AssetOption, CanvasElement } from '../../types/constructor'; +import type { TourPage } from '../../types/entities'; +import { + CarouselSettingsSectionCompact, + CommonSettingsSectionCompact, + DescriptionSettingsSectionCompact, + GalleryCarouselSettingsSectionCompact, + GallerySettingsSectionCompact, + InfoPanelSettingsSectionCompact, + MediaSettingsSectionCompact, +} from '../ElementSettings'; +import NavigationSettingsSectionCompact from '../ElementSettings/NavigationSettingsSectionCompact'; interface ElementEditorGeneralTabProps { selectedElement: CanvasElement; diff --git a/frontend/src/components/Constructor/ElementEditorPanel.tsx b/frontend/src/components/Constructor/ElementEditorPanel.tsx index f05e245..c57b57d 100644 --- a/frontend/src/components/Constructor/ElementEditorPanel.tsx +++ b/frontend/src/components/Constructor/ElementEditorPanel.tsx @@ -9,33 +9,31 @@ import React from 'react'; import { - useConstructorContext, - useConstructorElements, - useConstructorBackground, useConstructorAssets, + useConstructorBackground, useConstructorCollectionOps, useConstructorDuration, - useConstructorNavigation, useConstructorEditorTab, + useConstructorElements, useConstructorMenu, + useConstructorNavigation, } from '../../context/ConstructorContext'; +import { + isGalleryElementType, + isInfoPanelElementType, +} from '../../lib/elementTypeGuards'; +import type { SystemUiControlSettings } from '../../types/uiControls'; import { ElementSettingsTabsCompact, GallerySectionStyleInputs, InfoPanelStyleInputs, } from '../ElementSettings'; -import ElementEditorHeader from './ElementEditorHeader'; -import { - isGalleryElementType, - isCarouselElementType, - isInfoPanelElementType, -} from '../../lib/elementTypeGuards'; -import type { SystemUiControlSettings } from '../../types/uiControls'; import { ElementEditorBackgroundSettings } from './ElementEditorBackgroundSettings'; import { ElementEditorCommonCssSection } from './ElementEditorCommonCssSection'; import { ElementEditorEffectsTab } from './ElementEditorEffectsTab'; import { ElementEditorGalleryCssSection } from './ElementEditorGalleryCssSection'; import { ElementEditorGeneralTab } from './ElementEditorGeneralTab'; +import ElementEditorHeader from './ElementEditorHeader'; import { InfoPanelMediaStyleSection } from './InfoPanelMediaStyleSection'; import { InfoPanelTextStyleSection } from './InfoPanelTextStyleSection'; import { SystemControlSettingsEditor } from './SystemControlSettingsEditor'; @@ -74,7 +72,6 @@ export function ElementEditorPanel({ // Get state from context const { selectedElement, - selectedElementId, updateSelectedElement, removeSelectedElement, selectedSystemControl, diff --git a/frontend/src/components/Constructor/InfoPanelMediaStyleSection.tsx b/frontend/src/components/Constructor/InfoPanelMediaStyleSection.tsx index 41525ea..cc54994 100644 --- a/frontend/src/components/Constructor/InfoPanelMediaStyleSection.tsx +++ b/frontend/src/components/Constructor/InfoPanelMediaStyleSection.tsx @@ -1,5 +1,5 @@ -import type { CanvasElement } from '../../types/constructor'; import { FONT_OPTIONS } from '../../lib/fonts'; +import type { CanvasElement } from '../../types/constructor'; interface InfoPanelMediaStyleSectionProps { selectedElement: CanvasElement; diff --git a/frontend/src/components/Constructor/InfoPanelTextStyleSection.tsx b/frontend/src/components/Constructor/InfoPanelTextStyleSection.tsx index 75ba249..95ed08b 100644 --- a/frontend/src/components/Constructor/InfoPanelTextStyleSection.tsx +++ b/frontend/src/components/Constructor/InfoPanelTextStyleSection.tsx @@ -1,5 +1,5 @@ -import type { CanvasElement } from '../../types/constructor'; import { FONT_OPTIONS } from '../../lib/fonts'; +import type { CanvasElement } from '../../types/constructor'; type TextAlignValue = 'left' | 'center' | 'right'; diff --git a/frontend/src/components/Constructor/SystemControlSettingsEditor.tsx b/frontend/src/components/Constructor/SystemControlSettingsEditor.tsx index 9d3aa41..1c9c391 100644 --- a/frontend/src/components/Constructor/SystemControlSettingsEditor.tsx +++ b/frontend/src/components/Constructor/SystemControlSettingsEditor.tsx @@ -1,13 +1,15 @@ -import { - getSystemControlAnchorBounds, - type SystemUiControlSettings, -} from '../../types/uiControls'; -import type { AssetOption } from '../../types/constructor'; import { addFallbackAssetOption } from '../../lib/constructorHelpers'; import { opacityToPercentInput, percentInputToOpacityNumber, } from '../../lib/opacityPercent'; +import type { AssetOption } from '../../types/constructor'; +import { + DEFAULT_UI_CONTROL_SETTINGS, + getSystemControlAnchorBounds, + type SystemUiControlAnchor, + type SystemUiControlSettings, +} from '../../types/uiControls'; interface SystemControlSettingsEditorProps { settings: SystemUiControlSettings; @@ -61,9 +63,13 @@ export function SystemControlSettingsEditor({ canvasAspectRatio, onChange, }: SystemControlSettingsEditorProps) { + const effectiveSettings = { + ...DEFAULT_UI_CONTROL_SETTINGS.fullscreen, + ...settings, + }; const bounds = getSystemControlAnchorBounds( - settings.anchor, - settings.buttonSizePercent, + effectiveSettings.anchor, + effectiveSettings.buttonSizePercent, canvasAspectRatio, ); @@ -79,7 +85,7 @@ export function SystemControlSettingsEditor({ min={bounds.minX} max={bounds.maxX} className='w-full rounded border border-gray-300 px-2 py-1 text-xs' - value={settings.xPercent} + value={effectiveSettings.xPercent} onChange={(event) => onChange({ xPercent: clamp( @@ -100,7 +106,7 @@ export function SystemControlSettingsEditor({ min={bounds.minY} max={bounds.maxY} className='w-full rounded border border-gray-300 px-2 py-1 text-xs' - value={settings.yPercent} + value={effectiveSettings.yPercent} onChange={(event) => onChange({ yPercent: clamp( @@ -118,25 +124,24 @@ export function SystemControlSettingsEditor({ { const value = Number(event.target.value) || 0; if (key === 'buttonSizePercent') { const nextBounds = getSystemControlAnchorBounds( - settings.anchor, + effectiveSettings.anchor, value, canvasAspectRatio, ); @@ -251,12 +256,12 @@ export function SystemControlSettingsEditor({ onChange({ buttonSizePercent: value, xPercent: clamp( - settings.xPercent, + effectiveSettings.xPercent, nextBounds.minX, nextBounds.maxX, ), yPercent: clamp( - settings.yPercent, + effectiveSettings.yPercent, nextBounds.minY, nextBounds.maxY, ), diff --git a/frontend/src/components/Constructor/TransitionPreviewOverlay.tsx b/frontend/src/components/Constructor/TransitionPreviewOverlay.tsx index db78799..3294927 100644 --- a/frontend/src/components/Constructor/TransitionPreviewOverlay.tsx +++ b/frontend/src/components/Constructor/TransitionPreviewOverlay.tsx @@ -14,7 +14,7 @@ * - Then hides instantly (no CSS transition) since last video frame = new bg */ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import CanvasLoadingSpinner from '../CanvasLoadingSpinner'; interface TransitionPreviewOverlayProps { diff --git a/frontend/src/components/Constructor/constructorContextValue.helpers.test.ts b/frontend/src/components/Constructor/constructorContextValue.helpers.test.ts index a7c5668..5cb35fa 100644 --- a/frontend/src/components/Constructor/constructorContextValue.helpers.test.ts +++ b/frontend/src/components/Constructor/constructorContextValue.helpers.test.ts @@ -1,11 +1,11 @@ import assert from 'node:assert/strict'; import test from 'node:test'; +import type { CanvasElement } from '../../types/constructor'; import { removeConstructorContextElementById, updateConstructorContextElementById, } from './constructorContextValue.helpers'; -import type { CanvasElement } from '../../types/constructor'; const elements = [ { id: 'first', type: 'description', label: 'First', xPercent: 10 }, diff --git a/frontend/src/components/Constructor/constructorDerivedState.helpers.ts b/frontend/src/components/Constructor/constructorDerivedState.helpers.ts index c0fa165..a3ce716 100644 --- a/frontend/src/components/Constructor/constructorDerivedState.helpers.ts +++ b/frontend/src/components/Constructor/constructorDerivedState.helpers.ts @@ -1,7 +1,7 @@ -import type { CanvasElement } from '../../types/constructor'; -import type { TourPage, User } from '../../types/entities'; import { hasPermission } from '../../helpers/userPermissions'; import { presentationHasAudio } from '../../lib/presentationAudio'; +import type { CanvasElement } from '../../types/constructor'; +import type { TourPage, User } from '../../types/entities'; export const getConstructorPageElementsListHref = (projectId: string) => projectId diff --git a/frontend/src/components/Constructor/constructorGalleryCarousel.helpers.test.ts b/frontend/src/components/Constructor/constructorGalleryCarousel.helpers.test.ts index d3d1ddc..63e8fcb 100644 --- a/frontend/src/components/Constructor/constructorGalleryCarousel.helpers.test.ts +++ b/frontend/src/components/Constructor/constructorGalleryCarousel.helpers.test.ts @@ -1,12 +1,12 @@ import assert from 'node:assert/strict'; import test from 'node:test'; +import type { CanvasElement } from '../../types/constructor'; import { getCarouselButtonPositionPatch, getGalleryCarouselButtonPositionPatch, updateElementById, } from './constructorGalleryCarousel.helpers'; -import type { CanvasElement } from '../../types/constructor'; test('getGalleryCarouselButtonPositionPatch maps overlay buttons to their coordinate fields', () => { assert.deepEqual(getGalleryCarouselButtonPositionPatch('prev', 10, 20), { diff --git a/frontend/src/components/Constructor/constructorMediaDuration.helpers.test.ts b/frontend/src/components/Constructor/constructorMediaDuration.helpers.test.ts index f91a2ec..8a12955 100644 --- a/frontend/src/components/Constructor/constructorMediaDuration.helpers.test.ts +++ b/frontend/src/components/Constructor/constructorMediaDuration.helpers.test.ts @@ -1,11 +1,11 @@ import assert from 'node:assert/strict'; import test from 'node:test'; +import type { CanvasElement } from '../../types/constructor'; import { applyNavigationTransitionDurations, getConstructorDurationNotes, } from './constructorMediaDuration.helpers'; -import type { CanvasElement } from '../../types/constructor'; test('getConstructorDurationNotes maps media notes into editor shape', () => { assert.deepEqual( diff --git a/frontend/src/components/Constructor/constructorMediaPreload.helpers.ts b/frontend/src/components/Constructor/constructorMediaPreload.helpers.ts index 011e915..777359a 100644 --- a/frontend/src/components/Constructor/constructorMediaPreload.helpers.ts +++ b/frontend/src/components/Constructor/constructorMediaPreload.helpers.ts @@ -5,7 +5,7 @@ export const resolveConstructorPreloadedUrl = ({ }: { url: string | undefined; getReadyBlobUrl: (url: string) => string | null; - resolveAssetUrl: (url: string) => string; + resolveAssetUrl: (url: string | undefined) => string; }) => { if (!url) return ''; diff --git a/frontend/src/components/Constructor/constructorPage.helpers.test.ts b/frontend/src/components/Constructor/constructorPage.helpers.test.ts index 0aeefd3..cb2b0f3 100644 --- a/frontend/src/components/Constructor/constructorPage.helpers.test.ts +++ b/frontend/src/components/Constructor/constructorPage.helpers.test.ts @@ -1,14 +1,17 @@ import assert from 'node:assert/strict'; import test from 'node:test'; +import type { CanvasElement } from '../../types/constructor'; +import type { TourPage } from '../../types/entities'; +import type { TransitionPreviewState } from '../../types/presentation'; import { areConstructorElementIconsReady, - buildConstructorTransitionPlaybackConfig, buildConstructorIconPreloadTargets, - getDeleteFallbackPageId, + buildConstructorTransitionPlaybackConfig, getConstructorBackgroundSources, getConstructorEditorTitle, getConstructorNavigationErrorMessage, + getDeleteFallbackPageId, getExistingPageSlugs, getFirstQueryParam, getLastProjectSaveAt, @@ -18,9 +21,6 @@ import { shouldRenderConstructorCanvasElement, sortTourPagesForDisplay, } from './constructorPage.helpers'; -import type { TourPage } from '../../types/entities'; -import type { CanvasElement } from '../../types/constructor'; -import type { TransitionPreviewState } from '../../types/presentation'; const page = ( id: string, diff --git a/frontend/src/components/Constructor/constructorPage.helpers.ts b/frontend/src/components/Constructor/constructorPage.helpers.ts index 86df9f1..4f4ed57 100644 --- a/frontend/src/components/Constructor/constructorPage.helpers.ts +++ b/frontend/src/components/Constructor/constructorPage.helpers.ts @@ -1,12 +1,12 @@ -import type { TourPage } from '../../types/entities'; +import type { TransitionConfig } from '../../hooks/useTransitionPlayback'; import type { CanvasElement, CanvasElementType, EditorMenuItem, } from '../../types/constructor'; +import type { TourPage } from '../../types/entities'; import type { TransitionPreviewState } from '../../types/presentation'; import type { SystemUiControlType } from '../../types/uiControls'; -import type { TransitionConfig } from '../../hooks/useTransitionPlayback'; export type ConstructorInteractionMode = 'edit' | 'interact'; @@ -125,7 +125,7 @@ export const getDeleteFallbackPageId = ({ export const buildConstructorIconPreloadTargets = ( elements: CanvasElement[], - resolveUrl: (url: string) => string, + resolveUrl: (url: string | undefined) => string, ) => { const urls = elements .filter( @@ -146,7 +146,7 @@ export const isConstructorElementIconReady = ({ }: { element: CanvasElement; preloadedIconUrlMap: Record; - resolveUrl: (url: string) => string; + resolveUrl: (url: string | undefined) => string; }) => { const isPreloadableIconElement = ICON_PRELOADABLE_TYPES.includes(element.type) && Boolean(element.iconUrl); @@ -166,7 +166,7 @@ export const areConstructorElementIconsReady = ({ }: { elements: CanvasElement[]; preloadedIconUrlMap: Record; - resolveUrl: (url: string) => string; + resolveUrl: (url: string | undefined) => string; }) => elements.every((element) => isConstructorElementIconReady({ @@ -214,7 +214,7 @@ export const getConstructorBackgroundSources = ({ embedUrl: string; audioUrl: string; }; - resolveUrl: (url: string) => string; + resolveUrl: (url: string | undefined) => string; }) => ({ imageUrl: isEditMode && edit.imageUrl @@ -243,7 +243,7 @@ export const buildConstructorTransitionPlaybackConfig = ({ transitionPreview: TransitionPreviewState | null; isVideoElementReady: boolean; pendingNavigationPageId: string; - resolveUrl: (url: string) => string; + resolveUrl: (url: string | undefined) => string; }): TransitionConfig | null => { if (!transitionPreview || !isVideoElementReady) { return null; diff --git a/frontend/src/components/Constructor/constructorPageManagement.actions.ts b/frontend/src/components/Constructor/constructorPageManagement.actions.ts index 37b269b..048e229 100644 --- a/frontend/src/components/Constructor/constructorPageManagement.actions.ts +++ b/frontend/src/components/Constructor/constructorPageManagement.actions.ts @@ -1,6 +1,6 @@ +import { buildUniqueSlug } from '../../lib/slugHelpers'; import type { EditorMenuItem } from '../../types/constructor'; import type { TourPage } from '../../types/entities'; -import { buildUniqueSlug } from '../../lib/slugHelpers'; import { getDeleteFallbackPageId, getReorderedPageIds, diff --git a/frontend/src/components/Constructor/constructorPageNavigationLifecycle.helpers.test.ts b/frontend/src/components/Constructor/constructorPageNavigationLifecycle.helpers.test.ts index a5dd92e..d8ba768 100644 --- a/frontend/src/components/Constructor/constructorPageNavigationLifecycle.helpers.test.ts +++ b/frontend/src/components/Constructor/constructorPageNavigationLifecycle.helpers.test.ts @@ -1,13 +1,13 @@ import assert from 'node:assert/strict'; import test from 'node:test'; +import type { CanvasElement } from '../../types/constructor'; +import type { TourPage } from '../../types/entities'; import { normalizeForcedNavigationElementTypes, shouldInitializeConstructorPageBackground, toConstructorNavigablePage, } from './constructorPageNavigationLifecycle.helpers'; -import type { CanvasElement } from '../../types/constructor'; -import type { TourPage } from '../../types/entities'; test('toConstructorNavigablePage keeps only navigation background fields', () => { assert.deepEqual( diff --git a/frontend/src/components/Constructor/constructorPageNavigationLifecycle.helpers.ts b/frontend/src/components/Constructor/constructorPageNavigationLifecycle.helpers.ts index 63f1687..1785f3f 100644 --- a/frontend/src/components/Constructor/constructorPageNavigationLifecycle.helpers.ts +++ b/frontend/src/components/Constructor/constructorPageNavigationLifecycle.helpers.ts @@ -1,8 +1,8 @@ +import type { NavigationElementType } from '../../context/ConstructorContext'; +import type { NavigablePage } from '../../hooks/usePageNavigationState'; import { isNavigationElementType } from '../../lib/elementTypeGuards'; import type { CanvasElement } from '../../types/constructor'; import type { TourPage } from '../../types/entities'; -import type { NavigationElementType } from '../../context/ConstructorContext'; -import type { NavigablePage } from '../../hooks/usePageNavigationState'; export const toConstructorNavigablePage = ( page: TourPage | null, diff --git a/frontend/src/components/Constructor/constructorSchema.helpers.ts b/frontend/src/components/Constructor/constructorSchema.helpers.ts index dd45d78..1983c0d 100644 --- a/frontend/src/components/Constructor/constructorSchema.helpers.ts +++ b/frontend/src/components/Constructor/constructorSchema.helpers.ts @@ -1,10 +1,10 @@ +import { getNavigationButtonKind } from '../../lib/elementDefaultConstants'; import { mergeElementWithDefaults, normalizeAppearDelaySec, normalizeAppearDurationSec, } from '../../lib/elementDefaults'; import { clamp, createLocalId } from '../../lib/elementInstance.helpers'; -import { getNavigationButtonKind } from '../../lib/elementDefaultConstants'; import { isNavigationElementType, isVideoPlayerElementType, diff --git a/frontend/src/components/Constructor/constructorSystemControls.helpers.test.ts b/frontend/src/components/Constructor/constructorSystemControls.helpers.test.ts index b580658..fb62b25 100644 --- a/frontend/src/components/Constructor/constructorSystemControls.helpers.test.ts +++ b/frontend/src/components/Constructor/constructorSystemControls.helpers.test.ts @@ -1,14 +1,14 @@ import assert from 'node:assert/strict'; import test from 'node:test'; -import { - buildUpdatedSystemControlSettings, - clampConstructorSystemControlPosition, -} from './constructorSystemControls.helpers'; import { DEFAULT_UI_CONTROL_SETTINGS, type ResolvedUiControlsSettings, } from '../../types/uiControls'; +import { + buildUpdatedSystemControlSettings, + clampConstructorSystemControlPosition, +} from './constructorSystemControls.helpers'; test('buildUpdatedSystemControlSettings merges a control patch without dropping existing controls', () => { const next = buildUpdatedSystemControlSettings({ diff --git a/frontend/src/components/Constructor/constructorTransitionPreviewPlayback.helpers.test.ts b/frontend/src/components/Constructor/constructorTransitionPreviewPlayback.helpers.test.ts index d58b997..b9e59ce 100644 --- a/frontend/src/components/Constructor/constructorTransitionPreviewPlayback.helpers.test.ts +++ b/frontend/src/components/Constructor/constructorTransitionPreviewPlayback.helpers.test.ts @@ -1,11 +1,11 @@ import assert from 'node:assert/strict'; import test from 'node:test'; +import type { TransitionPreviewState } from '../../types/presentation'; import { getConstructorTransitionPreviewBuffering, shouldCloseConstructorTransitionPreview, } from './constructorTransitionPreviewPlayback.helpers'; -import type { TransitionPreviewState } from '../../types/presentation'; const preview = { videoUrl: 'assets/transition.mp4', diff --git a/frontend/src/components/Constructor/types.ts b/frontend/src/components/Constructor/types.ts index c6abbd1..a2cf865 100644 --- a/frontend/src/components/Constructor/types.ts +++ b/frontend/src/components/Constructor/types.ts @@ -5,19 +5,19 @@ */ import type { + AssetOption, CanvasElement, CanvasElementType, - ConstructorAsset, - AssetOption, - GalleryCard, CarouselSlide, - NavigationButtonKind, + ConstructorAsset, EditorMenuItem, EditorTab, + GalleryCard, + NavigationButtonKind, } from '../../types/constructor'; import type { - PageBackgroundVideoSettings, PageBackgroundAudioSettings, + PageBackgroundVideoSettings, } from '../../types/pageBackground'; /** @@ -313,11 +313,11 @@ export interface TransitionPreviewOverlayProps { // Re-export types from constructor.ts export type { + AssetOption, CanvasElement, CanvasElementType, - ConstructorAsset, - AssetOption, - GalleryCard, CarouselSlide, + ConstructorAsset, + GalleryCard, NavigationButtonKind, }; diff --git a/frontend/src/components/Constructor/useActiveConstructorPageHydration.ts b/frontend/src/components/Constructor/useActiveConstructorPageHydration.ts index 746e56b..1b8b639 100644 --- a/frontend/src/components/Constructor/useActiveConstructorPageHydration.ts +++ b/frontend/src/components/Constructor/useActiveConstructorPageHydration.ts @@ -5,6 +5,7 @@ import { useEffect, useRef, } from 'react'; +import { ELEMENT_TYPE_LABELS } from '../../lib/elementDefaultConstants'; import { parseJsonObject } from '../../lib/parseJson'; import type { CanvasElement } from '../../types/constructor'; import type { TourPage } from '../../types/entities'; @@ -18,7 +19,6 @@ import { type ElementDefaultsByType, type RawConstructorSchema, } from './constructorSchema.helpers'; -import { ELEMENT_TYPE_LABELS } from '../../lib/elementDefaultConstants'; export function useActiveConstructorPageHydration({ activePage, diff --git a/frontend/src/components/Constructor/useConstructorAuthRedirect.ts b/frontend/src/components/Constructor/useConstructorAuthRedirect.ts index ddba42a..6994fd0 100644 --- a/frontend/src/components/Constructor/useConstructorAuthRedirect.ts +++ b/frontend/src/components/Constructor/useConstructorAuthRedirect.ts @@ -1,5 +1,5 @@ -import { useEffect } from 'react'; import type { NextRouter } from 'next/router'; +import { useEffect } from 'react'; export function useConstructorAuthRedirect({ router, diff --git a/frontend/src/components/Constructor/useConstructorCanvasInteractions.ts b/frontend/src/components/Constructor/useConstructorCanvasInteractions.ts index 51c8961..6b3aa9c 100644 --- a/frontend/src/components/Constructor/useConstructorCanvasInteractions.ts +++ b/frontend/src/components/Constructor/useConstructorCanvasInteractions.ts @@ -1,26 +1,26 @@ -import { flushSync } from 'react-dom'; import { useCallback, type MouseEvent, type PointerEvent } from 'react'; -import type { CanvasElement, EditorMenuItem } from '../../types/constructor'; -import type { TourPage } from '../../types/entities'; -import type { NavigationContext } from '../../lib/navigationHelpers'; -import type { - SystemUiControlSettings, - SystemUiControlType, -} from '../../types/uiControls'; -import type { ElementTransitionSettings } from '../../types/transition'; +import { flushSync } from 'react-dom'; import type { NavigationElementType } from '../../context/ConstructorContext'; +import { resolveAssetPlaybackUrl } from '../../lib/assetUrl'; +import { isElementFlagEnabled } from '../../lib/elementFlags'; +import { isNavigationElementType } from '../../lib/elementTypeGuards'; +import { logger } from '../../lib/logger'; +import type { NavigationContext } from '../../lib/navigationHelpers'; import { getNavigationDirection, hasPlayableTransition, isBackNavigation, resolveNavigationTarget, } from '../../lib/navigationHelpers'; -import { isNavigationElementType } from '../../lib/elementTypeGuards'; -import { isElementFlagEnabled } from '../../lib/elementFlags'; -import { resolveAssetPlaybackUrl } from '../../lib/assetUrl'; import { downloadManager } from '../../lib/offline/DownloadManager'; -import { logger } from '../../lib/logger'; +import type { CanvasElement, EditorMenuItem } from '../../types/constructor'; +import type { TourPage } from '../../types/entities'; +import type { ElementTransitionSettings } from '../../types/transition'; import { extractElementTransitionSettings } from '../../types/transition'; +import type { + ResolvedUiControlsSettings, + SystemUiControlType, +} from '../../types/uiControls'; import { getConstructorNavigationErrorMessage } from './constructorPage.helpers'; type ConstructorCanvasInteractionsOptions = { @@ -35,10 +35,7 @@ type ConstructorCanvasInteractionsOptions = { downlink?: number; rtt?: number; }; - resolvedUiControlsSettings: Record< - SystemUiControlType, - SystemUiControlSettings - >; + resolvedUiControlsSettings: ResolvedUiControlsSettings; getNavigationContext: () => NavigationContext; getReadyTransitionBlobUrl: (url: string) => string | null; selectElementForEdit: (elementId: string) => void; diff --git a/frontend/src/components/Constructor/useConstructorDerivedState.ts b/frontend/src/components/Constructor/useConstructorDerivedState.ts index e261e6d..1560e1f 100644 --- a/frontend/src/components/Constructor/useConstructorDerivedState.ts +++ b/frontend/src/components/Constructor/useConstructorDerivedState.ts @@ -2,10 +2,6 @@ import { useMemo } from 'react'; import type { CanvasElement } from '../../types/constructor'; import type { TourPage, User } from '../../types/entities'; -import { - getExistingPageSlugs, - getLastProjectSaveAt, -} from './constructorPage.helpers'; import { canCurrentUserDeleteConstructorPage, getConstructorPageElementsListHref, @@ -13,6 +9,10 @@ import { hasConstructorFullWidthCarousel, hasConstructorPresentationAudio, } from './constructorDerivedState.helpers'; +import { + getExistingPageSlugs, + getLastProjectSaveAt, +} from './constructorPage.helpers'; type UseConstructorDerivedStateOptions = { projectId: string; diff --git a/frontend/src/components/Constructor/useConstructorGalleryCarousel.ts b/frontend/src/components/Constructor/useConstructorGalleryCarousel.ts index c481f4c..33e593c 100644 --- a/frontend/src/components/Constructor/useConstructorGalleryCarousel.ts +++ b/frontend/src/components/Constructor/useConstructorGalleryCarousel.ts @@ -6,6 +6,7 @@ import { type SetStateAction, } from 'react'; +import type { CanvasElement } from '../../types/constructor'; import type { ActiveConstructorGalleryCarousel } from './ConstructorRuntimeOverlays'; import { getCarouselButtonPositionPatch, @@ -14,7 +15,6 @@ import { type CarouselButton, type GalleryCarouselButton, } from './constructorGalleryCarousel.helpers'; -import type { CanvasElement } from '../../types/constructor'; export const useConstructorGalleryCarousel = ({ elements, diff --git a/frontend/src/components/Constructor/useConstructorInfoPanels.ts b/frontend/src/components/Constructor/useConstructorInfoPanels.ts index 2799dd2..7ee99f5 100644 --- a/frontend/src/components/Constructor/useConstructorInfoPanels.ts +++ b/frontend/src/components/Constructor/useConstructorInfoPanels.ts @@ -1,9 +1,4 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { - buildInfoPanelGalleryState, - getExternalHref, - getInfoPanelBackgroundSources, -} from '../UiElements/infoPanelMedia.helpers'; import { findDefaultOpenInfoPanelElements } from '../../lib/elementFlags'; import { isInfoPanelElementType } from '../../lib/elementTypeGuards'; import type { @@ -11,8 +6,13 @@ import type { EditorMenuItem, GalleryCarouselMediaItem, } from '../../types/constructor'; -import type { InfoPanelImage } from '../../types/infoPanel'; import type { TourPage } from '../../types/entities'; +import type { InfoPanelImage } from '../../types/infoPanel'; +import { + buildInfoPanelGalleryState, + getExternalHref, + getInfoPanelBackgroundSources, +} from '../UiElements/infoPanelMedia.helpers'; interface UseConstructorInfoPanelsOptions { elements: CanvasElement[]; @@ -26,7 +26,7 @@ interface UseConstructorInfoPanelsOptions { setSelectedMenuItem: (item: EditorMenuItem) => void; setErrorMessage: (message: string) => void; currentAudioUrl: string; - resolveAssetUrl: (url: string) => string; + resolveAssetUrl: (url: string | undefined) => string; setBackgroundDirectly: ( imageUrl?: string, videoUrl?: string, @@ -53,7 +53,7 @@ export function useConstructorInfoPanels({ const defaultInfoPanelPageIdRef = useRef(null); const [activeInfoPanelIds, setActiveInfoPanelIds] = useState([]); const [activeDetailImages, setActiveDetailImages] = useState< - Record + Record >({}); const [activeInfoPanelGallery, setActiveInfoPanelGallery] = useState<{ panelId: string; @@ -117,7 +117,7 @@ export function useConstructorInfoPanels({ }, []); const setDetailImage = useCallback( - (panelId: string, image: InfoPanelImage) => { + (panelId: string, image: InfoPanelImage | null) => { setActiveDetailImages((current) => ({ ...current, [panelId]: image, diff --git a/frontend/src/components/Constructor/useConstructorMediaPreloadState.ts b/frontend/src/components/Constructor/useConstructorMediaPreloadState.ts index fbc3109..1ae8bd1 100644 --- a/frontend/src/components/Constructor/useConstructorMediaPreloadState.ts +++ b/frontend/src/components/Constructor/useConstructorMediaPreloadState.ts @@ -3,12 +3,12 @@ import { useCallback, useMemo } from 'react'; import { useIconPreload } from '../../hooks/useIconPreload'; import type { PreloadCacheProvider } from '../../hooks/video'; import type { CanvasElement } from '../../types/constructor'; +import { resolveConstructorPreloadedUrl } from './constructorMediaPreload.helpers'; import { areConstructorElementIconsReady, buildConstructorIconPreloadTargets, getConstructorBackgroundSources, } from './constructorPage.helpers'; -import { resolveConstructorPreloadedUrl } from './constructorMediaPreload.helpers'; type ConstructorPreloadOrchestrator = { getReadyBlob: (url: string) => Blob | null; @@ -22,7 +22,7 @@ type UseConstructorMediaPreloadStateOptions = { isLoading: boolean; isEditMode: boolean; preloadOrchestrator: ConstructorPreloadOrchestrator; - resolveAssetUrl: (url: string) => string; + resolveAssetUrl: (url: string | undefined) => string; editBackground: { imageUrl?: string; videoUrl?: string; @@ -81,11 +81,7 @@ export function useConstructorMediaPreloadState({ getReadyBlobUrl: preloadOrchestrator.getReadyBlobUrl, resolveAssetUrl, }), - [ - preloadOrchestrator.getReadyBlobUrl, - preloadOrchestrator.readyUrlsVersion, - resolveAssetUrl, - ], + [preloadOrchestrator.getReadyBlobUrl, resolveAssetUrl], ); const backgroundSources = useMemo( diff --git a/frontend/src/components/Constructor/useConstructorPageManagement.ts b/frontend/src/components/Constructor/useConstructorPageManagement.ts index 10081ba..86aac23 100644 --- a/frontend/src/components/Constructor/useConstructorPageManagement.ts +++ b/frontend/src/components/Constructor/useConstructorPageManagement.ts @@ -1,9 +1,9 @@ -import { useCallback, useState } from 'react'; import axios from 'axios'; -import type { EditorMenuItem } from '../../types/constructor'; -import type { TourPage } from '../../types/entities'; +import { useCallback, useState } from 'react'; import { logger } from '../../lib/logger'; import { queryClient, queryKeys } from '../../lib/queryClient'; +import type { EditorMenuItem } from '../../types/constructor'; +import type { TourPage } from '../../types/entities'; import { deleteConstructorPage, duplicateConstructorPage, @@ -78,7 +78,6 @@ export function useConstructorPageManagement({ }, [ activePage, - activePage?.environment, activePageId, handleReload, isReorderingPages, diff --git a/frontend/src/components/Constructor/useConstructorPageNavigationLifecycle.ts b/frontend/src/components/Constructor/useConstructorPageNavigationLifecycle.ts index f016bc5..7f4367b 100644 --- a/frontend/src/components/Constructor/useConstructorPageNavigationLifecycle.ts +++ b/frontend/src/components/Constructor/useConstructorPageNavigationLifecycle.ts @@ -6,10 +6,10 @@ import { type SetStateAction, } from 'react'; +import type { NavigationElementType } from '../../context/ConstructorContext'; import type { UsePageNavigationStateResult } from '../../hooks/usePageNavigationState'; import type { CanvasElement } from '../../types/constructor'; import type { TourPage } from '../../types/entities'; -import type { NavigationElementType } from '../../context/ConstructorContext'; import { normalizeForcedNavigationElementTypes, shouldInitializeConstructorPageBackground, diff --git a/frontend/src/components/Constructor/useConstructorPageWorkflow.ts b/frontend/src/components/Constructor/useConstructorPageWorkflow.ts index cf37818..3fbf385 100644 --- a/frontend/src/components/Constructor/useConstructorPageWorkflow.ts +++ b/frontend/src/components/Constructor/useConstructorPageWorkflow.ts @@ -6,8 +6,8 @@ import type { CanvasElement, EditorMenuItem } from '../../types/constructor'; import type { TourPage } from '../../types/entities'; import type { PageBackgroundState } from '../../types/pageBackground'; import type { UiControlsSettings } from '../../types/uiControls'; -import { useConstructorPageManagement } from './useConstructorPageManagement'; import { shouldRestoreConstructorPageAfterReload } from './constructorPageWorkflow.helpers'; +import { useConstructorPageManagement } from './useConstructorPageManagement'; type ConstructorWorkflowProject = { id?: string; @@ -21,7 +21,7 @@ type UseConstructorPageWorkflowOptions = { project?: ConstructorWorkflowProject | null; pages: TourPage[]; activePage: TourPage | null; - activePageId: string; + activePageId: string | null; elements: CanvasElement[]; getElements: () => CanvasElement[]; pageBackground: PageBackgroundState; @@ -65,7 +65,9 @@ export function useConstructorPageWorkflow({ pages, }) ) { - setActivePageId(currentPageId); + if (currentPageId) { + setActivePageId(currentPageId); + } } }, [activePageId, pages, refetchData, setActivePageId]); @@ -83,7 +85,7 @@ export function useConstructorPageWorkflow({ project, pages, activePage, - activePageId, + activePageId: activePageId || '', elements, getElements, pageBackground, @@ -99,7 +101,7 @@ export function useConstructorPageWorkflow({ projectId, pages, activePage, - activePageId, + activePageId: activePageId || '', existingSlugs, refetchData, handleReload, diff --git a/frontend/src/components/Constructor/useConstructorSettingsFetch.ts b/frontend/src/components/Constructor/useConstructorSettingsFetch.ts index 56fa4db..f98e86d 100644 --- a/frontend/src/components/Constructor/useConstructorSettingsFetch.ts +++ b/frontend/src/components/Constructor/useConstructorSettingsFetch.ts @@ -1,9 +1,9 @@ import { useEffect } from 'react'; -import type { AppDispatch } from '../../stores/store'; import { fetch as fetchGlobalTransitionDefaults } from '../../stores/global_transition_defaults/globalTransitionDefaultsSlice'; import { fetch as fetchGlobalUiControlDefaults } from '../../stores/global_ui_control_defaults/globalUiControlDefaultsSlice'; import { fetchByProjectAndEnv as fetchProjectTransitionSettings } from '../../stores/project_transition_settings/projectTransitionSettingsSlice'; import { fetchByProjectAndEnv as fetchProjectUiControlSettings } from '../../stores/project_ui_control_settings/projectUiControlSettingsSlice'; +import type { AppDispatch } from '../../stores/store'; interface UseConstructorSettingsFetchOptions { dispatch: AppDispatch; diff --git a/frontend/src/components/Constructor/useConstructorTransitionPreviewPlayback.ts b/frontend/src/components/Constructor/useConstructorTransitionPreviewPlayback.ts index a9a97f9..711bbd7 100644 --- a/frontend/src/components/Constructor/useConstructorTransitionPreviewPlayback.ts +++ b/frontend/src/components/Constructor/useConstructorTransitionPreviewPlayback.ts @@ -48,7 +48,7 @@ export const useConstructorTransitionPreviewPlayback = ({ resetNavigationToIdle: () => void; onTransitionEnded: () => void; onVideoBufferStateChange: (isBuffering: boolean) => void; - resolveUrl: (url: string) => string; + resolveUrl: (url: string | undefined) => string; }) => { const transitionVideoRef = useRef(null); const [isTransitionVideoElementReady, setIsTransitionVideoElementReady] = diff --git a/frontend/src/components/DataGrid/configBuilderFactory.tsx b/frontend/src/components/DataGrid/configBuilderFactory.tsx index d7cceee..0214e59 100644 --- a/frontend/src/components/DataGrid/configBuilderFactory.tsx +++ b/frontend/src/components/DataGrid/configBuilderFactory.tsx @@ -1,15 +1,15 @@ -import React from 'react'; -import axios from 'axios'; import { GridColDef, GridRenderCellParams, GridSingleSelectColDef, } from '@mui/x-data-grid'; +import axios from 'axios'; +import React from 'react'; import dataFormatter from '../../helpers/dataFormatter'; -import DataGridMultiSelect from '../DataGridMultiSelect'; -import ListActionsPopover from '../ListActionsPopover'; import { hasPermission } from '../../helpers/userPermissions'; import { logger } from '../../lib/logger'; +import DataGridMultiSelect from '../DataGridMultiSelect'; +import ListActionsPopover from '../ListActionsPopover'; export interface ColumnMetadata { field: string; @@ -163,9 +163,14 @@ function buildColumn( singleSelectColumn.type = 'singleSelect'; singleSelectColumn.sortable = false; - singleSelectColumn.getOptionValue = (value: { id?: string }) => value?.id; - singleSelectColumn.getOptionLabel = (value: { label?: string }) => - value?.label; + singleSelectColumn.getOptionValue = (value: unknown) => + typeof value === 'object' && value !== null && 'id' in value + ? String(value.id ?? '') + : String(value ?? ''); + singleSelectColumn.getOptionLabel = (value: unknown) => + typeof value === 'object' && value !== null && 'label' in value + ? String(value.label ?? '') + : String(value ?? ''); singleSelectColumn.valueOptions = valueOptions; singleSelectColumn.valueGetter = ( value: { id?: string; label?: string; name?: string } | string | null, diff --git a/frontend/src/components/DataGridMultiSelect.tsx b/frontend/src/components/DataGridMultiSelect.tsx index 561eac7..a849de6 100644 --- a/frontend/src/components/DataGridMultiSelect.tsx +++ b/frontend/src/components/DataGridMultiSelect.tsx @@ -1,19 +1,26 @@ +import { MenuItem, Select, type SelectChangeEvent } from '@mui/material'; import { GridRenderEditCellParams, useGridApiContext } from '@mui/x-data-grid'; -import React, { useEffect, useState } from 'react'; import axios from 'axios'; -import { MenuItem, Select } from '@mui/material'; +import { useEffect, useState } from 'react'; interface Props { entityName: string; } +interface SelectOption { + id: string; + label: string; +} + const DataGridMultiSelect = (props: GridRenderEditCellParams & Props) => { const { id, value, field, entityName } = props; const apiRef = useGridApiContext(); - const [options, setOptions] = useState([]); + const [options, setOptions] = useState([]); async function callApi(entityName: string) { - const data = await axios(`/${entityName}/autocomplete?limit=50`); + const data = await axios.get( + `/${entityName}/autocomplete?limit=50`, + ); return data.data; } @@ -21,18 +28,24 @@ const DataGridMultiSelect = (props: GridRenderEditCellParams & Props) => { callApi(entityName).then((data) => { setOptions(data); }); - }, []); + }, [entityName]); - const handleChange = (event) => { + const handleChange = (event: SelectChangeEvent) => { const eventValue = event.target.value; // The new value entered by the user const newValue = - typeof eventValue === 'string' ? value.split(',') : eventValue; + typeof eventValue === 'string' + ? eventValue.split(',') + : Array.isArray(eventValue) + ? eventValue + : []; apiRef.current.setEditCellValue({ id, field, - value: newValue.filter((x) => x !== ''), + value: newValue.filter( + (x): x is string => typeof x === 'string' && x !== '', + ), }); }; diff --git a/frontend/src/components/DragDropFilePicker.tsx b/frontend/src/components/DragDropFilePicker.tsx index 821570d..c751b1e 100644 --- a/frontend/src/components/DragDropFilePicker.tsx +++ b/frontend/src/components/DragDropFilePicker.tsx @@ -1,26 +1,26 @@ -import React, { ChangeEvent, useEffect, useState } from 'react'; -import BaseIcon from './BaseIcon'; import { mdiFileUploadOutline } from '@mdi/js'; +import React, { useEffect, useRef, useState } from 'react'; +import BaseIcon from './BaseIcon'; type Props = { file: File | null; - setFile: (file: File) => void; + setFile: (file: File | null) => void; formats?: string; }; const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => { const [highlight, setHighlight] = useState(false); const [errorMessage, setErrorMessage] = useState(''); - const fileInput = React.createRef(); + const fileInput = useRef(null); useEffect(() => { - if (!file && fileInput) fileInput.current.value = ''; + if (!file && fileInput.current) fileInput.current.value = ''; }, [file, fileInput]); function onFilesAdded(files: FileList | null) { if (files && files[0]) { const newFile = files[0]; - const fileExtension = newFile.name.split('.').pop().toLowerCase(); + const fileExtension = newFile.name.split('.').pop()?.toLowerCase() || ''; if (formats.includes(fileExtension) || !formats) { setFile(newFile); @@ -31,7 +31,7 @@ const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => { } } - function onDragOver(e) { + function onDragOver(e: React.DragEvent) { e.preventDefault(); setHighlight(true); } @@ -40,7 +40,7 @@ const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => { setHighlight(false); } - function onDrop(e) { + function onDrop(e: React.DragEvent) { e.preventDefault(); const files = e.dataTransfer.files; @@ -49,11 +49,6 @@ const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => { setHighlight(false); } - const onClear = () => { - setFile(null); - setErrorMessage(''); - }; - return (
      = ({ carouselPrevIconUrl, diff --git a/frontend/src/components/ElementSettings/CarouselSettingsSectionCompact.tsx b/frontend/src/components/ElementSettings/CarouselSettingsSectionCompact.tsx index 74f3da7..52ffc79 100644 --- a/frontend/src/components/ElementSettings/CarouselSettingsSectionCompact.tsx +++ b/frontend/src/components/ElementSettings/CarouselSettingsSectionCompact.tsx @@ -6,9 +6,9 @@ */ import React from 'react'; -import type { CarouselSlide, AssetOption } from '../../types/constructor'; import { addFallbackAssetOption } from '../../lib/constructorHelpers'; import { FONT_OPTIONS } from '../../lib/fonts'; +import type { AssetOption, CarouselSlide } from '../../types/constructor'; interface CarouselSettingsSectionCompactProps { carouselSlides: CarouselSlide[]; diff --git a/frontend/src/components/ElementSettings/DescriptionSettingsSection.tsx b/frontend/src/components/ElementSettings/DescriptionSettingsSection.tsx index d1fb940..cc6987c 100644 --- a/frontend/src/components/ElementSettings/DescriptionSettingsSection.tsx +++ b/frontend/src/components/ElementSettings/DescriptionSettingsSection.tsx @@ -5,9 +5,9 @@ */ import React from 'react'; +import { FONT_OPTIONS } from '../../lib/fonts'; import FormField from '../FormField'; import type { DescriptionSettingsSectionProps } from './types'; -import { FONT_OPTIONS } from '../../lib/fonts'; const DescriptionSettingsSection: React.FC = ({ iconUrl, diff --git a/frontend/src/components/ElementSettings/DescriptionSettingsSectionCompact.tsx b/frontend/src/components/ElementSettings/DescriptionSettingsSectionCompact.tsx index bec5f51..61cdac0 100644 --- a/frontend/src/components/ElementSettings/DescriptionSettingsSectionCompact.tsx +++ b/frontend/src/components/ElementSettings/DescriptionSettingsSectionCompact.tsx @@ -6,9 +6,9 @@ */ import React from 'react'; -import type { AssetOption } from '../../types/constructor'; import { addFallbackAssetOption } from '../../lib/constructorHelpers'; import { FONT_OPTIONS } from '../../lib/fonts'; +import type { AssetOption } from '../../types/constructor'; interface DescriptionSettingsSectionCompactProps { iconUrl: string; diff --git a/frontend/src/components/ElementSettings/EffectsSettingsSection.tsx b/frontend/src/components/ElementSettings/EffectsSettingsSection.tsx index f366f2b..4bbc732 100644 --- a/frontend/src/components/ElementSettings/EffectsSettingsSection.tsx +++ b/frontend/src/components/ElementSettings/EffectsSettingsSection.tsx @@ -6,12 +6,12 @@ */ import React from 'react'; -import FormField from '../FormField'; -import type { EffectsSettingsSectionProps } from './types'; import { opacityToPercentInput, percentInputToOpacityValue, } from '../../lib/opacityPercent'; +import FormField from '../FormField'; +import type { EffectsSettingsSectionProps } from './types'; const EffectsSettingsSection: React.FC = ({ values, diff --git a/frontend/src/components/ElementSettings/EffectsSettingsSectionCompact.tsx b/frontend/src/components/ElementSettings/EffectsSettingsSectionCompact.tsx index e395de3..9cbd713 100644 --- a/frontend/src/components/ElementSettings/EffectsSettingsSectionCompact.tsx +++ b/frontend/src/components/ElementSettings/EffectsSettingsSectionCompact.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import type { CanvasElementType, AssetOption } from '../../types/constructor'; +import type { AssetOption, CanvasElementType } from '../../types/constructor'; import EffectsCompactActiveSection from './EffectsCompactActiveSection'; import EffectsCompactAppearSection from './EffectsCompactAppearSection'; import EffectsCompactAudioVolumeSection from './EffectsCompactAudioVolumeSection'; diff --git a/frontend/src/components/ElementSettings/GalleryCarouselSettingsSectionCompact.tsx b/frontend/src/components/ElementSettings/GalleryCarouselSettingsSectionCompact.tsx index f4b9239..52b265b 100644 --- a/frontend/src/components/ElementSettings/GalleryCarouselSettingsSectionCompact.tsx +++ b/frontend/src/components/ElementSettings/GalleryCarouselSettingsSectionCompact.tsx @@ -6,8 +6,8 @@ */ import React from 'react'; -import type { AssetOption } from '../../types/constructor'; import { addFallbackAssetOption } from '../../lib/constructorHelpers'; +import type { AssetOption } from '../../types/constructor'; interface GalleryCarouselSettingsSectionCompactProps { prevIconUrl: string; diff --git a/frontend/src/components/ElementSettings/GallerySettingsSection.tsx b/frontend/src/components/ElementSettings/GallerySettingsSection.tsx index bb2ba5f..f99ed29 100644 --- a/frontend/src/components/ElementSettings/GallerySettingsSection.tsx +++ b/frontend/src/components/ElementSettings/GallerySettingsSection.tsx @@ -5,13 +5,13 @@ * Manages gallery cards with images, titles, and descriptions. */ -import React from 'react'; import { mdiPlus, mdiTrashCan } from '@mdi/js'; +import React from 'react'; +import { FONT_OPTIONS } from '../../lib/fonts'; import BaseButton from '../BaseButton'; import CardBox from '../CardBox'; import FormField from '../FormField'; import type { GallerySettingsSectionProps } from './types'; -import { FONT_OPTIONS } from '../../lib/fonts'; const GallerySettingsSection: React.FC = ({ galleryCards, diff --git a/frontend/src/components/ElementSettings/GallerySettingsSectionCompact.tsx b/frontend/src/components/ElementSettings/GallerySettingsSectionCompact.tsx index d1a1f16..abeb117 100644 --- a/frontend/src/components/ElementSettings/GallerySettingsSectionCompact.tsx +++ b/frontend/src/components/ElementSettings/GallerySettingsSectionCompact.tsx @@ -7,12 +7,12 @@ */ import React from 'react'; +import { addFallbackAssetOption } from '../../lib/constructorHelpers'; import type { + AssetOption, GalleryCard, GalleryInfoSpan, - AssetOption, } from '../../types/constructor'; -import { addFallbackAssetOption } from '../../lib/constructorHelpers'; interface GallerySettingsSectionCompactProps { // Header settings diff --git a/frontend/src/components/ElementSettings/InfoPanelBasicSectionsCompact.tsx b/frontend/src/components/ElementSettings/InfoPanelBasicSectionsCompact.tsx index f086fe0..5ce1555 100644 --- a/frontend/src/components/ElementSettings/InfoPanelBasicSectionsCompact.tsx +++ b/frontend/src/components/ElementSettings/InfoPanelBasicSectionsCompact.tsx @@ -1,7 +1,7 @@ import React from 'react'; +import { addFallbackAssetOption } from '../../lib/constructorHelpers'; import type { AssetOption, CanvasElement } from '../../types/constructor'; import type { InfoPanelSectionInstance } from '../../types/infoPanel'; -import { addFallbackAssetOption } from '../../lib/constructorHelpers'; import { LinkDestinationFields } from './InfoPanelSettingsControls'; type PageOption = Array<{ id: string; slug?: string; name?: string }>; @@ -11,7 +11,7 @@ interface BasicSectionProps { sectionHeader: React.ReactNode; element: CanvasElement; pages: PageOption; - activePageId: string; + activePageId: string | null; onChange: (prop: string, value: string | number | boolean) => void; onUpdateSection?: ( sectionId: string, diff --git a/frontend/src/components/ElementSettings/InfoPanelCardStyleSection.tsx b/frontend/src/components/ElementSettings/InfoPanelCardStyleSection.tsx index cc16472..5698445 100644 --- a/frontend/src/components/ElementSettings/InfoPanelCardStyleSection.tsx +++ b/frontend/src/components/ElementSettings/InfoPanelCardStyleSection.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import FormField from '../FormField'; import { FONT_OPTIONS } from '../../lib/fonts'; +import FormField from '../FormField'; import type { InfoPanelSettingsSectionProps } from './types'; interface InfoPanelCardStyleSectionProps { diff --git a/frontend/src/components/ElementSettings/InfoPanelCardsSectionCompact.tsx b/frontend/src/components/ElementSettings/InfoPanelCardsSectionCompact.tsx index 2a82822..d377169 100644 --- a/frontend/src/components/ElementSettings/InfoPanelCardsSectionCompact.tsx +++ b/frontend/src/components/ElementSettings/InfoPanelCardsSectionCompact.tsx @@ -1,18 +1,18 @@ import React from 'react'; +import { addFallbackAssetOption } from '../../lib/constructorHelpers'; import type { AssetOption } from '../../types/constructor'; import type { InfoPanelImage, InfoPanelItemType, InfoPanelSectionInstance, } from '../../types/infoPanel'; -import { addFallbackAssetOption } from '../../lib/constructorHelpers'; +import { InfoPanelSectionHeader } from './InfoPanelSectionHeader'; import { ImageDestinationFields, MediaOpenModeField, UseAsBackgroundField, mediaTypePatch, } from './InfoPanelSettingsControls'; -import { InfoPanelSectionHeader } from './InfoPanelSectionHeader'; interface InfoPanelCardsSectionCompactProps { section: InfoPanelSectionInstance; @@ -21,7 +21,7 @@ interface InfoPanelCardsSectionCompactProps { imageAssetOptions: AssetOption[]; videoAssetOptions: AssetOption[]; pages: Array<{ id: string; slug?: string; name?: string }>; - activePageId: string; + activePageId: string | null; isFirst: boolean; isLast: boolean; hasSectionOps: boolean; diff --git a/frontend/src/components/ElementSettings/InfoPanelDetailPanelSection.tsx b/frontend/src/components/ElementSettings/InfoPanelDetailPanelSection.tsx index 1031696..24a4148 100644 --- a/frontend/src/components/ElementSettings/InfoPanelDetailPanelSection.tsx +++ b/frontend/src/components/ElementSettings/InfoPanelDetailPanelSection.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import FormField from '../FormField'; import { FONT_OPTIONS } from '../../lib/fonts'; +import FormField from '../FormField'; import type { InfoPanelSettingsSectionProps } from './types'; interface InfoPanelDetailPanelSectionProps { diff --git a/frontend/src/components/ElementSettings/InfoPanelHeaderSection.tsx b/frontend/src/components/ElementSettings/InfoPanelHeaderSection.tsx index 9f85598..55ba153 100644 --- a/frontend/src/components/ElementSettings/InfoPanelHeaderSection.tsx +++ b/frontend/src/components/ElementSettings/InfoPanelHeaderSection.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import FormField from '../FormField'; import { FONT_OPTIONS } from '../../lib/fonts'; import type { AssetOption } from '../../types/constructor'; +import FormField from '../FormField'; import { getInfoPanelHeaderImageFieldConfig } from './InfoPanelSettingsSection.helpers'; interface InfoPanelHeaderSectionProps { diff --git a/frontend/src/components/ElementSettings/InfoPanelImagesSectionCompact.tsx b/frontend/src/components/ElementSettings/InfoPanelImagesSectionCompact.tsx index fae101a..3aa6494 100644 --- a/frontend/src/components/ElementSettings/InfoPanelImagesSectionCompact.tsx +++ b/frontend/src/components/ElementSettings/InfoPanelImagesSectionCompact.tsx @@ -1,18 +1,18 @@ import React from 'react'; +import { addFallbackAssetOption } from '../../lib/constructorHelpers'; import type { AssetOption, CanvasElement } from '../../types/constructor'; import type { InfoPanelImage, InfoPanelItemType, InfoPanelSectionInstance, } from '../../types/infoPanel'; -import { addFallbackAssetOption } from '../../lib/constructorHelpers'; +import { InfoPanelSectionHeader } from './InfoPanelSectionHeader'; import { ImageDestinationFields, MediaOpenModeField, UseAsBackgroundField, mediaTypePatch, } from './InfoPanelSettingsControls'; -import { InfoPanelSectionHeader } from './InfoPanelSectionHeader'; interface InfoPanelImagesSectionCompactProps { section: InfoPanelSectionInstance; @@ -24,7 +24,7 @@ interface InfoPanelImagesSectionCompactProps { iconAssetOptions: AssetOption[]; embedAssetOptions: AssetOption[]; pages: Array<{ id: string; slug?: string; name?: string }>; - activePageId: string; + activePageId: string | null; isFirst: boolean; isLast: boolean; hasSectionOps: boolean; diff --git a/frontend/src/components/ElementSettings/InfoPanelSettingsControls.tsx b/frontend/src/components/ElementSettings/InfoPanelSettingsControls.tsx index a0f386a..f224cf5 100644 --- a/frontend/src/components/ElementSettings/InfoPanelSettingsControls.tsx +++ b/frontend/src/components/ElementSettings/InfoPanelSettingsControls.tsx @@ -30,7 +30,7 @@ export const mediaTypePatch = ( const targetPageOptions = ( pages: Array<{ id: string; slug?: string; name?: string }>, - activePageId: string, + activePageId: string | null, ) => pages .filter((page) => page.id !== activePageId && page.slug) @@ -66,7 +66,7 @@ export const LinkDestinationFields: React.FC<{ targetPageSlug?: string; externalUrl?: string; pages: Array<{ id: string; slug?: string; name?: string }>; - activePageId: string; + activePageId: string | null; onChange: (patch: { clickAction?: InfoPanelLinkClickAction; targetPageSlug?: string; @@ -156,7 +156,7 @@ export const ImageDestinationFields: React.FC<{ externalUrl?: string; disabled?: boolean; pages: Array<{ id: string; slug?: string; name?: string }>; - activePageId: string; + activePageId: string | null; onChange: (patch: { clickAction?: InfoPanelImageClickAction; targetPageSlug?: string; diff --git a/frontend/src/components/ElementSettings/InfoPanelSettingsSection.tsx b/frontend/src/components/ElementSettings/InfoPanelSettingsSection.tsx index d0e7c86..9aadcbf 100644 --- a/frontend/src/components/ElementSettings/InfoPanelSettingsSection.tsx +++ b/frontend/src/components/ElementSettings/InfoPanelSettingsSection.tsx @@ -6,7 +6,6 @@ */ import React, { useMemo } from 'react'; -import type { InfoPanelSettingsSectionProps } from './types'; import type { InfoPanelSectionInstance } from '../../types/infoPanel'; import InfoPanelCardStyleSection from './InfoPanelCardStyleSection'; import InfoPanelContentSection from './InfoPanelContentSection'; @@ -14,12 +13,13 @@ import InfoPanelDetailPanelSection from './InfoPanelDetailPanelSection'; import InfoPanelHeaderSection from './InfoPanelHeaderSection'; import InfoPanelMediaLayoutSection from './InfoPanelMediaLayoutSection'; import InfoPanelSectionOrder from './InfoPanelSectionOrder'; -import InfoPanelSpanStyleSection from './InfoPanelSpanStyleSection'; -import InfoPanelTriggerButtonSection from './InfoPanelTriggerButtonSection'; import { canManageInfoPanelSections, getEffectiveInfoPanelSections, } from './InfoPanelSettingsSection.helpers'; +import InfoPanelSpanStyleSection from './InfoPanelSpanStyleSection'; +import InfoPanelTriggerButtonSection from './InfoPanelTriggerButtonSection'; +import type { InfoPanelSettingsSectionProps } from './types'; const InfoPanelSettingsSection: React.FC = ({ // Trigger button settings diff --git a/frontend/src/components/ElementSettings/InfoPanelSettingsSectionCompact.tsx b/frontend/src/components/ElementSettings/InfoPanelSettingsSectionCompact.tsx index f70e176..ba0e9d9 100644 --- a/frontend/src/components/ElementSettings/InfoPanelSettingsSectionCompact.tsx +++ b/frontend/src/components/ElementSettings/InfoPanelSettingsSectionCompact.tsx @@ -5,19 +5,18 @@ * Supports multiple instances of the same section type with independent settings/data. */ -import React, { useState, useMemo } from 'react'; -import type { CanvasElement, AssetOption } from '../../types/constructor'; +import React, { useMemo, useState } from 'react'; +import type { AssetOption, CanvasElement } from '../../types/constructor'; import type { InfoPanelImage, InfoPanelInfoSpan, - InfoPanelSectionType, InfoPanelSectionInstance, + InfoPanelSectionType, } from '../../types/infoPanel'; import { getInfoPanelSections, INFO_PANEL_SECTION_LABELS, } from '../../types/infoPanel'; -import { CollapsibleSection } from './InfoPanelSettingsControls'; import { InfoPanelAddSectionControl } from './InfoPanelAddSectionControl'; import { InfoPanelHeaderSectionCompact, @@ -28,6 +27,7 @@ import { InfoPanelCardsSectionCompact } from './InfoPanelCardsSectionCompact'; import { InfoPanelImagesSectionCompact } from './InfoPanelImagesSectionCompact'; import { InfoPanelPanelOptionsCompact } from './InfoPanelPanelOptionsCompact'; import { InfoPanelSectionHeader } from './InfoPanelSectionHeader'; +import { CollapsibleSection } from './InfoPanelSettingsControls'; import { InfoPanelSpansSectionCompact } from './InfoPanelSpansSectionCompact'; import { InfoPanelTriggerSectionCompact } from './InfoPanelTriggerSectionCompact'; @@ -38,7 +38,7 @@ interface InfoPanelSettingsSectionCompactProps { iconAssetOptions: AssetOption[]; embedAssetOptions?: AssetOption[]; pages: Array<{ id: string; slug?: string; name?: string }>; - activePageId: string; + activePageId: string | null; onChange: (prop: string, value: string | number | boolean) => void; // Section operations (new per-instance API) onMoveSection?: (sectionId: string, direction: 'up' | 'down') => void; diff --git a/frontend/src/components/ElementSettings/InfoPanelSpanStyleSection.tsx b/frontend/src/components/ElementSettings/InfoPanelSpanStyleSection.tsx index 5e613bd..70f6151 100644 --- a/frontend/src/components/ElementSettings/InfoPanelSpanStyleSection.tsx +++ b/frontend/src/components/ElementSettings/InfoPanelSpanStyleSection.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import FormField from '../FormField'; import { FONT_OPTIONS } from '../../lib/fonts'; +import FormField from '../FormField'; import type { InfoPanelSettingsSectionProps } from './types'; interface InfoPanelSpanStyleSectionProps { diff --git a/frontend/src/components/ElementSettings/InfoPanelSpansSectionCompact.tsx b/frontend/src/components/ElementSettings/InfoPanelSpansSectionCompact.tsx index 9ddde96..7bc4fa9 100644 --- a/frontend/src/components/ElementSettings/InfoPanelSpansSectionCompact.tsx +++ b/frontend/src/components/ElementSettings/InfoPanelSpansSectionCompact.tsx @@ -1,12 +1,12 @@ import React from 'react'; +import { addFallbackAssetOption } from '../../lib/constructorHelpers'; import type { AssetOption } from '../../types/constructor'; import type { InfoPanelInfoSpan, InfoPanelSectionInstance, } from '../../types/infoPanel'; -import { addFallbackAssetOption } from '../../lib/constructorHelpers'; -import { LinkDestinationFields } from './InfoPanelSettingsControls'; import { InfoPanelSectionHeader } from './InfoPanelSectionHeader'; +import { LinkDestinationFields } from './InfoPanelSettingsControls'; interface InfoPanelSpansSectionCompactProps { section: InfoPanelSectionInstance; @@ -14,7 +14,7 @@ interface InfoPanelSpansSectionCompactProps { sectionSpans: InfoPanelInfoSpan[]; iconAssetOptions: AssetOption[]; pages: Array<{ id: string; slug?: string; name?: string }>; - activePageId: string; + activePageId: string | null; isFirst: boolean; isLast: boolean; hasSectionOps: boolean; diff --git a/frontend/src/components/ElementSettings/InfoPanelTriggerButtonSection.tsx b/frontend/src/components/ElementSettings/InfoPanelTriggerButtonSection.tsx index dfaf1e1..4aaff09 100644 --- a/frontend/src/components/ElementSettings/InfoPanelTriggerButtonSection.tsx +++ b/frontend/src/components/ElementSettings/InfoPanelTriggerButtonSection.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import FormField from '../FormField'; import { FONT_OPTIONS } from '../../lib/fonts'; import type { AssetOption } from '../../types/constructor'; +import FormField from '../FormField'; interface InfoPanelTriggerButtonSectionProps { iconUrl: string; diff --git a/frontend/src/components/ElementSettings/InfoPanelTriggerSectionCompact.tsx b/frontend/src/components/ElementSettings/InfoPanelTriggerSectionCompact.tsx index df5f96d..b3d6b60 100644 --- a/frontend/src/components/ElementSettings/InfoPanelTriggerSectionCompact.tsx +++ b/frontend/src/components/ElementSettings/InfoPanelTriggerSectionCompact.tsx @@ -1,5 +1,5 @@ -import type { AssetOption, CanvasElement } from '../../types/constructor'; import { addFallbackAssetOption } from '../../lib/constructorHelpers'; +import type { AssetOption, CanvasElement } from '../../types/constructor'; import { CollapsibleSection } from './InfoPanelSettingsControls'; interface InfoPanelTriggerSectionCompactProps { diff --git a/frontend/src/components/ElementSettings/MediaSettingsSection.tsx b/frontend/src/components/ElementSettings/MediaSettingsSection.tsx index f9c4f91..722bc95 100644 --- a/frontend/src/components/ElementSettings/MediaSettingsSection.tsx +++ b/frontend/src/components/ElementSettings/MediaSettingsSection.tsx @@ -5,8 +5,8 @@ */ import React from 'react'; -import FormField from '../FormField'; import { isVideoPlayerElementType } from '../../lib/elementTypeGuards'; +import FormField from '../FormField'; import type { MediaSettingsSectionProps } from './types'; const MediaSettingsSection: React.FC = ({ diff --git a/frontend/src/components/ElementSettings/MediaSettingsSectionCompact.tsx b/frontend/src/components/ElementSettings/MediaSettingsSectionCompact.tsx index 3948a07..e20017f 100644 --- a/frontend/src/components/ElementSettings/MediaSettingsSectionCompact.tsx +++ b/frontend/src/components/ElementSettings/MediaSettingsSectionCompact.tsx @@ -6,8 +6,8 @@ */ import React from 'react'; -import type { AssetOption } from '../../types/constructor'; import { addFallbackAssetOption } from '../../lib/constructorHelpers'; +import type { AssetOption } from '../../types/constructor'; interface MediaSettingsSectionCompactProps { mediaType: 'video' | 'audio'; diff --git a/frontend/src/components/ElementSettings/NavigationCompactBasicFields.tsx b/frontend/src/components/ElementSettings/NavigationCompactBasicFields.tsx index 1cc6459..2703c74 100644 --- a/frontend/src/components/ElementSettings/NavigationCompactBasicFields.tsx +++ b/frontend/src/components/ElementSettings/NavigationCompactBasicFields.tsx @@ -1,10 +1,10 @@ import React from 'react'; +import { addFallbackAssetOption } from '../../lib/constructorHelpers'; +import { FONT_OPTIONS } from '../../lib/fonts'; import type { AssetOption, NavigationButtonKind, } from '../../types/constructor'; -import { addFallbackAssetOption } from '../../lib/constructorHelpers'; -import { FONT_OPTIONS } from '../../lib/fonts'; import { buildNavigationTypeChangePatch, type NavigationElementType, diff --git a/frontend/src/components/ElementSettings/NavigationCompactTransitionSection.tsx b/frontend/src/components/ElementSettings/NavigationCompactTransitionSection.tsx index 3258972..9c098e1 100644 --- a/frontend/src/components/ElementSettings/NavigationCompactTransitionSection.tsx +++ b/frontend/src/components/ElementSettings/NavigationCompactTransitionSection.tsx @@ -1,7 +1,7 @@ import React from 'react'; +import { addFallbackAssetOption } from '../../lib/constructorHelpers'; import type { AssetOption } from '../../types/constructor'; import type { EasingFunction, TransitionType } from '../../types/transition'; -import { addFallbackAssetOption } from '../../lib/constructorHelpers'; import { CSS_EASING_OPTIONS, CSS_TRANSITION_TYPES, diff --git a/frontend/src/components/ElementSettings/NavigationSettingsSection.tsx b/frontend/src/components/ElementSettings/NavigationSettingsSection.tsx index 900296f..b785c7a 100644 --- a/frontend/src/components/ElementSettings/NavigationSettingsSection.tsx +++ b/frontend/src/components/ElementSettings/NavigationSettingsSection.tsx @@ -5,9 +5,9 @@ */ import React from 'react'; +import { FONT_OPTIONS } from '../../lib/fonts'; import FormField from '../FormField'; import type { NavigationSettingsSectionProps } from './types'; -import { FONT_OPTIONS } from '../../lib/fonts'; const NavigationSettingsSection: React.FC = ({ iconUrl, @@ -27,7 +27,6 @@ const NavigationSettingsSection: React.FC = ({ pageOptions = [], }) => { const isConstructor = context === 'constructor'; - const isGlobal = context === 'global'; const isProject = context === 'project'; return ( diff --git a/frontend/src/components/ElementSettings/StyleSettingsSection.tsx b/frontend/src/components/ElementSettings/StyleSettingsSection.tsx index 64f8d8a..d8f83ec 100644 --- a/frontend/src/components/ElementSettings/StyleSettingsSection.tsx +++ b/frontend/src/components/ElementSettings/StyleSettingsSection.tsx @@ -6,12 +6,12 @@ */ import React from 'react'; -import FormField from '../FormField'; -import type { StyleSettingsSectionProps } from './types'; import { opacityToPercentInput, percentInputToOpacityValue, } from '../../lib/opacityPercent'; +import FormField from '../FormField'; +import type { StyleSettingsSectionProps } from './types'; const StyleSettingsSection: React.FC = ({ values, diff --git a/frontend/src/components/ElementSettings/StyleSettingsSectionCompact.tsx b/frontend/src/components/ElementSettings/StyleSettingsSectionCompact.tsx index 6612e97..3fa7877 100644 --- a/frontend/src/components/ElementSettings/StyleSettingsSectionCompact.tsx +++ b/frontend/src/components/ElementSettings/StyleSettingsSectionCompact.tsx @@ -6,12 +6,12 @@ */ import React from 'react'; -import type { StyleSettingsSectionProps } from './types'; import { FONT_OPTIONS } from '../../lib/fonts'; import { opacityToPercentInput, percentInputToOpacityValue, } from '../../lib/opacityPercent'; +import type { StyleSettingsSectionProps } from './types'; const StyleSettingsSectionCompact: React.FC = ({ values, diff --git a/frontend/src/components/ElementSettings/elementSettingsFormSerialization.test.ts b/frontend/src/components/ElementSettings/elementSettingsFormSerialization.test.ts index 45f09fb..61028c2 100644 --- a/frontend/src/components/ElementSettings/elementSettingsFormSerialization.test.ts +++ b/frontend/src/components/ElementSettings/elementSettingsFormSerialization.test.ts @@ -1,10 +1,10 @@ import assert from 'node:assert/strict'; import { test } from 'node:test'; -import { createInitialElementSettingsFormState } from './elementSettingsFormState'; import { buildElementSettingsJson, normalizeElementSettingsFormState, } from './elementSettingsFormSerialization'; +import { createInitialElementSettingsFormState } from './elementSettingsFormState'; test('normalizeElementSettingsFormState parses settings into form fields', () => { const state = normalizeElementSettingsFormState( diff --git a/frontend/src/components/ElementSettings/elementSettingsFormSerialization.ts b/frontend/src/components/ElementSettings/elementSettingsFormSerialization.ts index dfd99be..a595a7e 100644 --- a/frontend/src/components/ElementSettings/elementSettingsFormSerialization.ts +++ b/frontend/src/components/ElementSettings/elementSettingsFormSerialization.ts @@ -1,7 +1,8 @@ -import type { InfoPanelSectionInstance } from '../../types/infoPanel'; -import { DEFAULT_INFO_PANEL_SECTIONS } from '../../types/infoPanel'; import { createLocalId } from '../../lib/elementInstance.helpers'; import { parseJsonObject } from '../../lib/parseJson'; +import type { InfoPanelSectionInstance } from '../../types/infoPanel'; +import { DEFAULT_INFO_PANEL_SECTIONS } from '../../types/infoPanel'; +import type { FormState } from './elementSettingsFormState'; import { clampPercent, extractNumericValue, @@ -9,7 +10,6 @@ import { toOptionalTrimmed, toUnitValue, } from './types'; -import type { FormState } from './elementSettingsFormState'; export interface BuildElementSettingsJsonOptions { isNavigationType: boolean; diff --git a/frontend/src/components/ElementSettings/index.ts b/frontend/src/components/ElementSettings/index.ts index acb7d1d..dcf0333 100644 --- a/frontend/src/components/ElementSettings/index.ts +++ b/frontend/src/components/ElementSettings/index.ts @@ -5,31 +5,31 @@ */ // Main components +export { default as CarouselSettingsSection } from './CarouselSettingsSection'; +export { default as CarouselSettingsSectionCompact } from './CarouselSettingsSectionCompact'; +export { default as CommonSettingsSection } from './CommonSettingsSection'; +export { default as CommonSettingsSectionCompact } from './CommonSettingsSectionCompact'; +export { default as DescriptionSettingsSection } from './DescriptionSettingsSection'; +export { default as DescriptionSettingsSectionCompact } from './DescriptionSettingsSectionCompact'; +export { default as EffectsSettingsSection } from './EffectsSettingsSection'; +export { default as EffectsSettingsSectionCompact } from './EffectsSettingsSectionCompact'; export { default as ElementSettingsTabs, ElementSettingsTabsCompact, } from './ElementSettingsTabs'; -export { default as StyleSettingsSection } from './StyleSettingsSection'; -export { default as StyleSettingsSectionCompact } from './StyleSettingsSectionCompact'; -export { default as EffectsSettingsSection } from './EffectsSettingsSection'; -export { default as EffectsSettingsSectionCompact } from './EffectsSettingsSectionCompact'; -export { default as CommonSettingsSection } from './CommonSettingsSection'; -export { default as CommonSettingsSectionCompact } from './CommonSettingsSectionCompact'; -export { default as NavigationSettingsSection } from './NavigationSettingsSection'; -export { default as NavigationSettingsSectionCompact } from './NavigationSettingsSectionCompact'; -export { default as DescriptionSettingsSection } from './DescriptionSettingsSection'; -export { default as DescriptionSettingsSectionCompact } from './DescriptionSettingsSectionCompact'; -export { default as MediaSettingsSection } from './MediaSettingsSection'; -export { default as MediaSettingsSectionCompact } from './MediaSettingsSectionCompact'; +export { default as GalleryCarouselSettingsSectionCompact } from './GalleryCarouselSettingsSectionCompact'; +export { default as GallerySectionStyleInputs } from './GallerySectionStyleInputs'; export { default as GallerySettingsSection } from './GallerySettingsSection'; export { default as GallerySettingsSectionCompact } from './GallerySettingsSectionCompact'; -export { default as GallerySectionStyleInputs } from './GallerySectionStyleInputs'; -export { default as CarouselSettingsSection } from './CarouselSettingsSection'; -export { default as CarouselSettingsSectionCompact } from './CarouselSettingsSectionCompact'; -export { default as GalleryCarouselSettingsSectionCompact } from './GalleryCarouselSettingsSectionCompact'; export { default as InfoPanelSettingsSection } from './InfoPanelSettingsSection'; export { default as InfoPanelSettingsSectionCompact } from './InfoPanelSettingsSectionCompact'; export { default as InfoPanelStyleInputs } from './InfoPanelStyleInputs'; +export { default as MediaSettingsSection } from './MediaSettingsSection'; +export { default as MediaSettingsSectionCompact } from './MediaSettingsSectionCompact'; +export { default as NavigationSettingsSection } from './NavigationSettingsSection'; +export { default as NavigationSettingsSectionCompact } from './NavigationSettingsSectionCompact'; +export { default as StyleSettingsSection } from './StyleSettingsSection'; +export { default as StyleSettingsSectionCompact } from './StyleSettingsSectionCompact'; // Hook export { useElementSettingsForm } from './useElementSettingsForm'; diff --git a/frontend/src/components/ElementSettings/types.ts b/frontend/src/components/ElementSettings/types.ts index c4ff9e3..6369ebe 100644 --- a/frontend/src/components/ElementSettings/types.ts +++ b/frontend/src/components/ElementSettings/types.ts @@ -6,18 +6,15 @@ */ import type { ElementStyleProperties } from '../../lib/elementStyles'; -import type { ElementEffectProperties } from '../../lib/elementEffects'; import type { - CanvasElement, - CanvasElementType, + AssetOption, + CarouselSlide, GalleryCard, GalleryInfoSpan, - CarouselSlide, - AssetOption, } from '../../types/constructor'; import type { - InfoPanelSectionType, InfoPanelSectionInstance, + InfoPanelSectionType, } from '../../types/infoPanel'; /** diff --git a/frontend/src/components/ElementSettings/useElementSettingsForm.ts b/frontend/src/components/ElementSettings/useElementSettingsForm.ts index 4c8cc8f..a45f0e0 100644 --- a/frontend/src/components/ElementSettings/useElementSettingsForm.ts +++ b/frontend/src/components/ElementSettings/useElementSettingsForm.ts @@ -6,31 +6,31 @@ */ import { useCallback, useState } from 'react'; -import type { ElementStyleProperties } from '../../lib/elementStyles'; -import type { GalleryCard, CarouselSlide } from '../../types/constructor'; -import type { - InfoPanelSectionType, - InfoPanelSectionInstance, -} from '../../types/infoPanel'; -import { generateSectionId } from '../../types/infoPanel'; import { createLocalId } from '../../lib/elementInstance.helpers'; +import type { ElementStyleProperties } from '../../lib/elementStyles'; import { - isNavigationElementType, + isCarouselElementType, isDescriptionElementType, isGalleryElementType, - isCarouselElementType, - isMediaElementType, isInfoPanelElementType, + isMediaElementType, + isNavigationElementType, } from '../../lib/elementTypeGuards'; +import type { CarouselSlide, GalleryCard } from '../../types/constructor'; +import type { + InfoPanelSectionInstance, + InfoPanelSectionType, +} from '../../types/infoPanel'; +import { generateSectionId } from '../../types/infoPanel'; +import { + buildElementSettingsJson, + normalizeElementSettingsFormState, +} from './elementSettingsFormSerialization'; import { createInitialElementSettingsFormState, type FormState, type UseElementSettingsFormOptions, } from './elementSettingsFormState'; -import { - buildElementSettingsJson, - normalizeElementSettingsFormState, -} from './elementSettingsFormSerialization'; export function useElementSettingsForm(options: UseElementSettingsFormOptions) { const { elementType } = options; diff --git a/frontend/src/components/ElementTypeDefaults/ElementTypeDefaultSettingsForm.tsx b/frontend/src/components/ElementTypeDefaults/ElementTypeDefaultSettingsForm.tsx index 2a36c7b..4605cc1 100644 --- a/frontend/src/components/ElementTypeDefaults/ElementTypeDefaultSettingsForm.tsx +++ b/frontend/src/components/ElementTypeDefaults/ElementTypeDefaultSettingsForm.tsx @@ -1,5 +1,5 @@ +import type { CanvasElementType } from '../../types/constructor'; import CardBox from '../CardBox'; -import FormField from '../FormField'; import CarouselSettingsSection from '../ElementSettings/CarouselSettingsSection'; import CommonSettingsSection from '../ElementSettings/CommonSettingsSection'; import DescriptionSettingsSection from '../ElementSettings/DescriptionSettingsSection'; @@ -11,7 +11,7 @@ import MediaSettingsSection from '../ElementSettings/MediaSettingsSection'; import NavigationSettingsSection from '../ElementSettings/NavigationSettingsSection'; import StyleSettingsSection from '../ElementSettings/StyleSettingsSection'; import type { useElementSettingsForm } from '../ElementSettings/useElementSettingsForm'; -import type { CanvasElementType } from '../../types/constructor'; +import FormField from '../FormField'; import { ELEMENT_TYPE_DEFAULT_SETTINGS_TABS, parseElementTypeDefaultSortOrder, @@ -33,7 +33,7 @@ type Props = { onIsActiveChange: (value: boolean) => void; onTabChange: (value: string) => void; onCommonChange: (field: string, value: string) => void; - onTypeChange: (field: string, value: string | boolean) => void; + onTypeChange: (field: string, value: string | number | boolean) => void; onStyleChange: (prop: string, value: string) => void; onEffectChange: (prop: string, value: string) => void; }; diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx index 20cd4d1..a3d01bc 100644 --- a/frontend/src/components/ErrorBoundary.tsx +++ b/frontend/src/components/ErrorBoundary.tsx @@ -1,8 +1,8 @@ -import React, { Component, ErrorInfo, ReactNode } from 'react'; import { mdiAlertCircle } from '@mdi/js'; -import BaseIcon from './BaseIcon'; -import { logger } from '../lib/logger'; +import { Component, ErrorInfo, ReactNode } from 'react'; import { isProduction } from '../config'; +import { logger } from '../lib/logger'; +import BaseIcon from './BaseIcon'; // Define the props and state interfaces interface ErrorBoundaryProps { @@ -39,9 +39,9 @@ class ErrorBoundary extends Component { } componentDidUpdate( - prevProps: Readonly, - prevState: Readonly, - snapshot?: any, + _prevProps: Readonly, + _prevState: Readonly, + _snapshot?: unknown, ) { if (!isProduction) { logger.debug('componentDidUpdate'); diff --git a/frontend/src/components/Factory/createTableComponent.tsx b/frontend/src/components/Factory/createTableComponent.tsx index c6f561a..890aabd 100644 --- a/frontend/src/components/Factory/createTableComponent.tsx +++ b/frontend/src/components/Factory/createTableComponent.tsx @@ -5,14 +5,19 @@ * Reduces boilerplate in entity-specific table components. */ -import React from 'react'; -import GenericTable from '../Generic/GenericTable'; -import type { RootState } from '../../stores/store'; -import type { Filter, FilterItem } from '../../types/filters'; import type { GridColDef } from '@mui/x-data-grid'; -import type { AsyncThunk } from '@reduxjs/toolkit'; -import type { EntitySliceState } from '../../types/redux'; +import React from 'react'; +import type { RootState } from '../../stores/store'; import type { BaseEntity } from '../../types/entities'; +import type { Filter, FilterItem } from '../../types/filters'; +import type { + EntityDeleteByIdsThunk, + EntityDeleteThunk, + EntityFetchThunk, + EntitySliceState, + EntityUpdateThunk, +} from '../../types/redux'; +import GenericTable from '../Generic/GenericTable'; /** * Configuration for creating a table component @@ -23,17 +28,13 @@ export interface TableComponentConfig { /** Redux slice selector */ sliceSelector: (state: RootState) => EntitySliceState; /** Fetch action thunk */ - fetchAction: AsyncThunk< - T | { rows: T[]; count: number }, - { id?: string; query?: string }, - object - >; + fetchAction: EntityFetchThunk; /** Update action thunk */ - updateAction: AsyncThunk }, object>; + updateAction: EntityUpdateThunk; /** Delete single item action thunk */ - deleteAction: AsyncThunk; + deleteAction: EntityDeleteThunk; /** Delete multiple items action thunk */ - deleteByIdsAction: AsyncThunk; + deleteByIdsAction: EntityDeleteByIdsThunk; /** Set refetch flag action */ setRefetchAction: (refetch: boolean) => { type: string; payload: boolean }; /** Column loader function */ diff --git a/frontend/src/components/FooterBar.tsx b/frontend/src/components/FooterBar.tsx index ec78468..f6e7160 100644 --- a/frontend/src/components/FooterBar.tsx +++ b/frontend/src/components/FooterBar.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import { ReactNode } from 'react'; import { containerMaxW } from '../config'; import Logo from './Logo'; diff --git a/frontend/src/components/FormCheckRadioGroup.tsx b/frontend/src/components/FormCheckRadioGroup.tsx index 5d23a75..5f9aaac 100644 --- a/frontend/src/components/FormCheckRadioGroup.tsx +++ b/frontend/src/components/FormCheckRadioGroup.tsx @@ -1,4 +1,4 @@ -import { Children, cloneElement, ReactElement, ReactNode } from 'react'; +import { Children, cloneElement, isValidElement, type ReactNode } from 'react'; type Props = { isColumn?: boolean; @@ -10,11 +10,15 @@ const FormCheckRadioGroup = (props: Props) => {
      - {Children.map(props.children, (child: ReactElement) => - cloneElement(child as ReactElement<{ className?: string }>, { - className: `mr-6 mb-3 last:mr-0 ${(child.props as { className?: string }).className || ''}`, - }), - )} + {Children.map(props.children, (child) => { + if (!isValidElement<{ className?: string }>(child)) { + return child; + } + + return cloneElement(child, { + className: `mr-6 mb-3 last:mr-0 ${child.props.className || ''}`, + }); + })}
      ); }; diff --git a/frontend/src/components/FormField.tsx b/frontend/src/components/FormField.tsx index 006d938..2047c03 100644 --- a/frontend/src/components/FormField.tsx +++ b/frontend/src/components/FormField.tsx @@ -1,6 +1,6 @@ -import { Children, cloneElement, ReactElement, ReactNode } from 'react'; -import BaseIcon from './BaseIcon'; +import { Children, cloneElement, isValidElement, type ReactNode } from 'react'; import { useAppSelector } from '../stores/hooks'; +import BaseIcon from './BaseIcon'; type Props = { label?: string; @@ -22,7 +22,6 @@ const FormField = ({ icons = [], ...props }: Props) => { const bgColor = useAppSelector((state) => state.style.cardsColor); const focusRing = useAppSelector((state) => state.style.focusRingColor); const corners = useAppSelector((state) => state.style.corners); - const bgWebsiteColor = useAppSelector((state) => state.style.bgLayoutColor); let elementWrapperClass = ''; switch (childrenCount) { @@ -58,21 +57,27 @@ const FormField = ({ icons = [], ...props }: Props) => { )}
      - {Children.map(props.children, (child: ReactElement, index) => ( -
      - {cloneElement(child as ReactElement<{ className?: string }>, { - className: `${controlClassName} ${icons[index] ? 'pl-10' : ''}`, - })} - {icons[index] && ( - - )} -
      - ))} + {Children.map(props.children, (child, index) => { + if (!isValidElement<{ className?: string }>(child)) { + return child; + } + + return ( +
      + {cloneElement(child, { + className: `${controlClassName} ${icons[index] ? 'pl-10' : ''}`, + })} + {icons[index] && ( + + )} +
      + ); + })}
      {props.help && (
      diff --git a/frontend/src/components/FormFilePicker.tsx b/frontend/src/components/FormFilePicker.tsx index b7d84ee..ab20444 100644 --- a/frontend/src/components/FormFilePicker.tsx +++ b/frontend/src/components/FormFilePicker.tsx @@ -1,11 +1,11 @@ -import { useEffect, useState } from 'react'; +import { mdiReload } from '@mdi/js'; +import type { FieldInputProps, FormikProps } from 'formik'; +import { useEffect, useState, type ChangeEvent } from 'react'; +import { useAppSelector } from '../stores/hooks'; +import type { ImageFile } from '../types/entities'; import { ColorButtonKey } from '../types/ui'; import BaseButton from './BaseButton'; import FileUploader from './Uploaders/UploadService'; -import { mdiReload } from '@mdi/js'; -import { useAppSelector } from '../stores/hooks'; -import type { FieldInputProps, FormikProps } from 'formik'; -import type { ImageFile } from '../types/entities'; type Props = { label?: string; @@ -30,7 +30,7 @@ const FormFilePicker = ({ form, field, }: Props) => { - const [file, setFile] = useState(null); + const [file, setFile] = useState(null); const [loading, setLoading] = useState(false); const corners = useAppSelector((state) => state.style.corners); const bgColor = useAppSelector((state) => state.style.cardsColor); @@ -50,8 +50,9 @@ const FormFilePicker = ({ setFile(field.value[0]); } }, [field.value]); - const handleFileChange = async (event) => { - const file = event.currentTarget.files[0]; + const handleFileChange = async (event: ChangeEvent) => { + const file = event.currentTarget.files?.[0]; + if (!file) return; setFile(file); FileUploader.validate(file, schema); @@ -72,9 +73,9 @@ const FormFilePicker = ({ showFilename ? 'rounded-r-none' : '' }`} iconSize={isRoundIcon ? 24 : undefined} - label={isRoundIcon ? null : label} + label={isRoundIcon ? undefined : label} icon={loading ? mdiReload : icon} - iconClassName={loading && 'animate-spin'} + iconClassName={loading ? 'animate-spin' : undefined} color={color} roundedFull={isRoundIcon} asAnchor @@ -92,7 +93,7 @@ const FormFilePicker = ({ className={` px-4 py-2 max-w-full flex-grow-0 overflow-x-hidden ${bgColor} dark:bg-slate-800 border-gray-200 dark:border-slate-700 border ${cornersRight} `} > - {file.name} + {'name' in file ? file.name : ''}
      )} diff --git a/frontend/src/components/FormImagePicker.tsx b/frontend/src/components/FormImagePicker.tsx index 860956d..99306d4 100644 --- a/frontend/src/components/FormImagePicker.tsx +++ b/frontend/src/components/FormImagePicker.tsx @@ -1,11 +1,11 @@ -import { useState, useEffect } from 'react'; +import { mdiReload } from '@mdi/js'; +import type { FieldInputProps, FormikProps } from 'formik'; +import { useEffect, useState, type ChangeEvent } from 'react'; +import { useAppSelector } from '../stores/hooks'; +import type { ImageFile } from '../types/entities'; import { ColorButtonKey } from '../types/ui'; import BaseButton from './BaseButton'; import FileUploader from './Uploaders/UploadService'; -import { mdiReload } from '@mdi/js'; -import { useAppSelector } from '../stores/hooks'; -import type { FieldInputProps, FormikProps } from 'formik'; -import type { ImageFile } from '../types/entities'; type Props = { label?: string; @@ -30,7 +30,7 @@ const FormImagePicker = ({ form, field, }: Props) => { - const [file, setFile] = useState(null); + const [file, setFile] = useState(null); const [loading, setLoading] = useState(false); const corners = useAppSelector((state) => state.style.corners); const bgColor = useAppSelector((state) => state.style.cardsColor); @@ -50,8 +50,9 @@ const FormImagePicker = ({ setFile(field.value[0]); } }, [field.value]); - const handleFileChange = async (event) => { - const file = event.currentTarget.files[0]; + const handleFileChange = async (event: ChangeEvent) => { + const file = event.currentTarget.files?.[0]; + if (!file) return; setFile(file); FileUploader.validate(file, schema); @@ -72,9 +73,9 @@ const FormImagePicker = ({ showFilename ? 'rounded-r-none' : '' }`} iconSize={isRoundIcon ? 24 : undefined} - label={isRoundIcon ? null : label} + label={isRoundIcon ? undefined : label} icon={loading ? mdiReload : icon} - iconClassName={loading && 'animate-spin'} + iconClassName={loading ? 'animate-spin' : undefined} color={color} roundedFull={isRoundIcon} asAnchor @@ -92,7 +93,7 @@ const FormImagePicker = ({ className={` ${cornersRight} px-4 py-2 max-w-full flex-grow-0 overflow-x-hidden ${bgColor} dark:bg-slate-800 border-gray-200 dark:border-slate-700 border `} > - {file.name} + {'name' in file ? file.name : ''}
      )} diff --git a/frontend/src/components/Generic/GenericFormField.tsx b/frontend/src/components/Generic/GenericFormField.tsx index 2a4f749..fdeebc7 100644 --- a/frontend/src/components/Generic/GenericFormField.tsx +++ b/frontend/src/components/Generic/GenericFormField.tsx @@ -2,19 +2,19 @@ * GenericFormField Component */ -import React from 'react'; +import { mdiUpload } from '@mdi/js'; +import dayjs from 'dayjs'; import { Field, useFormikContext } from 'formik'; +import React from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; -import { mdiUpload } from '@mdi/js'; +import type { FormFieldConfig } from '../../types/forms'; import FormField from '../FormField'; import FormImagePicker from '../FormImagePicker'; import { SelectField } from '../SelectField'; import { SelectFieldMany } from '../SelectFieldMany'; import { SwitchField } from '../SwitchField'; -import type { FormFieldConfig } from '../../types/forms'; interface GenericFormFieldProps { config: FormFieldConfig; diff --git a/frontend/src/components/Generic/GenericTable.tsx b/frontend/src/components/Generic/GenericTable.tsx index 9fb98dd..dd65968 100644 --- a/frontend/src/components/Generic/GenericTable.tsx +++ b/frontend/src/components/Generic/GenericTable.tsx @@ -5,43 +5,44 @@ * filtering, and editing entity data using MUI X DataGrid. */ -import React, { useEffect, useState, useMemo, useCallback } from 'react'; import { DataGrid, - GridRowsProp, GridColDef, - GridRowModesModel, GridEventListener, + GridPaginationModel, GridRowEditStopReasons, GridRowModel, + GridRowModesModel, GridRowSelectionModel, - GridPaginationModel, + GridRowsProp, GridSortModel, } from '@mui/x-data-grid'; +import { Field, Form, Formik } from 'formik'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { ToastContainer, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; -import CardBox from '../CardBox'; -import BaseButton from '../BaseButton'; import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { Field, Form, Formik } from 'formik'; import type { RootState } from '../../stores/store'; -import type { Filter, FilterItem, FilterFields } from '../../types/filters'; -import type { NotificationState, EntitySliceState } from '../../types/redux'; -import type { AsyncThunk } from '@reduxjs/toolkit'; import type { BaseEntity } from '../../types/entities'; +import type { Filter, FilterFields, FilterItem } from '../../types/filters'; +import type { + EntityDeleteByIdsThunk, + EntityDeleteThunk, + EntityFetchThunk, + EntitySliceState, + EntityUpdateThunk, +} from '../../types/redux'; +import BaseButton from '../BaseButton'; +import CardBox from '../CardBox'; // Props interface for GenericTable interface GenericTableProps { entityName: string; sliceSelector: (state: RootState) => EntitySliceState; - fetchAction: AsyncThunk< - T | { rows: T[]; count: number }, - { id?: string; query?: string }, - object - >; - updateAction: AsyncThunk }, object>; - deleteAction: AsyncThunk; - deleteByIdsAction?: AsyncThunk; + fetchAction: EntityFetchThunk; + updateAction: EntityUpdateThunk; + deleteAction: EntityDeleteThunk; + deleteByIdsAction?: EntityDeleteByIdsThunk; setRefetchAction: (refetch: boolean) => { type: string; payload: boolean }; loadColumnsFunction: ( onDelete: (id: string) => void, diff --git a/frontend/src/components/Generic/index.ts b/frontend/src/components/Generic/index.ts index e7f8095..aa49257 100644 --- a/frontend/src/components/Generic/index.ts +++ b/frontend/src/components/Generic/index.ts @@ -2,5 +2,5 @@ * Generic Components Index */ -export { default as GenericTable } from './GenericTable'; export { default as GenericFormField } from './GenericFormField'; +export { default as GenericTable } from './GenericTable'; diff --git a/frontend/src/components/IconRounded.tsx b/frontend/src/components/IconRounded.tsx index e3dfc93..3301ed2 100644 --- a/frontend/src/components/IconRounded.tsx +++ b/frontend/src/components/IconRounded.tsx @@ -1,6 +1,5 @@ -import React from 'react'; -import { ColorKey } from '../types/ui'; import { colorsBgLight, colorsText } from '../colors'; +import { ColorKey } from '../types/ui'; import BaseIcon from './BaseIcon'; type Props = { diff --git a/frontend/src/components/ImageField.tsx b/frontend/src/components/ImageField.tsx index 71a58af..2f1c6ff 100644 --- a/frontend/src/components/ImageField.tsx +++ b/frontend/src/components/ImageField.tsx @@ -1,6 +1,6 @@ -import React, { ReactNode } from 'react'; -import Image from 'next/image'; import { mdiImageOutline } from '@mdi/js'; +import Image from 'next/image'; +import { ReactNode } from 'react'; import BaseIcon from './BaseIcon'; type Props = { diff --git a/frontend/src/components/IntroGuide.tsx b/frontend/src/components/IntroGuide.tsx index 7271c83..0bb90d2 100644 --- a/frontend/src/components/IntroGuide.tsx +++ b/frontend/src/components/IntroGuide.tsx @@ -1,6 +1,6 @@ -import React from 'react'; -import { Steps, Hints } from 'intro.js-react'; +import { Steps } from 'intro.js-react'; import { useRouter } from 'next/router'; +import React from 'react'; interface IntroGuideProps { steps: Array<{ element: string; diff --git a/frontend/src/components/LanguageSwitcher.tsx b/frontend/src/components/LanguageSwitcher.tsx index f2f0b4a..0a55f8c 100644 --- a/frontend/src/components/LanguageSwitcher.tsx +++ b/frontend/src/components/LanguageSwitcher.tsx @@ -1,8 +1,8 @@ import React, { useEffect, useState } from 'react'; import Select, { components, - SingleValueProps, OptionProps, + SingleValueProps, } from 'react-select'; type LanguageOption = { label: string; value: string }; diff --git a/frontend/src/components/ListActionsPopover.tsx b/frontend/src/components/ListActionsPopover.tsx index 7465d91..09d52c6 100644 --- a/frontend/src/components/ListActionsPopover.tsx +++ b/frontend/src/components/ListActionsPopover.tsx @@ -1,15 +1,14 @@ -import React from 'react'; -import Link from 'next/link'; -import Button from '@mui/material/Button'; -import BaseIcon from './BaseIcon'; import { mdiDotsVertical, mdiEye, mdiPencilOutline, mdiTrashCan, } from '@mdi/js'; -import Popover from '@mui/material/Popover'; import { IconButton } from '@mui/material'; +import Button from '@mui/material/Button'; +import Popover from '@mui/material/Popover'; +import React from 'react'; +import BaseIcon from './BaseIcon'; type Props = { itemId: string; @@ -30,8 +29,10 @@ const ListActionsPopover = ({ pathEdit, pathView, }: Props) => { - const [anchorEl, setAnchorEl] = React.useState(null); - const handleClick = (event) => { + const [anchorEl, setAnchorEl] = React.useState( + null, + ); + const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; const linkView = pathView; diff --git a/frontend/src/components/LoadingSpinner.tsx b/frontend/src/components/LoadingSpinner.tsx index 2a56d8d..700f6c1 100644 --- a/frontend/src/components/LoadingSpinner.tsx +++ b/frontend/src/components/LoadingSpinner.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - const LoadingSpinner = () => { return (
      diff --git a/frontend/src/components/Logo/index.tsx b/frontend/src/components/Logo/index.tsx index d075aff..2a82b10 100644 --- a/frontend/src/components/Logo/index.tsx +++ b/frontend/src/components/Logo/index.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import Image from 'next/image'; type Props = { diff --git a/frontend/src/components/NavBar.tsx b/frontend/src/components/NavBar.tsx index 914e40c..5c46696 100644 --- a/frontend/src/components/NavBar.tsx +++ b/frontend/src/components/NavBar.tsx @@ -1,11 +1,11 @@ -import React, { ReactNode, useState, useEffect } from 'react'; import { mdiClose, mdiDotsVertical } from '@mdi/js'; +import { ReactNode, useEffect, useState } from 'react'; import { containerMaxW } from '../config'; +import { useAppSelector } from '../stores/hooks'; +import { MenuNavBarItem } from '../types/menu'; import BaseIcon from './BaseIcon'; import NavBarItemPlain from './NavBarItemPlain'; import NavBarMenuList from './NavBarMenuList'; -import { MenuNavBarItem } from '../types/menu'; -import { useAppSelector } from '../stores/hooks'; type Props = { menu: MenuNavBarItem[]; diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx index 047619a..150e261 100644 --- a/frontend/src/components/NavBarItem.tsx +++ b/frontend/src/components/NavBarItem.tsx @@ -1,16 +1,16 @@ -import React, { useEffect, useRef, useState } from 'react'; +import { mdiChevronDown, mdiChevronUp } from '@mdi/js'; import Link from 'next/link'; -import { mdiChevronUp, mdiChevronDown } from '@mdi/js'; +import { useRouter } from 'next/router'; +import { useEffect, useRef, useState } from 'react'; +import { logoutUser } from '../stores/authSlice'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; +import { setDarkMode } from '../stores/styleSlice'; +import { MenuNavBarItem } from '../types/menu'; import BaseDivider from './BaseDivider'; import BaseIcon from './BaseIcon'; -import UserAvatarCurrentUser from './UserAvatarCurrentUser'; -import NavBarMenuList from './NavBarMenuList'; -import { useAppDispatch, useAppSelector } from '../stores/hooks'; -import { MenuNavBarItem } from '../types/menu'; -import { setDarkMode } from '../stores/styleSlice'; -import { logoutUser } from '../stores/authSlice'; -import { useRouter } from 'next/router'; import ClickOutside from './ClickOutside'; +import NavBarMenuList from './NavBarMenuList'; +import UserAvatarCurrentUser from './UserAvatarCurrentUser'; type Props = { item: MenuNavBarItem; @@ -67,7 +67,7 @@ export default function NavBarItem({ item }: Props) { } }; - const getItemId = (label) => { + const getItemId = (label?: string) => { switch (label) { case 'Light/Dark': return 'themeToggle'; diff --git a/frontend/src/components/NavBarMenuList.tsx b/frontend/src/components/NavBarMenuList.tsx index fc59453..be5d76c 100644 --- a/frontend/src/components/NavBarMenuList.tsx +++ b/frontend/src/components/NavBarMenuList.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { MenuNavBarItem } from '../types/menu'; import NavBarItem from './NavBarItem'; diff --git a/frontend/src/components/NotificationBar.tsx b/frontend/src/components/NotificationBar.tsx index 4a839f0..85bdc7f 100644 --- a/frontend/src/components/NotificationBar.tsx +++ b/frontend/src/components/NotificationBar.tsx @@ -1,7 +1,7 @@ import { mdiClose } from '@mdi/js'; import React, { ReactNode, useState } from 'react'; -import { ColorKey } from '../types/ui'; import { colorsBgLight, colorsOutline } from '../colors'; +import { ColorKey } from '../types/ui'; import BaseButton from './BaseButton'; import BaseIcon from './BaseIcon'; diff --git a/frontend/src/components/Offline/DownloadProgressPanel.tsx b/frontend/src/components/Offline/DownloadProgressPanel.tsx index 771585c..fd74bd4 100644 --- a/frontend/src/components/Offline/DownloadProgressPanel.tsx +++ b/frontend/src/components/Offline/DownloadProgressPanel.tsx @@ -5,14 +5,12 @@ * Similar to hoboken's VideoOptimizationDropdown pattern. */ -import React from 'react'; import { - mdiPause, - mdiPlay, - mdiClose, - mdiCheck, mdiAlertCircle, + mdiCheck, + mdiClose, mdiLoading, + mdiPause, } from '@mdi/js'; import Icon from '@mdi/react'; import { usePreloadProgress } from '../../hooks/usePreloadProgress'; diff --git a/frontend/src/components/Offline/OfflineStatusIndicator.tsx b/frontend/src/components/Offline/OfflineStatusIndicator.tsx index bd03399..a36d17e 100644 --- a/frontend/src/components/Offline/OfflineStatusIndicator.tsx +++ b/frontend/src/components/Offline/OfflineStatusIndicator.tsx @@ -4,7 +4,6 @@ * Small badge showing offline status (for use in headers/toolbars). */ -import React from 'react'; import { mdiCloudCheck, mdiCloudOffOutline, mdiCloudSync } from '@mdi/js'; import Icon from '@mdi/react'; import { useNetworkAware } from '../../hooks/useNetworkAware'; diff --git a/frontend/src/components/Offline/OfflineToggle.tsx b/frontend/src/components/Offline/OfflineToggle.tsx index b5c2003..e3e01be 100644 --- a/frontend/src/components/Offline/OfflineToggle.tsx +++ b/frontend/src/components/Offline/OfflineToggle.tsx @@ -5,21 +5,21 @@ * Shows download status, size estimate, and provides download/delete actions. */ -import React, { useEffect, useRef } from 'react'; import { - mdiCloudDownload, mdiCloudCheck, + mdiCloudDownload, mdiCloudOff, mdiDelete, } from '@mdi/js'; import Icon from '@mdi/react'; +import { useEffect, useRef } from 'react'; import { toast } from 'react-toastify'; -import BaseButton from '../BaseButton'; import { useOfflineMode } from '../../hooks/useOfflineMode'; import { useStorageQuota } from '../../hooks/useStorageQuota'; +import { logger } from '../../lib/logger'; import type { ProjectOfflineStatus } from '../../types/offline'; import type { PreloadPage } from '../../types/preload'; -import { logger } from '../../lib/logger'; +import BaseButton from '../BaseButton'; interface OfflineToggleProps { projectId: string | null; @@ -49,8 +49,6 @@ export function OfflineToggle({ progress, startDownload, pauseDownload, - resumeDownload, - cancelDownload, deleteOfflineData, estimatedSize, formatSize, diff --git a/frontend/src/components/Offline/StorageUsageDisplay.tsx b/frontend/src/components/Offline/StorageUsageDisplay.tsx index 41e5ea4..006362a 100644 --- a/frontend/src/components/Offline/StorageUsageDisplay.tsx +++ b/frontend/src/components/Offline/StorageUsageDisplay.tsx @@ -4,7 +4,6 @@ * Visual display of storage quota usage. */ -import React from 'react'; import { mdiDatabase } from '@mdi/js'; import Icon from '@mdi/react'; import { useStorageQuota } from '../../hooks/useStorageQuota'; diff --git a/frontend/src/components/Offline/index.ts b/frontend/src/components/Offline/index.ts index 6fbabb5..e53daa7 100644 --- a/frontend/src/components/Offline/index.ts +++ b/frontend/src/components/Offline/index.ts @@ -4,7 +4,7 @@ * Export all offline-related UI components. */ -export { OfflineToggle } from './OfflineToggle'; export { DownloadProgressPanel } from './DownloadProgressPanel'; export { OfflineStatusIndicator } from './OfflineStatusIndicator'; +export { OfflineToggle } from './OfflineToggle'; export { StorageUsageDisplay } from './StorageUsageDisplay'; diff --git a/frontend/src/components/Pagination.tsx b/frontend/src/components/Pagination.tsx index a542ca6..6d71b93 100644 --- a/frontend/src/components/Pagination.tsx +++ b/frontend/src/components/Pagination.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { mdiChevronDoubleLeft, mdiChevronDoubleRight, diff --git a/frontend/src/components/PasswordSetOrReset.tsx b/frontend/src/components/PasswordSetOrReset.tsx index 5fdf30a..2167ed5 100644 --- a/frontend/src/components/PasswordSetOrReset.tsx +++ b/frontend/src/components/PasswordSetOrReset.tsx @@ -1,16 +1,15 @@ import React from 'react'; -import { toast, ToastContainer } from 'react-toastify'; - +import { ToastContainer } from 'react-toastify'; import Head from 'next/head'; +import { useRouter } from 'next/router'; import CardBox from '../components/CardBox'; import SectionFullScreen from '../components/SectionFullScreen'; -import { useRouter } from 'next/router'; import { getPageTitle } from '../config'; import { Field, Form, Formik } from 'formik'; -import FormField from '../components/FormField'; -import BaseButtons from '../components/BaseButtons'; import BaseButton from '../components/BaseButton'; +import BaseButtons from '../components/BaseButtons'; +import FormField from '../components/FormField'; import { passwordReset } from '../stores/authSlice'; import { useAppDispatch } from '../stores/hooks'; @@ -20,8 +19,6 @@ export default function PasswordSetOrReset() { const router = useRouter(); const { token, invitation } = router.query; - const notify = (type, msg) => toast(msg, { type }); - const dispatch = useAppDispatch(); React.useEffect(() => { @@ -30,14 +27,14 @@ export default function PasswordSetOrReset() { } }, [invitation]); - const handleSubmit = async (value) => { + const handleSubmit = async (value: { password: string; confirm: string }) => { setLoading(true); if (typeof token === 'string') { await dispatch( passwordReset({ token, password: value.password, - type: isInvitation && 'invitation', + type: isInvitation ? 'invitation' : '', }), ); await router.push('/login'); @@ -67,7 +64,7 @@ export default function PasswordSetOrReset() { }} onSubmit={(values) => handleSubmit(values)} > - {({ errors, touched }) => ( + {() => (
      {!loading && - permissions.map((item, index) => ( + permissions.map((item) => (
    • state.auth.currentUser); const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PERMISSIONS'); - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( <>
      diff --git a/frontend/src/components/Permissions/TablePermissions.tsx b/frontend/src/components/Permissions/TablePermissions.tsx index f6b3e0d..733b969 100644 --- a/frontend/src/components/Permissions/TablePermissions.tsx +++ b/frontend/src/components/Permissions/TablePermissions.tsx @@ -1,4 +1,3 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { DataGrid, GridColDef, @@ -11,13 +10,10 @@ import { GridSortModel, } from '@mui/x-data-grid'; import { Field, Form, Formik } from 'formik'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { ToastContainer, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; -import BaseButton from '../BaseButton'; -import CardBox from '../CardBox'; -import { loadColumns } from './configurePermissionsCols'; -import { useAppSelector } from '../../stores/hooks'; import { buildPermissionListFilters, useDeletePermissionMutation, @@ -25,7 +21,11 @@ import { usePermissionsListQuery, useUpdatePermissionMutation, } from '../../hooks/queries'; +import { useAppSelector } from '../../stores/hooks'; import type { Filter, FilterFields, FilterItem } from '../../types/filters'; +import BaseButton from '../BaseButton'; +import CardBox from '../CardBox'; +import { loadColumns } from './configurePermissionsCols'; interface TablePermissionsProps { filterItems: FilterItem[]; diff --git a/frontend/src/components/Permissions/configurePermissionsCols.tsx b/frontend/src/components/Permissions/configurePermissionsCols.tsx index 0dad3f2..c4ec9d7 100644 --- a/frontend/src/components/Permissions/configurePermissionsCols.tsx +++ b/frontend/src/components/Permissions/configurePermissionsCols.tsx @@ -1,6 +1,6 @@ import { - createColumnLoader, ColumnMetadata, + createColumnLoader, } from '../DataGrid/configBuilderFactory'; const PERMISSIONS_COLUMNS: ColumnMetadata[] = [ diff --git a/frontend/src/components/Presigned_url_requests/CardPresigned_url_requests.tsx b/frontend/src/components/Presigned_url_requests/CardPresigned_url_requests.tsx index a8ff56a..9ee8f35 100644 --- a/frontend/src/components/Presigned_url_requests/CardPresigned_url_requests.tsx +++ b/frontend/src/components/Presigned_url_requests/CardPresigned_url_requests.tsx @@ -1,12 +1,9 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; -import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; +import dataFormatter from '../../helpers/dataFormatter'; +import { useAppSelector } from '../../stores/hooks'; +import ListActionsPopover from '../ListActionsPopover'; +import LoadingSpinner from '../LoadingSpinner'; +import { Pagination } from '../Pagination'; import { hasPermission } from '../../helpers/userPermissions'; import type { PresignedUrlRequest } from '../../types/entities'; @@ -50,7 +47,7 @@ const CardPresigned_url_requests = ({ className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > {!loading && - presigned_url_requests.map((item, index) => ( + presigned_url_requests.map((item) => (
    • state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( <>
      diff --git a/frontend/src/components/Presigned_url_requests/TablePresigned_url_requests.tsx b/frontend/src/components/Presigned_url_requests/TablePresigned_url_requests.tsx index 611c188..19ee4ae 100644 --- a/frontend/src/components/Presigned_url_requests/TablePresigned_url_requests.tsx +++ b/frontend/src/components/Presigned_url_requests/TablePresigned_url_requests.tsx @@ -1,13 +1,13 @@ -import { createTableComponent } from '../Factory/createTableComponent'; import { - fetch, - update, deleteItem, - setRefetch, deleteItemsByIds, + fetch, + setRefetch, + update, } from '../../stores/presigned_url_requests/presigned_url_requestsSlice'; -import { loadColumns } from './configurePresigned_url_requestsCols'; import type { PresignedUrlRequest } from '../../types/entities'; +import { createTableComponent } from '../Factory/createTableComponent'; +import { loadColumns } from './configurePresigned_url_requestsCols'; const TablePresigned_url_requests = createTableComponent({ entityName: 'presigned_url_requests', diff --git a/frontend/src/components/Presigned_url_requests/configurePresigned_url_requestsCols.tsx b/frontend/src/components/Presigned_url_requests/configurePresigned_url_requestsCols.tsx index 6d171e1..27c8adf 100644 --- a/frontend/src/components/Presigned_url_requests/configurePresigned_url_requestsCols.tsx +++ b/frontend/src/components/Presigned_url_requests/configurePresigned_url_requestsCols.tsx @@ -1,6 +1,6 @@ import { - createColumnLoader, ColumnMetadata, + createColumnLoader, } from '../DataGrid/configBuilderFactory'; const PRESIGNED_URL_REQUESTS_COLUMNS: ColumnMetadata[] = [ diff --git a/frontend/src/components/Project_audio_tracks/CardProject_audio_tracks.tsx b/frontend/src/components/Project_audio_tracks/CardProject_audio_tracks.tsx index 4a87d48..9e55b98 100644 --- a/frontend/src/components/Project_audio_tracks/CardProject_audio_tracks.tsx +++ b/frontend/src/components/Project_audio_tracks/CardProject_audio_tracks.tsx @@ -1,12 +1,9 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; -import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; +import dataFormatter from '../../helpers/dataFormatter'; +import { useAppSelector } from '../../stores/hooks'; +import ListActionsPopover from '../ListActionsPopover'; +import LoadingSpinner from '../LoadingSpinner'; +import { Pagination } from '../Pagination'; import { hasPermission } from '../../helpers/userPermissions'; import type { ProjectAudioTrack } from '../../types/entities'; @@ -50,7 +47,7 @@ const CardProject_audio_tracks = ({ className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > {!loading && - project_audio_tracks.map((item, index) => ( + project_audio_tracks.map((item) => (
    • state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( <>
      diff --git a/frontend/src/components/Project_audio_tracks/TableProject_audio_tracks.tsx b/frontend/src/components/Project_audio_tracks/TableProject_audio_tracks.tsx index 60dc4db..3db8391 100644 --- a/frontend/src/components/Project_audio_tracks/TableProject_audio_tracks.tsx +++ b/frontend/src/components/Project_audio_tracks/TableProject_audio_tracks.tsx @@ -1,13 +1,13 @@ -import { createTableComponent } from '../Factory/createTableComponent'; import { - fetch, - update, deleteItem, - setRefetch, deleteItemsByIds, + fetch, + setRefetch, + update, } from '../../stores/project_audio_tracks/project_audio_tracksSlice'; -import { loadColumns } from './configureProject_audio_tracksCols'; import type { ProjectAudioTrack } from '../../types/entities'; +import { createTableComponent } from '../Factory/createTableComponent'; +import { loadColumns } from './configureProject_audio_tracksCols'; const TableProject_audio_tracks = createTableComponent({ entityName: 'project_audio_tracks', diff --git a/frontend/src/components/Project_audio_tracks/configureProject_audio_tracksCols.tsx b/frontend/src/components/Project_audio_tracks/configureProject_audio_tracksCols.tsx index a3ddf53..d3754da 100644 --- a/frontend/src/components/Project_audio_tracks/configureProject_audio_tracksCols.tsx +++ b/frontend/src/components/Project_audio_tracks/configureProject_audio_tracksCols.tsx @@ -1,6 +1,6 @@ import { - createColumnLoader, ColumnMetadata, + createColumnLoader, } from '../DataGrid/configBuilderFactory'; const PROJECT_AUDIO_TRACKS_COLUMNS: ColumnMetadata[] = [ diff --git a/frontend/src/components/Project_memberships/CardProject_memberships.tsx b/frontend/src/components/Project_memberships/CardProject_memberships.tsx index 488c292..21ad512 100644 --- a/frontend/src/components/Project_memberships/CardProject_memberships.tsx +++ b/frontend/src/components/Project_memberships/CardProject_memberships.tsx @@ -1,12 +1,9 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; -import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; +import dataFormatter from '../../helpers/dataFormatter'; +import { useAppSelector } from '../../stores/hooks'; +import ListActionsPopover from '../ListActionsPopover'; +import LoadingSpinner from '../LoadingSpinner'; +import { Pagination } from '../Pagination'; import { hasPermission } from '../../helpers/userPermissions'; import type { ProjectMembership } from '../../types/entities'; @@ -50,7 +47,7 @@ const CardProject_memberships = ({ className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > {!loading && - project_memberships.map((item, index) => ( + project_memberships.map((item) => (
    • state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( <>
      diff --git a/frontend/src/components/Project_memberships/TableProject_memberships.tsx b/frontend/src/components/Project_memberships/TableProject_memberships.tsx index 3e6a8b6..aa9d693 100644 --- a/frontend/src/components/Project_memberships/TableProject_memberships.tsx +++ b/frontend/src/components/Project_memberships/TableProject_memberships.tsx @@ -1,13 +1,13 @@ -import { createTableComponent } from '../Factory/createTableComponent'; import { - fetch, - update, deleteItem, - setRefetch, deleteItemsByIds, + fetch, + setRefetch, + update, } from '../../stores/project_memberships/project_membershipsSlice'; -import { loadColumns } from './configureProject_membershipsCols'; import type { ProjectMembership } from '../../types/entities'; +import { createTableComponent } from '../Factory/createTableComponent'; +import { loadColumns } from './configureProject_membershipsCols'; const TableProject_memberships = createTableComponent({ entityName: 'project_memberships', diff --git a/frontend/src/components/Project_memberships/configureProject_membershipsCols.tsx b/frontend/src/components/Project_memberships/configureProject_membershipsCols.tsx index 5dd5e62..18fda04 100644 --- a/frontend/src/components/Project_memberships/configureProject_membershipsCols.tsx +++ b/frontend/src/components/Project_memberships/configureProject_membershipsCols.tsx @@ -1,6 +1,6 @@ import { - createColumnLoader, ColumnMetadata, + createColumnLoader, } from '../DataGrid/configBuilderFactory'; const PROJECT_MEMBERSHIPS_COLUMNS: ColumnMetadata[] = [ diff --git a/frontend/src/components/Projects/CardProjects.tsx b/frontend/src/components/Projects/CardProjects.tsx index c292b49..3c5e8c0 100644 --- a/frontend/src/components/Projects/CardProjects.tsx +++ b/frontend/src/components/Projects/CardProjects.tsx @@ -1,12 +1,9 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; -import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; +import dataFormatter from '../../helpers/dataFormatter'; +import { useAppSelector } from '../../stores/hooks'; +import ListActionsPopover from '../ListActionsPopover'; +import LoadingSpinner from '../LoadingSpinner'; +import { Pagination } from '../Pagination'; import { hasPermission } from '../../helpers/userPermissions'; import type { Project } from '../../types/entities'; @@ -47,7 +44,7 @@ const CardProjects = ({ className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > {!loading && - projects.map((item, index) => ( + projects.map((item) => (
    • state.auth.currentUser); const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PROJECTS'); - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( <>
      diff --git a/frontend/src/components/Projects/ProjectEditForm.tsx b/frontend/src/components/Projects/ProjectEditForm.tsx index 4a505de..8d6a00b 100644 --- a/frontend/src/components/Projects/ProjectEditForm.tsx +++ b/frontend/src/components/Projects/ProjectEditForm.tsx @@ -1,10 +1,10 @@ -import React from 'react'; import { Field, Form, Formik } from 'formik'; +import React from 'react'; +import { CANVAS_CONFIG } from '../../config/canvas.config'; import BaseButton from '../BaseButton'; import BaseButtons from '../BaseButtons'; import BaseDivider from '../BaseDivider'; import FormField from '../FormField'; -import { CANVAS_CONFIG } from '../../config/canvas.config'; import { getProjectLogoAssetLabel, getProjectLogoAssetValue, diff --git a/frontend/src/components/Projects/ProjectElementDefaultSettingsForm.tsx b/frontend/src/components/Projects/ProjectElementDefaultSettingsForm.tsx index 46af06d..396f943 100644 --- a/frontend/src/components/Projects/ProjectElementDefaultSettingsForm.tsx +++ b/frontend/src/components/Projects/ProjectElementDefaultSettingsForm.tsx @@ -1,19 +1,19 @@ +import type { AssetOption, CanvasElementType } from '../../types/constructor'; import CardBox from '../CardBox'; -import FormField from '../FormField'; -import type { CanvasElementType, AssetOption } from '../../types/constructor'; -import { - ElementSettingsTabs, - StyleSettingsSection, - EffectsSettingsSection, - CommonSettingsSection, - NavigationSettingsSection, - DescriptionSettingsSection, - MediaSettingsSection, - GallerySettingsSection, - CarouselSettingsSection, - InfoPanelSettingsSection, -} from '../ElementSettings'; import type { useElementSettingsForm } from '../ElementSettings'; +import { + CarouselSettingsSection, + CommonSettingsSection, + DescriptionSettingsSection, + EffectsSettingsSection, + ElementSettingsTabs, + GallerySettingsSection, + InfoPanelSettingsSection, + MediaSettingsSection, + NavigationSettingsSection, + StyleSettingsSection, +} from '../ElementSettings'; +import FormField from '../FormField'; import { parseProjectElementDefaultSortOrder, PROJECT_ELEMENT_DEFAULT_SETTINGS_TABS, @@ -36,7 +36,7 @@ type Props = { onSortOrderChange: (value: number) => void; onTabChange: (value: string) => void; onCommonChange: (field: string, value: string) => void; - onTypeChange: (field: string, value: string | boolean) => void; + onTypeChange: (field: string, value: string | number | boolean) => void; onStyleChange: (prop: string, value: string) => void; onEffectChange: (prop: string, value: string) => void; }; diff --git a/frontend/src/components/Projects/ProjectRelationTable.tsx b/frontend/src/components/Projects/ProjectRelationTable.tsx index 4185cd5..13c18ba 100644 --- a/frontend/src/components/Projects/ProjectRelationTable.tsx +++ b/frontend/src/components/Projects/ProjectRelationTable.tsx @@ -1,6 +1,6 @@ -import React, { ReactNode } from 'react'; -import CardBox from '../CardBox'; +import { ReactNode } from 'react'; import dataFormatter from '../../helpers/dataFormatter'; +import CardBox from '../CardBox'; import { getProjectRelationHref, getProjectRelationRows, diff --git a/frontend/src/components/Projects/TableProjects.tsx b/frontend/src/components/Projects/TableProjects.tsx index e9ab682..ccb1090 100644 --- a/frontend/src/components/Projects/TableProjects.tsx +++ b/frontend/src/components/Projects/TableProjects.tsx @@ -1,13 +1,13 @@ -import { createTableComponent } from '../Factory/createTableComponent'; import { - fetch, - update, deleteItem, - setRefetch, deleteItemsByIds, + fetch, + setRefetch, + update, } from '../../stores/projects/projectsSlice'; -import { loadColumns } from './configureProjectsCols'; import type { Project } from '../../types/entities'; +import { createTableComponent } from '../Factory/createTableComponent'; +import { loadColumns } from './configureProjectsCols'; const TableProjects = createTableComponent({ entityName: 'projects', diff --git a/frontend/src/components/Projects/configureProjectsCols.tsx b/frontend/src/components/Projects/configureProjectsCols.tsx index 779adf4..f7f034a 100644 --- a/frontend/src/components/Projects/configureProjectsCols.tsx +++ b/frontend/src/components/Projects/configureProjectsCols.tsx @@ -1,6 +1,6 @@ import { - createColumnLoader, ColumnMetadata, + createColumnLoader, } from '../DataGrid/configBuilderFactory'; const PROJECTS_COLUMNS: ColumnMetadata[] = [ diff --git a/frontend/src/components/Publish_events/CardPublish_events.tsx b/frontend/src/components/Publish_events/CardPublish_events.tsx index 26c1897..afc1eb1 100644 --- a/frontend/src/components/Publish_events/CardPublish_events.tsx +++ b/frontend/src/components/Publish_events/CardPublish_events.tsx @@ -1,12 +1,9 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; -import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; +import dataFormatter from '../../helpers/dataFormatter'; +import { useAppSelector } from '../../stores/hooks'; +import ListActionsPopover from '../ListActionsPopover'; +import LoadingSpinner from '../LoadingSpinner'; +import { Pagination } from '../Pagination'; import { hasPermission } from '../../helpers/userPermissions'; import type { PublishEvent } from '../../types/entities'; @@ -50,7 +47,7 @@ const CardPublish_events = ({ className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > {!loading && - publish_events.map((item, index) => ( + publish_events.map((item) => (
    • state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( <>
      diff --git a/frontend/src/components/Publish_events/TablePublish_events.tsx b/frontend/src/components/Publish_events/TablePublish_events.tsx index 2125386..f467b66 100644 --- a/frontend/src/components/Publish_events/TablePublish_events.tsx +++ b/frontend/src/components/Publish_events/TablePublish_events.tsx @@ -1,13 +1,13 @@ -import { createTableComponent } from '../Factory/createTableComponent'; import { - fetch, - update, deleteItem, - setRefetch, deleteItemsByIds, + fetch, + setRefetch, + update, } from '../../stores/publish_events/publish_eventsSlice'; -import { loadColumns } from './configurePublish_eventsCols'; import type { PublishEvent } from '../../types/entities'; +import { createTableComponent } from '../Factory/createTableComponent'; +import { loadColumns } from './configurePublish_eventsCols'; const TablePublish_events = createTableComponent({ entityName: 'publish_events', diff --git a/frontend/src/components/Publish_events/configurePublish_eventsCols.tsx b/frontend/src/components/Publish_events/configurePublish_eventsCols.tsx index 86be7d2..30a7450 100644 --- a/frontend/src/components/Publish_events/configurePublish_eventsCols.tsx +++ b/frontend/src/components/Publish_events/configurePublish_eventsCols.tsx @@ -1,6 +1,6 @@ import { - createColumnLoader, ColumnMetadata, + createColumnLoader, } from '../DataGrid/configBuilderFactory'; const PUBLISH_EVENTS_COLUMNS: ColumnMetadata[] = [ diff --git a/frontend/src/components/Pwa_caches/CardPwa_caches.tsx b/frontend/src/components/Pwa_caches/CardPwa_caches.tsx index a5b935e..73baf2a 100644 --- a/frontend/src/components/Pwa_caches/CardPwa_caches.tsx +++ b/frontend/src/components/Pwa_caches/CardPwa_caches.tsx @@ -1,12 +1,9 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; -import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; +import dataFormatter from '../../helpers/dataFormatter'; +import { useAppSelector } from '../../stores/hooks'; +import ListActionsPopover from '../ListActionsPopover'; +import LoadingSpinner from '../LoadingSpinner'; +import { Pagination } from '../Pagination'; import { hasPermission } from '../../helpers/userPermissions'; import type { PwaCache } from '../../types/entities'; @@ -47,7 +44,7 @@ const CardPwa_caches = ({ className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > {!loading && - pwa_caches.map((item, index) => ( + pwa_caches.map((item) => (
    • state.auth.currentUser); const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PWA_CACHES'); - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( <>
      diff --git a/frontend/src/components/Pwa_caches/TablePwa_caches.tsx b/frontend/src/components/Pwa_caches/TablePwa_caches.tsx index 3eea1fd..691323b 100644 --- a/frontend/src/components/Pwa_caches/TablePwa_caches.tsx +++ b/frontend/src/components/Pwa_caches/TablePwa_caches.tsx @@ -1,13 +1,13 @@ -import { createTableComponent } from '../Factory/createTableComponent'; import { - fetch, - update, deleteItem, - setRefetch, deleteItemsByIds, + fetch, + setRefetch, + update, } from '../../stores/pwa_caches/pwa_cachesSlice'; -import { loadColumns } from './configurePwa_cachesCols'; import type { PwaCache } from '../../types/entities'; +import { createTableComponent } from '../Factory/createTableComponent'; +import { loadColumns } from './configurePwa_cachesCols'; const TablePwa_caches = createTableComponent({ entityName: 'pwa_caches', diff --git a/frontend/src/components/Pwa_caches/configurePwa_cachesCols.tsx b/frontend/src/components/Pwa_caches/configurePwa_cachesCols.tsx index 5393eb7..4032a0b 100644 --- a/frontend/src/components/Pwa_caches/configurePwa_cachesCols.tsx +++ b/frontend/src/components/Pwa_caches/configurePwa_cachesCols.tsx @@ -1,6 +1,6 @@ import { - createColumnLoader, ColumnMetadata, + createColumnLoader, } from '../DataGrid/configBuilderFactory'; const PWA_CACHES_COLUMNS: ColumnMetadata[] = [ diff --git a/frontend/src/components/Roles/CardRoles.tsx b/frontend/src/components/Roles/CardRoles.tsx index 4121245..e02e541 100644 --- a/frontend/src/components/Roles/CardRoles.tsx +++ b/frontend/src/components/Roles/CardRoles.tsx @@ -1,12 +1,9 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; -import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; +import dataFormatter from '../../helpers/dataFormatter'; +import { useAppSelector } from '../../stores/hooks'; +import ListActionsPopover from '../ListActionsPopover'; +import LoadingSpinner from '../LoadingSpinner'; +import { Pagination } from '../Pagination'; import { hasPermission } from '../../helpers/userPermissions'; import type { Role } from '../../types/entities'; @@ -47,7 +44,7 @@ const CardRoles = ({ className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > {!loading && - roles.map((item, index) => ( + roles.map((item) => (
    • state.auth.currentUser); const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ROLES'); - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( <>
      diff --git a/frontend/src/components/Roles/TableRoles.tsx b/frontend/src/components/Roles/TableRoles.tsx index 8e40cde..9d97e00 100644 --- a/frontend/src/components/Roles/TableRoles.tsx +++ b/frontend/src/components/Roles/TableRoles.tsx @@ -1,13 +1,13 @@ -import { createTableComponent } from '../Factory/createTableComponent'; import { - fetch, - update, deleteItem, - setRefetch, deleteItemsByIds, + fetch, + setRefetch, + update, } from '../../stores/roles/rolesSlice'; -import { loadColumns } from './configureRolesCols'; import type { Role } from '../../types/entities'; +import { createTableComponent } from '../Factory/createTableComponent'; +import { loadColumns } from './configureRolesCols'; const TableRoles = createTableComponent({ entityName: 'roles', diff --git a/frontend/src/components/Roles/configureRolesCols.tsx b/frontend/src/components/Roles/configureRolesCols.tsx index e5f243e..4d03dc2 100644 --- a/frontend/src/components/Roles/configureRolesCols.tsx +++ b/frontend/src/components/Roles/configureRolesCols.tsx @@ -1,6 +1,6 @@ import { - createColumnLoader, ColumnMetadata, + createColumnLoader, } from '../DataGrid/configBuilderFactory'; const ROLES_COLUMNS: ColumnMetadata[] = [ diff --git a/frontend/src/components/RotatePrompt.tsx b/frontend/src/components/RotatePrompt.tsx index e7b0daf..ce3e7a0 100644 --- a/frontend/src/components/RotatePrompt.tsx +++ b/frontend/src/components/RotatePrompt.tsx @@ -5,9 +5,9 @@ * Follows pattern: components/Offline/OfflineStatus.tsx */ -import React from 'react'; -import Icon from '@mdi/react'; import { mdiScreenRotation } from '@mdi/js'; +import Icon from '@mdi/react'; +import React from 'react'; interface RotatePromptProps { /** Whether to show the prompt */ diff --git a/frontend/src/components/Runtime/RuntimeCanvasStage.tsx b/frontend/src/components/Runtime/RuntimeCanvasStage.tsx index be5f901..e51bb80 100644 --- a/frontend/src/components/Runtime/RuntimeCanvasStage.tsx +++ b/frontend/src/components/Runtime/RuntimeCanvasStage.tsx @@ -1,20 +1,10 @@ import { Fragment, type CSSProperties, type RefObject } from 'react'; -import { BackdropPortalProvider } from '../BackdropPortal'; -import CanvasBackground from '../Constructor/CanvasBackground'; -import TransitionPreviewOverlay from '../Constructor/TransitionPreviewOverlay'; -import CanvasLoadingSpinner from '../CanvasLoadingSpinner'; -import RuntimeControls from './RuntimeControls'; -import RuntimeElement from '../RuntimeElement'; -import GalleryCarouselOverlay from '../UiElements/GalleryCarouselOverlay'; -import ImageDetailPanel from '../UiElements/ImageDetailPanel'; -import InfoPanelOverlay from '../UiElements/InfoPanelOverlay'; -import TransitionBlackOverlay from '../TransitionBlackOverlay'; +import type { PreloadCacheProvider } from '../../hooks/video'; import { isSafari } from '../../lib/browserUtils'; import { isElementFlagEnabled } from '../../lib/elementFlags'; import { isInfoPanelElementType } from '../../lib/elementTypeGuards'; import { isNavigationType } from '../../lib/navigationHelpers'; -import type { PreloadCacheProvider } from '../../hooks/video'; import type { CanvasElement, GalleryCarouselMediaItem, @@ -24,13 +14,23 @@ import type { InfoPanelImage } from '../../types/infoPanel'; import type { TransitionPhase } from '../../types/presentation'; import type { ResolvedTransitionSettings } from '../../types/transition'; import type { ResolvedUiControlsSettings } from '../../types/uiControls'; -import type { RuntimeTransitionPreview } from './runtimePresentation.helpers'; +import { BackdropPortalProvider } from '../BackdropPortal'; +import CanvasLoadingSpinner from '../CanvasLoadingSpinner'; +import CanvasBackground from '../Constructor/CanvasBackground'; +import TransitionPreviewOverlay from '../Constructor/TransitionPreviewOverlay'; +import RuntimeElement from '../RuntimeElement'; +import TransitionBlackOverlay from '../TransitionBlackOverlay'; +import GalleryCarouselOverlay from '../UiElements/GalleryCarouselOverlay'; +import ImageDetailPanel from '../UiElements/ImageDetailPanel'; +import InfoPanelOverlay from '../UiElements/InfoPanelOverlay'; import { isRuntimeTransitionOverlayBuffering, shouldShowRuntimeCanvasElements, shouldShowRuntimeCanvasSpinner, shouldShowRuntimeControls, } from './RuntimeCanvasStage.helpers'; +import RuntimeControls from './RuntimeControls'; +import type { RuntimeTransitionPreview } from './runtimePresentation.helpers'; type ActiveRuntimeGalleryCarousel = { element: CanvasElement; @@ -105,7 +105,7 @@ type RuntimeCanvasStageProps = { activeInfoPanelGallery: ActiveRuntimeInfoPanelGallery; activeInfoPanelGalleryElement: CanvasElement | null; activeInfoPanelElements: CanvasElement[]; - activeDetailImages: Record; + activeDetailImages: Record; runtimeSelectedImageIds: Record; onCanvasInteraction: () => void; onBackgroundReady: () => void; @@ -120,7 +120,7 @@ type RuntimeCanvasStageProps = { onCloseInfoPanelGallery: () => void; onCloseAllInfoPanels: () => void; onCloseInfoPanel: (panelId: string) => void; - onSetDetailImage: (panelId: string, image: InfoPanelImage) => void; + onSetDetailImage: (panelId: string, image: InfoPanelImage | null) => void; onCloseDetailImage: (panelId: string) => void; onOpenInfoPanelGallery: ( panelId: string, diff --git a/frontend/src/components/Runtime/RuntimeControls.tsx b/frontend/src/components/Runtime/RuntimeControls.tsx index bd530f1..9c21736 100644 --- a/frontend/src/components/Runtime/RuntimeControls.tsx +++ b/frontend/src/components/Runtime/RuntimeControls.tsx @@ -6,24 +6,24 @@ * across devices for projects with the same canvas ratio. */ -import React, { CSSProperties, useEffect, useState } from 'react'; import { mdiFullscreen, mdiFullscreenExit, mdiVolumeHigh, mdiVolumeOff, } from '@mdi/js'; +import React, { CSSProperties, useEffect, useState } from 'react'; import type { PreloadPage } from '../../types/preload'; import { DEFAULT_UI_CONTROL_SETTINGS, type ResolvedUiControlsSettings, type SystemUiControlType, } from '../../types/uiControls'; +import { RuntimeControlButton } from './RuntimeControlButton'; import { getRuntimeControlOrder, getRuntimeControlPositionStyle, } from './runtimeControls.helpers'; -import { RuntimeControlButton } from './RuntimeControlButton'; import { RuntimeOfflineControl } from './RuntimeOfflineControl'; interface RuntimeControlsProps { diff --git a/frontend/src/components/Runtime/RuntimeOfflineControl.tsx b/frontend/src/components/Runtime/RuntimeOfflineControl.tsx index 8fe2a2d..8daa420 100644 --- a/frontend/src/components/Runtime/RuntimeOfflineControl.tsx +++ b/frontend/src/components/Runtime/RuntimeOfflineControl.tsx @@ -6,8 +6,8 @@ import { logger } from '../../lib/logger'; import type { ProjectOfflineStatus } from '../../types/offline'; import type { PreloadPage } from '../../types/preload'; import type { ResolvedSystemUiControlSettings } from '../../types/uiControls'; -import { getOfflineControlPresentation } from './runtimeControls.helpers'; import { RuntimeControlButton } from './RuntimeControlButton'; +import { getOfflineControlPresentation } from './runtimeControls.helpers'; export function RuntimeOfflineControl({ projectId, diff --git a/frontend/src/components/Runtime/runtimeControls.helpers.test.ts b/frontend/src/components/Runtime/runtimeControls.helpers.test.ts index 852540f..2d842b7 100644 --- a/frontend/src/components/Runtime/runtimeControls.helpers.test.ts +++ b/frontend/src/components/Runtime/runtimeControls.helpers.test.ts @@ -1,9 +1,9 @@ +import { mdiCloudDownload, mdiCloudOff, mdiDelete, mdiLoading } from '@mdi/js'; import assert from 'node:assert/strict'; import test from 'node:test'; -import { mdiCloudDownload, mdiCloudOff, mdiDelete, mdiLoading } from '@mdi/js'; -import { DEFAULT_UI_CONTROL_SETTINGS } from '../../types/uiControls'; import type { ResolvedUiControlsSettings } from '../../types/uiControls'; +import { DEFAULT_UI_CONTROL_SETTINGS } from '../../types/uiControls'; import { buildRuntimeControlButtonStyle, getOfflineControlPresentation, diff --git a/frontend/src/components/Runtime/runtimeControls.helpers.ts b/frontend/src/components/Runtime/runtimeControls.helpers.ts index 6faa81c..07c6a02 100644 --- a/frontend/src/components/Runtime/runtimeControls.helpers.ts +++ b/frontend/src/components/Runtime/runtimeControls.helpers.ts @@ -1,5 +1,6 @@ -import type { CSSProperties } from 'react'; import { mdiCloudDownload, mdiCloudOff, mdiDelete, mdiLoading } from '@mdi/js'; +import type { CSSProperties } from 'react'; +import type { ProjectOfflineStatus } from '../../types/offline'; import { getAnchorTransform, getSystemControlAnchorBounds, @@ -7,7 +8,6 @@ import { type ResolvedUiControlsSettings, type SystemUiControlType, } from '../../types/uiControls'; -import type { ProjectOfflineStatus } from '../../types/offline'; export const runtimeControlButtonColors = { info: { diff --git a/frontend/src/components/Runtime/runtimePresentation.helpers.test.ts b/frontend/src/components/Runtime/runtimePresentation.helpers.test.ts index db459d1..cc394d6 100644 --- a/frontend/src/components/Runtime/runtimePresentation.helpers.test.ts +++ b/frontend/src/components/Runtime/runtimePresentation.helpers.test.ts @@ -1,18 +1,18 @@ import assert from 'node:assert/strict'; import test from 'node:test'; +import type { PreloadPageLink } from '../../types/preload'; import { buildRuntimeTransitionPlaybackConfig, extractRuntimePageElements, - getRuntimeBackgroundSources, getRuntimeBackgroundPlaybackSettings, + getRuntimeBackgroundSources, getRuntimePreloadElements, getRuntimeUiControlsSettings, resolveRuntimeUrlWithBlob, - type RuntimeTransitionPreview, type RuntimePageWithSchema, + type RuntimeTransitionPreview, } from './runtimePresentation.helpers'; -import type { PreloadPageLink } from '../../types/preload'; const page = ( id: string, diff --git a/frontend/src/components/Runtime/runtimePresentation.helpers.ts b/frontend/src/components/Runtime/runtimePresentation.helpers.ts index 604a545..b8b75b2 100644 --- a/frontend/src/components/Runtime/runtimePresentation.helpers.ts +++ b/frontend/src/components/Runtime/runtimePresentation.helpers.ts @@ -1,8 +1,8 @@ +import type { TransitionConfig } from '../../hooks/useTransitionPlayback'; +import { extractElementsForPages } from '../../lib/extractPageLinks'; import type { CanvasElement } from '../../types/constructor'; import type { TourPage } from '../../types/entities'; import type { PreloadElement, PreloadPageLink } from '../../types/preload'; -import type { TransitionConfig } from '../../hooks/useTransitionPlayback'; -import { extractElementsForPages } from '../../lib/extractPageLinks'; import { parseUiControlsSettings, resolveUiControlsSettings, @@ -30,7 +30,7 @@ type RuntimePreloadLog = { }; export type RuntimeTransitionPreview = { - targetPageId: string; + targetPageId: string | undefined; videoUrl: string; storageKey: string; isBack: boolean; @@ -92,7 +92,7 @@ export const resolveRuntimeUrlWithBlob = ({ resolveAssetUrl, }: { url: string | undefined; - getReadyBlobUrl?: (url: string) => string | undefined; + getReadyBlobUrl?: (url: string) => string | null | undefined; resolveAssetUrl: (url: string) => string; }) => { if (!url) return ''; @@ -131,8 +131,8 @@ export const getRuntimeUiControlsSettings = ({ pageSettingsJson?: unknown; }) => resolveUiControlsSettings( - globalSettingsJson, - projectSettingsJson, + parseUiControlsSettings(globalSettingsJson), + parseUiControlsSettings(projectSettingsJson), parseUiControlsSettings(pageSettingsJson), ); diff --git a/frontend/src/components/Runtime/useRuntimeInfoPanels.ts b/frontend/src/components/Runtime/useRuntimeInfoPanels.ts index 1661a0e..0b1016f 100644 --- a/frontend/src/components/Runtime/useRuntimeInfoPanels.ts +++ b/frontend/src/components/Runtime/useRuntimeInfoPanels.ts @@ -1,9 +1,4 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { - buildInfoPanelGalleryState, - getExternalHref, - getInfoPanelBackgroundSources, -} from '../UiElements/infoPanelMedia.helpers'; import { findDefaultOpenInfoPanelElements } from '../../lib/elementFlags'; import { isInfoPanelElementType } from '../../lib/elementTypeGuards'; import type { @@ -11,6 +6,11 @@ import type { GalleryCarouselMediaItem, } from '../../types/constructor'; import type { InfoPanelImage } from '../../types/infoPanel'; +import { + buildInfoPanelGalleryState, + getExternalHref, + getInfoPanelBackgroundSources, +} from '../UiElements/infoPanelMedia.helpers'; interface UseRuntimeInfoPanelsOptions { pageElements: CanvasElement[]; @@ -18,7 +18,7 @@ interface UseRuntimeInfoPanelsOptions { isNavigationBlocked: boolean; navigateToPage: (targetPageId: string) => void; currentAudioUrl: string; - resolveAssetUrl: (url: string) => string; + resolveAssetUrl: (url: string | undefined) => string; setBackgroundDirectly: ( imageUrl?: string, videoUrl?: string, @@ -41,7 +41,7 @@ export function useRuntimeInfoPanels({ const defaultInfoPanelPageIdRef = useRef(null); const [activeInfoPanelIds, setActiveInfoPanelIds] = useState([]); const [activeDetailImages, setActiveDetailImages] = useState< - Record + Record >({}); const [activeInfoPanelGallery, setActiveInfoPanelGallery] = useState<{ panelId: string; @@ -104,7 +104,7 @@ export function useRuntimeInfoPanels({ }, []); const setDetailImage = useCallback( - (panelId: string, image: InfoPanelImage) => { + (panelId: string, image: InfoPanelImage | null) => { setActiveDetailImages((current) => ({ ...current, [panelId]: image, diff --git a/frontend/src/components/Runtime/useRuntimeNavigationState.ts b/frontend/src/components/Runtime/useRuntimeNavigationState.ts index 6ff13c2..cbb05cd 100644 --- a/frontend/src/components/Runtime/useRuntimeNavigationState.ts +++ b/frontend/src/components/Runtime/useRuntimeNavigationState.ts @@ -11,11 +11,11 @@ import type { GlobalTransitionDefaults, ProjectTransitionSettings, } from '../../types/transition'; +import { getRuntimePageLinkLogDetails } from './runtimeNavigationState.helpers'; import { getRuntimePreloadElements, getRuntimeUiControlsSettings, } from './runtimePresentation.helpers'; -import { getRuntimePageLinkLogDetails } from './runtimeNavigationState.helpers'; type UseRuntimeNavigationStateOptions = { pages: TourPage[]; diff --git a/frontend/src/components/Runtime/useRuntimePrivateAuthRedirect.ts b/frontend/src/components/Runtime/useRuntimePrivateAuthRedirect.ts index 922b70b..0a2b48f 100644 --- a/frontend/src/components/Runtime/useRuntimePrivateAuthRedirect.ts +++ b/frontend/src/components/Runtime/useRuntimePrivateAuthRedirect.ts @@ -1,7 +1,7 @@ -import { useEffect } from 'react'; import type { NextRouter } from 'next/router'; -import type { AppDispatch } from '../../stores/store'; +import { useEffect } from 'react'; import { logoutUser } from '../../stores/authSlice'; +import type { AppDispatch } from '../../stores/store'; interface UseRuntimePrivateAuthRedirectOptions { dispatch: AppDispatch; diff --git a/frontend/src/components/Runtime/useRuntimeSettingsFetch.ts b/frontend/src/components/Runtime/useRuntimeSettingsFetch.ts index df1a57f..260fcdc 100644 --- a/frontend/src/components/Runtime/useRuntimeSettingsFetch.ts +++ b/frontend/src/components/Runtime/useRuntimeSettingsFetch.ts @@ -1,9 +1,9 @@ import { useEffect } from 'react'; -import type { AppDispatch } from '../../stores/store'; import { fetch as fetchGlobalTransitionDefaults } from '../../stores/global_transition_defaults/globalTransitionDefaultsSlice'; import { fetch as fetchGlobalUiControlDefaults } from '../../stores/global_ui_control_defaults/globalUiControlDefaultsSlice'; import { fetchByProjectAndEnv as fetchProjectTransitionSettings } from '../../stores/project_transition_settings/projectTransitionSettingsSlice'; import { fetchByProjectAndEnv as fetchProjectUiControlSettings } from '../../stores/project_ui_control_settings/projectUiControlSettingsSlice'; +import type { AppDispatch } from '../../stores/store'; interface UseRuntimeSettingsFetchOptions { dispatch: AppDispatch; diff --git a/frontend/src/components/Runtime/useRuntimeTransitionPlaybackController.ts b/frontend/src/components/Runtime/useRuntimeTransitionPlaybackController.ts index fe726c2..14f02a2 100644 --- a/frontend/src/components/Runtime/useRuntimeTransitionPlaybackController.ts +++ b/frontend/src/components/Runtime/useRuntimeTransitionPlaybackController.ts @@ -1,8 +1,8 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import { useTransitionPlayback } from '../../hooks/useTransitionPlayback'; -import { downloadManager } from '../../lib/offline/DownloadManager'; import { logger } from '../../lib/logger'; +import { downloadManager } from '../../lib/offline/DownloadManager'; import type { TourPage } from '../../types/entities'; import { buildRuntimeTransitionPlaybackConfig, diff --git a/frontend/src/components/RuntimeElement.tsx b/frontend/src/components/RuntimeElement.tsx index 852d5ef..ae9fc5c 100644 --- a/frontend/src/components/RuntimeElement.tsx +++ b/frontend/src/components/RuntimeElement.tsx @@ -7,20 +7,20 @@ */ import React from 'react'; -import UiElementRenderer from './UiElements/UiElementRenderer'; -import { useElementEffects } from '../hooks/useElementEffects'; import { useAudioEffects } from '../hooks/useAudioEffects'; +import { useElementEffects } from '../hooks/useElementEffects'; +import type { PreloadCacheProvider } from '../hooks/video'; import { - buildTransitionStyle, buildAppearAnimationStyle, - hasAnyEffects, + buildTransitionStyle, extractEffectProperties, + hasAnyEffects, } from '../lib/elementEffects'; +import { normalizeZIndexValue } from '../lib/elementStyles'; +import { isInfoPanelElementType } from '../lib/elementTypeGuards'; import type { CanvasElement } from '../types/constructor'; import type { ResolvedTransitionSettings } from '../types/transition'; -import type { PreloadCacheProvider } from '../hooks/video'; -import { isInfoPanelElementType } from '../lib/elementTypeGuards'; -import { normalizeZIndexValue } from '../lib/elementStyles'; +import UiElementRenderer from './UiElements/UiElementRenderer'; interface RuntimeElementProps { element: CanvasElement; diff --git a/frontend/src/components/RuntimePresentation.tsx b/frontend/src/components/RuntimePresentation.tsx index db3ddfe..262fcf9 100644 --- a/frontend/src/components/RuntimePresentation.tsx +++ b/frontend/src/components/RuntimePresentation.tsx @@ -7,7 +7,7 @@ import Head from 'next/head'; import { useRouter } from 'next/router'; -import React, { +import { ReactElement, useCallback, useEffect, @@ -18,56 +18,56 @@ import React, { import { flushSync } from 'react-dom'; import { ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; -import CardBox from './CardBox'; -import { RotatePrompt } from './RotatePrompt'; import { useCanvasScale } from '../hooks/useCanvasScale'; -import LayoutGuest from '../layouts/Guest'; -import { useVideoSoundControl } from '../hooks/useVideoSoundControl'; -import { usePageDataLoader } from '../hooks/usePageDataLoader'; -import { useProjectAssets } from '../hooks/useProjectAssets'; -import { usePageNavigation } from '../hooks/usePageNavigation'; -import { - extractRuntimePageElements, - getRuntimeBackgroundSources, - getRuntimeBackgroundPlaybackSettings, - resolveRuntimeUrlWithBlob, -} from './Runtime/runtimePresentation.helpers'; import { useNetworkAware } from '../hooks/useNetworkAware'; +import { usePageDataLoader } from '../hooks/usePageDataLoader'; +import { usePageNavigation } from '../hooks/usePageNavigation'; +import { useProjectAssets } from '../hooks/useProjectAssets'; +import { useVideoSoundControl } from '../hooks/useVideoSoundControl'; +import LayoutGuest from '../layouts/Guest'; import { resolveAssetPlaybackUrl } from '../lib/assetUrl'; -import { presentationHasAudio } from '../lib/presentationAudio'; -import { downloadManager } from '../lib/offline/DownloadManager'; -import { logger } from '../lib/logger'; import { backgroundAudioController } from '../lib/backgroundAudioController'; +import { isElementFlagEnabled } from '../lib/elementFlags'; +import { isInfoPanelElementType } from '../lib/elementTypeGuards'; +import { logger } from '../lib/logger'; import { - resolveNavigationTarget, - isTransitionBlocking, isBackNavigation, isNavigationType, + isTransitionBlocking, + resolveNavigationTarget, } from '../lib/navigationHelpers'; -import { useAppSelector, useAppDispatch } from '../stores/hooks'; +import { downloadManager } from '../lib/offline/DownloadManager'; +import { presentationHasAudio } from '../lib/presentationAudio'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; import { selectByProjectAndEnv as selectProjectTransitionSettings } from '../stores/project_transition_settings/projectTransitionSettingsSlice'; import { selectByProjectAndEnv as selectProjectUiControlSettings } from '../stores/project_ui_control_settings/projectUiControlSettingsSlice'; -import type { TransitionPhase } from '../types/presentation'; import type { CanvasElement } from '../types/constructor'; -import { isInfoPanelElementType } from '../lib/elementTypeGuards'; -import { isElementFlagEnabled } from '../lib/elementFlags'; +import type { TransitionPhase } from '../types/presentation'; import type { ElementTransitionSettings } from '../types/transition'; import { entityToProjectSettings, extractElementTransitionSettings, } from '../types/transition'; -import { useRuntimeNavigationState } from './Runtime/useRuntimeNavigationState'; -import { useRuntimeTransitionPlaybackController } from './Runtime/useRuntimeTransitionPlaybackController'; -import { useRuntimeInfoPanels } from './Runtime/useRuntimeInfoPanels'; -import { useRuntimeFullscreen } from './Runtime/useRuntimeFullscreen'; -import { useRuntimePrivateAuthRedirect } from './Runtime/useRuntimePrivateAuthRedirect'; -import { useRuntimeSettingsFetch } from './Runtime/useRuntimeSettingsFetch'; +import CardBox from './CardBox'; +import { RotatePrompt } from './RotatePrompt'; +import RuntimeCanvasStage from './Runtime/RuntimeCanvasStage'; import { buildRuntimeTransitionPreview, buildSkippedTransitionDownloadJob, getRuntimeNavigationDecision, } from './Runtime/runtimeNavigation.actions'; -import RuntimeCanvasStage from './Runtime/RuntimeCanvasStage'; +import { + extractRuntimePageElements, + getRuntimeBackgroundPlaybackSettings, + getRuntimeBackgroundSources, + resolveRuntimeUrlWithBlob, +} from './Runtime/runtimePresentation.helpers'; +import { useRuntimeFullscreen } from './Runtime/useRuntimeFullscreen'; +import { useRuntimeInfoPanels } from './Runtime/useRuntimeInfoPanels'; +import { useRuntimeNavigationState } from './Runtime/useRuntimeNavigationState'; +import { useRuntimePrivateAuthRedirect } from './Runtime/useRuntimePrivateAuthRedirect'; +import { useRuntimeSettingsFetch } from './Runtime/useRuntimeSettingsFetch'; +import { useRuntimeTransitionPlaybackController } from './Runtime/useRuntimeTransitionPlaybackController'; interface RuntimePresentationProps { projectSlug: string; @@ -235,7 +235,7 @@ export default function RuntimePresentation({ getCachedBlobUrl: preloadOrchestrator.getCachedBlobUrl, } : undefined, - [preloadOrchestrator?.getReadyBlob, preloadOrchestrator?.getCachedBlobUrl], + [preloadOrchestrator], ); const { @@ -340,8 +340,8 @@ export default function RuntimePresentation({ // Play transition using useTransitionPlayback hook setTransitionPreview( buildRuntimeTransitionPreview({ - targetPageId, - transitionVideoUrl, + targetPageId: targetPageId || '', + transitionVideoUrl: transitionVideoUrl || '', reverseVideoUrl, isBack, resolveAssetUrl: resolveAssetPlaybackUrl, @@ -399,6 +399,7 @@ export default function RuntimePresentation({ resetFadeIn, applyPageSelection, startTransition, + setTransitionPreview, shouldUseVideoTransitions, networkInfo, preloadOrchestrator, @@ -441,8 +442,6 @@ export default function RuntimePresentation({ // Page loading state from unified navigation state machine // navShowSpinner: true when phase is 'preparing', 'loading_bg', or 'transition_done' // navShowElements: true when phase is 'idle' or 'fading_in' - const areTransitionsReady = preloadOrchestrator?.areTransitionsReady ?? true; - const handleElementClick = useCallback( (element: CanvasElement) => { if ( @@ -538,7 +537,7 @@ export default function RuntimePresentation({ // Handler for gallery card clicks const handleGalleryCardClick = useCallback( (element: CanvasElement, cardIndex: number) => { - if (element.galleryCards?.length > 0) { + if ((element.galleryCards?.length ?? 0) > 0) { setActiveGalleryCarousel({ element, initialIndex: cardIndex }); } }, @@ -564,9 +563,7 @@ export default function RuntimePresentation({ embedUrl: navCurrentBgEmbedUrl, audioUrl: navCurrentBgAudioUrl, }); - const backgroundImageUrl = backgroundSources.imageUrl; const backgroundVideoUrl = backgroundSources.videoUrl; - const backgroundEmbedUrl = backgroundSources.embedUrl; const backgroundAudioUrl = backgroundSources.audioUrl; const backgroundPlaybackSettings = diff --git a/frontend/src/components/Search.tsx b/frontend/src/components/Search.tsx index 4f6de00..abbedc7 100644 --- a/frontend/src/components/Search.tsx +++ b/frontend/src/components/Search.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Field, Form, Formik } from 'formik'; import { useRouter } from 'next/router'; import { useAppSelector } from '../stores/hooks'; @@ -8,8 +7,8 @@ const Search = () => { const focusRing = useAppSelector((state) => state.style.focusRingColor); const corners = useAppSelector((state) => state.style.corners); const cardsStyle = useAppSelector((state) => state.style.cardsStyle); - const validateSearch = (value) => { - let error; + const validateSearch = (value: string) => { + let error: string | undefined; if (!value) { error = 'Required'; } else if (value.length < 2) { diff --git a/frontend/src/components/SearchResults.tsx b/frontend/src/components/SearchResults.tsx index 4a417ca..76c376c 100644 --- a/frontend/src/components/SearchResults.tsx +++ b/frontend/src/components/SearchResults.tsx @@ -1,9 +1,16 @@ -import React from 'react'; -import CardBox from './CardBox'; import { useRouter } from 'next/router'; import { humanize } from '../helpers/humanize'; +import CardBox from './CardBox'; -const SearchResults = ({ searchResults, searchQuery }) => { +export type SearchResultRow = Record; +export type SearchResultsByTable = Record; + +interface SearchResultsProps { + searchResults: SearchResultsByTable; + searchQuery: string; +} + +const SearchResults = ({ searchResults, searchQuery }: SearchResultsProps) => { const router = useRouter(); return ( @@ -56,7 +63,7 @@ const SearchResults = ({ searchResults, searchQuery }) => { ) } > - {item[key]} + {String(item[key] ?? '')} ); } diff --git a/frontend/src/components/SectionFullScreen.tsx b/frontend/src/components/SectionFullScreen.tsx index 3186d9a..a7799d8 100644 --- a/frontend/src/components/SectionFullScreen.tsx +++ b/frontend/src/components/SectionFullScreen.tsx @@ -1,12 +1,12 @@ -import React, { ReactNode } from 'react'; -import { BgKey } from '../types/ui'; +import { ReactNode } from 'react'; import { - gradientBgPurplePink, gradientBgDark, gradientBgPinkRed, + gradientBgPurplePink, gradientBgViolet, } from '../colors'; import { useAppSelector } from '../stores/hooks'; +import { BgKey } from '../types/ui'; type Props = { bg: BgKey; diff --git a/frontend/src/components/SectionMain.tsx b/frontend/src/components/SectionMain.tsx index ba57321..e0c32a9 100644 --- a/frontend/src/components/SectionMain.tsx +++ b/frontend/src/components/SectionMain.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import { ReactNode } from 'react'; import { containerMaxW } from '../config'; type Props = { diff --git a/frontend/src/components/SectionTitle.tsx b/frontend/src/components/SectionTitle.tsx index c07d85b..f2409ff 100644 --- a/frontend/src/components/SectionTitle.tsx +++ b/frontend/src/components/SectionTitle.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import { ReactNode } from 'react'; type Props = { custom?: boolean; diff --git a/frontend/src/components/SectionTitleLineWithButton.tsx b/frontend/src/components/SectionTitleLineWithButton.tsx index 8cfd157..8d8eea2 100644 --- a/frontend/src/components/SectionTitleLineWithButton.tsx +++ b/frontend/src/components/SectionTitleLineWithButton.tsx @@ -1,9 +1,9 @@ import { mdiCog } from '@mdi/js'; -import React, { Children, ReactNode } from 'react'; +import { Children, ReactNode } from 'react'; +import { humanize } from '../helpers/humanize'; import BaseButton from './BaseButton'; import BaseIcon from './BaseIcon'; import IconRounded from './IconRounded'; -import { humanize } from '../helpers/humanize'; type Props = { icon: string; diff --git a/frontend/src/components/SelectField.tsx b/frontend/src/components/SelectField.tsx index 307cc7f..693b67f 100644 --- a/frontend/src/components/SelectField.tsx +++ b/frontend/src/components/SelectField.tsx @@ -1,6 +1,36 @@ -import React, { useEffect, useId, useState } from 'react'; -import { AsyncPaginate } from 'react-select-async-paginate'; import axios from 'axios'; +import { useEffect, useId, useState } from 'react'; +import type { SingleValue } from 'react-select'; +import { AsyncPaginate, type LoadOptions } from 'react-select-async-paginate'; + +export interface SelectOption { + value: string; + label: string; +} + +interface SelectFieldProps { + options?: Record | null; + field: { + name: string; + value?: Record | string | null; + }; + form: { + setFieldValue: ( + field: string, + value: string | null, + shouldValidate?: boolean, + ) => void; + }; + itemRef: string; + showField: string; + disabled?: boolean; + onOptionChange?: (option: SelectOption | null) => void; +} + +interface AutocompleteItem { + id: string; + label: string; +} export const SelectField = ({ options, @@ -10,40 +40,48 @@ export const SelectField = ({ showField, disabled, onOptionChange, -}) => { - const [value, setValue] = useState(null); +}: SelectFieldProps) => { + const [value, setValue] = useState(null); const PAGE_SIZE = 50; useEffect(() => { - if (options?.id && field?.value?.id) { - setValue({ value: field.value?.id, label: field.value[showField] }); - form.setFieldValue(field.name, field.value?.id); + const fieldValue = + typeof field.value === 'object' && field.value !== null + ? field.value + : null; + + if (options?.id && typeof fieldValue?.id === 'string') { + setValue({ + value: fieldValue.id, + label: String(fieldValue[showField] || ''), + }); + form.setFieldValue(field.name, fieldValue.id); } else if (!field.value) { setValue(null); } - }, [options?.id, field?.value?.id, field?.value]); + }, [options?.id, field.name, field.value, form, showField]); - const mapResponseToValuesAndLabels = (data) => ({ + const mapResponseToValuesAndLabels = (data: AutocompleteItem) => ({ value: data.id, label: data.label, }); - const handleChange = (option) => { + const handleChange = (option: SingleValue) => { form.setFieldValue(field.name, option?.value || null); setValue(option); onOptionChange?.(option || null); }; - async function callApi( + const callApi: LoadOptions = async ( inputValue: string, - loadedOptions: Array<{ value: string; label: string }>, - ) { + loadedOptions, + ) => { const path = `/${itemRef}/autocomplete?limit=${PAGE_SIZE}&offset=${loadedOptions.length}${inputValue ? `&query=${inputValue}` : ''}`; - const { data } = await axios(path); + const { data } = await axios.get(path); return { options: data.map(mapResponseToValuesAndLabels), hasMore: data.length === PAGE_SIZE, }; - } + }; return ( +interface SelectOption { + value: string; + label: string; +} + +interface SelectedEntity { + id: string; + [field: string]: unknown; +} + +interface SelectFieldManyProps { + options?: SelectedEntity[] | null; + field: { + name: string; + value?: Array | null; + }; + form: { + setFieldValue: ( + field: string, + value: string[], + shouldValidate?: boolean, + ) => void; + }; + itemRef: string; + showField: string; +} + +interface AutocompleteItem { + id: string; + label: string; +} + +const areStringArraysEqual = (left: string[] = [], right: string[] = []) => left.length === right.length && left.every((value, index) => value === right[index]); -const areSelectOptionsEqual = (left = [], right = []) => +const areSelectOptionsEqual = ( + left: SelectOption[] = [], + right: SelectOption[] = [], +) => left.length === right.length && left.every( (option, index) => @@ -20,16 +56,19 @@ export const SelectFieldMany = ({ form, itemRef, showField, -}) => { - const [value, setValue] = useState([]); +}: SelectFieldManyProps) => { + const [value, setValue] = useState([]); const appliedOptionsSignatureRef = useRef(null); const PAGE_SIZE = 50; useEffect(() => { if (field.value?.[0] && typeof field.value[0] !== 'string') { - const normalizedValue = field.value.map((el) => el.id); + const normalizedValue = field.value.map((el) => + typeof el === 'string' ? el : el.id, + ); const isAlreadyNormalized = Array.isArray(field.value) && + field.value.every((item): item is string => typeof item === 'string') && areStringArraysEqual(field.value, normalizedValue); if (!isAlreadyNormalized) { @@ -46,14 +85,14 @@ export const SelectFieldMany = ({ if (Array.isArray(options) && options.length > 0) { const selectedOptions = options.map((el) => ({ value: el.id, - label: el[showField], + label: String(el[showField] || ''), })); const selectedIds = options.map((el) => el.id); const optionsSignature = selectedIds.join('|'); const shouldApplyInitialOptions = appliedOptionsSignatureRef.current !== optionsSignature; const fieldValueMatchesInitialOptions = areStringArraysEqual( - field.value, + field.value?.filter((item): item is string => typeof item === 'string'), selectedIds, ); @@ -72,33 +111,31 @@ export const SelectFieldMany = ({ } }, [field.name, field.value, form, options, showField]); - const mapResponseToValuesAndLabels = (data) => ({ + const mapResponseToValuesAndLabels = (data: AutocompleteItem) => ({ value: data.id, label: data.label, }); - const handleChange = ( - data: Array<{ value: string; label: string }> | null, - ) => { - const selectedOptions = data || []; + const handleChange = (data: MultiValue) => { + const selectedOptions = [...data]; setValue(selectedOptions); form.setFieldValue( field.name, - selectedOptions.map((el) => el?.value || null), + selectedOptions.map((el) => el.value), ); }; - async function callApi( + const callApi: LoadOptions = async ( inputValue: string, - loadedOptions: Array<{ value: string; label: string }>, - ) { + loadedOptions, + ) => { const path = `/${itemRef}/autocomplete?limit=${PAGE_SIZE}&offset=${loadedOptions.length}${inputValue ? `&query=${inputValue}` : ''}`; - const { data } = await axios(path); + const { data } = await axios.get(path); return { options: data.map(mapResponseToValuesAndLabels), hasMore: data.length === PAGE_SIZE, }; - } + }; return ( { +interface SwitchFieldProps { + field: { + name: string; + value?: boolean; + }; + form: { + setFieldValue: (field: string, value: boolean) => unknown; + }; + disabled?: boolean; +} + +export const SwitchField = ({ field, form, disabled }: SwitchFieldProps) => { const handleChange = (checked: boolean) => { form.setFieldValue(field.name, checked); }; diff --git a/frontend/src/components/TourFlow/ProjectTransitionSettingsPanel.tsx b/frontend/src/components/TourFlow/ProjectTransitionSettingsPanel.tsx index b8cd2ad..db218ee 100644 --- a/frontend/src/components/TourFlow/ProjectTransitionSettingsPanel.tsx +++ b/frontend/src/components/TourFlow/ProjectTransitionSettingsPanel.tsx @@ -1,9 +1,9 @@ import { mdiChevronDown, mdiChevronUp } from '@mdi/js'; import Icon from '@mdi/react'; import type { Dispatch, SetStateAction } from 'react'; +import type { EasingFunction, TransitionType } from '../../types/transition'; import BaseButton from '../BaseButton'; import CardBox from '../CardBox'; -import type { EasingFunction, TransitionType } from '../../types/transition'; import { EASING_OPTIONS, parseTransitionDurationInput, diff --git a/frontend/src/components/TourFlow/TourFlowList.tsx b/frontend/src/components/TourFlow/TourFlowList.tsx index eccdf38..f7b7eae 100644 --- a/frontend/src/components/TourFlow/TourFlowList.tsx +++ b/frontend/src/components/TourFlow/TourFlowList.tsx @@ -1,11 +1,11 @@ import { mdiClose, mdiPencil } from '@mdi/js'; import type React from 'react'; -import BaseButton from '../BaseButton'; -import CardBox from '../CardBox'; import type { TourFlowListEntry, TourFlowPage, } from '../../lib/tourFlowHelpers'; +import BaseButton from '../BaseButton'; +import CardBox from '../CardBox'; type Props = { isLoading: boolean; diff --git a/frontend/src/components/TourFlow/tourFlowPresentation.helpers.ts b/frontend/src/components/TourFlow/tourFlowPresentation.helpers.ts index 0c833d2..ed043a2 100644 --- a/frontend/src/components/TourFlow/tourFlowPresentation.helpers.ts +++ b/frontend/src/components/TourFlow/tourFlowPresentation.helpers.ts @@ -1,5 +1,5 @@ -import type { EasingFunction, TransitionType } from '../../types/transition'; import type { TourFlowProjectOption } from '../../lib/tourFlowHelpers'; +import type { EasingFunction, TransitionType } from '../../types/transition'; export const TRANSITION_TYPE_OPTIONS: { value: TransitionType | ''; diff --git a/frontend/src/components/TourFlowManager.tsx b/frontend/src/components/TourFlowManager.tsx index 5cecebc..f8ccf42 100644 --- a/frontend/src/components/TourFlowManager.tsx +++ b/frontend/src/components/TourFlowManager.tsx @@ -4,14 +4,10 @@ import Head from 'next/head'; import { useRouter } from 'next/router'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { toast } from 'react-toastify'; -import CardBox from './CardBox'; -import SectionMain from './SectionMain'; -import SectionTitleLineWithButton from './SectionTitleLineWithButton'; import { getPageTitle } from '../config'; import { hasPermission } from '../helpers/userPermissions'; -import { useAppSelector, useAppDispatch } from '../stores/hooks'; import { logger } from '../lib/logger'; -import { sanitizeSlug, buildUniqueSlug } from '../lib/slugHelpers'; +import { buildUniqueSlug, sanitizeSlug } from '../lib/slugHelpers'; import { createTourFlowPage, deleteTourFlowItem, @@ -34,26 +30,25 @@ import { type TourFlowProjectOption, type TourFlowTransition, } from '../lib/tourFlowHelpers'; +import { fetch as fetchGlobalTransitionDefaults } from '../stores/global_transition_defaults/globalTransitionDefaultsSlice'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; +import { + deleteByProjectAndEnv, + fetchByProjectAndEnv, + selectByProjectAndEnv, + upsertByProjectAndEnv, +} from '../stores/project_transition_settings/projectTransitionSettingsSlice'; +import type { EasingFunction, TransitionType } from '../types/transition'; +import { entityToProjectSettings } from '../types/transition'; +import CardBox from './CardBox'; +import SectionMain from './SectionMain'; +import SectionTitleLineWithButton from './SectionTitleLineWithButton'; import ProjectTransitionSettingsPanel from './TourFlow/ProjectTransitionSettingsPanel'; import ProjectUiControlsPanel from './TourFlow/ProjectUiControlsPanel'; import TourFlowList from './TourFlow/TourFlowList'; import TourFlowPageNameModal from './TourFlow/TourFlowPageNameModal'; import TourFlowToolbar from './TourFlow/TourFlowToolbar'; import { getTourFlowProjectLabel } from './TourFlow/tourFlowPresentation.helpers'; -import { fetch as fetchGlobalTransitionDefaults } from '../stores/global_transition_defaults/globalTransitionDefaultsSlice'; -import { - fetchByProjectAndEnv, - upsertByProjectAndEnv, - deleteByProjectAndEnv, - selectByProjectAndEnv, - selectIsLoading as selectTransitionSettingsLoading, -} from '../stores/project_transition_settings/projectTransitionSettingsSlice'; -import type { - ProjectTransitionSettings, - TransitionType, - EasingFunction, -} from '../types/transition'; -import { entityToProjectSettings } from '../types/transition'; const TourFlowManager = () => { const router = useRouter(); @@ -74,7 +69,6 @@ const TourFlowManager = () => { const [selectedProjectId, setSelectedProjectId] = useState(''); const [isLoading, setIsLoading] = useState(true); const [isCreatingPage, setIsCreatingPage] = useState(false); - const [isCreatingTransition, setIsCreatingTransition] = useState(false); const [isCreatePageModalActive, setIsCreatePageModalActive] = useState(false); const [newPageName, setNewPageName] = useState(''); const [deletingId, setDeletingId] = useState(''); @@ -92,11 +86,6 @@ const TourFlowManager = () => { ? selectByProjectAndEnv(state, selectedProjectId, 'dev') : undefined, ); - const isTransitionSettingsLoading = useAppSelector((state) => - selectedProjectId - ? selectTransitionSettingsLoading(state, selectedProjectId, 'dev') - : false, - ); // Project transition settings state const [isTransitionSettingsExpanded, setIsTransitionSettingsExpanded] = useState(false); @@ -529,7 +518,7 @@ const TourFlowManager = () => { canCreatePage={canCreatePage} canCreateTransition={canCreateTransition} isCreatingPage={isCreatingPage} - isCreatingTransition={isCreatingTransition} + isCreatingTransition={false} onCreatePage={openCreatePageModal} onCreateTransition={handleCreateTransition} /> diff --git a/frontend/src/components/Tour_pages/CardTour_pages.tsx b/frontend/src/components/Tour_pages/CardTour_pages.tsx index 64c178b..4781ab2 100644 --- a/frontend/src/components/Tour_pages/CardTour_pages.tsx +++ b/frontend/src/components/Tour_pages/CardTour_pages.tsx @@ -1,12 +1,9 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; -import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; +import dataFormatter from '../../helpers/dataFormatter'; +import { useAppSelector } from '../../stores/hooks'; +import ListActionsPopover from '../ListActionsPopover'; +import LoadingSpinner from '../LoadingSpinner'; +import { Pagination } from '../Pagination'; import { hasPermission } from '../../helpers/userPermissions'; import type { TourPage } from '../../types/entities'; @@ -47,7 +44,7 @@ const CardTour_pages = ({ className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > {!loading && - tour_pages.map((item, index) => ( + tour_pages.map((item) => (
    • state.auth.currentUser); const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_TOUR_PAGES'); - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( <>
      diff --git a/frontend/src/components/Tour_pages/TableTour_pages.tsx b/frontend/src/components/Tour_pages/TableTour_pages.tsx index 23a8f03..509f2fd 100644 --- a/frontend/src/components/Tour_pages/TableTour_pages.tsx +++ b/frontend/src/components/Tour_pages/TableTour_pages.tsx @@ -1,13 +1,13 @@ -import { createTableComponent } from '../Factory/createTableComponent'; import { - fetch, - update, deleteItem, - setRefetch, deleteItemsByIds, + fetch, + setRefetch, + update, } from '../../stores/tour_pages/tour_pagesSlice'; -import { loadColumns } from './configureTour_pagesCols'; import type { TourPage } from '../../types/entities'; +import { createTableComponent } from '../Factory/createTableComponent'; +import { loadColumns } from './configureTour_pagesCols'; const TableTour_pages = createTableComponent({ entityName: 'tour_pages', diff --git a/frontend/src/components/Tour_pages/configureTour_pagesCols.tsx b/frontend/src/components/Tour_pages/configureTour_pagesCols.tsx index 448cb42..602a85a 100644 --- a/frontend/src/components/Tour_pages/configureTour_pagesCols.tsx +++ b/frontend/src/components/Tour_pages/configureTour_pagesCols.tsx @@ -1,6 +1,6 @@ import { - createColumnLoader, ColumnMetadata, + createColumnLoader, } from '../DataGrid/configBuilderFactory'; const TOUR_PAGES_COLUMNS: ColumnMetadata[] = [ diff --git a/frontend/src/components/UiControls/UiControlsSettingsForm.tsx b/frontend/src/components/UiControls/UiControlsSettingsForm.tsx index 207b1f3..f2bda1f 100644 --- a/frontend/src/components/UiControls/UiControlsSettingsForm.tsx +++ b/frontend/src/components/UiControls/UiControlsSettingsForm.tsx @@ -1,5 +1,4 @@ -import React, { useState } from 'react'; -import ElementSettingsTabs from '../ElementSettings/ElementSettingsTabs'; +import { useState } from 'react'; import type { SystemUiControlAnchor, SystemUiControlSettings, @@ -7,6 +6,7 @@ import type { UiControlsSettings, } from '../../types/uiControls'; import { resolveUiControlsSettings } from '../../types/uiControls'; +import ElementSettingsTabs from '../ElementSettings/ElementSettingsTabs'; const CONTROL_TYPES: SystemUiControlType[] = ['offline', 'fullscreen', 'sound']; diff --git a/frontend/src/components/UiElements/GalleryCarouselOverlay.tsx b/frontend/src/components/UiElements/GalleryCarouselOverlay.tsx index d5a9cff..4c22823 100644 --- a/frontend/src/components/UiElements/GalleryCarouselOverlay.tsx +++ b/frontend/src/components/UiElements/GalleryCarouselOverlay.tsx @@ -6,24 +6,24 @@ * In constructor mode, buttons are draggable for positioning. */ -import React, { useEffect, useCallback, useRef } from 'react'; -import { mdiChevronLeft, mdiChevronRight, mdiArrowLeft } from '@mdi/js'; -import type { - GalleryCard, - GalleryCarouselMediaItem, - CanvasElement, -} from '../../types/constructor'; -import type { ResolvedTransitionSettings } from '../../types/transition'; +import { mdiArrowLeft, mdiChevronLeft, mdiChevronRight } from '@mdi/js'; +import React, { useCallback, useEffect, useRef } from 'react'; +import { useGlobalAudioMute } from '../../hooks/useGlobalAudioMute'; +import { useSlideTransition } from '../../hooks/useSlideTransition'; import { resolveAssetPlaybackUrl } from '../../lib/assetUrl'; import { - resolveSlideTransition, extractGallerySlideOverride, + resolveSlideTransition, } from '../../lib/resolveSlideTransition'; -import { useSlideTransition } from '../../hooks/useSlideTransition'; -import { useGlobalAudioMute } from '../../hooks/useGlobalAudioMute'; +import type { + CanvasElement, + GalleryCard, + GalleryCarouselMediaItem, +} from '../../types/constructor'; +import type { ResolvedTransitionSettings } from '../../types/transition'; +import type { GalleryCarouselOverlayButton } from './GalleryCarouselOverlay.helpers'; import GalleryCarouselOverlayMedia from './GalleryCarouselOverlayMedia'; import GalleryCarouselOverlayNavButton from './GalleryCarouselOverlayNavButton'; -import type { GalleryCarouselOverlayButton } from './GalleryCarouselOverlay.helpers'; import { useGalleryCarouselOverlayButtons } from './useGalleryCarouselOverlayButtons'; interface GalleryCarouselOverlayProps { diff --git a/frontend/src/components/UiElements/GalleryCarouselOverlayMedia.tsx b/frontend/src/components/UiElements/GalleryCarouselOverlayMedia.tsx index 4669a52..25c38bc 100644 --- a/frontend/src/components/UiElements/GalleryCarouselOverlayMedia.tsx +++ b/frontend/src/components/UiElements/GalleryCarouselOverlayMedia.tsx @@ -1,9 +1,9 @@ import React from 'react'; +import { buildChromeFreeEmbedUrl, isValidEmbedUrl } from '../../lib/embedUrl'; import type { GalleryCard, GalleryCarouselMediaItem, } from '../../types/constructor'; -import { buildChromeFreeEmbedUrl, isValidEmbedUrl } from '../../lib/embedUrl'; import { getGalleryCarouselEmbedUrl, getGalleryCarouselMediaTitle, diff --git a/frontend/src/components/UiElements/GalleryCarouselOverlayNavButton.tsx b/frontend/src/components/UiElements/GalleryCarouselOverlayNavButton.tsx index d6b721e..1f1004c 100644 --- a/frontend/src/components/UiElements/GalleryCarouselOverlayNavButton.tsx +++ b/frontend/src/components/UiElements/GalleryCarouselOverlayNavButton.tsx @@ -1,5 +1,5 @@ -import React from 'react'; import Icon from '@mdi/react'; +import React from 'react'; import type { GalleryCarouselOverlayButton } from './GalleryCarouselOverlay.helpers'; import { toGalleryCarouselViewportUnit } from './GalleryCarouselOverlay.helpers'; diff --git a/frontend/src/components/UiElements/ImageDetailPanel.helpers.ts b/frontend/src/components/UiElements/ImageDetailPanel.helpers.ts index bb86f9c..50ebff3 100644 --- a/frontend/src/components/UiElements/ImageDetailPanel.helpers.ts +++ b/frontend/src/components/UiElements/ImageDetailPanel.helpers.ts @@ -1,9 +1,9 @@ import type { CSSProperties } from 'react'; -import type { CanvasElement } from '../../types/constructor'; -import type { InfoPanelImage } from '../../types/infoPanel'; import { buildChromeFreeEmbedUrl, isValidEmbedUrl } from '../../lib/embedUrl'; import { getFontByKey, getFontStyle } from '../../lib/fonts'; import { normalizeCanvasUnitValue } from '../../lib/styleValueUtils'; +import type { CanvasElement } from '../../types/constructor'; +import type { InfoPanelImage } from '../../types/infoPanel'; export interface ImageDetailPanelMediaState { hasImage: boolean; diff --git a/frontend/src/components/UiElements/ImageDetailPanel.tsx b/frontend/src/components/UiElements/ImageDetailPanel.tsx index 8d3f3f4..9a38aea 100644 --- a/frontend/src/components/UiElements/ImageDetailPanel.tsx +++ b/frontend/src/components/UiElements/ImageDetailPanel.tsx @@ -6,12 +6,10 @@ * Supports embed URL validation for security. */ -import React, { useState, useEffect, useCallback, useRef } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; -import type { CanvasElement } from '../../types/constructor'; -import type { InfoPanelImage } from '../../types/infoPanel'; -import { resolveAssetPlaybackUrl } from '../../lib/assetUrl'; import { useGlobalAudioMute } from '../../hooks/useGlobalAudioMute'; +import { resolveAssetPlaybackUrl } from '../../lib/assetUrl'; import { exitNativeFullscreen, getFullscreenElement, @@ -19,6 +17,8 @@ import { requestElementFullscreen, requestEmbeddingFrameFullscreen, } from '../../lib/fullscreen'; +import type { CanvasElement } from '../../types/constructor'; +import type { InfoPanelImage } from '../../types/infoPanel'; import { buildImageDetailCaptionStyle, buildImageDetailPanelStyle, diff --git a/frontend/src/components/UiElements/InfoPanelOverlay.tsx b/frontend/src/components/UiElements/InfoPanelOverlay.tsx index 1bfbbb3..f2c1b6b 100644 --- a/frontend/src/components/UiElements/InfoPanelOverlay.tsx +++ b/frontend/src/components/UiElements/InfoPanelOverlay.tsx @@ -7,26 +7,26 @@ * Supports keyboard navigation, click outside to close, and mobile touch. */ -import React, { useState, useEffect, useRef, useMemo } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { resolveAssetPlaybackUrl } from '../../lib/assetUrl'; +import { + buildInfoPanelCardStyle, + buildInfoPanelCardTitleStyle, + buildInfoPanelHeaderStyle, + buildInfoPanelSpanStyle, + buildInfoPanelTextStyle, + buildInfoPanelTitleStyle, +} from '../../lib/infoPanelSectionStyles'; import type { CanvasElement } from '../../types/constructor'; import type { InfoPanelImage } from '../../types/infoPanel'; import { getInfoPanelSections } from '../../types/infoPanel'; -import { resolveAssetPlaybackUrl } from '../../lib/assetUrl'; -import { - buildInfoPanelHeaderStyle, - buildInfoPanelTitleStyle, - buildInfoPanelTextStyle, - buildInfoPanelSpanStyle, - buildInfoPanelCardStyle, - buildInfoPanelCardTitleStyle, -} from '../../lib/infoPanelSectionStyles'; +import { useInfoPanelOverlayActions } from './InfoPanelOverlay.actions'; import { buildInfoPanelPanelStyle, getInfoPanelOverlayColor, isInfoPanelOverlayVisible, toCanvasUnit, } from './infoPanelOverlay.helpers'; -import { useInfoPanelOverlayActions } from './InfoPanelOverlay.actions'; import InfoPanelOverlaySections from './InfoPanelOverlaySections'; import { useInfoPanelOverlayDrag } from './useInfoPanelOverlayDrag'; diff --git a/frontend/src/components/UiElements/InfoPanelOverlayBasicSections.tsx b/frontend/src/components/UiElements/InfoPanelOverlayBasicSections.tsx index 15a653a..74f5c14 100644 --- a/frontend/src/components/UiElements/InfoPanelOverlayBasicSections.tsx +++ b/frontend/src/components/UiElements/InfoPanelOverlayBasicSections.tsx @@ -1,10 +1,10 @@ import React from 'react'; +import { buildInfoPanelSpanGridStyleWithSection } from '../../lib/infoPanelGridStyles'; import type { CanvasElement } from '../../types/constructor'; import type { InfoPanelLinkClickAction, InfoPanelSectionInstance, } from '../../types/infoPanel'; -import { buildInfoPanelSpanGridStyleWithSection } from '../../lib/infoPanelGridStyles'; type LinkTarget = { clickAction?: InfoPanelLinkClickAction; diff --git a/frontend/src/components/UiElements/InfoPanelOverlayMediaSections.tsx b/frontend/src/components/UiElements/InfoPanelOverlayMediaSections.tsx index 8fe7cb3..f526f19 100644 --- a/frontend/src/components/UiElements/InfoPanelOverlayMediaSections.tsx +++ b/frontend/src/components/UiElements/InfoPanelOverlayMediaSections.tsx @@ -1,10 +1,10 @@ import React from 'react'; +import { buildInfoPanelCardGridStyleWithSection } from '../../lib/infoPanelGridStyles'; import type { CanvasElement } from '../../types/constructor'; import type { InfoPanelImage, InfoPanelSectionInstance, } from '../../types/infoPanel'; -import { buildInfoPanelCardGridStyleWithSection } from '../../lib/infoPanelGridStyles'; import { InfoPanelVideoThumbnail } from './InfoPanelVideoThumbnail'; interface MediaSectionProps { diff --git a/frontend/src/components/UiElements/UiElementRenderer.tsx b/frontend/src/components/UiElements/UiElementRenderer.tsx index 1423ea3..8da4526 100644 --- a/frontend/src/components/UiElements/UiElementRenderer.tsx +++ b/frontend/src/components/UiElements/UiElementRenderer.tsx @@ -9,34 +9,34 @@ */ import React from 'react'; -import type { CanvasElement } from '../../types/constructor'; -import type { ResolvedTransitionSettings } from '../../types/transition'; import type { PreloadCacheProvider } from '../../hooks/video'; -import { useElementWrapperStyle } from './shared/useElementWrapperStyle'; import { - isNavigationElementType, + isAudioPlayerElementType, + isCarouselElementType, isDescriptionElementType, isGalleryElementType, - isCarouselElementType, - isVideoPlayerElementType, - isAudioPlayerElementType, - isLogoElementType, - isSpotElementType, - isPopupElementType, isInfoPanelElementType, + isLogoElementType, + isNavigationElementType, + isPopupElementType, + isSpotElementType, + isVideoPlayerElementType, } from '../../lib/elementTypeGuards'; +import type { CanvasElement } from '../../types/constructor'; +import type { ResolvedTransitionSettings } from '../../types/transition'; +import { useElementWrapperStyle } from './shared/useElementWrapperStyle'; // Import per-type components -import NavigationElement from './elements/NavigationElement'; -import GalleryElement from './elements/GalleryElement'; -import DescriptionElement from './elements/DescriptionElement'; +import AudioPlayerElement from './elements/AudioPlayerElement'; import CarouselElement from './elements/CarouselElement'; +import DescriptionElement from './elements/DescriptionElement'; +import GalleryElement from './elements/GalleryElement'; +import InfoPanelElement from './elements/InfoPanelElement'; import LogoElement from './elements/LogoElement'; +import NavigationElement from './elements/NavigationElement'; +import PopupElement from './elements/PopupElement'; import SpotElement from './elements/SpotElement'; import VideoPlayerElement from './elements/VideoPlayerElement'; -import AudioPlayerElement from './elements/AudioPlayerElement'; -import PopupElement from './elements/PopupElement'; -import InfoPanelElement from './elements/InfoPanelElement'; export interface UiElementRendererProps { element: CanvasElement; diff --git a/frontend/src/components/UiElements/defaults.ts b/frontend/src/components/UiElements/defaults.ts index a60f04e..87606b2 100644 --- a/frontend/src/components/UiElements/defaults.ts +++ b/frontend/src/components/UiElements/defaults.ts @@ -185,7 +185,7 @@ const defaultSettingsByType: Record = { }, }; -const parseJsonObject = (value?: string): Record => { +const parseJsonObject = (value?: string): Record => { if (!value) return {}; try { diff --git a/frontend/src/components/UiElements/elements/AudioPlayerElement.tsx b/frontend/src/components/UiElements/elements/AudioPlayerElement.tsx index 21ca294..033bb3d 100644 --- a/frontend/src/components/UiElements/elements/AudioPlayerElement.tsx +++ b/frontend/src/components/UiElements/elements/AudioPlayerElement.tsx @@ -5,12 +5,12 @@ * Pauses background audio when playing (ducking). */ -import React, { useRef, useEffect } from 'react'; import type { CSSProperties } from 'react'; -import type { CanvasElement } from '../../../types/constructor'; +import React, { useEffect, useRef } from 'react'; +import { useGlobalAudioMute } from '../../../hooks/useGlobalAudioMute'; import { resolveAssetPlaybackUrl } from '../../../lib/assetUrl'; import { backgroundAudioController } from '../../../lib/backgroundAudioController'; -import { useGlobalAudioMute } from '../../../hooks/useGlobalAudioMute'; +import type { CanvasElement } from '../../../types/constructor'; interface AudioPlayerElementProps { element: CanvasElement; diff --git a/frontend/src/components/UiElements/elements/CarouselElement.helpers.ts b/frontend/src/components/UiElements/elements/CarouselElement.helpers.ts index dfe374e..a07046a 100644 --- a/frontend/src/components/UiElements/elements/CarouselElement.helpers.ts +++ b/frontend/src/components/UiElements/elements/CarouselElement.helpers.ts @@ -1,7 +1,7 @@ import type { CSSProperties } from 'react'; -import type { CanvasElement } from '../../../types/constructor'; import { toCU } from '../../../lib/canvasScale'; import { getFontByKey, getFontStyle } from '../../../lib/fonts'; +import type { CanvasElement } from '../../../types/constructor'; export interface CarouselButtonPositions { prevX: number; diff --git a/frontend/src/components/UiElements/elements/CarouselElement.tsx b/frontend/src/components/UiElements/elements/CarouselElement.tsx index e3edd0f..333cd8e 100644 --- a/frontend/src/components/UiElements/elements/CarouselElement.tsx +++ b/frontend/src/components/UiElements/elements/CarouselElement.tsx @@ -14,18 +14,18 @@ * - Navigation-style rendering when custom icons with dimensions are set */ +import { mdiChevronLeft, mdiChevronRight } from '@mdi/js'; +import type { CSSProperties } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; -import type { CSSProperties } from 'react'; -import { mdiChevronLeft, mdiChevronRight } from '@mdi/js'; -import type { CanvasElement, CarouselSlide } from '../../../types/constructor'; -import type { ResolvedTransitionSettings } from '../../../types/transition'; +import { useSlideTransition } from '../../../hooks/useSlideTransition'; import { resolveAssetPlaybackUrl } from '../../../lib/assetUrl'; import { - resolveSlideTransition, extractCarouselSlideOverride, + resolveSlideTransition, } from '../../../lib/resolveSlideTransition'; -import { useSlideTransition } from '../../../hooks/useSlideTransition'; +import type { CanvasElement, CarouselSlide } from '../../../types/constructor'; +import type { ResolvedTransitionSettings } from '../../../types/transition'; import { buildCarouselCaptionStyle, getCarouselButtonPositions, @@ -103,6 +103,7 @@ const CarouselElement: React.FC = ({ useEffect(() => { setPositions(getCarouselButtonPositions(element)); }, [ + element, element.carouselPrevX, element.carouselPrevY, element.carouselNextX, diff --git a/frontend/src/components/UiElements/elements/CarouselNavigationButton.tsx b/frontend/src/components/UiElements/elements/CarouselNavigationButton.tsx index 6b04ee8..dbb0a74 100644 --- a/frontend/src/components/UiElements/elements/CarouselNavigationButton.tsx +++ b/frontend/src/components/UiElements/elements/CarouselNavigationButton.tsx @@ -1,5 +1,5 @@ -import type { MouseEvent } from 'react'; import Icon from '@mdi/react'; +import type { MouseEvent } from 'react'; import { clampCarouselPercent, toCarouselCanvasUnit, diff --git a/frontend/src/components/UiElements/elements/DescriptionElement.tsx b/frontend/src/components/UiElements/elements/DescriptionElement.tsx index aef9828..cacba72 100644 --- a/frontend/src/components/UiElements/elements/DescriptionElement.tsx +++ b/frontend/src/components/UiElements/elements/DescriptionElement.tsx @@ -5,12 +5,12 @@ * Renders with unified wrapper styling + content. */ -import React, { useMemo } from 'react'; import type { CSSProperties } from 'react'; -import type { CanvasElement } from '../../../types/constructor'; +import React, { useMemo } from 'react'; import { resolveAssetPlaybackUrl } from '../../../lib/assetUrl'; -import { getFontByKey, getFontStyle } from '../../../lib/fonts'; import { normalizePixelValue } from '../../../lib/elementStyles'; +import { getFontByKey, getFontStyle } from '../../../lib/fonts'; +import type { CanvasElement } from '../../../types/constructor'; interface DescriptionElementProps { element: CanvasElement; diff --git a/frontend/src/components/UiElements/elements/GalleryElement.tsx b/frontend/src/components/UiElements/elements/GalleryElement.tsx index c0d93ce..ddb9718 100644 --- a/frontend/src/components/UiElements/elements/GalleryElement.tsx +++ b/frontend/src/components/UiElements/elements/GalleryElement.tsx @@ -5,25 +5,25 @@ * Renders with unified wrapper styling + content using section-specific styles. */ -import React, { useMemo } from 'react'; import type { CSSProperties } from 'react'; +import React, { useMemo } from 'react'; +import { useBackdropEffect } from '../../../hooks/useBackdropEffect'; +import { resolveAssetPlaybackUrl } from '../../../lib/assetUrl'; +import { + buildGalleryCardGridStyle, + buildGalleryCardStyle, + buildGalleryCardTitleStyle, + buildGalleryHeaderStyle, + buildGallerySpanGridStyle, + buildGallerySpanStyle, + buildGalleryTitleStyle, + GALLERY_SECTION_DEFAULTS, +} from '../../../lib/gallerySectionStyles'; import type { CanvasElement, GalleryCard, GalleryInfoSpan, } from '../../../types/constructor'; -import { resolveAssetPlaybackUrl } from '../../../lib/assetUrl'; -import { - buildGalleryHeaderStyle, - buildGalleryTitleStyle, - buildGallerySpanStyle, - buildGallerySpanGridStyle, - buildGalleryCardStyle, - buildGalleryCardTitleStyle, - buildGalleryCardGridStyle, - GALLERY_SECTION_DEFAULTS, -} from '../../../lib/gallerySectionStyles'; -import { useBackdropEffect } from '../../../hooks/useBackdropEffect'; /** Convert CSS textAlign to flexbox justifyContent */ const textAlignToJustify = (textAlign?: string): string => { diff --git a/frontend/src/components/UiElements/elements/InfoPanelElement.tsx b/frontend/src/components/UiElements/elements/InfoPanelElement.tsx index 529c94a..44d3454 100644 --- a/frontend/src/components/UiElements/elements/InfoPanelElement.tsx +++ b/frontend/src/components/UiElements/elements/InfoPanelElement.tsx @@ -6,11 +6,11 @@ * Uses hover reveal effect from Task 1 for "appear on hover". */ -import React from 'react'; import type { CSSProperties } from 'react'; -import type { CanvasElement } from '../../../types/constructor'; +import React from 'react'; import { resolveAssetPlaybackUrl } from '../../../lib/assetUrl'; import { isElementFlagEnabled } from '../../../lib/elementFlags'; +import type { CanvasElement } from '../../../types/constructor'; interface InfoPanelElementProps { element: CanvasElement; diff --git a/frontend/src/components/UiElements/elements/LogoElement.tsx b/frontend/src/components/UiElements/elements/LogoElement.tsx index acd85a1..6607298 100644 --- a/frontend/src/components/UiElements/elements/LogoElement.tsx +++ b/frontend/src/components/UiElements/elements/LogoElement.tsx @@ -5,10 +5,10 @@ * Renders with unified wrapper styling + content. */ -import React from 'react'; import type { CSSProperties } from 'react'; -import type { CanvasElement } from '../../../types/constructor'; +import React from 'react'; import { resolveAssetPlaybackUrl } from '../../../lib/assetUrl'; +import type { CanvasElement } from '../../../types/constructor'; interface LogoElementProps { element: CanvasElement; diff --git a/frontend/src/components/UiElements/elements/NavigationElement.tsx b/frontend/src/components/UiElements/elements/NavigationElement.tsx index cab3c59..7fa0ef8 100644 --- a/frontend/src/components/UiElements/elements/NavigationElement.tsx +++ b/frontend/src/components/UiElements/elements/NavigationElement.tsx @@ -5,11 +5,11 @@ * Renders with unified wrapper styling + content. */ -import React, { useMemo } from 'react'; import type { CSSProperties } from 'react'; -import type { CanvasElement } from '../../../types/constructor'; +import React, { useMemo } from 'react'; import { resolveAssetPlaybackUrl } from '../../../lib/assetUrl'; import { getFontByKey, getFontStyle } from '../../../lib/fonts'; +import type { CanvasElement } from '../../../types/constructor'; interface NavigationElementProps { element: CanvasElement; diff --git a/frontend/src/components/UiElements/elements/PopupElement.tsx b/frontend/src/components/UiElements/elements/PopupElement.tsx index e634014..ac7e965 100644 --- a/frontend/src/components/UiElements/elements/PopupElement.tsx +++ b/frontend/src/components/UiElements/elements/PopupElement.tsx @@ -5,8 +5,8 @@ * Renders with unified wrapper styling + content. */ -import React from 'react'; import type { CSSProperties } from 'react'; +import React from 'react'; import type { CanvasElement } from '../../../types/constructor'; interface PopupElementProps { diff --git a/frontend/src/components/UiElements/elements/SpotElement.tsx b/frontend/src/components/UiElements/elements/SpotElement.tsx index e3618e7..5bc10b2 100644 --- a/frontend/src/components/UiElements/elements/SpotElement.tsx +++ b/frontend/src/components/UiElements/elements/SpotElement.tsx @@ -5,10 +5,10 @@ * Renders with unified wrapper styling + content. */ -import React from 'react'; import type { CSSProperties } from 'react'; -import type { CanvasElement } from '../../../types/constructor'; +import React from 'react'; import { resolveAssetPlaybackUrl } from '../../../lib/assetUrl'; +import type { CanvasElement } from '../../../types/constructor'; interface SpotElementProps { element: CanvasElement; diff --git a/frontend/src/components/UiElements/elements/VideoPlayerElement.tsx b/frontend/src/components/UiElements/elements/VideoPlayerElement.tsx index 77aec76..024a7b6 100644 --- a/frontend/src/components/UiElements/elements/VideoPlayerElement.tsx +++ b/frontend/src/components/UiElements/elements/VideoPlayerElement.tsx @@ -5,13 +5,13 @@ * Pauses background audio when playing (ducking). */ -import React, { useEffect } from 'react'; import type { CSSProperties } from 'react'; -import type { CanvasElement } from '../../../types/constructor'; +import React, { useEffect } from 'react'; +import { useGlobalAudioMute } from '../../../hooks/useGlobalAudioMute'; import type { PreloadCacheProvider } from '../../../hooks/video'; import { useVideoPlayer } from '../../../hooks/video'; import { backgroundAudioController } from '../../../lib/backgroundAudioController'; -import { useGlobalAudioMute } from '../../../hooks/useGlobalAudioMute'; +import type { CanvasElement } from '../../../types/constructor'; interface VideoPlayerElementProps { element: CanvasElement; diff --git a/frontend/src/components/UiElements/infoPanelMedia.helpers.test.ts b/frontend/src/components/UiElements/infoPanelMedia.helpers.test.ts index 6919e86..c8f5877 100644 --- a/frontend/src/components/UiElements/infoPanelMedia.helpers.test.ts +++ b/frontend/src/components/UiElements/infoPanelMedia.helpers.test.ts @@ -1,15 +1,15 @@ import assert from 'node:assert/strict'; import test from 'node:test'; +import type { InfoPanelImage } from '../../types/infoPanel'; import { - buildInfoPanelGalleryState, buildInfoPanelGalleryItems, + buildInfoPanelGalleryState, canUseInfoPanelItemAsBackground, getExternalHref, getInfoPanelBackgroundSources, getInfoPanelMediaType, } from './infoPanelMedia.helpers'; -import type { InfoPanelImage } from '../../types/infoPanel'; test('getInfoPanelMediaType normalizes missing itemType to image', () => { assert.equal(getInfoPanelMediaType({ id: 'image-1' }), 'image'); diff --git a/frontend/src/components/UiElements/infoPanelOverlay.helpers.test.ts b/frontend/src/components/UiElements/infoPanelOverlay.helpers.test.ts index d55f895..eeafb76 100644 --- a/frontend/src/components/UiElements/infoPanelOverlay.helpers.test.ts +++ b/frontend/src/components/UiElements/infoPanelOverlay.helpers.test.ts @@ -1,11 +1,11 @@ import assert from 'node:assert/strict'; import { test } from 'node:test'; +import type { CanvasElement } from '../../types/constructor'; import { buildInfoPanelPanelStyle, isInfoPanelOverlayVisible, toCanvasUnit, } from './infoPanelOverlay.helpers'; -import type { CanvasElement } from '../../types/constructor'; test('toCanvasUnit converts bare numbers and preserves CSS values', () => { assert.equal(toCanvasUnit('12'), 'calc(12 * var(--cu, 1px))'); diff --git a/frontend/src/components/UiElements/shared/useElementWrapperStyle.ts b/frontend/src/components/UiElements/shared/useElementWrapperStyle.ts index 5a1f342..c2c50c4 100644 --- a/frontend/src/components/UiElements/shared/useElementWrapperStyle.ts +++ b/frontend/src/components/UiElements/shared/useElementWrapperStyle.ts @@ -6,20 +6,20 @@ * to ensure WYSIWYG consistency. */ -import { useMemo } from 'react'; import type { CSSProperties } from 'react'; -import type { CanvasElement } from '../../../types/constructor'; -import { buildElementStyle } from '../../../lib/elementStyles'; +import { useMemo } from 'react'; import { toCU } from '../../../lib/canvasScale'; +import { buildElementStyle } from '../../../lib/elementStyles'; import { - isDescriptionElementType, - isNavigationElementType, - isGalleryElementType, isCarouselElementType, - isLogoElementType, - isSpotElementType, + isDescriptionElementType, + isGalleryElementType, isInfoPanelElementType, + isLogoElementType, + isNavigationElementType, + isSpotElementType, } from '../../../lib/elementTypeGuards'; +import type { CanvasElement } from '../../../types/constructor'; interface UseElementWrapperStyleOptions { element: CanvasElement; diff --git a/frontend/src/components/UserAvatar.tsx b/frontend/src/components/UserAvatar.tsx index c7f0537..448c327 100644 --- a/frontend/src/components/UserAvatar.tsx +++ b/frontend/src/components/UserAvatar.tsx @@ -1,7 +1,7 @@ -import React, { ReactNode } from 'react'; -import Image from 'next/image'; -import BaseIcon from './BaseIcon'; import { mdiAccountCircleOutline } from '@mdi/js'; +import Image from 'next/image'; +import { ReactNode } from 'react'; +import BaseIcon from './BaseIcon'; type Props = { username: string; diff --git a/frontend/src/components/UserAvatarCurrentUser.tsx b/frontend/src/components/UserAvatarCurrentUser.tsx index 6bc95d9..ec7a1d6 100644 --- a/frontend/src/components/UserAvatarCurrentUser.tsx +++ b/frontend/src/components/UserAvatarCurrentUser.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, useMemo } from 'react'; +import { ReactNode, useMemo } from 'react'; import { useAppSelector } from '../stores/hooks'; import UserAvatar from './UserAvatar'; diff --git a/frontend/src/components/UserCard.tsx b/frontend/src/components/UserCard.tsx index a109f23..d7577bc 100644 --- a/frontend/src/components/UserCard.tsx +++ b/frontend/src/components/UserCard.tsx @@ -1,4 +1,3 @@ -import { mdiCheckDecagram } from '@mdi/js'; import { Field, Form, Formik } from 'formik'; import { useMemo } from 'react'; import { useAppSelector } from '../stores/hooks'; diff --git a/frontend/src/components/Users/CardUsers.tsx b/frontend/src/components/Users/CardUsers.tsx index b07a63e..86d0209 100644 --- a/frontend/src/components/Users/CardUsers.tsx +++ b/frontend/src/components/Users/CardUsers.tsx @@ -1,12 +1,10 @@ -import React from 'react'; +import Link from 'next/link'; +import dataFormatter from '../../helpers/dataFormatter'; +import { useAppSelector } from '../../stores/hooks'; import ImageField from '../ImageField'; import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; import LoadingSpinner from '../LoadingSpinner'; -import Link from 'next/link'; +import { Pagination } from '../Pagination'; import { hasPermission } from '../../helpers/userPermissions'; import type { User } from '../../types/entities'; @@ -47,7 +45,7 @@ const CardUsers = ({ className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > {!loading && - users.map((item, index) => ( + users.map((item) => (
    • state.auth.currentUser); const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_USERS'); - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( <>
      diff --git a/frontend/src/components/Users/TableUsers.tsx b/frontend/src/components/Users/TableUsers.tsx index 843ec57..2bb6f56 100644 --- a/frontend/src/components/Users/TableUsers.tsx +++ b/frontend/src/components/Users/TableUsers.tsx @@ -1,13 +1,13 @@ -import { createTableComponent } from '../Factory/createTableComponent'; import { - fetch, - update, deleteItem, - setRefetch, deleteItemsByIds, + fetch, + setRefetch, + update, } from '../../stores/users/usersSlice'; -import { loadColumns } from './configureUsersCols'; import type { User } from '../../types/entities'; +import { createTableComponent } from '../Factory/createTableComponent'; +import { loadColumns } from './configureUsersCols'; const TableUsers = createTableComponent({ entityName: 'users', diff --git a/frontend/src/components/Users/UserDetailsView.tsx b/frontend/src/components/Users/UserDetailsView.tsx index 6af6cb4..6e4b48f 100644 --- a/frontend/src/components/Users/UserDetailsView.tsx +++ b/frontend/src/components/Users/UserDetailsView.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from 'react'; import dataFormatter from '../../helpers/dataFormatter'; -import BaseDivider from '../BaseDivider'; import BaseButton from '../BaseButton'; +import BaseDivider from '../BaseDivider'; import CardBox from '../CardBox'; import FormField from '../FormField'; import ImageField from '../ImageField'; diff --git a/frontend/src/components/Users/configureUsersCols.tsx b/frontend/src/components/Users/configureUsersCols.tsx index 8ecad32..ad6f4b9 100644 --- a/frontend/src/components/Users/configureUsersCols.tsx +++ b/frontend/src/components/Users/configureUsersCols.tsx @@ -1,10 +1,9 @@ -import React from 'react'; import { GridRenderCellParams } from '@mui/x-data-grid'; -import ImageField from '../ImageField'; import { - createColumnLoader, ColumnMetadata, + createColumnLoader, } from '../DataGrid/configBuilderFactory'; +import ImageField from '../ImageField'; const USERS_COLUMNS: ColumnMetadata[] = [ { diff --git a/frontend/src/context/CanvasScaleContext.tsx b/frontend/src/context/CanvasScaleContext.tsx index eb5009d..cd72ac1 100644 --- a/frontend/src/context/CanvasScaleContext.tsx +++ b/frontend/src/context/CanvasScaleContext.tsx @@ -6,14 +6,14 @@ * Follows pattern: context/DownloadContext.tsx */ -import React, { +import { createContext, useContext, - useState, useEffect, useMemo, - type ReactNode, + useState, type CSSProperties, + type ReactNode, } from 'react'; import { CANVAS_CONFIG } from '../config/canvas.config'; import { calculateCanvasScale, getCanvasCssVars } from '../lib/canvasScale'; diff --git a/frontend/src/context/ConstructorContext.tsx b/frontend/src/context/ConstructorContext.tsx index 61548f6..dae1d09 100644 --- a/frontend/src/context/ConstructorContext.tsx +++ b/frontend/src/context/ConstructorContext.tsx @@ -17,27 +17,27 @@ import React, { type ReactNode, } from 'react'; import type { + AssetOption, CanvasElement, CanvasElementType, + CarouselSlide, EditorMenuItem, EditorTab, GalleryCard, GalleryInfoSpan, - CarouselSlide, - AssetOption, } from '../types/constructor'; +import type { Asset, TourPage } from '../types/entities'; import type { InfoPanelImage, InfoPanelInfoSpan, - InfoPanelSectionType, InfoPanelSectionInstance, + InfoPanelSectionType, } from '../types/infoPanel'; import type { + PageBackgroundAudioSettings, PageBackgroundState, PageBackgroundVideoSettings, - PageBackgroundAudioSettings, } from '../types/pageBackground'; -import type { TourPage, Asset } from '../types/entities'; import type { ResolvedUiControlsSettings, SystemUiControlSettings, diff --git a/frontend/src/context/DownloadContext.tsx b/frontend/src/context/DownloadContext.tsx index 03ca487..726cd9d 100644 --- a/frontend/src/context/DownloadContext.tsx +++ b/frontend/src/context/DownloadContext.tsx @@ -5,24 +5,23 @@ * Integrates with DownloadEventBus for real-time progress updates. */ -import React, { +import { createContext, + useCallback, useContext, useEffect, - useState, - useCallback, useMemo, + useState, type ReactNode, } from 'react'; -import { downloadEventBus } from '../lib/offline/DownloadEventBus'; import { OFFLINE_CONFIG } from '../config/offline.config'; import { PRELOAD_CONFIG } from '../config/preload.config'; +import { downloadEventBus } from '../lib/offline/DownloadEventBus'; import type { - PreloadJob, - PreloadJobStatus, - PreloadProgressEvent, PreloadCompleteEvent, PreloadErrorEvent, + PreloadJob, + PreloadProgressEvent, PreloadStartEvent, } from '../types/offline'; diff --git a/frontend/src/context/PageNavigationContext.tsx b/frontend/src/context/PageNavigationContext.tsx index fdb9021..6c5fd5c 100644 --- a/frontend/src/context/PageNavigationContext.tsx +++ b/frontend/src/context/PageNavigationContext.tsx @@ -18,7 +18,7 @@ * ``` */ -import React, { createContext, useContext, ReactNode, useMemo } from 'react'; +import { createContext, ReactNode, useContext, useMemo } from 'react'; import { usePageNavigationState, UsePageNavigationStateOptions, diff --git a/frontend/src/factories/createFormPage.tsx b/frontend/src/factories/createFormPage.tsx index 48f639b..87e53c4 100644 --- a/frontend/src/factories/createFormPage.tsx +++ b/frontend/src/factories/createFormPage.tsx @@ -3,25 +3,27 @@ */ import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; import { Field, Form, Formik } from 'formik'; +import Head from 'next/head'; import { useRouter } from 'next/router'; +import React, { ReactElement, useEffect, useState } from 'react'; +import BaseButton from '../components/BaseButton'; +import BaseButtons from '../components/BaseButtons'; +import BaseDivider from '../components/BaseDivider'; import CardBox from '../components/CardBox'; -import LayoutAuthenticated from '../layouts/Authenticated'; +import FormField from '../components/FormField'; +import FormImagePicker from '../components/FormImagePicker'; import SectionMain from '../components/SectionMain'; import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; -import FormField from '../components/FormField'; -import BaseDivider from '../components/BaseDivider'; -import BaseButtons from '../components/BaseButtons'; -import BaseButton from '../components/BaseButton'; -import { getPageTitle } from '../config'; -import { useAppDispatch, useAppSelector } from '../stores/hooks'; import { SelectField } from '../components/SelectField'; import { SelectFieldMany } from '../components/SelectFieldMany'; import { SwitchField } from '../components/SwitchField'; -import FormImagePicker from '../components/FormImagePicker'; +import { getPageTitle } from '../config'; +import LayoutAuthenticated from '../layouts/Authenticated'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; import type { AppDispatch, RootState } from '../stores/store'; +import type { BaseEntity } from '../types/entities'; +import type { EntityFetchThunk } from '../types/redux'; type DispatchableAction = Parameters[0]; type FormThunkAction = (arg: Arg) => DispatchableAction; @@ -57,13 +59,13 @@ export interface FormFieldConfig { props?: Record; // Additional props for custom components } -interface FormPageConfig { +interface FormPageConfig { entityName: string; entityTitle: string; singularTitle: string; mode: 'create' | 'edit'; sliceSelector: (state: RootState) => { [key: string]: T | T[] }; - fetchAction?: FormThunkAction<{ id: string }>; + fetchAction?: EntityFetchThunk; createAction?: FormThunkAction; updateAction?: FormThunkAction<{ id: string; data: T }>; permission: string; @@ -72,12 +74,11 @@ interface FormPageConfig { validate?: (values: T) => Record; } -export function createFormPage>( +export function createFormPage>( config: FormPageConfig, ) { const { entityName, - entityTitle, singularTitle, mode, sliceSelector, diff --git a/frontend/src/factories/createListPage.tsx b/frontend/src/factories/createListPage.tsx index 38d9a54..9473b78 100644 --- a/frontend/src/factories/createListPage.tsx +++ b/frontend/src/factories/createListPage.tsx @@ -3,22 +3,22 @@ */ import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; import axios from 'axios'; -import CardBox from '../components/CardBox'; -import LayoutAuthenticated from '../layouts/Authenticated'; -import SectionMain from '../components/SectionMain'; -import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; +import uniqueId from 'lodash/uniqueId'; +import Head from 'next/head'; +import React, { ReactElement, useState } from 'react'; import BaseButton from '../components/BaseButton'; +import CardBox from '../components/CardBox'; import CardBoxModal from '../components/CardBoxModal'; import DragDropFilePicker from '../components/DragDropFilePicker'; +import SectionMain from '../components/SectionMain'; +import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; import { getPageTitle } from '../config'; import { hasPermission } from '../helpers/userPermissions'; +import LayoutAuthenticated from '../layouts/Authenticated'; import { useAppDispatch, useAppSelector } from '../stores/hooks'; -import type { AsyncThunk } from '@reduxjs/toolkit'; import type { Filter, FilterItem } from '../types/filters'; +import type { EntityUploadCsvThunk } from '../types/redux'; interface ListPageConfig { entityName: string; @@ -32,7 +32,7 @@ interface ListPageConfig { filters: Filter[]; readPermission: string; createPermission: string; - uploadCsvAction: AsyncThunk; + uploadCsvAction: EntityUploadCsvThunk; setRefetchAction: (value: boolean) => { type: string; payload: boolean }; newItemLabel?: string; cardBoxId?: string; diff --git a/frontend/src/helpers/fileSaver.ts b/frontend/src/helpers/fileSaver.ts index 242b540..1a73eb1 100644 --- a/frontend/src/helpers/fileSaver.ts +++ b/frontend/src/helpers/fileSaver.ts @@ -1,6 +1,6 @@ import { saveAs } from 'file-saver'; -export const saveFile = (e, url: string, name: string) => { +export const saveFile = (e: React.MouseEvent, url: string, name: string) => { e.stopPropagation(); saveAs(url, name); }; diff --git a/frontend/src/helpers/notifyStateHandler.ts b/frontend/src/helpers/notifyStateHandler.ts index ae21899..11c923f 100644 --- a/frontend/src/helpers/notifyStateHandler.ts +++ b/frontend/src/helpers/notifyStateHandler.ts @@ -1,13 +1,26 @@ -export const resetNotify = (state) => { +import type { PayloadAction } from '@reduxjs/toolkit'; +import type { ApiError } from '../types/api'; + +interface NotifyState { + notify: { + showNotification: boolean; + typeNotification: string; + textNotification: string; + }; +} + +type RejectAction = PayloadAction; + +export const resetNotify = (state: NotifyState) => { state.notify.showNotification = false; state.notify.typeNotification = ''; state.notify.textNotification = ''; }; -export const rejectNotify = (state, action) => { +export const rejectNotify = (state: NotifyState, action: RejectAction) => { if (typeof action.payload === 'string') { state.notify.textNotification = action.payload; } else if (typeof action === 'object') { - const obj = { ...action.payload?.errors }; + const obj = { ...(action.payload as ApiError | undefined)?.errors }; delete obj['_errors']; let msg = ''; @@ -25,7 +38,7 @@ export const rejectNotify = (state, action) => { state.notify.typeNotification = 'error'; state.notify.showNotification = true; }; -export const fulfilledNotify = (state, msg) => { +export const fulfilledNotify = (state: NotifyState, msg: string) => { state.notify.textNotification = msg; state.notify.typeNotification = 'success'; state.notify.showNotification = true; diff --git a/frontend/src/helpers/userPermissions.ts b/frontend/src/helpers/userPermissions.ts index 06802d5..15371f0 100644 --- a/frontend/src/helpers/userPermissions.ts +++ b/frontend/src/helpers/userPermissions.ts @@ -1,17 +1,25 @@ -export function hasPermission(user, permission_name: string | string[]) { - if (!user?.app_role?.name) return false; +import type { User } from '../types/entities'; + +export function hasPermission( + user: User | null | undefined | unknown, + permission_name: string | string[], +) { + if (!user || typeof user !== 'object' || !('app_role' in user)) return false; + const typedUser = user as User; + if (!typedUser.app_role?.name) return false; if (!permission_name) { return true; } // Get permissions from custom_permissions and app_role.permissions (nested) const permissions = new Set([ - ...(user?.custom_permissions ?? []).map((p) => p.name), - ...(user?.app_role?.permissions ?? []).map((p) => p.name), + ...(typedUser.custom_permissions ?? []).map((p) => p.name), + ...(typedUser.app_role?.permissions ?? []).map((p) => p.name), ]); if (typeof permission_name === 'string') { return ( - permissions.has(permission_name) || user.app_role.name === 'Administrator' + permissions.has(permission_name) || + typedUser.app_role.name === 'Administrator' ); } else { return permission_name.some((permission) => permissions.has(permission)); diff --git a/frontend/src/hooks/constructorElementOperations.test.ts b/frontend/src/hooks/constructorElementOperations.test.ts index 3af3aa4..6b1d83a 100644 --- a/frontend/src/hooks/constructorElementOperations.test.ts +++ b/frontend/src/hooks/constructorElementOperations.test.ts @@ -13,8 +13,8 @@ import { getNextSelectedElementIdAfterRemoval, moveInfoPanelSection, normalizeNavigationElementType, - removeElementById, removeCarouselSlide, + removeElementById, removeGalleryCard, removeInfoPanelImage, removeInfoPanelSection, diff --git a/frontend/src/hooks/constructorElementOperations.ts b/frontend/src/hooks/constructorElementOperations.ts index bf40e69..fbb537a 100644 --- a/frontend/src/hooks/constructorElementOperations.ts +++ b/frontend/src/hooks/constructorElementOperations.ts @@ -1,3 +1,14 @@ +import { + ELEMENT_TYPE_LABELS, + getNavigationButtonKind, + getNavigationButtonLabel, +} from '../lib/elementDefaultConstants'; +import { + createDefaultElement, + mergeElementWithDefaults, +} from '../lib/elementDefaults'; +import { createLocalId } from '../lib/elementInstance.helpers'; +import { isNavigationElementType } from '../lib/elementTypeGuards'; import type { CanvasElement, CanvasElementType, @@ -16,17 +27,6 @@ import { generateSectionId, getInfoPanelSections, } from '../types/infoPanel'; -import { - createDefaultElement, - mergeElementWithDefaults, -} from '../lib/elementDefaults'; -import { createLocalId } from '../lib/elementInstance.helpers'; -import { - ELEMENT_TYPE_LABELS, - getNavigationButtonKind, - getNavigationButtonLabel, -} from '../lib/elementDefaultConstants'; -import { isNavigationElementType } from '../lib/elementTypeGuards'; export type NavigationElementType = Extract< CanvasElementType, diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts index 94e78e8..0aa9bf8 100644 --- a/frontend/src/hooks/index.ts +++ b/frontend/src/hooks/index.ts @@ -2,18 +2,31 @@ * Hooks Index - Central export for all custom React hooks */ -export { useFilterItems } from './useFilterItems'; -export { useCSVHandling } from './useCSVHandling'; -export { useFormSync } from './useFormSync'; -export { useEntityTable } from './useEntityTable'; -export { useTransitionPlayback } from './useTransitionPlayback'; +export { useAssetOptions } from './useAssetOptions'; export type { - ReverseMode, - TransitionConfig, - UseTransitionPlaybackOptions, - PlaybackPhase, - UseTransitionPlaybackResult, -} from './useTransitionPlayback'; + AssetOptionsResult, + UseAssetOptionsOptions, +} from './useAssetOptions'; +export { useBackgroundVideoPlayback } from './useBackgroundVideoPlayback'; +export type { + UseBackgroundVideoPlaybackOptions, + UseBackgroundVideoPlaybackResult, +} from './useBackgroundVideoPlayback'; +export { useConstructorData } from './useConstructorData'; +export { useCSVHandling } from './useCSVHandling'; +export { useEntityTable } from './useEntityTable'; +export { useFilterItems } from './useFilterItems'; +export { useFormSync } from './useFormSync'; +export { usePageBackground } from './usePageBackground'; +export type { + UsePageBackgroundOptions, + UsePageBackgroundResult, +} from './usePageBackground'; +export { usePageDataLoader } from './usePageDataLoader'; +export type { + UsePageDataLoaderOptions, + UsePageDataLoaderResult, +} from './usePageDataLoader'; export { usePageNavigation } from './usePageNavigation'; export type { NavigablePage, @@ -22,41 +35,28 @@ export type { } from './usePageNavigation'; export { usePageNavigationState } from './usePageNavigationState'; export type { - NavigationPhase, NavigablePage as NavStatePage, + NavigationPhase, PreloadCacheProvider, UsePageNavigationStateOptions, UsePageNavigationStateResult, } from './usePageNavigationState'; -export { useBackgroundVideoPlayback } from './useBackgroundVideoPlayback'; -export type { - UseBackgroundVideoPlaybackOptions, - UseBackgroundVideoPlaybackResult, -} from './useBackgroundVideoPlayback'; -export { usePageDataLoader } from './usePageDataLoader'; -export type { - UsePageDataLoaderOptions, - UsePageDataLoaderResult, -} from './usePageDataLoader'; -export { usePageBackground } from './usePageBackground'; -export type { - UsePageBackgroundOptions, - UsePageBackgroundResult, -} from './usePageBackground'; -export { useConstructorData } from './useConstructorData'; -export { useAssetOptions } from './useAssetOptions'; -export type { - AssetOptionsResult, - UseAssetOptionsOptions, -} from './useAssetOptions'; +export { useSlideTransition } from './useSlideTransition'; export { useTransitionCreation } from './useTransitionCreation'; export type { - TransitionCreationState, TransitionCreationActions, + TransitionCreationState, UseTransitionCreationOptions, UseTransitionCreationResult, } from './useTransitionCreation'; -export { useSlideTransition } from './useSlideTransition'; +export { useTransitionPlayback } from './useTransitionPlayback'; +export type { + PlaybackPhase, + ReverseMode, + TransitionConfig, + UseTransitionPlaybackOptions, + UseTransitionPlaybackResult, +} from './useTransitionPlayback'; // Constructor hooks - import directly for better tree-shaking: // import { useOutsideClick } from '../hooks/useOutsideClick'; diff --git a/frontend/src/hooks/preloadOrchestrator.helpers.test.ts b/frontend/src/hooks/preloadOrchestrator.helpers.test.ts index 1048a19..1de9560 100644 --- a/frontend/src/hooks/preloadOrchestrator.helpers.test.ts +++ b/frontend/src/hooks/preloadOrchestrator.helpers.test.ts @@ -1,6 +1,11 @@ import assert from 'node:assert/strict'; import test from 'node:test'; +import type { + PreloadElement, + PreloadPage, + PreloadPageLink, +} from '../types/preload'; import { arePreloadTransitionsReady, buildPreloadPlan, @@ -17,11 +22,6 @@ import { mapPreloadAssetType, type PreloadQueueItem, } from './preloadOrchestrator.helpers'; -import type { - PreloadElement, - PreloadPage, - PreloadPageLink, -} from '../types/preload'; const queueItem = ( patch: Partial = {}, diff --git a/frontend/src/hooks/queries/index.ts b/frontend/src/hooks/queries/index.ts index 2138071..22fe750 100644 --- a/frontend/src/hooks/queries/index.ts +++ b/frontend/src/hooks/queries/index.ts @@ -4,67 +4,67 @@ * Centralized exports for React Query hooks. */ +export { useAccessLogsQuery } from './useAccessLogsQuery'; export { - useProjectsQuery, - useProjectQuery, - useUpdateProjectMutation, -} from './useProjectQuery'; -export { - usePagesQuery, - usePageQuery, - useUpdatePageMutation, - useCreatePageMutation, - useDeletePageMutation, -} from './usePagesQuery'; -export { - useAssetsQuery, useAssetQuery, - useUpdateAssetMutation, + useAssetsQuery, useDeleteAssetMutation, + useUpdateAssetMutation, } from './useAssetsQuery'; +export { useAssetVariantsQuery } from './useAssetVariantsQuery'; export { useElementDefaultsQuery } from './useElementDefaultsQuery'; export { - useUsersQuery, - useCurrentUserQuery, - useUserQuery, - useUpdateUserMutation, - useCreateUserMutation, - useDeleteUserMutation, -} from './useUsersQuery'; + useCreatePageMutation, + useDeletePageMutation, + usePageQuery, + usePagesQuery, + useUpdatePageMutation, +} from './usePagesQuery'; export { - useRolesQuery, - useRoleQuery, - useUpdateRoleMutation, - useCreateRoleMutation, - useDeleteRoleMutation, -} from './useRolesQuery'; -export { - usePermissionsQuery, - usePermissionsListQuery, - usePermissionQuery, + buildPermissionListFilters, + downloadPermissionsCsv, useCreatePermissionMutation, - useUpdatePermissionMutation, useDeletePermissionMutation, useDeletePermissionsByIdsMutation, + usePermissionQuery, + usePermissionsListQuery, + usePermissionsQuery, + useUpdatePermissionMutation, useUploadPermissionsCsvMutation, - downloadPermissionsCsv, - buildPermissionListFilters, } from './usePermissionsQuery'; -export { useAccessLogsQuery } from './useAccessLogsQuery'; -export { useAssetVariantsQuery } from './useAssetVariantsQuery'; export { - useProjectMembershipsQuery, - useCreateProjectMembershipMutation, - useDeleteProjectMembershipMutation, -} from './useProjectMembershipsQuery'; -export { - useProjectAudioTracksQuery, - useProjectAudioTrackQuery, useCreateProjectAudioTrackMutation, useDeleteProjectAudioTrackMutation, + useProjectAudioTrackQuery, + useProjectAudioTracksQuery, } from './useProjectAudioTracksQuery'; +export { + useCreateProjectMembershipMutation, + useDeleteProjectMembershipMutation, + useProjectMembershipsQuery, +} from './useProjectMembershipsQuery'; +export { + useProjectQuery, + useProjectsQuery, + useUpdateProjectMutation, +} from './useProjectQuery'; export { usePublishEventsQuery } from './usePublishEventsQuery'; export { - usePwaCachesQuery, useDeletePwaCacheMutation, + usePwaCachesQuery, } from './usePwaCachesQuery'; +export { + useCreateRoleMutation, + useDeleteRoleMutation, + useRoleQuery, + useRolesQuery, + useUpdateRoleMutation, +} from './useRolesQuery'; +export { + useCreateUserMutation, + useCurrentUserQuery, + useDeleteUserMutation, + useUpdateUserMutation, + useUserQuery, + useUsersQuery, +} from './useUsersQuery'; diff --git a/frontend/src/hooks/queries/useAssetsQuery.ts b/frontend/src/hooks/queries/useAssetsQuery.ts index bad419b..47e3280 100644 --- a/frontend/src/hooks/queries/useAssetsQuery.ts +++ b/frontend/src/hooks/queries/useAssetsQuery.ts @@ -4,7 +4,7 @@ * React Query hooks for fetching and caching asset data. */ -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import axios from 'axios'; import { queryKeys } from '../../lib/queryClient'; import type { Asset } from '../../types/entities'; diff --git a/frontend/src/hooks/queries/useElementDefaultsQuery.ts b/frontend/src/hooks/queries/useElementDefaultsQuery.ts index 84b0d40..9fc787e 100644 --- a/frontend/src/hooks/queries/useElementDefaultsQuery.ts +++ b/frontend/src/hooks/queries/useElementDefaultsQuery.ts @@ -7,11 +7,11 @@ import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; import { queryKeys } from '../../lib/queryClient'; -import type { CanvasElementType, CanvasElement } from '../../types/constructor'; +import type { CanvasElement, CanvasElementType } from '../../types/constructor'; import type { NormalizedElementDefault } from '../../types/elementDefaults'; import { - normalizeElementDefault, buildElementDefaultsMap, + normalizeElementDefault, } from '../../types/elementDefaults'; interface ElementDefaultsResponse { diff --git a/frontend/src/hooks/queries/usePagesQuery.ts b/frontend/src/hooks/queries/usePagesQuery.ts index b9e09fe..068e1cd 100644 --- a/frontend/src/hooks/queries/usePagesQuery.ts +++ b/frontend/src/hooks/queries/usePagesQuery.ts @@ -4,7 +4,7 @@ * React Query hooks for fetching and caching tour page data. */ -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import axios from 'axios'; import { queryKeys } from '../../lib/queryClient'; import type { TourPage } from '../../types/entities'; diff --git a/frontend/src/hooks/queries/useProjectAudioTracksQuery.ts b/frontend/src/hooks/queries/useProjectAudioTracksQuery.ts index 331e50c..f81b3a9 100644 --- a/frontend/src/hooks/queries/useProjectAudioTracksQuery.ts +++ b/frontend/src/hooks/queries/useProjectAudioTracksQuery.ts @@ -4,13 +4,13 @@ * React Query hooks for fetching project audio track data. */ -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import axios from 'axios'; import { queryKeys } from '../../lib/queryClient'; interface ProjectAudioTrack { id: string; - projectId: string; + _projectId: string; name: string; cdn_url: string; storage_key: string; @@ -32,7 +32,7 @@ export function useProjectAudioTracksQuery(projectId: string | undefined) { queryKey: queryKeys.projectAudioTracks.list(projectId || ''), queryFn: async (): Promise => { const response = await axios.get( - `project_audio_tracks?projectId=${projectId}`, + `project_audio_tracks?_projectId=${projectId}`, ); return response.data.rows; }, @@ -76,7 +76,7 @@ export function useCreateProjectAudioTrackMutation() { }, onSuccess: (_, variables) => { queryClient.invalidateQueries({ - queryKey: queryKeys.projectAudioTracks.list(variables.projectId || ''), + queryKey: queryKeys.projectAudioTracks.list(variables._projectId || ''), }); }, }); @@ -91,7 +91,6 @@ export function useDeleteProjectAudioTrackMutation() { return useMutation({ mutationFn: async ({ id, - projectId, }: { id: string; projectId: string; diff --git a/frontend/src/hooks/queries/useProjectMembershipsQuery.ts b/frontend/src/hooks/queries/useProjectMembershipsQuery.ts index 947b468..9a3cab5 100644 --- a/frontend/src/hooks/queries/useProjectMembershipsQuery.ts +++ b/frontend/src/hooks/queries/useProjectMembershipsQuery.ts @@ -4,13 +4,13 @@ * React Query hooks for fetching project membership data. */ -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import axios from 'axios'; import { queryKeys } from '../../lib/queryClient'; interface ProjectMembership { id: string; - projectId: string; + _projectId: string; userId: string; role: string; user?: { @@ -34,7 +34,7 @@ export function useProjectMembershipsQuery(projectId: string | undefined) { queryKey: queryKeys.projectMemberships.list(projectId || ''), queryFn: async (): Promise => { const response = await axios.get( - `project_memberships?projectId=${projectId}`, + `project_memberships?_projectId=${projectId}`, ); return response.data.rows; }, @@ -61,7 +61,7 @@ export function useCreateProjectMembershipMutation() { }, onSuccess: (_, variables) => { queryClient.invalidateQueries({ - queryKey: queryKeys.projectMemberships.list(variables.projectId || ''), + queryKey: queryKeys.projectMemberships.list(variables._projectId || ''), }); }, }); @@ -76,7 +76,6 @@ export function useDeleteProjectMembershipMutation() { return useMutation({ mutationFn: async ({ id, - projectId, }: { id: string; projectId: string; diff --git a/frontend/src/hooks/queries/useProjectQuery.ts b/frontend/src/hooks/queries/useProjectQuery.ts index c49967a..1322fe5 100644 --- a/frontend/src/hooks/queries/useProjectQuery.ts +++ b/frontend/src/hooks/queries/useProjectQuery.ts @@ -4,7 +4,7 @@ * React Query hooks for fetching and caching project data. */ -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import axios from 'axios'; import { queryKeys } from '../../lib/queryClient'; import type { Project } from '../../types/entities'; diff --git a/frontend/src/hooks/queries/usePwaCachesQuery.ts b/frontend/src/hooks/queries/usePwaCachesQuery.ts index 727b0b2..a6a52e5 100644 --- a/frontend/src/hooks/queries/usePwaCachesQuery.ts +++ b/frontend/src/hooks/queries/usePwaCachesQuery.ts @@ -4,13 +4,13 @@ * React Query hooks for fetching PWA cache data. */ -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import axios from 'axios'; import { queryKeys } from '../../lib/queryClient'; interface PwaCache { id: string; - projectId: string; + _projectId: string; asset_url: string; asset_type: string; size_bytes: number; @@ -31,7 +31,7 @@ export function usePwaCachesQuery(projectId: string | undefined) { queryKey: queryKeys.pwaCaches.list(projectId || ''), queryFn: async (): Promise => { const response = await axios.get( - `pwa_caches?projectId=${projectId}`, + `pwa_caches?_projectId=${projectId}`, ); return response.data.rows; }, @@ -49,7 +49,6 @@ export function useDeletePwaCacheMutation() { return useMutation({ mutationFn: async ({ id, - projectId, }: { id: string; projectId: string; diff --git a/frontend/src/hooks/queries/useRolesQuery.ts b/frontend/src/hooks/queries/useRolesQuery.ts index c671316..9c1ba13 100644 --- a/frontend/src/hooks/queries/useRolesQuery.ts +++ b/frontend/src/hooks/queries/useRolesQuery.ts @@ -4,7 +4,7 @@ * React Query hooks for fetching and caching role data. */ -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import axios from 'axios'; import { queryKeys } from '../../lib/queryClient'; import type { Role } from '../../types/entities'; diff --git a/frontend/src/hooks/queries/useUsersQuery.ts b/frontend/src/hooks/queries/useUsersQuery.ts index 7d2b4ad..ab17670 100644 --- a/frontend/src/hooks/queries/useUsersQuery.ts +++ b/frontend/src/hooks/queries/useUsersQuery.ts @@ -4,7 +4,7 @@ * React Query hooks for fetching and caching user data. */ -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import axios from 'axios'; import { queryKeys } from '../../lib/queryClient'; import type { User } from '../../types/entities'; diff --git a/frontend/src/hooks/transitionPlayback.helpers.test.ts b/frontend/src/hooks/transitionPlayback.helpers.test.ts index 167f029..6a80fa6 100644 --- a/frontend/src/hooks/transitionPlayback.helpers.test.ts +++ b/frontend/src/hooks/transitionPlayback.helpers.test.ts @@ -11,8 +11,8 @@ import { getTransitionStorageKey, initialTransitionState, isTransitionBuffering, - shouldMarkTransitionVideoReady, shouldFinishOnTimeUpdate, + shouldMarkTransitionVideoReady, transitionReducer, type TransitionConfig, } from './transitionPlayback.helpers'; diff --git a/frontend/src/hooks/useAppearAnimation.ts b/frontend/src/hooks/useAppearAnimation.ts index 289a481..a2e0829 100644 --- a/frontend/src/hooks/useAppearAnimation.ts +++ b/frontend/src/hooks/useAppearAnimation.ts @@ -14,8 +14,8 @@ * browsers (Chrome, Firefox, Safari, Edge, iOS Safari, Android Chrome). */ -import { useState, useCallback, useRef, useEffect } from 'react'; -import type { CSSProperties, AnimationEventHandler } from 'react'; +import type { AnimationEventHandler, CSSProperties } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { buildAppearAnimationStyle, type ElementEffectProperties, diff --git a/frontend/src/hooks/useAssetOptions.ts b/frontend/src/hooks/useAssetOptions.ts index aaa02c6..a5a5df0 100644 --- a/frontend/src/hooks/useAssetOptions.ts +++ b/frontend/src/hooks/useAssetOptions.ts @@ -6,20 +6,18 @@ */ import { useMemo } from 'react'; -import type { - ConstructorAsset as ProjectAsset, - AssetOption, -} from '../types/constructor'; import { - buildAssetOptions, - buildBackgroundImageOptions, - buildVideoAssetOptions, buildAudioAssetOptions, - buildTransitionVideoOptions, + buildBackgroundImageOptions, buildIconAssetOptions, buildImageAssetOptions, + buildTransitionVideoOptions, getAssetSourceValue, } from '../lib/constructorHelpers'; +import type { + AssetOption, + ConstructorAsset as ProjectAsset, +} from '../types/constructor'; export interface AssetOptionsResult { /** All image assets (for galleries, carousels) */ diff --git a/frontend/src/hooks/useAudioEffects.ts b/frontend/src/hooks/useAudioEffects.ts index 6481269..9f4fb9c 100644 --- a/frontend/src/hooks/useAudioEffects.ts +++ b/frontend/src/hooks/useAudioEffects.ts @@ -14,9 +14,9 @@ * - Touch on element counts as user gesture (unlocks audio context) */ -import { useEffect, useRef, useCallback, useState } from 'react'; -import { logger } from '../lib/logger'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { backgroundAudioController } from '../lib/backgroundAudioController'; +import { logger } from '../lib/logger'; import { useGlobalAudioMute } from './useGlobalAudioMute'; /** diff --git a/frontend/src/hooks/useBackdropEffect.ts b/frontend/src/hooks/useBackdropEffect.ts index 34c249f..911ad24 100644 --- a/frontend/src/hooks/useBackdropEffect.ts +++ b/frontend/src/hooks/useBackdropEffect.ts @@ -32,8 +32,8 @@ * ``` */ -import { useEffect, useLayoutEffect, useRef, useId, useCallback } from 'react'; import type { CSSProperties } from 'react'; +import { useCallback, useEffect, useId, useLayoutEffect, useRef } from 'react'; import { useBackdropPortal } from '../components/BackdropPortal'; interface UseBackdropEffectOptions { diff --git a/frontend/src/hooks/useBackgroundAudioPlayback.ts b/frontend/src/hooks/useBackgroundAudioPlayback.ts index 0781c10..62b17b9 100644 --- a/frontend/src/hooks/useBackgroundAudioPlayback.ts +++ b/frontend/src/hooks/useBackgroundAudioPlayback.ts @@ -9,10 +9,10 @@ * and stops on subsequent page visits (until browser refresh). */ -import { useEffect, useRef, useCallback, useMemo, type RefObject } from 'react'; +import { useCallback, useEffect, useMemo, useRef, type RefObject } from 'react'; +import { backgroundAudioController } from '../lib/backgroundAudioController'; import { logger } from '../lib/logger'; import { useAudioEventManager } from './audio/useAudioEventManager'; -import { backgroundAudioController } from '../lib/backgroundAudioController'; import { useGlobalAudioMute } from './useGlobalAudioMute'; // Session-scoped tracking of audio that has finished playing (when loop=false) diff --git a/frontend/src/hooks/useBackgroundVideoPlayback.ts b/frontend/src/hooks/useBackgroundVideoPlayback.ts index dce5b9c..5917768 100644 --- a/frontend/src/hooks/useBackgroundVideoPlayback.ts +++ b/frontend/src/hooks/useBackgroundVideoPlayback.ts @@ -8,7 +8,7 @@ * and show the last frame on subsequent page visits (until browser refresh). */ -import { useEffect, useRef, useCallback, useMemo, type RefObject } from 'react'; +import { useCallback, useEffect, useMemo, useRef, type RefObject } from 'react'; import { logger } from '../lib/logger'; import { useVideoEventManager } from './video/useVideoEventManager'; diff --git a/frontend/src/hooks/useCSVHandling.ts b/frontend/src/hooks/useCSVHandling.ts index d76034e..cb65902 100644 --- a/frontend/src/hooks/useCSVHandling.ts +++ b/frontend/src/hooks/useCSVHandling.ts @@ -2,11 +2,11 @@ * useCSVHandling Hook - Manages CSV upload and download operations */ -import { useState, useCallback } from 'react'; -import axios from 'axios'; -import { useAppDispatch } from '../stores/hooks'; import type { AsyncThunk } from '@reduxjs/toolkit'; +import axios from 'axios'; +import { useCallback, useState } from 'react'; import { logger } from '../lib/logger'; +import { useAppDispatch } from '../stores/hooks'; interface UseCSVHandlingReturn { csvFile: File | null; diff --git a/frontend/src/hooks/useCanvasElapsedTime.ts b/frontend/src/hooks/useCanvasElapsedTime.ts index 1bdd375..d61de10 100644 --- a/frontend/src/hooks/useCanvasElapsedTime.ts +++ b/frontend/src/hooks/useCanvasElapsedTime.ts @@ -5,11 +5,11 @@ * Used in constructor.tsx to control element appear/disappear based on timing. */ -import { useState, useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; interface UseCanvasElapsedTimeOptions { /** Identifier for the current page (resets timer on change) */ - pageId: string; + pageId: string | null; /** Whether the timer is active */ enabled?: boolean; /** Update interval in milliseconds (default: 100ms) */ diff --git a/frontend/src/hooks/useCanvasElementDrag.ts b/frontend/src/hooks/useCanvasElementDrag.ts index 699970a..405594f 100644 --- a/frontend/src/hooks/useCanvasElementDrag.ts +++ b/frontend/src/hooks/useCanvasElementDrag.ts @@ -5,7 +5,7 @@ * Used in constructor.tsx for repositioning UI elements on the canvas. */ -import { useRef, useEffect, useCallback, RefObject } from 'react'; +import { RefObject, useCallback, useEffect, useRef } from 'react'; interface ElementDragState { id: string; diff --git a/frontend/src/hooks/useCanvasScale.ts b/frontend/src/hooks/useCanvasScale.ts index dfbaf17..902827d 100644 --- a/frontend/src/hooks/useCanvasScale.ts +++ b/frontend/src/hooks/useCanvasScale.ts @@ -6,7 +6,7 @@ * Follows pattern: hooks/useStorageQuota.ts */ -import { useState, useEffect, useMemo, type CSSProperties } from 'react'; +import { useEffect, useMemo, useState, type CSSProperties } from 'react'; import { CANVAS_CONFIG } from '../config/canvas.config'; import { calculateCanvasScale, getCanvasCssVars } from '../lib/canvasScale'; diff --git a/frontend/src/hooks/useConstructorData.ts b/frontend/src/hooks/useConstructorData.ts index b6bfe23..72fe4f4 100644 --- a/frontend/src/hooks/useConstructorData.ts +++ b/frontend/src/hooks/useConstructorData.ts @@ -6,14 +6,14 @@ */ import { useMemo } from 'react'; -import { useProjectQuery } from './queries/useProjectQuery'; -import { usePagesQuery } from './queries/usePagesQuery'; +import { extractPageLinksAndElements } from '../lib/extractPageLinks'; +import type { CanvasElement, CanvasElementType } from '../types/constructor'; +import type { Asset, TourPage } from '../types/entities'; +import type { PreloadElement, PreloadPageLink } from '../types/preload'; import { useAssetsQuery } from './queries/useAssetsQuery'; import { useElementDefaultsQuery } from './queries/useElementDefaultsQuery'; -import { extractPageLinksAndElements } from '../lib/extractPageLinks'; -import type { TourPage, Asset } from '../types/entities'; -import type { CanvasElementType, CanvasElement } from '../types/constructor'; -import type { PreloadPageLink, PreloadElement } from '../types/preload'; +import { usePagesQuery } from './queries/usePagesQuery'; +import { useProjectQuery } from './queries/useProjectQuery'; interface UseConstructorDataParams { projectId: string | undefined; diff --git a/frontend/src/hooks/useConstructorElements.ts b/frontend/src/hooks/useConstructorElements.ts index 6711055..d519f43 100644 --- a/frontend/src/hooks/useConstructorElements.ts +++ b/frontend/src/hooks/useConstructorElements.ts @@ -5,26 +5,26 @@ * Used in constructor.tsx for adding, updating, and removing UI elements. */ -import { useState, useCallback, useMemo, useRef } from 'react'; +import { useCallback, useMemo, useRef, useState } from 'react'; +import { cloneElementForPaste } from '../lib/elementInstance.helpers'; +import { + isCarouselElementType, + isGalleryElementType, + isInfoPanelElementType, +} from '../lib/elementTypeGuards'; import type { CanvasElement, CanvasElementType, + CarouselSlide, GalleryCard, GalleryInfoSpan, - CarouselSlide, } from '../types/constructor'; import type { InfoPanelImage, InfoPanelInfoSpan, - InfoPanelSectionType, InfoPanelSectionInstance, + InfoPanelSectionType, } from '../types/infoPanel'; -import { cloneElementForPaste } from '../lib/elementInstance.helpers'; -import { - isGalleryElementType, - isCarouselElementType, - isInfoPanelElementType, -} from '../lib/elementTypeGuards'; import { addCarouselSlide, addGalleryCard, diff --git a/frontend/src/hooks/useConstructorPageActions.helpers.ts b/frontend/src/hooks/useConstructorPageActions.helpers.ts index 9846db3..4209498 100644 --- a/frontend/src/hooks/useConstructorPageActions.helpers.ts +++ b/frontend/src/hooks/useConstructorPageActions.helpers.ts @@ -1,8 +1,8 @@ +import { parseJsonObject } from '../lib/parseJson'; import type { CanvasElement } from '../types/constructor'; import type { TourPage } from '../types/entities'; import type { PageBackgroundState } from '../types/pageBackground'; import type { UiControlsSettings } from '../types/uiControls'; -import { parseJsonObject } from '../lib/parseJson'; export interface ConstructorProjectDimensions { design_width?: number; diff --git a/frontend/src/hooks/useConstructorPageActions.ts b/frontend/src/hooks/useConstructorPageActions.ts index 42a006d..3df806a 100644 --- a/frontend/src/hooks/useConstructorPageActions.ts +++ b/frontend/src/hooks/useConstructorPageActions.ts @@ -5,14 +5,13 @@ * Manages async state and API calls for page operations. */ -import { useState, useCallback } from 'react'; import axios from 'axios'; +import { useCallback, useState } from 'react'; +import { logger } from '../lib/logger'; import type { CanvasElement } from '../types/constructor'; import type { TourPage } from '../types/entities'; import type { PageBackgroundState } from '../types/pageBackground'; import type { UiControlsSettings } from '../types/uiControls'; -import { logger } from '../lib/logger'; -import { useReverseVideoPolling } from './useReverseVideoPolling'; import { buildConstructorPageSavePayload, buildCreateConstructorPagePayload, @@ -23,6 +22,7 @@ import { getPendingReverseVideoKeys, type ConstructorProjectDimensions, } from './useConstructorPageActions.helpers'; +import { useReverseVideoPolling } from './useReverseVideoPolling'; interface Project extends ConstructorProjectDimensions { id?: string; @@ -188,20 +188,13 @@ export function useConstructorPageActions({ setIsSaving(false); } }, [ - activePage?.environment, - activePage?.name, - activePage?.requires_auth, - activePage?.slug, - activePage?.sort_order, - activePage?.source_key, - activePage?.ui_schema_json, + activePage, activePageId, pageBackground, uiControlsSettings, elements, getElements, - project?.design_width, - project?.design_height, + project, onError, onReload, onSuccess, @@ -287,15 +280,14 @@ export function useConstructorPageActions({ } }, [ - activePage?.environment, + activePage, onError, onReload, onSetActivePageId, onSetMenuOpen, onSuccess, pages, - project?.design_width, - project?.design_height, + project, projectId, ], ); diff --git a/frontend/src/hooks/useDashboardCounts.ts b/frontend/src/hooks/useDashboardCounts.ts index bd35d8c..69874ce 100644 --- a/frontend/src/hooks/useDashboardCounts.ts +++ b/frontend/src/hooks/useDashboardCounts.ts @@ -5,8 +5,8 @@ * with permission-based filtering and proper error handling. */ -import { useState, useEffect, useCallback, useRef } from 'react'; import axios from 'axios'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { hasPermission } from '../helpers/userPermissions'; // User type from auth state (currentUser) diff --git a/frontend/src/hooks/useDevCompilationStatus.ts b/frontend/src/hooks/useDevCompilationStatus.ts index 3bb14c1..343b74a 100644 --- a/frontend/src/hooks/useDevCompilationStatus.ts +++ b/frontend/src/hooks/useDevCompilationStatus.ts @@ -1,5 +1,5 @@ -import { useState, useEffect } from 'react'; import { useRouter } from 'next/router'; +import { useEffect, useState } from 'react'; import { isDevelopment } from '../config'; type CompilationStatus = 'ready' | 'compiling' | 'error' | 'initial'; diff --git a/frontend/src/hooks/useDraggable.ts b/frontend/src/hooks/useDraggable.ts index 97484b1..0985746 100644 --- a/frontend/src/hooks/useDraggable.ts +++ b/frontend/src/hooks/useDraggable.ts @@ -5,7 +5,7 @@ * Used in constructor.tsx for draggable controls, menu, and editor panels. */ -import { useState, useRef, useEffect, useCallback } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; interface Position { x: number; diff --git a/frontend/src/hooks/useEditPageSync.ts b/frontend/src/hooks/useEditPageSync.ts index 754649f..3a76445 100644 --- a/frontend/src/hooks/useEditPageSync.ts +++ b/frontend/src/hooks/useEditPageSync.ts @@ -8,17 +8,19 @@ * Reduces ~50 lines of duplicated code across edit pages. */ -import { useEffect, useState, useCallback } from 'react'; import { useRouter } from 'next/router'; -import { AsyncThunk } from '@reduxjs/toolkit'; +import { useEffect, useState } from 'react'; import { useAppDispatch, useAppSelector } from '../stores/hooks'; -import type { RootState } from '../stores/store'; +import type { AppDispatch, RootState } from '../stores/store'; + +type DispatchableAction = Parameters[0]; +type FetchAction = (arg: { id?: string; query?: string }) => unknown; interface UseEditPageSyncOptions> { /** Redux selector to get the entity from state (can return single entity, array, or any slice state value) */ entitySelector: (state: RootState) => unknown; /** Redux async thunk to fetch the entity */ - fetchAction: AsyncThunk; + fetchAction: FetchAction; /** Initial form values */ initialValues: T; /** Optional post-processing of entity data before setting form values */ @@ -108,7 +110,7 @@ export function useEditPageSync>( useEffect(() => { if (id) { setIsLoading(true); - dispatch(fetchAction({ id })) + Promise.resolve(dispatch(fetchAction({ id }) as DispatchableAction)) .then(() => setIsLoading(false)) .catch(() => setIsLoading(false)); } diff --git a/frontend/src/hooks/useElementEffects.ts b/frontend/src/hooks/useElementEffects.ts index 6a09780..70dd6e7 100644 --- a/frontend/src/hooks/useElementEffects.ts +++ b/frontend/src/hooks/useElementEffects.ts @@ -6,17 +6,17 @@ * handles state-based style application via JavaScript events. */ -import { useCallback, useState, useEffect } from 'react'; import type { CSSProperties } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { - buildHoverStyle, - buildFocusStyle, buildActiveStyle, - buildTransitionStyle, + buildFocusStyle, buildHoverRevealStyle, - hasHoverEffects, - hasFocusEffects, + buildHoverStyle, + buildTransitionStyle, hasActiveEffects, + hasFocusEffects, + hasHoverEffects, hasHoverReveal, type ElementEffectProperties, } from '../lib/elementEffects'; diff --git a/frontend/src/hooks/useEntityTable.ts b/frontend/src/hooks/useEntityTable.ts index d4d401b..58964a9 100644 --- a/frontend/src/hooks/useEntityTable.ts +++ b/frontend/src/hooks/useEntityTable.ts @@ -2,15 +2,15 @@ * useEntityTable Hook */ -import { useState, useEffect, useCallback, useMemo } from 'react'; +import type { GridColDef, GridSortModel } from '@mui/x-data-grid'; +import type { AsyncThunk } from '@reduxjs/toolkit'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useAppDispatch, useAppSelector } from '../stores/hooks'; import type { RootState } from '../stores/store'; -import type { AsyncThunk } from '@reduxjs/toolkit'; -import type { GridColDef, GridSortModel } from '@mui/x-data-grid'; -import type { BaseEntity } from '../types/entities'; import type { FetchParams } from '../types/api'; -import type { NotificationState, EntitySliceState } from '../types/redux'; +import type { BaseEntity } from '../types/entities'; import type { Filter, FilterItem } from '../types/filters'; +import type { EntitySliceState } from '../types/redux'; interface UseEntityTableOptions { entityName: string; @@ -105,7 +105,7 @@ export function useEntityTable( // Extract data from state const data = (entityState[entityName] as T[]) || []; - const { loading, count, refetch, notify } = entityState; + const { loading, count, refetch } = entityState; // Local state const [currentPage, setCurrentPage] = useState(0); @@ -140,22 +140,11 @@ export function useEntityTable( [currentPage, filterRequest, sortModel, perPage, dispatch, fetchAction], ); - // Load columns when user is available - useEffect(() => { - if (!currentUser) return; - - loadColumnsFunction( - (id: string) => handleDeleteClick(id), - entityName, - currentUser, - ).then(setColumns); - }, [currentUser, entityName, loadColumnsFunction]); - // Load data when sort model changes useEffect(() => { if (!currentUser) return; loadData(); - }, [sortModel, currentUser]); + }, [sortModel, currentUser, loadData]); // Handle refetch flag useEffect(() => { @@ -211,6 +200,17 @@ export function useEntityTable( setIsDeleteModalActive(true); }, []); + // Load columns when user is available + useEffect(() => { + if (!currentUser) return; + + loadColumnsFunction( + (id: string) => handleDeleteClick(id), + entityName, + currentUser, + ).then(setColumns); + }, [currentUser, entityName, handleDeleteClick, loadColumnsFunction]); + const handleDeleteConfirm = useCallback(async () => { if (!deleteTargetId) return; diff --git a/frontend/src/hooks/useFilterItems.ts b/frontend/src/hooks/useFilterItems.ts index 1561322..ec80869 100644 --- a/frontend/src/hooks/useFilterItems.ts +++ b/frontend/src/hooks/useFilterItems.ts @@ -2,12 +2,12 @@ * useFilterItems Hook - Manages filter state for data tables */ -import { useState, useMemo, useCallback } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import { v4 as uuidv4 } from 'uuid'; import type { Filter, - FilterItem, FilterFields, + FilterItem, FilterRequest, } from '../types/filters'; diff --git a/frontend/src/hooks/useFormSync.ts b/frontend/src/hooks/useFormSync.ts index b15a5e9..fa0954f 100644 --- a/frontend/src/hooks/useFormSync.ts +++ b/frontend/src/hooks/useFormSync.ts @@ -2,13 +2,13 @@ * useFormSync Hook */ -import { useState, useEffect, useCallback } from 'react'; +import type { AsyncThunk } from '@reduxjs/toolkit'; import { useRouter } from 'next/router'; +import { useCallback, useEffect, useState } from 'react'; import { useAppDispatch, useAppSelector } from '../stores/hooks'; import type { RootState } from '../stores/store'; -import type { AsyncThunk } from '@reduxjs/toolkit'; -import type { BaseEntity } from '../types/entities'; import type { FetchParams } from '../types/api'; +import type { BaseEntity } from '../types/entities'; interface UseFormSyncOptions { entitySelector: (state: RootState) => T | T[] | undefined; diff --git a/frontend/src/hooks/useIconPreload.ts b/frontend/src/hooks/useIconPreload.ts index 0ea110e..6a132a0 100644 --- a/frontend/src/hooks/useIconPreload.ts +++ b/frontend/src/hooks/useIconPreload.ts @@ -5,7 +5,7 @@ * Used in constructor.tsx to preload navigation/description icons. */ -import { useState, useEffect, useRef, useCallback, useMemo } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { resolveAssetPlaybackUrl } from '../lib/assetUrl'; import { logger } from '../lib/logger'; diff --git a/frontend/src/hooks/useMediaDurationProbe.ts b/frontend/src/hooks/useMediaDurationProbe.ts index 22e37c4..ae6b7b5 100644 --- a/frontend/src/hooks/useMediaDurationProbe.ts +++ b/frontend/src/hooks/useMediaDurationProbe.ts @@ -5,12 +5,12 @@ * Used in constructor.tsx for displaying video/audio duration info. */ -import { useState, useEffect, useRef, useCallback, useMemo } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { logger } from '../lib/logger'; import { formatDurationNote, resolveDurationWithFallback, } from '../lib/mediaHelpers'; -import { logger } from '../lib/logger'; interface DurationProbeTarget { source: string; diff --git a/frontend/src/hooks/useNetworkAware.ts b/frontend/src/hooks/useNetworkAware.ts index 6486edb..3646876 100644 --- a/frontend/src/hooks/useNetworkAware.ts +++ b/frontend/src/hooks/useNetworkAware.ts @@ -4,7 +4,7 @@ * Monitors network conditions and adapts behavior accordingly. */ -import { useState, useEffect, useCallback } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import type { NetworkInfo } from '../types/offline'; interface NetworkInformation extends EventTarget { diff --git a/frontend/src/hooks/useOfflineDownloadEvents.ts b/frontend/src/hooks/useOfflineDownloadEvents.ts index 16a9a68..02da588 100644 --- a/frontend/src/hooks/useOfflineDownloadEvents.ts +++ b/frontend/src/hooks/useOfflineDownloadEvents.ts @@ -1,9 +1,9 @@ import { useEffect } from 'react'; import { OFFLINE_CONFIG } from '../config/offline.config'; import type { AssetToCache } from '../lib/assetCache'; +import { logger } from '../lib/logger'; import { downloadEventBus } from '../lib/offline/DownloadEventBus'; import { OfflineDbManager } from '../lib/offlineDb/OfflineDbManager'; -import { logger } from '../lib/logger'; import type { PreloadCompleteEvent, PreloadErrorEvent, diff --git a/frontend/src/hooks/useOfflineMode.helpers.ts b/frontend/src/hooks/useOfflineMode.helpers.ts index 5c40334..262b055 100644 --- a/frontend/src/hooks/useOfflineMode.helpers.ts +++ b/frontend/src/hooks/useOfflineMode.helpers.ts @@ -1,10 +1,10 @@ import type { AssetToCache } from '../lib/assetCache'; import { discoverProjectAssets } from '../lib/assetCache'; -import { extractPageLinksAndElements } from '../lib/extractPageLinks'; import { isRelativeStoragePath, resolveAssetPlaybackUrl, } from '../lib/assetUrl'; +import { extractPageLinksAndElements } from '../lib/extractPageLinks'; import type { OfflineProject } from '../types/offline'; import type { PreloadPage } from '../types/preload'; diff --git a/frontend/src/hooks/useOfflineMode.ts b/frontend/src/hooks/useOfflineMode.ts index 3a093ff..b97519e 100644 --- a/frontend/src/hooks/useOfflineMode.ts +++ b/frontend/src/hooks/useOfflineMode.ts @@ -6,31 +6,30 @@ * No backend manifest dependency - single source of truth in frontend. */ -import { useState, useCallback, useMemo, useRef } from 'react'; +import { useCallback, useMemo, useRef, useState } from 'react'; +import type { AssetToCache } from '../lib/assetCache'; +import { queuePresignedUrls } from '../lib/assetUrl'; +import { logger } from '../lib/logger'; import { downloadManager } from '../lib/offline/DownloadManager'; import { StorageManager } from '../lib/offline/StorageManager'; import { OfflineDbManager } from '../lib/offlineDb/OfflineDbManager'; -import { queuePresignedUrls } from '../lib/assetUrl'; -import type { AssetToCache } from '../lib/assetCache'; -import { logger } from '../lib/logger'; -import { - buildOfflineAssetCachePlan, - buildOfflineDownloadJobParams, - buildOfflineProjectRecord, - calculateOfflineProgress, - discoverOfflineProjectAssets, - formatOfflineBytes, - getOfflineAssetsTotalSize, - getOfflineStoragePathsForPresign, -} from './useOfflineMode.helpers'; -import { useOfflineDownloadEvents } from './useOfflineDownloadEvents'; -import { useOfflineProjectInfo } from './useOfflineProjectInfo'; import type { CachedAssetInfo, OfflineProject, ProjectOfflineStatus, } from '../types/offline'; import type { PreloadPage } from '../types/preload'; +import { useOfflineDownloadEvents } from './useOfflineDownloadEvents'; +import { + buildOfflineAssetCachePlan, + buildOfflineDownloadJobParams, + buildOfflineProjectRecord, + discoverOfflineProjectAssets, + formatOfflineBytes, + getOfflineAssetsTotalSize, + getOfflineStoragePathsForPresign, +} from './useOfflineMode.helpers'; +import { useOfflineProjectInfo } from './useOfflineProjectInfo'; interface UseOfflineModeOptions { projectId: string | null; diff --git a/frontend/src/hooks/useOutsideClick.ts b/frontend/src/hooks/useOutsideClick.ts index 4349e8f..0db3254 100644 --- a/frontend/src/hooks/useOutsideClick.ts +++ b/frontend/src/hooks/useOutsideClick.ts @@ -5,7 +5,7 @@ * Used in constructor.tsx to deselect elements when clicking outside. */ -import { useEffect, useCallback, RefObject } from 'react'; +import { RefObject, useCallback, useEffect } from 'react'; interface UseOutsideClickOptions { /** Ref to the element whose outside clicks should be detected */ @@ -15,7 +15,7 @@ interface UseOutsideClickOptions { /** Data attribute to check on clicked elements (if present, won't trigger) */ ignoreDataAttribute?: string; /** Current selected value to check (e.g., element ID) */ - selectedValue?: string; + selectedValue?: string | null; /** Callback when click outside is detected */ onOutsideClick: () => void; /** Whether the hook is active */ diff --git a/frontend/src/hooks/usePageBackground.ts b/frontend/src/hooks/usePageBackground.ts index c434265..7c2f73a 100644 --- a/frontend/src/hooks/usePageBackground.ts +++ b/frontend/src/hooks/usePageBackground.ts @@ -16,11 +16,11 @@ * - backgroundVideoEndTime, setBackgroundVideoEndTime */ -import { useState, useCallback } from 'react'; +import { useCallback, useState } from 'react'; import type { + PageBackgroundAudioSettings, PageBackgroundState, PageBackgroundVideoSettings, - PageBackgroundAudioSettings, } from '../types/pageBackground'; import { DEFAULT_PAGE_BACKGROUND, diff --git a/frontend/src/hooks/usePageDataLoader.ts b/frontend/src/hooks/usePageDataLoader.ts index 9860633..d2b4303 100644 --- a/frontend/src/hooks/usePageDataLoader.ts +++ b/frontend/src/hooks/usePageDataLoader.ts @@ -12,10 +12,10 @@ * - Supports reloading with page preservation */ -import { useState, useCallback, useEffect, useMemo } from 'react'; import axios from 'axios'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { logger } from '../lib/logger'; -import type { RuntimeProject, RuntimePage } from '../types/runtime'; +import type { RuntimePage, RuntimeProject } from '../types/runtime'; /** * Configuration for the page data loader diff --git a/frontend/src/hooks/usePageNavigationState.ts b/frontend/src/hooks/usePageNavigationState.ts index ca2dbf8..63faeb6 100644 --- a/frontend/src/hooks/usePageNavigationState.ts +++ b/frontend/src/hooks/usePageNavigationState.ts @@ -21,10 +21,10 @@ * - fading_in: Black overlay fading out to reveal new page */ -import { useReducer, useCallback, useRef, useEffect, useMemo } from 'react'; -import { isSafari, getCrossfadeDuration } from '../lib/browserUtils'; -import { logger } from '../lib/logger'; +import { useCallback, useEffect, useMemo, useReducer, useRef } from 'react'; import { isDevelopment } from '../config'; +import { getCrossfadeDuration, isSafari } from '../lib/browserUtils'; +import { logger } from '../lib/logger'; import type { ResolvedTransitionSettings } from '../types/transition'; import { getPageNavigationDerivedState, @@ -144,10 +144,10 @@ export interface UsePageNavigationStateResult { /** Direct background update for edit mode (bypasses navigation flow) */ setBackgroundDirectly: ( - imageUrl: string, - videoUrl: string, - embedUrl: string, - audioUrl: string, + imageUrl?: string, + videoUrl?: string, + embedUrl?: string, + audioUrl?: string, ) => void; /** Reset to idle state */ @@ -171,8 +171,6 @@ export function usePageNavigationState( options: UsePageNavigationStateOptions = {}, ): UsePageNavigationStateResult { const { preloadCache, transitionSettings } = options; - const fadeDurationMs = - options.fadeDurationMs ?? transitionSettings?.durationMs ?? 700; const [state, dispatch] = useReducer( navigationReducer, @@ -306,12 +304,7 @@ export function usePageNavigationState( }, []); const setBackgroundDirectly = useCallback( - ( - imageUrl: string, - videoUrl: string, - embedUrl: string, - audioUrl: string, - ) => { + (imageUrl = '', videoUrl = '', embedUrl = '', audioUrl = '') => { dispatch({ type: 'SET_BACKGROUND_DIRECTLY', payload: { imageUrl, videoUrl, embedUrl, audioUrl }, @@ -387,11 +380,7 @@ export function usePageNavigationState( // Transition style const transitionStyle: React.CSSProperties = useMemo( () => getPageNavigationTransitionStyle(transitionSettings), - [ - transitionSettings?.durationMs, - transitionSettings?.easing, - transitionSettings?.overlayColor, - ], + [transitionSettings], ); return { diff --git a/frontend/src/hooks/usePreloadOrchestrator.ts b/frontend/src/hooks/usePreloadOrchestrator.ts index 0795bb7..50c4e21 100644 --- a/frontend/src/hooks/usePreloadOrchestrator.ts +++ b/frontend/src/hooks/usePreloadOrchestrator.ts @@ -5,28 +5,26 @@ * Preloads current page assets and outgoing transition videos. */ -import { useEffect, useRef, useCallback, useState } from 'react'; -import { useNetworkAware } from './useNetworkAware'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { OFFLINE_CONFIG } from '../config/offline.config'; +import { PRELOAD_CONFIG } from '../config/preload.config'; import { extractElementAssets } from '../lib/assetCache'; +import { + extractStoragePath, + isPresignedUrl, + isRelativeStoragePath, + markPresignedUrlsVerified, + queuePresignedUrls, + resolveAssetPlaybackUrl, +} from '../lib/assetUrl'; +import { logger } from '../lib/logger'; import { downloadEventBus } from '../lib/offline/DownloadEventBus'; import { downloadManager } from '../lib/offline/DownloadManager'; import { StorageManager } from '../lib/offline/StorageManager'; -import { PRELOAD_CONFIG } from '../config/preload.config'; -import { OFFLINE_CONFIG } from '../config/offline.config'; -import { - resolveAssetPlaybackUrl, - extractStoragePath, - queuePresignedUrls, - isRelativeStoragePath, - markPresignedUrlsVerified, - isPresignedUrl, -} from '../lib/assetUrl'; -import { logger } from '../lib/logger'; -import type { BlobUrlReadyEvent } from '../types/offline'; import type { + PreloadElement, PreloadPage, PreloadPageLink, - PreloadElement, } from '../types/preload'; import { arePreloadTransitionsReady, @@ -46,6 +44,7 @@ import { type PreloadPlanJob, type PreloadQueueItem, } from './preloadOrchestrator.helpers'; +import { useNetworkAware } from './useNetworkAware'; interface UsePreloadOrchestratorOptions { pages: PreloadPage[]; @@ -99,9 +98,16 @@ export function usePreloadOrchestrator( OFFLINE_CONFIG.events.blobUrlReady as Parameters< typeof downloadEventBus.on >[0], - (data: BlobUrlReadyEvent) => { - preloadedUrls.add(data.storageKey); - setReadyUrlsVersion((v) => v + 1); + (data) => { + if ( + data && + typeof data === 'object' && + 'storageKey' in data && + typeof data.storageKey === 'string' + ) { + preloadedUrls.add(data.storageKey); + setReadyUrlsVersion((v) => v + 1); + } }, ); return unsubscribe; @@ -480,6 +486,7 @@ export function usePreloadOrchestrator( elements, pages, pageLinks, + preloadedUrls, ]); const isCurrentPageReady = isCurrentPreloadPageReady(currentPhase); diff --git a/frontend/src/hooks/usePreloadProgress.ts b/frontend/src/hooks/usePreloadProgress.ts index 782e1b5..b3de170 100644 --- a/frontend/src/hooks/usePreloadProgress.ts +++ b/frontend/src/hooks/usePreloadProgress.ts @@ -5,16 +5,16 @@ * Adapted from hoboken's useVideoOptimizationProgress pattern. */ -import { useState, useEffect, useCallback, useMemo } from 'react'; -import { downloadEventBus } from '../lib/offline/DownloadEventBus'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { OFFLINE_CONFIG } from '../config/offline.config'; import { PRELOAD_CONFIG } from '../config/preload.config'; +import { downloadEventBus } from '../lib/offline/DownloadEventBus'; import type { - PreloadJob, - PreloadStartEvent, - PreloadProgressEvent, PreloadCompleteEvent, PreloadErrorEvent, + PreloadJob, + PreloadProgressEvent, + PreloadStartEvent, } from '../types/offline'; interface UsePreloadProgressResult { diff --git a/frontend/src/hooks/useProjectAssets.ts b/frontend/src/hooks/useProjectAssets.ts index 9bc7133..7d0f7e4 100644 --- a/frontend/src/hooks/useProjectAssets.ts +++ b/frontend/src/hooks/useProjectAssets.ts @@ -5,7 +5,7 @@ * Handles both storage_key (relative paths) and legacy cdn_url (full S3 URLs) formats. */ -import { useState, useEffect, useRef, useCallback } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { extractStoragePath, queuePresignedUrls, diff --git a/frontend/src/hooks/usePublishStatus.ts b/frontend/src/hooks/usePublishStatus.ts index 6518654..874a292 100644 --- a/frontend/src/hooks/usePublishStatus.ts +++ b/frontend/src/hooks/usePublishStatus.ts @@ -4,8 +4,8 @@ * Fetches the last publish/save events for a project to display timestamps. * Follows useDashboardCounts pattern with mountedRef and robust error handling. */ -import { useState, useEffect, useCallback, useRef } from 'react'; import axios from 'axios'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { logger } from '../lib/logger'; interface UsePublishStatusOptions { diff --git a/frontend/src/hooks/useReverseVideoPolling.ts b/frontend/src/hooks/useReverseVideoPolling.ts index 6761c61..b83b5d9 100644 --- a/frontend/src/hooks/useReverseVideoPolling.ts +++ b/frontend/src/hooks/useReverseVideoPolling.ts @@ -5,8 +5,8 @@ * Automatically reloads page data when all pending videos are generated. */ -import { useRef, useCallback } from 'react'; import axios from 'axios'; +import { useCallback, useRef } from 'react'; import { logger } from '../lib/logger'; interface ReverseVideoStatusResponse { diff --git a/frontend/src/hooks/useSlideTransition.ts b/frontend/src/hooks/useSlideTransition.ts index 8aab237..86619ca 100644 --- a/frontend/src/hooks/useSlideTransition.ts +++ b/frontend/src/hooks/useSlideTransition.ts @@ -5,8 +5,8 @@ * Implements fade-through-overlay: Slide 1 -> fade out -> overlay -> fade in -> Slide 2 */ -import { useState, useCallback, useRef, useEffect } from 'react'; import type { CSSProperties } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import type { ResolvedSlideTransition } from '../lib/resolveSlideTransition'; // Transition phases: idle -> fadingOut -> fadingIn -> idle diff --git a/frontend/src/hooks/useStorageQuota.ts b/frontend/src/hooks/useStorageQuota.ts index 30a8009..566be29 100644 --- a/frontend/src/hooks/useStorageQuota.ts +++ b/frontend/src/hooks/useStorageQuota.ts @@ -4,9 +4,9 @@ * Monitors storage quota and usage for offline assets. */ -import { useState, useEffect, useCallback } from 'react'; -import { StorageManager } from '../lib/offline/StorageManager'; +import { useCallback, useEffect, useState } from 'react'; import { PRELOAD_CONFIG } from '../config/preload.config'; +import { StorageManager } from '../lib/offline/StorageManager'; import type { StorageQuotaInfo } from '../types/offline'; interface UseStorageQuotaResult extends StorageQuotaInfo { diff --git a/frontend/src/hooks/useTransitionCreation.ts b/frontend/src/hooks/useTransitionCreation.ts index fa735be..3b64d52 100644 --- a/frontend/src/hooks/useTransitionCreation.ts +++ b/frontend/src/hooks/useTransitionCreation.ts @@ -5,7 +5,7 @@ * Used in the constructor to manage the "Create Transition" form. */ -import { useState, useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import type { AssetOption } from '../types/constructor'; export interface TransitionCreationState { diff --git a/frontend/src/hooks/useTransitionPlayback.ts b/frontend/src/hooks/useTransitionPlayback.ts index dc55ed8..029578e 100644 --- a/frontend/src/hooks/useTransitionPlayback.ts +++ b/frontend/src/hooks/useTransitionPlayback.ts @@ -13,11 +13,6 @@ import { type RefObject, } from 'react'; import { logger } from '../lib/logger'; -import { - useVideoBlobUrl, - useVideoTimeouts, - type PreloadCacheProvider, -} from './video'; import { getTransitionSourceUrl, getTransitionStorageKey, @@ -32,6 +27,11 @@ import { clearTransitionVideoElement, useTransitionVideoElement, } from './useTransitionVideoElement'; +import { + useVideoBlobUrl, + useVideoTimeouts, + type PreloadCacheProvider, +} from './video'; export type { PlaybackPhase, ReverseMode, TransitionConfig }; diff --git a/frontend/src/hooks/useTransitionPreview.ts b/frontend/src/hooks/useTransitionPreview.ts index a97dc9d..5bd048f 100644 --- a/frontend/src/hooks/useTransitionPreview.ts +++ b/frontend/src/hooks/useTransitionPreview.ts @@ -5,9 +5,9 @@ * Used to preview forward and reverse transitions before navigation. */ -import { useState, useCallback, useMemo } from 'react'; -import type { TransitionPreviewState } from '../types/presentation'; +import { useCallback, useMemo, useState } from 'react'; import { logger } from '../lib/logger'; +import type { TransitionPreviewState } from '../types/presentation'; export type { TransitionPreviewState }; diff --git a/frontend/src/hooks/useTransitionSettings.ts b/frontend/src/hooks/useTransitionSettings.ts index 3e22db9..c133385 100644 --- a/frontend/src/hooks/useTransitionSettings.ts +++ b/frontend/src/hooks/useTransitionSettings.ts @@ -9,13 +9,12 @@ import { useMemo } from 'react'; import type { + EasingFunction, + ElementTransitionSettings, GlobalTransitionDefaults, ProjectTransitionSettings, - ElementTransitionSettings, ResolvedTransitionSettings, TransitionType, - EasingFunction, - DEFAULT_TRANSITION_SETTINGS, } from '../types/transition'; export interface UseTransitionSettingsParams { diff --git a/frontend/src/hooks/useTransitionVideoElement.ts b/frontend/src/hooks/useTransitionVideoElement.ts index 0c3da67..835baad 100644 --- a/frontend/src/hooks/useTransitionVideoElement.ts +++ b/frontend/src/hooks/useTransitionVideoElement.ts @@ -7,18 +7,18 @@ import { import { TRANSITION_CONFIG } from '../config/transition.config'; import { isSafari, scheduleAfterPaint } from '../lib/browserUtils'; import { logger } from '../lib/logger'; -import type { UseVideoTimeoutsResult } from './video/useVideoTimeouts'; import { getTransitionDurationSec, getTransitionFinishDelayMs, getTransitionNoProgressTimeoutMs, getTransitionPlaybackStartTimeoutMs, getTransitionSourceKey, - shouldMarkTransitionVideoReady, shouldFinishOnTimeUpdate, + shouldMarkTransitionVideoReady, type TransitionAction, type TransitionConfig, } from './transitionPlayback.helpers'; +import type { UseVideoTimeoutsResult } from './video/useVideoTimeouts'; interface TransitionVideoElementRefs { didFinishRef: MutableRefObject; diff --git a/frontend/src/hooks/video/index.ts b/frontend/src/hooks/video/index.ts index ed464d0..bf327fe 100644 --- a/frontend/src/hooks/video/index.ts +++ b/frontend/src/hooks/video/index.ts @@ -8,10 +8,10 @@ // Primitive hooks export { useVideoEventManager, - type VideoEventType, + type UseVideoEventManagerOptions, type VideoEventHandler, type VideoEventHandlers, - type UseVideoEventManagerOptions, + type VideoEventType, } from './useVideoEventManager'; export { diff --git a/frontend/src/hooks/video/useVideoBlobUrl.ts b/frontend/src/hooks/video/useVideoBlobUrl.ts index 1f34504..b41bb35 100644 --- a/frontend/src/hooks/video/useVideoBlobUrl.ts +++ b/frontend/src/hooks/video/useVideoBlobUrl.ts @@ -7,9 +7,9 @@ * 3. Proxy URL fallback */ -import { useState, useCallback, useRef, useEffect } from 'react'; -import { logger } from '../../lib/logger'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { resolveAssetPlaybackUrl } from '../../lib/assetUrl'; +import { logger } from '../../lib/logger'; export interface PreloadCacheProvider { getReadyBlob?: (key: string) => Blob | null; diff --git a/frontend/src/hooks/video/useVideoBufferingState.ts b/frontend/src/hooks/video/useVideoBufferingState.ts index 7bb6597..91e687f 100644 --- a/frontend/src/hooks/video/useVideoBufferingState.ts +++ b/frontend/src/hooks/video/useVideoBufferingState.ts @@ -8,14 +8,14 @@ */ import { - useState, - useRef, useCallback, useEffect, + useRef, + useState, type RefObject, } from 'react'; -import { logger } from '../../lib/logger'; import { TRANSITION_CONFIG } from '../../config/transition.config'; +import { logger } from '../../lib/logger'; export interface UseVideoBufferingStateOptions { videoRef: RefObject; diff --git a/frontend/src/hooks/video/useVideoErrorRecovery.ts b/frontend/src/hooks/video/useVideoErrorRecovery.ts index 97bc38e..96a8e57 100644 --- a/frontend/src/hooks/video/useVideoErrorRecovery.ts +++ b/frontend/src/hooks/video/useVideoErrorRecovery.ts @@ -6,15 +6,15 @@ * - Proxy URL fallback on presigned URL failure */ -import { useRef, useCallback, useEffect, type RefObject } from 'react'; -import { logger } from '../../lib/logger'; +import { useCallback, useEffect, useRef, type RefObject } from 'react'; +import { TRANSITION_CONFIG } from '../../config/transition.config'; import { - isPresignedUrl, buildProxyUrl, + isPresignedUrl, markPresignedUrlFailed, } from '../../lib/assetUrl'; -import { TRANSITION_CONFIG } from '../../config/transition.config'; import { isSafari } from '../../lib/browserUtils'; +import { logger } from '../../lib/logger'; export interface UseVideoErrorRecoveryOptions { videoRef: RefObject; diff --git a/frontend/src/hooks/video/useVideoFirstFrame.ts b/frontend/src/hooks/video/useVideoFirstFrame.ts index 58c6f23..4637034 100644 --- a/frontend/src/hooks/video/useVideoFirstFrame.ts +++ b/frontend/src/hooks/video/useVideoFirstFrame.ts @@ -7,14 +7,14 @@ */ import { - useState, - useRef, useCallback, useEffect, + useRef, + useState, type RefObject, } from 'react'; -import { logger } from '../../lib/logger'; import { scheduleAfterPaint } from '../../lib/browserUtils'; +import { logger } from '../../lib/logger'; export interface UseVideoFirstFrameOptions { videoRef: RefObject; diff --git a/frontend/src/hooks/video/useVideoPlaybackCore.ts b/frontend/src/hooks/video/useVideoPlaybackCore.ts index 3301dad..8d05518 100644 --- a/frontend/src/hooks/video/useVideoPlaybackCore.ts +++ b/frontend/src/hooks/video/useVideoPlaybackCore.ts @@ -16,8 +16,8 @@ import { import { logger } from '../../lib/logger'; import { useVideoBlobUrl, type PreloadCacheProvider } from './useVideoBlobUrl'; import { useVideoBufferingState } from './useVideoBufferingState'; -import { useVideoFirstFrame } from './useVideoFirstFrame'; import { useVideoErrorRecovery } from './useVideoErrorRecovery'; +import { useVideoFirstFrame } from './useVideoFirstFrame'; import { useVideoTimeouts } from './useVideoTimeouts'; export interface UseVideoPlaybackCoreOptions { diff --git a/frontend/src/hooks/video/useVideoPlayer.ts b/frontend/src/hooks/video/useVideoPlayer.ts index aeb282e..7f9333d 100644 --- a/frontend/src/hooks/video/useVideoPlayer.ts +++ b/frontend/src/hooks/video/useVideoPlayer.ts @@ -12,9 +12,9 @@ */ import { - useRef, useCallback, useEffect, + useRef, useState, type RefObject, } from 'react'; @@ -92,8 +92,8 @@ export function useVideoPlayer({ storageKey, preloadCache, autoplay = false, - loop = false, - muted = true, + loop: _loop = false, + muted: _muted = true, trackBuffering = false, onPlay, onPause, diff --git a/frontend/src/hooks/video/useVideoTimeouts.ts b/frontend/src/hooks/video/useVideoTimeouts.ts index f95e6a4..51fbf30 100644 --- a/frontend/src/hooks/video/useVideoTimeouts.ts +++ b/frontend/src/hooks/video/useVideoTimeouts.ts @@ -7,7 +7,7 @@ * - Custom timers */ -import { useRef, useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; export interface UseVideoTimeoutsResult { /** Set a named timer */ diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 72a826c..97a43e0 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -1,7 +1,7 @@ import i18n from 'i18next'; -import { initReactI18next } from 'react-i18next'; -import HttpApi from 'i18next-http-backend'; import LanguageDetector from 'i18next-browser-languagedetector'; +import HttpApi from 'i18next-http-backend'; +import { initReactI18next } from 'react-i18next'; i18n .use(HttpApi) diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx index 4640726..343274a 100644 --- a/frontend/src/layouts/Authenticated.tsx +++ b/frontend/src/layouts/Authenticated.tsx @@ -1,20 +1,20 @@ -import React, { ReactNode, useEffect, useRef, useState } from 'react'; +import { mdiBackburger, mdiForwardburger, mdiMenu } from '@mdi/js'; import axios from 'axios'; +import { useRouter } from 'next/router'; +import { ReactNode, useEffect, useRef, useState } from 'react'; import { toast } from 'react-toastify'; -import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'; -import menuAside from '../menuAside'; -import menuNavBar from '../menuNavBar'; +import AsideMenu from '../components/AsideMenu'; import BaseIcon from '../components/BaseIcon'; +import FooterBar from '../components/FooterBar'; import NavBar from '../components/NavBar'; import NavBarItemPlain from '../components/NavBarItemPlain'; -import AsideMenu from '../components/AsideMenu'; -import FooterBar from '../components/FooterBar'; -import { useAppDispatch, useAppSelector } from '../stores/hooks'; import Search from '../components/Search'; -import { useRouter } from 'next/router'; -import { findMe, logoutUser } from '../stores/authSlice'; -import { logger } from '../lib/logger'; import { isAuthTokenValid } from '../lib/authToken'; +import { logger } from '../lib/logger'; +import menuAside from '../menuAside'; +import menuNavBar from '../menuNavBar'; +import { findMe, logoutUser } from '../stores/authSlice'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; import { hasPermission } from '../helpers/userPermissions'; @@ -82,7 +82,7 @@ export default function LayoutAuthenticated({ } else if (currentUser) { setIsAuthChecked(true); } - }, [router.isReady, router.pathname, token, currentUser, dispatch]); + }, [router, router.isReady, router.pathname, token, currentUser, dispatch]); // Mark auth as checked once currentUser is loaded useEffect(() => { @@ -121,13 +121,13 @@ export default function LayoutAuthenticated({ }; checkProjects(); - }, [isAuthChecked, router.isReady, router.pathname]); + }, [isAuthChecked, router, router.isReady, router.pathname]); useEffect(() => { if (!permission || !currentUser) return; if (!hasPermission(currentUser, permission)) router.push('/error'); - }, [currentUser, permission]); + }, [currentUser, permission, router]); useEffect(() => { if (!isAuthChecked || minimal || !currentUser) return; diff --git a/frontend/src/layouts/Guest.tsx b/frontend/src/layouts/Guest.tsx index 49ac1b0..3e63ecb 100644 --- a/frontend/src/layouts/Guest.tsx +++ b/frontend/src/layouts/Guest.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import { ReactNode } from 'react'; import { useAppSelector } from '../stores/hooks'; type Props = { diff --git a/frontend/src/lib/assetCache/AssetCacheService.ts b/frontend/src/lib/assetCache/AssetCacheService.ts index 77208d9..5adbaa8 100644 --- a/frontend/src/lib/assetCache/AssetCacheService.ts +++ b/frontend/src/lib/assetCache/AssetCacheService.ts @@ -9,35 +9,30 @@ * - Managing storage keys consistently */ -import { StorageManager } from '../offline/StorageManager'; +import type { AssetVariantType, CachedAssetInfo } from '../../types/offline'; +import type { + PreloadElement, + PreloadPage, + PreloadPageLink, +} from '../../types/preload'; +import { + buildProxyUrl, + isPresignedUrl, + isRelativeStoragePath, + markPresignedUrlFailed, + markPresignedUrlsVerified, + queuePresignedUrls, + resolveAssetPlaybackUrl, +} from '../assetUrl'; +import { logger } from '../logger'; import { downloadManager } from '../offline/DownloadManager'; +import { StorageManager } from '../offline/StorageManager'; import { discoverProjectAssets, getPrioritizedAssets, - type AssetToCache, type AssetDiscoveryOptions, + type AssetToCache, } from './assetDiscovery'; -import { - queuePresignedUrls, - resolveAssetPlaybackUrl, - isPresignedUrl, - markPresignedUrlFailed, - markPresignedUrlsVerified, - buildProxyUrl, - isRelativeStoragePath, -} from '../assetUrl'; -import { PRELOAD_CONFIG } from '../../config/preload.config'; -import { logger } from '../logger'; -import type { - PreloadPage, - PreloadPageLink, - PreloadElement, -} from '../../types/preload'; -import type { - AssetType, - AssetVariantType, - CachedAssetInfo, -} from '../../types/offline'; // Re-export for convenience export type { CachedAssetInfo }; @@ -201,7 +196,7 @@ export class AssetCacheService { assets: AssetToCache[], options: QueueDownloadOptions, ): Promise { - const { mode, projectId = '', presignedUrls = {}, currentPageId } = options; + const { mode, projectId = '', presignedUrls = {} } = options; // Fetch presigned URLs for assets that need them const storagePaths = assets diff --git a/frontend/src/lib/assetCache/assetDiscovery.ts b/frontend/src/lib/assetCache/assetDiscovery.ts index f527758..bbf345b 100644 --- a/frontend/src/lib/assetCache/assetDiscovery.ts +++ b/frontend/src/lib/assetCache/assetDiscovery.ts @@ -7,14 +7,14 @@ */ import { PRELOAD_CONFIG } from '../../config/preload.config'; -import { extractStoragePath } from '../assetUrl'; +import type { AssetType } from '../../types/offline'; import type { + PreloadAssetInfo, + PreloadElement, PreloadPage, PreloadPageLink, - PreloadElement, - PreloadAssetInfo, } from '../../types/preload'; -import type { AssetType } from '../../types/offline'; +import { extractStoragePath } from '../assetUrl'; /** * Asset information for caching diff --git a/frontend/src/lib/assetCache/index.ts b/frontend/src/lib/assetCache/index.ts index 67c20fc..e7f200d 100644 --- a/frontend/src/lib/assetCache/index.ts +++ b/frontend/src/lib/assetCache/index.ts @@ -4,23 +4,23 @@ * Unified asset caching for online preload and offline download. */ +export type { CachedAssetInfo } from '../../types/offline'; export { AssetCacheService, type DownloadProgress, type QueueDownloadOptions, } from './AssetCacheService'; -export type { CachedAssetInfo } from '../../types/offline'; export { - discoverProjectAssets, + calculateAssetPriority, + classifyAssetType, discoverPageAssets, - getPrioritizedAssets, + discoverProjectAssets, extractElementAssets, extractPageBackgroundAssets, extractTransitionAssets, - calculateAssetPriority, - classifyAssetType, + getPrioritizedAssets, toPreloadAssetInfo, - type AssetToCache, type AssetDiscoveryOptions, + type AssetToCache, } from './assetDiscovery'; diff --git a/frontend/src/lib/constructorHelpers.ts b/frontend/src/lib/constructorHelpers.ts index 98f8e83..0d521c1 100644 --- a/frontend/src/lib/constructorHelpers.ts +++ b/frontend/src/lib/constructorHelpers.ts @@ -6,18 +6,18 @@ */ import type { + AssetOption, CanvasElement, ConstructorAsset as ProjectAsset, - AssetOption, } from '../types/constructor'; +import { getNavigationButtonLabel } from './elementDefaultConstants'; import { - isGalleryElementType, isCarouselElementType, isDescriptionElementType, - isNavigationElementType, + isGalleryElementType, isMediaElementType, + isNavigationElementType, } from './elementTypeGuards'; -import { getNavigationButtonLabel } from './elementDefaultConstants'; /** * Get trimmed CSS value as string. diff --git a/frontend/src/lib/elementDefaults.ts b/frontend/src/lib/elementDefaults.ts index dd9c689..8c95bff 100644 --- a/frontend/src/lib/elementDefaults.ts +++ b/frontend/src/lib/elementDefaults.ts @@ -7,7 +7,6 @@ import type { CanvasElement, CanvasElementType } from '../types/constructor'; import { DEFAULT_INFO_PANEL_SECTIONS } from '../types/infoPanel'; -import { clamp, createLocalId } from './elementInstance.helpers'; import { createDefaultCarouselSlide, createDefaultGalleryCard, @@ -16,8 +15,13 @@ import { normalizeGalleryInfoSpan, normalizeInfoPanelSections, } from './elementCollectionNormalizers'; +import { + ELEMENT_EFFECT_PROPS, + ELEMENT_TYPE_LABELS, + TYPE_SPECIFIC_DEFAULTS, +} from './elementDefaultConstants'; +import { clamp, createLocalId } from './elementInstance.helpers'; import { ELEMENT_STYLE_PROPS } from './elementStyles'; -import { GALLERY_SECTION_STYLE_PROPS } from './gallerySectionStyles'; import { isCarouselElementType, isDescriptionElementType, @@ -28,11 +32,7 @@ import { isNavigationElementType, isSpotElementType, } from './elementTypeGuards'; -import { - ELEMENT_EFFECT_PROPS, - ELEMENT_TYPE_LABELS, - TYPE_SPECIFIC_DEFAULTS, -} from './elementDefaultConstants'; +import { GALLERY_SECTION_STYLE_PROPS } from './gallerySectionStyles'; /** * Normalize appearDelaySec value diff --git a/frontend/src/lib/elementStyles.ts b/frontend/src/lib/elementStyles.ts index ef3b86a..dd806eb 100644 --- a/frontend/src/lib/elementStyles.ts +++ b/frontend/src/lib/elementStyles.ts @@ -28,39 +28,6 @@ import { toCU } from './canvasScale'; * @param unit - The unit to append (e.g., 'px', 'vw', 'vh', 's') * @returns Normalized value with unit, or empty string if invalid */ -function normalizeWithUnit( - value: string | number | undefined, - unit: string, -): string { - if (value === null || value === undefined || value === '') return ''; - - const str = String(value).trim(); - if (!str) return ''; - - // Zero doesn't need a unit - if (str === '0') return '0'; - - // Complex values (contain spaces) - return as-is - if (str.includes(' ')) return str; - - // CSS functions (calc, var, etc.) - return as-is - if (/^(calc|var|min|max|clamp)\(/i.test(str)) return str; - - // Already has a unit (letters or %) at the end - return as-is - if (/[a-z%]+$/i.test(str)) return str; - - // Handle numbers (including negative and decimals like ".5") - const num = parseFloat(str); - if (Number.isFinite(num)) { - // Zero after parsing - if (num === 0) return '0'; - return `${num}${unit}`; - } - - // Fallback: return as-is - return str; -} - /** Normalize pixel values (border, borderRadius, fontSize) - now uses canvas units */ export const normalizePixelValue = (value: string | number | undefined) => { if (value === null || value === undefined || value === '') return ''; diff --git a/frontend/src/lib/extractPageLinks.ts b/frontend/src/lib/extractPageLinks.ts index df5b90b..27d46c5 100644 --- a/frontend/src/lib/extractPageLinks.ts +++ b/frontend/src/lib/extractPageLinks.ts @@ -6,7 +6,7 @@ */ import { PRELOAD_CONFIG } from '../config/preload.config'; -import type { PreloadPageLink, PreloadElement } from '../types/preload'; +import type { PreloadElement, PreloadPageLink } from '../types/preload'; interface PageWithSchema { id: string; diff --git a/frontend/src/lib/gallerySectionStyles.ts b/frontend/src/lib/gallerySectionStyles.ts index 35a2c93..b963832 100644 --- a/frontend/src/lib/gallerySectionStyles.ts +++ b/frontend/src/lib/gallerySectionStyles.ts @@ -15,7 +15,6 @@ import { getFontByKey, getFontStyle } from './fonts'; import { applyStyleIfSet, applyStyleWithDefault, - getTrimmedStyleValue, normalizeCanvasUnitValue, } from './styleValueUtils'; @@ -70,8 +69,6 @@ export const GALLERY_SECTION_DEFAULTS: Record< }, }; -const getTrimmedValue = getTrimmedStyleValue; - /** * Normalize a numeric value to include a CSS unit suffix. * Handles edge cases: @@ -87,34 +84,6 @@ const getTrimmedValue = getTrimmedStyleValue; * @param unit - The unit to append (e.g., 'rem', 'px') * @returns Normalized value with unit, or empty string if invalid */ -const normalizeWithUnit = (value: unknown, unit: string): string => { - const trimmed = getTrimmedValue(value); - if (!trimmed) return ''; - - // Zero doesn't need a unit - if (trimmed === '0') return '0'; - - // Complex values (contain spaces) - return as-is (e.g., "0.5rem 0.75rem") - if (trimmed.includes(' ')) return trimmed; - - // CSS functions (calc, var, etc.) - return as-is - if (/^(calc|var|min|max|clamp)\(/i.test(trimmed)) return trimmed; - - // Already has a unit (letters or %) at the end - return as-is - if (/[a-z%]+$/i.test(trimmed)) return trimmed; - - // Handle numbers (including negative and decimals like ".5") - const num = parseFloat(trimmed); - if (Number.isFinite(num)) { - // Zero after parsing - if (num === 0) return '0'; - return `${num}${unit}`; - } - - // Fallback: return as-is (shouldn't normally reach here) - return trimmed; -}; - /** * Normalize values to canvas units. * Handles rem, px, and plain numbers. diff --git a/frontend/src/lib/imagePreDecode.ts b/frontend/src/lib/imagePreDecode.ts index db06bd1..bf4493d 100644 --- a/frontend/src/lib/imagePreDecode.ts +++ b/frontend/src/lib/imagePreDecode.ts @@ -5,8 +5,8 @@ * during page transitions. */ -import { resolveAssetPlaybackUrl } from './assetUrl'; import { PRELOAD_CONFIG } from '../config/preload.config'; +import { resolveAssetPlaybackUrl } from './assetUrl'; /** * Minimal page interface for image extraction diff --git a/frontend/src/lib/infoPanelMediaSectionStyles.ts b/frontend/src/lib/infoPanelMediaSectionStyles.ts index b48f9e9..3762bb2 100644 --- a/frontend/src/lib/infoPanelMediaSectionStyles.ts +++ b/frontend/src/lib/infoPanelMediaSectionStyles.ts @@ -1,8 +1,8 @@ import type { CSSProperties } from 'react'; import type { CanvasElement } from '../types/constructor'; import type { InfoPanelSectionInstance } from '../types/infoPanel'; -import { INFO_PANEL_SECTION_DEFAULTS } from './infoPanelSectionStyleConstants'; import { getImagesThumbnailStripLayout } from './infoPanelGridStyles'; +import { INFO_PANEL_SECTION_DEFAULTS } from './infoPanelSectionStyleConstants'; import { applyStyleWithDefault, normalizeCanvasUnitValue, diff --git a/frontend/src/lib/logger.ts b/frontend/src/lib/logger.ts index afe968a..d127d05 100644 --- a/frontend/src/lib/logger.ts +++ b/frontend/src/lib/logger.ts @@ -58,7 +58,7 @@ class Logger { private formatMessage( level: LogLevel, message: string, - context?: LogContext | Error, + context?: LogContext | Error | unknown, ): { formatted: string; data?: object } { const timestamp = new Date().toISOString(); const prefix = `[${timestamp}] [${level.toUpperCase()}]`; @@ -75,14 +75,19 @@ class Logger { return { formatted: `${prefix} ${message}`, - data: context, + data: + typeof context === 'object' && context !== null + ? (context as object) + : context === undefined + ? undefined + : { value: context }, }; } private output( level: LogLevel, message: string, - context?: LogContext | Error, + context?: LogContext | Error | unknown, ): void { if (!this.shouldLog(level)) return; @@ -112,19 +117,19 @@ class Logger { console[consoleFn](JSON.stringify(logEntry)); } - debug(message: string, context?: LogContext): void { + debug(message: string, context?: LogContext | unknown): void { this.output('debug', message, context); } - info(message: string, context?: LogContext): void { + info(message: string, context?: LogContext | unknown): void { this.output('info', message, context); } - warn(message: string, context?: LogContext | Error): void { + warn(message: string, context?: LogContext | Error | unknown): void { this.output('warn', message, context); } - error(message: string, context?: LogContext | Error): void { + error(message: string, context?: LogContext | Error | unknown): void { this.output('error', message, context); } @@ -171,4 +176,4 @@ class ChildLogger { export const logger = new Logger(); // Export types for consumers -export type { LogLevel, LogContext }; +export type { LogContext, LogLevel }; diff --git a/frontend/src/lib/navigationHelpers.ts b/frontend/src/lib/navigationHelpers.ts index e4c6ab4..f5dce77 100644 --- a/frontend/src/lib/navigationHelpers.ts +++ b/frontend/src/lib/navigationHelpers.ts @@ -5,12 +5,12 @@ * Handles target page resolution, back navigation detection, and transition blocking. */ -import type { RuntimePage } from '../types/runtime'; import type { NavigableElement, NavigationTarget, TransitionPhase, } from '../types/presentation'; +import type { RuntimePage } from '../types/runtime'; import { parseJsonObject } from './parseJson'; /** diff --git a/frontend/src/lib/offline/DownloadEventBus.ts b/frontend/src/lib/offline/DownloadEventBus.ts index d0949b5..2af3fba 100644 --- a/frontend/src/lib/offline/DownloadEventBus.ts +++ b/frontend/src/lib/offline/DownloadEventBus.ts @@ -6,17 +6,17 @@ */ import { OFFLINE_CONFIG } from '../../config/offline.config'; -import { logger } from '../logger'; import type { - PreloadStartEvent, - PreloadProgressEvent, + BlobUrlReadyEvent, PreloadCompleteEvent, PreloadErrorEvent, - ProjectDownloadProgressEvent, + PreloadProgressEvent, + PreloadStartEvent, ProjectDownloadCompleteEvent, - BlobUrlReadyEvent, + ProjectDownloadProgressEvent, StreamingReadyEvent, } from '../../types/offline'; +import { logger } from '../logger'; type EventMap = { [OFFLINE_CONFIG.events.preloadStart]: PreloadStartEvent; diff --git a/frontend/src/lib/offline/DownloadManager.ts b/frontend/src/lib/offline/DownloadManager.ts index f00e3b3..7f1d280 100644 --- a/frontend/src/lib/offline/DownloadManager.ts +++ b/frontend/src/lib/offline/DownloadManager.ts @@ -6,24 +6,21 @@ */ import { PRELOAD_CONFIG } from '../../config/preload.config'; -import { OFFLINE_CONFIG } from '../../config/offline.config'; -import { downloadEventBus } from './DownloadEventBus'; -import { StorageManager } from './StorageManager'; -import { OfflineDbManager } from '../offlineDb/OfflineDbManager'; +import type { AssetType, AssetVariantType } from '../../types/offline'; import { + buildProxyUrl, extractStoragePath, isPresignedUrl, markPresignedUrlFailed, - buildProxyUrl, } from '../assetUrl'; import { logger } from '../logger'; -import type { AssetVariantType, AssetType } from '../../types/offline'; +import { OfflineDbManager } from '../offlineDb/OfflineDbManager'; +import { downloadEventBus } from './DownloadEventBus'; import { calculateDownloadPriority, createDownloadJob, createRestoredDownloadJob, downloadJobToQueueItem, - type DownloadJob, getDownloadProgressPercent, getRetryDelayMs, getStreamingMinBufferBytes, @@ -35,7 +32,9 @@ import { resetJobForProxyFallback, shouldSignalStreamingReady, sortByPriorityDescending, + type DownloadJob, } from './DownloadManager.helpers'; +import { StorageManager } from './StorageManager'; class DownloadManagerClass { private queue: DownloadJob[] = []; diff --git a/frontend/src/lib/offline/StorageManager.ts b/frontend/src/lib/offline/StorageManager.ts index f23d9a9..fb178c0 100644 --- a/frontend/src/lib/offline/StorageManager.ts +++ b/frontend/src/lib/offline/StorageManager.ts @@ -7,16 +7,16 @@ import { OFFLINE_CONFIG } from '../../config/offline.config'; import { PRELOAD_CONFIG } from '../../config/preload.config'; -import { OfflineDbManager } from '../offlineDb/OfflineDbManager'; +import type { + AssetType, + AssetVariantType, + CachedAssetInfo, + OfflineAsset, + StorageQuotaInfo, +} from '../../types/offline'; import { extractStoragePath } from '../assetUrl'; import { logger } from '../logger'; -import type { - OfflineAsset, - AssetVariantType, - AssetType, - StorageQuotaInfo, - CachedAssetInfo, -} from '../../types/offline'; +import { OfflineDbManager } from '../offlineDb/OfflineDbManager'; export class StorageManager { /** diff --git a/frontend/src/lib/offlineDb/OfflineDbManager.ts b/frontend/src/lib/offlineDb/OfflineDbManager.ts index 44b2317..6bb1b14 100644 --- a/frontend/src/lib/offlineDb/OfflineDbManager.ts +++ b/frontend/src/lib/offlineDb/OfflineDbManager.ts @@ -5,14 +5,14 @@ * Handles large assets (videos > 5MB) and project metadata. */ -import { offlineDb } from './schema'; import type { + DownloadQueueItem, OfflineAsset, OfflineProject, - DownloadQueueItem, - ProjectOfflineStatus, PreloadJobStatus, + ProjectOfflineStatus, } from '../../types/offline'; +import { offlineDb } from './schema'; export class OfflineDbManager { // ============================================ diff --git a/frontend/src/lib/offlineDb/schema.ts b/frontend/src/lib/offlineDb/schema.ts index 74c3c49..87958d2 100644 --- a/frontend/src/lib/offlineDb/schema.ts +++ b/frontend/src/lib/offlineDb/schema.ts @@ -7,9 +7,9 @@ import Dexie, { type EntityTable } from 'dexie'; import { OFFLINE_CONFIG } from '../../config/offline.config'; import type { + DownloadQueueItem, OfflineAsset, OfflineProject, - DownloadQueueItem, } from '../../types/offline'; // Dexie database class @@ -38,4 +38,4 @@ class OfflineDatabase extends Dexie { export const offlineDb = new OfflineDatabase(); // Export types for convenience -export type { OfflineAsset, OfflineProject, DownloadQueueItem }; +export type { DownloadQueueItem, OfflineAsset, OfflineProject }; diff --git a/frontend/src/lib/resolveSlideTransition.ts b/frontend/src/lib/resolveSlideTransition.ts index e26db8c..c7ab369 100644 --- a/frontend/src/lib/resolveSlideTransition.ts +++ b/frontend/src/lib/resolveSlideTransition.ts @@ -9,8 +9,8 @@ */ import type { - ResolvedTransitionSettings, EasingFunction, + ResolvedTransitionSettings, SlideTransitionType, } from '../types/transition'; diff --git a/frontend/src/lib/tourFlowHelpers.test.ts b/frontend/src/lib/tourFlowHelpers.test.ts index 39bf172..fdbda6c 100644 --- a/frontend/src/lib/tourFlowHelpers.test.ts +++ b/frontend/src/lib/tourFlowHelpers.test.ts @@ -4,8 +4,8 @@ import test from 'node:test'; import { buildCreateTourFlowPagePayload, buildProjectTransitionSettingsSavePayload, - buildTourFlowPageNameUpdatePayload, buildTourFlowListEntries, + buildTourFlowPageNameUpdatePayload, getActiveTourFlowProjectId, getFirstQueryParam, getPageNameValidationError, diff --git a/frontend/src/lib/tourFlowHelpers.ts b/frontend/src/lib/tourFlowHelpers.ts index 8a55ac8..4d9d971 100644 --- a/frontend/src/lib/tourFlowHelpers.ts +++ b/frontend/src/lib/tourFlowHelpers.ts @@ -383,23 +383,23 @@ export const buildTourFlowPageNameUpdatePayload = ({ source: TourFlowPageNameUpdateSource; name: string; }): TourFlowPageNameUpdatePayload => ({ - environment: source.environment, - source_key: source.source_key, + environment: source.environment || 'dev', + source_key: source.source_key || '', name, - slug: source.slug, - sort_order: source.sort_order, - background_image_url: source.background_image_url, - background_video_url: source.background_video_url, - background_audio_url: source.background_audio_url, - background_loop: source.background_loop, - background_video_autoplay: source.background_video_autoplay, - background_video_loop: source.background_video_loop, - background_video_muted: source.background_video_muted, + slug: source.slug || '', + sort_order: source.sort_order || 0, + background_image_url: source.background_image_url || '', + background_video_url: source.background_video_url || '', + background_audio_url: source.background_audio_url || '', + background_loop: source.background_loop ?? false, + background_video_autoplay: source.background_video_autoplay ?? false, + background_video_loop: source.background_video_loop ?? false, + background_video_muted: source.background_video_muted ?? false, background_video_start_time: source.background_video_start_time, background_video_end_time: source.background_video_end_time, design_width: source.design_width, design_height: source.design_height, - requires_auth: source.requires_auth, + requires_auth: source.requires_auth ?? false, ui_schema_json: source.ui_schema_json, }); diff --git a/frontend/src/menuNavBar.ts b/frontend/src/menuNavBar.ts index 00e4bb8..fc76710 100644 --- a/frontend/src/menuNavBar.ts +++ b/frontend/src/menuNavBar.ts @@ -1,16 +1,4 @@ -import { - mdiMenu, - mdiClockOutline, - mdiCloud, - mdiCrop, - mdiAccount, - mdiCogOutline, - mdiEmail, - mdiLogout, - mdiThemeLightDark, - mdiGithub, - mdiVuejs, -} from '@mdi/js'; +import { mdiAccount, mdiLogout, mdiThemeLightDark } from '@mdi/js'; import { MenuNavBarItem } from './types/menu'; const menuNavBar: MenuNavBarItem[] = [ diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index c324387..d4df659 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -1,34 +1,35 @@ -import React from 'react'; -import type { AppProps } from 'next/app'; -import type { ReactElement, ReactNode } from 'react'; -import type { NextPage } from 'next'; -import Head from 'next/head'; -import { store } from '../stores/store'; -import { Provider } from 'react-redux'; import { QueryClientProvider } from '@tanstack/react-query'; +import type { NextPage } from 'next'; +import type { AppProps } from 'next/app'; +import Head from 'next/head'; +import type { ReactElement, ReactNode } from 'react'; +import React from 'react'; +import { Provider } from 'react-redux'; import { queryClient } from '../lib/queryClient'; +import { store } from '../stores/store'; // Import Instrument Sans font (self-hosted via Fontsource) import '@fontsource-variable/instrument-sans'; import '@fontsource-variable/instrument-sans/wdth.css'; // Condensed width axis -import '../css/main.css'; import axios from 'axios'; -import { apiBaseURL, isProduction } from '../config'; -import { useRouter } from 'next/router'; -import ErrorBoundary from '../components/ErrorBoundary'; import 'intro.js/introjs.css'; import { appWithTranslation } from 'next-i18next'; -import '../i18n'; +import { useRouter } from 'next/router'; +import ErrorBoundary from '../components/ErrorBoundary'; import IntroGuide from '../components/IntroGuide'; +import { apiBaseURL, isProduction } from '../config'; +import { DownloadProvider } from '../context/DownloadContext'; +import '../css/main.css'; +import '../i18n'; +import { disablePresignedUrls } from '../lib/assetUrl'; +import { logger } from '../lib/logger'; import { appSteps, loginSteps, - usersSteps, rolesSteps, + usersSteps, } from '../stores/introSteps'; -import { DownloadProvider } from '../context/DownloadContext'; -import { logger } from '../lib/logger'; -import { disablePresignedUrls } from '../lib/assetUrl'; +import type { IntroStep } from '../types/ui'; /** * Check if a URL is a presigned S3 URL @@ -118,7 +119,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { const router = useRouter(); const [stepsEnabled, setStepsEnabled] = React.useState(false); const [stepName, setStepName] = React.useState(''); - const [steps, setSteps] = React.useState([]); + const [steps, setSteps] = React.useState([]); // Register service worker for PWA offline support React.useEffect(() => { diff --git a/frontend/src/pages/access_logs/[access_logsId].tsx b/frontend/src/pages/access_logs/[access_logsId].tsx index 05dddf1..0ec9c94 100644 --- a/frontend/src/pages/access_logs/[access_logsId].tsx +++ b/frontend/src/pages/access_logs/[access_logsId].tsx @@ -1,55 +1,43 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; +import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; +import { ReactElement, useEffect, useMemo, useState } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { SwitchField } from '../../components/SwitchField'; -import { update, fetch } from '../../stores/access_logs/access_logsSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { useRouter } from 'next/router'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from '../../components/ImageField'; +import { fetch, update } from '../../stores/access_logs/access_logsSlice'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import type { AccessLog } from '../../types/entities'; const EditAccess_logs = () => { const router = useRouter(); const dispatch = useAppDispatch(); - const initVals = { - project: null as unknown, - - environment: '' as '' | 'admin' | 'stage' | 'production', - - user: null as unknown, - - path: '', - - ip_address: '', - - user_agent: '', - - accessed_at: new Date(), - }; + const initVals = useMemo( + () => ({ + project: null as unknown, + environment: '' as '' | 'admin' | 'stage' | 'production', + user: null as unknown, + path: '', + ip_address: '', + user_agent: '', + accessed_at: new Date(), + }), + [], + ); const [initialValues, setInitialValues] = useState(initVals); const access_logs = useAppSelector((state) => state.access_logs.data); @@ -81,7 +69,7 @@ const EditAccess_logs = () => { setInitialValues(newInitialVal); } - }, [access_logs]); + }, [access_logs, initVals]); const handleSubmit = async (data: typeof initVals) => { if (accessLogIdStr) { diff --git a/frontend/src/pages/access_logs/access_logs-edit.tsx b/frontend/src/pages/access_logs/access_logs-edit.tsx index 8fb466f..bc3e9d7 100644 --- a/frontend/src/pages/access_logs/access_logs-edit.tsx +++ b/frontend/src/pages/access_logs/access_logs-edit.tsx @@ -3,29 +3,29 @@ */ import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; -import { update, fetch } from '../../stores/access_logs/access_logsSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; import { useEditPageSync } from '../../hooks/useEditPageSync'; +import { fetch, update } from '../../stores/access_logs/access_logsSlice'; +import { useAppDispatch } from '../../stores/hooks'; import type { AccessLog } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/access_logs/access_logs-list.tsx b/frontend/src/pages/access_logs/access_logs-list.tsx index af7e0bc..a2c14b2 100644 --- a/frontend/src/pages/access_logs/access_logs-list.tsx +++ b/frontend/src/pages/access_logs/access_logs-list.tsx @@ -2,11 +2,11 @@ * Access Logs List Page */ -import { createListPage } from '../../factories/createListPage'; import TableAccess_logs from '../../components/Access_logs/TableAccess_logs'; +import { createListPage } from '../../factories/createListPage'; import { - uploadCsv, setRefetch, + uploadCsv, } from '../../stores/access_logs/access_logsSlice'; import { Filter } from '../../types/filters'; diff --git a/frontend/src/pages/access_logs/access_logs-new.tsx b/frontend/src/pages/access_logs/access_logs-new.tsx index 71849d3..3c44180 100644 --- a/frontend/src/pages/access_logs/access_logs-new.tsx +++ b/frontend/src/pages/access_logs/access_logs-new.tsx @@ -5,24 +5,24 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; +import { useRouter } from 'next/router'; import { create } from '../../stores/access_logs/access_logsSlice'; import { useAppDispatch } from '../../stores/hooks'; -import { useRouter } from 'next/router'; const initialValues = { project: '', diff --git a/frontend/src/pages/access_logs/access_logs-table.tsx b/frontend/src/pages/access_logs/access_logs-table.tsx index ee2119d..cdd2929 100644 --- a/frontend/src/pages/access_logs/access_logs-table.tsx +++ b/frontend/src/pages/access_logs/access_logs-table.tsx @@ -1,32 +1,31 @@ import { mdiChartTimelineVariant } from '@mdi/js'; +import axios from 'axios'; +import uniqueId from 'lodash/uniqueId'; import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; +import Link from 'next/link'; +import { ReactElement, useState } from 'react'; +import TableAccess_logs from '../../components/Access_logs/TableAccess_logs'; +import BaseButton from '../../components/BaseButton'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; +import CardBoxModal from '../../components/CardBoxModal'; +import DragDropFilePicker from '../../components/DragDropFilePicker'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; -import TableAccess_logs from '../../components/Access_logs/TableAccess_logs'; -import BaseButton from '../../components/BaseButton'; -import axios from 'axios'; -import Link from 'next/link'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import CardBoxModal from '../../components/CardBoxModal'; -import DragDropFilePicker from '../../components/DragDropFilePicker'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { setRefetch, uploadCsv, } from '../../stores/access_logs/access_logsSlice'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { hasPermission } from '../../helpers/userPermissions'; -import { Filter } from '../../types/filters'; +import { Filter, FilterItem } from '../../types/filters'; const Access_logsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); + const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); const { currentUser } = useAppSelector((state) => state.auth); diff --git a/frontend/src/pages/access_logs/access_logs-view.tsx b/frontend/src/pages/access_logs/access_logs-view.tsx index 41f5cff..6b30525 100644 --- a/frontend/src/pages/access_logs/access_logs-view.tsx +++ b/frontend/src/pages/access_logs/access_logs-view.tsx @@ -1,21 +1,20 @@ -import React, { ReactElement, useEffect } from 'react'; +import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; +import { useRouter } from 'next/router'; +import { ReactElement, useEffect } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { fetch } from '../../stores/access_logs/access_logsSlice'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import { getPageTitle } from '../../config'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import SectionMain from '../../components/SectionMain'; -import CardBox from '../../components/CardBox'; import BaseButton from '../../components/BaseButton'; import BaseDivider from '../../components/BaseDivider'; -import { mdiChartTimelineVariant } from '@mdi/js'; +import CardBox from '../../components/CardBox'; import FormField from '../../components/FormField'; -import type { AccessLog } from '../../types/entities'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { fetch } from '../../stores/access_logs/access_logsSlice'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; const Access_logsView = () => { const router = useRouter(); diff --git a/frontend/src/pages/api/logError.ts b/frontend/src/pages/api/logError.ts index b391ffc..8dd45aa 100644 --- a/frontend/src/pages/api/logError.ts +++ b/frontend/src/pages/api/logError.ts @@ -1,13 +1,17 @@ import fsPromises from 'fs/promises'; +import type { NextApiRequest, NextApiResponse } from 'next'; import path from 'path'; const dataFilePath = path.join(process.cwd(), 'json/runtimeError.json'); -export default async function handler(req, res) { +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { // Ensure directory exists try { await fsPromises.mkdir(path.dirname(dataFilePath), { recursive: true }); - } catch (error) { + } catch (_error) { // Ignore if directory already exists } @@ -16,7 +20,7 @@ export default async function handler(req, res) { // Check if file exists try { await fsPromises.access(dataFilePath); - } catch (error) { + } catch (_error) { // File doesn't exist, return empty object return res.status(200).json({}); } @@ -41,8 +45,8 @@ export default async function handler(req, res) { await fsPromises.writeFile(dataFilePath, '{}', 'utf-8'); return res.status(200).json({}); } - } catch (error) { - console.error('Error in GET handler:', error); + } catch (_error) { + console.error('Error in GET handler:', _error); return res.status(200).json({}); // Return empty object instead of error } } else if (req.method === 'POST') { @@ -57,8 +61,8 @@ export default async function handler(req, res) { // Send a success response res.status(200).json({ message: 'Data stored successfully' }); - } catch (error) { - console.error('Error in POST handler:', error); + } catch (_error) { + console.error('Error in POST handler:', _error); // Send an error response res.status(500).json({ message: 'Error storing data' }); } @@ -72,8 +76,8 @@ export default async function handler(req, res) { // Send a success response res.status(200).json({ message: 'Data deleted successfully' }); - } catch (error) { - console.error('Error in DELETE handler:', error); + } catch (_error) { + console.error('Error in DELETE handler:', _error); // Send an error response res.status(500).json({ message: 'Error deleting data' }); } diff --git a/frontend/src/pages/asset_variants/[asset_variantsId].tsx b/frontend/src/pages/asset_variants/[asset_variantsId].tsx index 9f0481c..e881d94 100644 --- a/frontend/src/pages/asset_variants/[asset_variantsId].tsx +++ b/frontend/src/pages/asset_variants/[asset_variantsId].tsx @@ -1,35 +1,24 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; +import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; -import DatePicker from 'react-datepicker'; +import { ReactElement, useEffect, useState } from 'react'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { SwitchField } from '../../components/SwitchField'; -import { update, fetch } from '../../stores/asset_variants/asset_variantsSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { useRouter } from 'next/router'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from '../../components/ImageField'; +import { fetch, update } from '../../stores/asset_variants/asset_variantsSlice'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import type { AssetVariant } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/asset_variants/asset_variants-edit.tsx b/frontend/src/pages/asset_variants/asset_variants-edit.tsx index e2ead1c..6856106 100644 --- a/frontend/src/pages/asset_variants/asset_variants-edit.tsx +++ b/frontend/src/pages/asset_variants/asset_variants-edit.tsx @@ -1,24 +1,24 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; -import { update, fetch } from '../../stores/asset_variants/asset_variantsSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; import { useEditPageSync } from '../../hooks/useEditPageSync'; +import { fetch, update } from '../../stores/asset_variants/asset_variantsSlice'; +import { useAppDispatch } from '../../stores/hooks'; import type { AssetVariant } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/asset_variants/asset_variants-list.tsx b/frontend/src/pages/asset_variants/asset_variants-list.tsx index f7de8d8..33c6b4a 100644 --- a/frontend/src/pages/asset_variants/asset_variants-list.tsx +++ b/frontend/src/pages/asset_variants/asset_variants-list.tsx @@ -2,11 +2,11 @@ * Asset Variants List Page */ -import { createListPage } from '../../factories/createListPage'; import TableAsset_variants from '../../components/Asset_variants/TableAsset_variants'; +import { createListPage } from '../../factories/createListPage'; import { - uploadCsv, setRefetch, + uploadCsv, } from '../../stores/asset_variants/asset_variantsSlice'; import { Filter } from '../../types/filters'; diff --git a/frontend/src/pages/asset_variants/asset_variants-new.tsx b/frontend/src/pages/asset_variants/asset_variants-new.tsx index d5578b9..3c36d35 100644 --- a/frontend/src/pages/asset_variants/asset_variants-new.tsx +++ b/frontend/src/pages/asset_variants/asset_variants-new.tsx @@ -5,24 +5,24 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; +import { useRouter } from 'next/router'; import { create } from '../../stores/asset_variants/asset_variantsSlice'; import { useAppDispatch } from '../../stores/hooks'; -import { useRouter } from 'next/router'; import type { AssetVariant } from '../../types/entities'; const initialValues = { diff --git a/frontend/src/pages/asset_variants/asset_variants-table.tsx b/frontend/src/pages/asset_variants/asset_variants-table.tsx index 7fac9b6..df4eac4 100644 --- a/frontend/src/pages/asset_variants/asset_variants-table.tsx +++ b/frontend/src/pages/asset_variants/asset_variants-table.tsx @@ -1,32 +1,31 @@ import { mdiChartTimelineVariant } from '@mdi/js'; +import axios from 'axios'; +import uniqueId from 'lodash/uniqueId'; import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; +import Link from 'next/link'; +import { ReactElement, useState } from 'react'; +import TableAsset_variants from '../../components/Asset_variants/TableAsset_variants'; +import BaseButton from '../../components/BaseButton'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; +import CardBoxModal from '../../components/CardBoxModal'; +import DragDropFilePicker from '../../components/DragDropFilePicker'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; -import TableAsset_variants from '../../components/Asset_variants/TableAsset_variants'; -import BaseButton from '../../components/BaseButton'; -import axios from 'axios'; -import Link from 'next/link'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import CardBoxModal from '../../components/CardBoxModal'; -import DragDropFilePicker from '../../components/DragDropFilePicker'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { setRefetch, uploadCsv, } from '../../stores/asset_variants/asset_variantsSlice'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { hasPermission } from '../../helpers/userPermissions'; -import { Filter } from '../../types/filters'; +import { Filter, FilterItem } from '../../types/filters'; const Asset_variantsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); + const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); const { currentUser } = useAppSelector((state) => state.auth); diff --git a/frontend/src/pages/asset_variants/asset_variants-view.tsx b/frontend/src/pages/asset_variants/asset_variants-view.tsx index 5cf9b0b..38abc05 100644 --- a/frontend/src/pages/asset_variants/asset_variants-view.tsx +++ b/frontend/src/pages/asset_variants/asset_variants-view.tsx @@ -1,25 +1,17 @@ -import React, { ReactElement, useEffect } from 'react'; +import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { useRouter } from 'next/router'; -import { fetch } from '../../stores/asset_variants/asset_variantsSlice'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from '../../components/ImageField'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import { getPageTitle } from '../../config'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import SectionMain from '../../components/SectionMain'; -import CardBox from '../../components/CardBox'; +import { ReactElement, useEffect } from 'react'; +import 'react-datepicker/dist/react-datepicker.css'; import BaseButton from '../../components/BaseButton'; import BaseDivider from '../../components/BaseDivider'; -import { mdiChartTimelineVariant } from '@mdi/js'; -import { SwitchField } from '../../components/SwitchField'; -import FormField from '../../components/FormField'; -import type { AssetVariant } from '../../types/entities'; +import CardBox from '../../components/CardBox'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { fetch } from '../../stores/asset_variants/asset_variantsSlice'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; interface AssetVariantData { asset?: { name?: string }; diff --git a/frontend/src/pages/assets/[assetsId].tsx b/frontend/src/pages/assets/[assetsId].tsx index ade840b..0fef919 100644 --- a/frontend/src/pages/assets/[assetsId].tsx +++ b/frontend/src/pages/assets/[assetsId].tsx @@ -1,35 +1,27 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; +import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; +import { ReactElement, useEffect, useState } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; import { SwitchField } from '../../components/SwitchField'; -import { update, fetch } from '../../stores/assets/assetsSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { useRouter } from 'next/router'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from '../../components/ImageField'; +import { fetch, update } from '../../stores/assets/assetsSlice'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import type { Asset } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/assets/assets-edit.tsx b/frontend/src/pages/assets/assets-edit.tsx index 8bf5be3..111d89d 100644 --- a/frontend/src/pages/assets/assets-edit.tsx +++ b/frontend/src/pages/assets/assets-edit.tsx @@ -1,28 +1,28 @@ import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; import { SwitchField } from '../../components/SwitchField'; -import { update, fetch } from '../../stores/assets/assetsSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; import { useEditPageSync } from '../../hooks/useEditPageSync'; +import { fetch, update } from '../../stores/assets/assetsSlice'; +import { useAppDispatch } from '../../stores/hooks'; import type { Asset } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/assets/assets-list.tsx b/frontend/src/pages/assets/assets-list.tsx index 410834b..55b1788 100644 --- a/frontend/src/pages/assets/assets-list.tsx +++ b/frontend/src/pages/assets/assets-list.tsx @@ -1,6 +1,6 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { +import { ReactElement, useCallback, useEffect, @@ -9,17 +9,6 @@ import React, { useState, } from 'react'; import { toast, ToastContainer } from 'react-toastify'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import { hasPermission } from '../../helpers/userPermissions'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { - fetch as fetchAssets, - deleteItem as deleteAsset, - clearState as clearAssets, -} from '../../stores/assets/assetsSlice'; import AssetSectionCard, { Asset, AssetSection, @@ -27,7 +16,18 @@ import AssetSectionCard, { import EmbedAssetSection from '../../components/Assets/EmbedAssetSection'; import { useProjectSelector } from '../../components/Assets/ProjectSelector'; import { useAssetUploader } from '../../components/Assets/useAssetUploader'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; +import { hasPermission } from '../../helpers/userPermissions'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { logger } from '../../lib/logger'; +import { + clearState as clearAssets, + deleteItem as deleteAsset, + fetch as fetchAssets, +} from '../../stores/assets/assetsSlice'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; const ASSET_SECTIONS: AssetSection[] = [ { diff --git a/frontend/src/pages/assets/assets-new.tsx b/frontend/src/pages/assets/assets-new.tsx index 8d2051f..27bc230 100644 --- a/frontend/src/pages/assets/assets-new.tsx +++ b/frontend/src/pages/assets/assets-new.tsx @@ -5,25 +5,25 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; -import { SwitchField } from '../../components/SwitchField'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; +import { SwitchField } from '../../components/SwitchField'; +import { useRouter } from 'next/router'; import { create } from '../../stores/assets/assetsSlice'; import { useAppDispatch } from '../../stores/hooks'; -import { useRouter } from 'next/router'; import type { Asset } from '../../types/entities'; diff --git a/frontend/src/pages/assets/assets-table.tsx b/frontend/src/pages/assets/assets-table.tsx index 71debba..0233005 100644 --- a/frontend/src/pages/assets/assets-table.tsx +++ b/frontend/src/pages/assets/assets-table.tsx @@ -1,29 +1,28 @@ import { mdiChartTimelineVariant } from '@mdi/js'; +import axios from 'axios'; +import uniqueId from 'lodash/uniqueId'; import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; +import Link from 'next/link'; +import { ReactElement, useState } from 'react'; +import TableAssets from '../../components/Assets/TableAssets'; +import BaseButton from '../../components/BaseButton'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; +import CardBoxModal from '../../components/CardBoxModal'; +import DragDropFilePicker from '../../components/DragDropFilePicker'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; -import TableAssets from '../../components/Assets/TableAssets'; -import BaseButton from '../../components/BaseButton'; -import axios from 'axios'; -import Link from 'next/link'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import CardBoxModal from '../../components/CardBoxModal'; -import DragDropFilePicker from '../../components/DragDropFilePicker'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { setRefetch, uploadCsv } from '../../stores/assets/assetsSlice'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { hasPermission } from '../../helpers/userPermissions'; -import { Filter } from '../../types/filters'; +import { Filter, FilterItem } from '../../types/filters'; const AssetsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); + const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); const { currentUser } = useAppSelector((state) => state.auth); diff --git a/frontend/src/pages/assets/assets-view.tsx b/frontend/src/pages/assets/assets-view.tsx index e17516f..7ba979f 100644 --- a/frontend/src/pages/assets/assets-view.tsx +++ b/frontend/src/pages/assets/assets-view.tsx @@ -1,24 +1,21 @@ -import React, { ReactElement, useEffect } from 'react'; +import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; +import { useRouter } from 'next/router'; +import { ReactElement, useEffect } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { fetch } from '../../stores/assets/assetsSlice'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from '../../components/ImageField'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import { getPageTitle } from '../../config'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import SectionMain from '../../components/SectionMain'; -import CardBox from '../../components/CardBox'; import BaseButton from '../../components/BaseButton'; import BaseDivider from '../../components/BaseDivider'; -import { mdiChartTimelineVariant } from '@mdi/js'; -import { SwitchField } from '../../components/SwitchField'; +import CardBox from '../../components/CardBox'; import FormField from '../../components/FormField'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { SwitchField } from '../../components/SwitchField'; +import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { fetch } from '../../stores/assets/assetsSlice'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import type { Asset, AssetVariant } from '../../types/entities'; // Extended type to handle asset variants from API (includes size_mb not in base type) diff --git a/frontend/src/pages/constructor.tsx b/frontend/src/pages/constructor.tsx index 4c83584..c3521ae 100644 --- a/frontend/src/pages/constructor.tsx +++ b/frontend/src/pages/constructor.tsx @@ -1,12 +1,12 @@ import Head from 'next/head'; import { useRouter } from 'next/router'; import { ReactElement, useCallback, useMemo, useRef, useState } from 'react'; +import ConstructorCanvasStage from '../components/Constructor/ConstructorCanvasStage'; import ConstructorPageModals from '../components/Constructor/ConstructorPageModals'; +import ConstructorRuntimeOverlays from '../components/Constructor/ConstructorRuntimeOverlays'; import ConstructorStatusOverlay from '../components/Constructor/ConstructorStatusOverlay'; import ConstructorToolbarLayer from '../components/Constructor/ConstructorToolbarLayer'; import TransitionPreviewOverlay from '../components/Constructor/TransitionPreviewOverlay'; -import ConstructorCanvasStage from '../components/Constructor/ConstructorCanvasStage'; -import ConstructorRuntimeOverlays from '../components/Constructor/ConstructorRuntimeOverlays'; import { getFirstQueryParam, type ConstructorInteractionMode, @@ -15,6 +15,7 @@ import { useActiveConstructorPageHydration } from '../components/Constructor/use import { useConstructorAuthRedirect } from '../components/Constructor/useConstructorAuthRedirect'; import { useConstructorCanvasInteractions } from '../components/Constructor/useConstructorCanvasInteractions'; import { useConstructorContextValue } from '../components/Constructor/useConstructorContextValue'; +import { useConstructorDerivedState } from '../components/Constructor/useConstructorDerivedState'; import { useConstructorElementShortcuts } from '../components/Constructor/useConstructorElementShortcuts'; import { useConstructorFullscreen } from '../components/Constructor/useConstructorFullscreen'; import { useConstructorGalleryCarousel } from '../components/Constructor/useConstructorGalleryCarousel'; @@ -23,55 +24,46 @@ import { useConstructorInitialization } from '../components/Constructor/useConst import { useConstructorMediaDurations } from '../components/Constructor/useConstructorMediaDurations'; import { useConstructorMediaPreloadState } from '../components/Constructor/useConstructorMediaPreloadState'; import { useConstructorMessages } from '../components/Constructor/useConstructorMessages'; +import { useConstructorNavigationState } from '../components/Constructor/useConstructorNavigationState'; import { useConstructorPageNavigationLifecycle } from '../components/Constructor/useConstructorPageNavigationLifecycle'; import { useConstructorPageWorkflow } from '../components/Constructor/useConstructorPageWorkflow'; -import { useConstructorDerivedState } from '../components/Constructor/useConstructorDerivedState'; import { useConstructorSelectionLifecycle } from '../components/Constructor/useConstructorSelectionLifecycle'; import { useConstructorSettingsFetch } from '../components/Constructor/useConstructorSettingsFetch'; import { useConstructorSystemControls } from '../components/Constructor/useConstructorSystemControls'; import { useConstructorTransitionPreviewPlayback } from '../components/Constructor/useConstructorTransitionPreviewPlayback'; import { useInitialCanvasFocus } from '../components/Constructor/useInitialCanvasFocus'; -import { useConstructorNavigationState } from '../components/Constructor/useConstructorNavigationState'; import { getPageTitle } from '../config'; -import LayoutAuthenticated from '../layouts/Authenticated'; import { usePageNavigation } from '../hooks/usePageNavigation'; +import LayoutAuthenticated from '../layouts/Authenticated'; +import { resolveAssetPlaybackUrl } from '../lib/assetUrl'; +import { backgroundAudioController } from '../lib/backgroundAudioController'; +import { isNavigationElementType } from '../lib/elementTypeGuards'; import { useAppDispatch, useAppSelector } from '../stores/hooks'; import { selectByProjectAndEnv as selectProjectTransitionSettings } from '../stores/project_transition_settings/projectTransitionSettingsSlice'; import { selectByProjectAndEnv as selectProjectUiControlSettings } from '../stores/project_ui_control_settings/projectUiControlSettingsSlice'; +import type { CanvasElement, EditorMenuItem } from '../types/constructor'; import type { ElementTransitionSettings } from '../types/transition'; import { entityToProjectSettings } from '../types/transition'; -import { backgroundAudioController } from '../lib/backgroundAudioController'; -import { resolveAssetPlaybackUrl } from '../lib/assetUrl'; -import { - isNavigationElementType, - isInfoPanelElementType, -} from '../lib/elementTypeGuards'; -import { isElementFlagEnabled } from '../lib/elementFlags'; -import type { - CanvasElementType, - CanvasElement, - EditorMenuItem, -} from '../types/constructor'; // Constructor-specific hooks -import { - useCanvasElapsedTime, - isElementVisibleAtTime, -} from '../hooks/useCanvasElapsedTime'; -import { useDraggable } from '../hooks/useDraggable'; -import { useCanvasElementDrag } from '../hooks/useCanvasElementDrag'; -import { useTransitionPreview } from '../hooks/useTransitionPreview'; -import { useConstructorElements } from '../hooks/useConstructorElements'; -import { usePageBackground } from '../hooks/usePageBackground'; -import { useConstructorData } from '../hooks/useConstructorData'; -import { useAssetOptions } from '../hooks/useAssetOptions'; import { ConstructorProvider, type NavigationElementType, } from '../context/ConstructorContext'; +import { useAssetOptions } from '../hooks/useAssetOptions'; +import { + isElementVisibleAtTime, + useCanvasElapsedTime, +} from '../hooks/useCanvasElapsedTime'; +import { useCanvasElementDrag } from '../hooks/useCanvasElementDrag'; import { useCanvasScale } from '../hooks/useCanvasScale'; -import { useVideoSoundControl } from '../hooks/useVideoSoundControl'; +import { useConstructorData } from '../hooks/useConstructorData'; +import { useConstructorElements } from '../hooks/useConstructorElements'; +import { useDraggable } from '../hooks/useDraggable'; import { useNetworkAware } from '../hooks/useNetworkAware'; +import { usePageBackground } from '../hooks/usePageBackground'; +import { useTransitionPreview } from '../hooks/useTransitionPreview'; +import { useVideoSoundControl } from '../hooks/useVideoSoundControl'; // TourPage type is imported from '../types/entities' // NavigationElementType is imported from '../context/ConstructorContext' @@ -93,6 +85,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => { const canvasRef = useRef(null); const elementEditorRef = useRef(null); const toolbarRef = useRef(null); + const clearSelectedSystemControlRef = useRef<(() => void) | null>(null); const [isAuthReady, setIsAuthReady] = useState(false); const isElementEditMode = mode === 'element_edit'; @@ -255,7 +248,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => { allowedNavigationTypes, initialSelectedElementId: elementIdFromRoute, onElementSelected: useCallback(() => { - setSelectedSystemControl(null); + clearSelectedSystemControlRef.current?.(); setSelectedMenuItem('none'); }, []), onSelectionCleared: useCallback(() => { @@ -264,18 +257,18 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => { onElementAdded: useCallback(() => { setSuccessMessage('Element added. Drag it to set position.'); setErrorMessage(''); - }, []), + }, [setErrorMessage, setSuccessMessage]), onElementRemoved: useCallback(() => { setSuccessMessage('Element removed.'); - }, []), + }, [setSuccessMessage]), onElementCopied: useCallback(() => { setSuccessMessage('Element copied.'); setErrorMessage(''); - }, []), + }, [setErrorMessage, setSuccessMessage]), onElementPasted: useCallback(() => { setSuccessMessage('Element pasted.'); setErrorMessage(''); - }, []), + }, [setErrorMessage, setSuccessMessage]), }); const { @@ -352,6 +345,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => { projectSettings: projectUiControlSettingsEntity?.settings_json, isEditMode: constructorInteractionMode === 'edit', }); + clearSelectedSystemControlRef.current = () => setSelectedSystemControl(null); // Transition preview state management const { @@ -732,7 +726,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => { galleryInfoSpans, carouselSlides, infoPanelSectionOps, - getDuration, + getDuration: (source) => getDuration(source || '') ?? undefined, durationNotes, allowedNavigationTypes, normalizeNavigationType, diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx index b987881..1c38362 100644 --- a/frontend/src/pages/dashboard.tsx +++ b/frontend/src/pages/dashboard.tsx @@ -1,18 +1,18 @@ import * as icon from '@mdi/js'; import Head from 'next/head'; -import React from 'react'; -import type { ReactElement } from 'react'; -import LayoutAuthenticated from '../layouts/Authenticated'; -import SectionMain from '../components/SectionMain'; -import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; -import BaseIcon from '../components/BaseIcon'; -import { getPageTitle } from '../config'; import Link from 'next/link'; import { useRouter } from 'next/router'; +import type { ReactElement } from 'react'; +import React from 'react'; +import BaseIcon from '../components/BaseIcon'; +import SectionMain from '../components/SectionMain'; +import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../config'; +import LayoutAuthenticated from '../layouts/Authenticated'; import { - useDashboardCounts, EntityCountValue, + useDashboardCounts, } from '../hooks/useDashboardCounts'; import { useAppSelector } from '../stores/hooks'; diff --git a/frontend/src/pages/element-type-defaults.tsx b/frontend/src/pages/element-type-defaults.tsx index 8e0158b..f9fd952 100644 --- a/frontend/src/pages/element-type-defaults.tsx +++ b/frontend/src/pages/element-type-defaults.tsx @@ -4,13 +4,13 @@ import Head from 'next/head'; import Link from 'next/link'; import React, { ReactElement, useCallback, useEffect, useState } from 'react'; import CardBox from '../components/CardBox'; -import LayoutAuthenticated from '../layouts/Authenticated'; import SectionMain from '../components/SectionMain'; import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; import { getPageTitle } from '../config'; +import LayoutAuthenticated from '../layouts/Authenticated'; import { logger } from '../lib/logger'; -import { useAppDispatch, useAppSelector } from '../stores/hooks'; import { fetch as fetchGlobalTransitionDefaults } from '../stores/global_transition_defaults/globalTransitionDefaultsSlice'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; import type { UiElementDefault } from '../types/constructor'; import type { SystemUiControlType } from '../types/uiControls'; diff --git a/frontend/src/pages/element-type-defaults/[id].tsx b/frontend/src/pages/element-type-defaults/[id].tsx index 0f59c87..ab4ff42 100644 --- a/frontend/src/pages/element-type-defaults/[id].tsx +++ b/frontend/src/pages/element-type-defaults/[id].tsx @@ -3,15 +3,10 @@ import axios from 'axios'; import Head from 'next/head'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import React, { - ReactElement, - useCallback, - useEffect, - useMemo, - useState, -} from 'react'; +import { ReactElement, useCallback, useEffect, useMemo, useState } from 'react'; import BaseButton from '../../components/BaseButton'; import CardBox from '../../components/CardBox'; +import { useElementSettingsForm } from '../../components/ElementSettings/useElementSettingsForm'; import ElementTypeDefaultSettingsForm from '../../components/ElementTypeDefaults/ElementTypeDefaultSettingsForm'; import { buildElementTypeDefaultSavePayload, @@ -20,13 +15,12 @@ import { parseElementTypeDefaultSortOrder, type ElementTypeDefault, } from '../../components/ElementTypeDefaults/elementTypeDefaultDetails.helpers'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { logger } from '../../lib/logger'; import type { CanvasElementType } from '../../types/constructor'; -import { useElementSettingsForm } from '../../components/ElementSettings/useElementSettingsForm'; const ElementTypeDefaultDetailsPage = () => { const router = useRouter(); @@ -144,7 +138,7 @@ const ElementTypeDefaultDetailsPage = () => { (prop: string, value: string) => { setField(prop as keyof typeof form.state, value); }, - [setField], + [form, setField], ); // Handler for effects section changes @@ -161,7 +155,7 @@ const ElementTypeDefaultDetailsPage = () => { setField(prop as keyof typeof form.state, value as never); } }, - [setField], + [form, setField], ); // Handler for common section changes @@ -169,15 +163,15 @@ const ElementTypeDefaultDetailsPage = () => { (field: string, value: string) => { setField(field as keyof typeof form.state, value); }, - [setField], + [form, setField], ); // Handler for type-specific section changes const handleTypeChange = useCallback( - (field: string, value: string | boolean) => { + (field: string, value: string | number | boolean) => { setField(field as keyof typeof form.state, value as never); }, - [setField], + [form, setField], ); return ( diff --git a/frontend/src/pages/error.tsx b/frontend/src/pages/error.tsx index 0e80c27..94e46fd 100644 --- a/frontend/src/pages/error.tsx +++ b/frontend/src/pages/error.tsx @@ -1,11 +1,10 @@ -import React from 'react'; -import type { ReactElement } from 'react'; import Head from 'next/head'; +import type { ReactElement } from 'react'; import BaseButton from '../components/BaseButton'; import CardBox from '../components/CardBox'; import SectionFullScreen from '../components/SectionFullScreen'; -import LayoutGuest from '../layouts/Guest'; import { getPageTitle } from '../config'; +import LayoutGuest from '../layouts/Guest'; export default function Error() { return ( diff --git a/frontend/src/pages/forgot.tsx b/frontend/src/pages/forgot.tsx index dc722bf..57ef9d3 100644 --- a/frontend/src/pages/forgot.tsx +++ b/frontend/src/pages/forgot.tsx @@ -1,32 +1,29 @@ -import React from 'react'; -import type { ReactElement } from 'react'; -import { ToastContainer, toast } from 'react-toastify'; -import Head from 'next/head'; -import BaseButton from '../components/BaseButton'; -import CardBox from '../components/CardBox'; -import SectionFullScreen from '../components/SectionFullScreen'; -import LayoutGuest from '../layouts/Guest'; -import { Field, Form, Formik } from 'formik'; -import FormField from '../components/FormField'; -import BaseDivider from '../components/BaseDivider'; -import BaseButtons from '../components/BaseButtons'; -import { useRouter } from 'next/router'; -import { getPageTitle } from '../config'; import axios from 'axios'; +import { Field, Form, Formik } from 'formik'; +import Head from 'next/head'; +import { useRouter } from 'next/router'; +import type { ReactElement } from 'react'; +import React from 'react'; +import { ToastContainer, toast, type TypeOptions } from 'react-toastify'; +import BaseButton from '../components/BaseButton'; +import BaseButtons from '../components/BaseButtons'; +import BaseDivider from '../components/BaseDivider'; +import CardBox from '../components/CardBox'; +import FormField from '../components/FormField'; +import SectionFullScreen from '../components/SectionFullScreen'; +import { getPageTitle } from '../config'; +import LayoutGuest from '../layouts/Guest'; import { logger } from '../lib/logger'; export default function Forgot() { const [loading, setLoading] = React.useState(false); const router = useRouter(); - const notify = (type, msg) => toast(msg, { type }); + const notify = (type: TypeOptions, msg: string) => toast(msg, { type }); - const handleSubmit = async (value) => { + const handleSubmit = async (value: { email: string }) => { setLoading(true); try { - const { data: response } = await axios.post( - '/auth/send-password-reset-email', - value, - ); + await axios.post('/auth/send-password-reset-email', value); setLoading(false); notify('success', 'Please check your email for verification link'); setTimeout(async () => { diff --git a/frontend/src/pages/global-transition-defaults.tsx b/frontend/src/pages/global-transition-defaults.tsx index d272570..4a38c91 100644 --- a/frontend/src/pages/global-transition-defaults.tsx +++ b/frontend/src/pages/global-transition-defaults.tsx @@ -1,7 +1,7 @@ import { mdiContentSave, mdiSwapHorizontal } from '@mdi/js'; import Head from 'next/head'; import Link from 'next/link'; -import React, { ReactElement, useEffect, useState } from 'react'; +import { ReactElement, useEffect, useState } from 'react'; import BaseButton from '../components/BaseButton'; import CardBox from '../components/CardBox'; import SectionMain from '../components/SectionMain'; @@ -9,11 +9,11 @@ import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton import { getPageTitle } from '../config'; import LayoutAuthenticated from '../layouts/Authenticated'; import { logger } from '../lib/logger'; -import { useAppDispatch, useAppSelector } from '../stores/hooks'; import { fetch as fetchGlobalTransitionDefaults, update as updateGlobalTransitionDefaults, } from '../stores/global_transition_defaults/globalTransitionDefaultsSlice'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; import type { EasingFunction, TransitionType } from '../types/transition'; const TRANSITION_TYPES: { value: TransitionType; label: string }[] = [ diff --git a/frontend/src/pages/global-ui-control-defaults.tsx b/frontend/src/pages/global-ui-control-defaults.tsx index 460516d..87a92f0 100644 --- a/frontend/src/pages/global-ui-control-defaults.tsx +++ b/frontend/src/pages/global-ui-control-defaults.tsx @@ -1,7 +1,7 @@ import { mdiCog } from '@mdi/js'; import Head from 'next/head'; import Link from 'next/link'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../components/CardBox'; import SectionMain from '../components/SectionMain'; import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; diff --git a/frontend/src/pages/global-ui-control-defaults/[controlType].tsx b/frontend/src/pages/global-ui-control-defaults/[controlType].tsx index 3ea2a49..496b093 100644 --- a/frontend/src/pages/global-ui-control-defaults/[controlType].tsx +++ b/frontend/src/pages/global-ui-control-defaults/[controlType].tsx @@ -2,7 +2,7 @@ import { mdiCog, mdiContentSave } from '@mdi/js'; import Head from 'next/head'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import React, { ReactElement, useEffect, useMemo, useState } from 'react'; +import { ReactElement, useEffect, useMemo, useState } from 'react'; import BaseButton from '../../components/BaseButton'; import CardBox from '../../components/CardBox'; import SectionMain from '../../components/SectionMain'; @@ -11,11 +11,11 @@ import UiControlsSettingsForm from '../../components/UiControls/UiControlsSettin import { getPageTitle } from '../../config'; import LayoutAuthenticated from '../../layouts/Authenticated'; import { logger } from '../../lib/logger'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { fetch as fetchGlobalUiControlDefaults, update as updateGlobalUiControlDefaults, } from '../../stores/global_ui_control_defaults/globalUiControlDefaultsSlice'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import type { SystemUiControlType, UiControlsSettings, diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 8a5f2fe..ff09497 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,9 +1,9 @@ -import React from 'react'; -import type { ReactElement } from 'react'; import Head from 'next/head'; import { useRouter } from 'next/router'; -import LayoutGuest from '../layouts/Guest'; +import type { ReactElement } from 'react'; +import React from 'react'; import { getPageTitle } from '../config'; +import LayoutGuest from '../layouts/Guest'; export default function HomeRedirect() { const router = useRouter(); diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx index 20fc4c0..d791bb6 100644 --- a/frontend/src/pages/login.tsx +++ b/frontend/src/pages/login.tsx @@ -1,29 +1,29 @@ -import React, { useEffect, useState } from 'react'; -import type { ReactElement } from 'react'; -import Head from 'next/head'; -import CardBox from '../components/CardBox'; -import BaseIcon from '../components/BaseIcon'; import { mdiEye, mdiEyeOff } from '@mdi/js'; -import SectionFullScreen from '../components/SectionFullScreen'; -import LayoutGuest from '../layouts/Guest'; import { Field, Form, Formik } from 'formik'; -import FormField from '../components/FormField'; -import FormCheckRadio from '../components/FormCheckRadio'; -import BaseDivider from '../components/BaseDivider'; -import BaseButtons from '../components/BaseButtons'; -import BaseButton from '../components/BaseButton'; +import Head from 'next/head'; +import Link from 'next/link'; import { useRouter } from 'next/router'; +import type { ReactElement } from 'react'; +import React, { useEffect, useState } from 'react'; +import { toast, ToastContainer, type TypeOptions } from 'react-toastify'; +import BaseButton from '../components/BaseButton'; +import BaseButtons from '../components/BaseButtons'; +import BaseDivider from '../components/BaseDivider'; +import BaseIcon from '../components/BaseIcon'; +import CardBox from '../components/CardBox'; +import FormCheckRadio from '../components/FormCheckRadio'; +import FormField from '../components/FormField'; +import SectionFullScreen from '../components/SectionFullScreen'; import { getPageTitle } from '../config'; +import LayoutGuest from '../layouts/Guest'; import { findMe, loginUser, resetAction } from '../stores/authSlice'; import { useAppDispatch, useAppSelector } from '../stores/hooks'; -import Link from 'next/link'; -import { toast, ToastContainer } from 'react-toastify'; export default function Login() { const router = useRouter(); const dispatch = useAppDispatch(); const textColor = useAppSelector((state) => state.style.linkColor); - const notify = (type, msg) => toast(msg, { type }); + const notify = (type: TypeOptions, msg: string) => toast(msg, { type }); const [showPassword, setShowPassword] = useState(false); const { currentUser, @@ -75,13 +75,13 @@ export default function Login() { notify('success', notifyState?.textNotification); dispatch(resetAction()); } - }, [notifyState?.showNotification]); + }, [dispatch, notifyState?.showNotification, notifyState?.textNotification]); const togglePasswordVisibility = () => { setShowPassword(!showPassword); }; - const handleSubmit = async (value) => { + const handleSubmit = async (value: typeof initialValues) => { const { remember, ...rest } = value; await dispatch(loginUser(rest)); }; diff --git a/frontend/src/pages/p/[projectSlug]/index.tsx b/frontend/src/pages/p/[projectSlug]/index.tsx index 437a55d..358d606 100644 --- a/frontend/src/pages/p/[projectSlug]/index.tsx +++ b/frontend/src/pages/p/[projectSlug]/index.tsx @@ -3,9 +3,9 @@ * URL: /p/[projectSlug] (e.g., /p/cardiff) */ -import { ReactElement } from 'react'; -import { useRouter } from 'next/router'; import Head from 'next/head'; +import { useRouter } from 'next/router'; +import { ReactElement } from 'react'; import RuntimePresentation from '../../../components/RuntimePresentation'; import LayoutGuest from '../../../layouts/Guest'; diff --git a/frontend/src/pages/p/[projectSlug]/stage.tsx b/frontend/src/pages/p/[projectSlug]/stage.tsx index ff8cff3..1f852af 100644 --- a/frontend/src/pages/p/[projectSlug]/stage.tsx +++ b/frontend/src/pages/p/[projectSlug]/stage.tsx @@ -3,9 +3,9 @@ * URL: /p/[projectSlug]/stage (e.g., /p/cardiff/stage) */ -import { ReactElement } from 'react'; -import { useRouter } from 'next/router'; import Head from 'next/head'; +import { useRouter } from 'next/router'; +import { ReactElement } from 'react'; import RuntimePresentation from '../../../components/RuntimePresentation'; import LayoutAuthenticated from '../../../layouts/Authenticated'; diff --git a/frontend/src/pages/password-reset.tsx b/frontend/src/pages/password-reset.tsx index e75c2f4..e56fff8 100644 --- a/frontend/src/pages/password-reset.tsx +++ b/frontend/src/pages/password-reset.tsx @@ -1,7 +1,6 @@ -import React from 'react'; import type { ReactElement } from 'react'; -import LayoutGuest from '../layouts/Guest'; import PasswordSetOrReset from '../components/PasswordSetOrReset'; +import LayoutGuest from '../layouts/Guest'; export default function Reset() { return ; diff --git a/frontend/src/pages/permissions/[permissionsId].tsx b/frontend/src/pages/permissions/[permissionsId].tsx index 688b9a0..8c8d8ea 100644 --- a/frontend/src/pages/permissions/[permissionsId].tsx +++ b/frontend/src/pages/permissions/[permissionsId].tsx @@ -1,18 +1,18 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { useRouter } from 'next/router'; import { diff --git a/frontend/src/pages/permissions/permissions-edit.tsx b/frontend/src/pages/permissions/permissions-edit.tsx index 954a273..913661a 100644 --- a/frontend/src/pages/permissions/permissions-edit.tsx +++ b/frontend/src/pages/permissions/permissions-edit.tsx @@ -1,18 +1,18 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { useRouter } from 'next/router'; import { diff --git a/frontend/src/pages/permissions/permissions-list.tsx b/frontend/src/pages/permissions/permissions-list.tsx index e61d6e2..40dfa73 100644 --- a/frontend/src/pages/permissions/permissions-list.tsx +++ b/frontend/src/pages/permissions/permissions-list.tsx @@ -3,25 +3,25 @@ */ import { mdiChartTimelineVariant } from '@mdi/js'; +import uniqueId from 'lodash/uniqueId'; import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; +import { ReactElement, useState } from 'react'; import BaseButton from '../../components/BaseButton'; import CardBox from '../../components/CardBox'; import CardBoxModal from '../../components/CardBoxModal'; import DragDropFilePicker from '../../components/DragDropFilePicker'; -import LayoutAuthenticated from '../../layouts/Authenticated'; +import TablePermissions from '../../components/Permissions/TablePermissions'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import TablePermissions from '../../components/Permissions/TablePermissions'; import { getPageTitle } from '../../config'; import { hasPermission } from '../../helpers/userPermissions'; -import { useAppSelector } from '../../stores/hooks'; import { downloadPermissionsCsv, useUploadPermissionsCsvMutation, } from '../../hooks/queries'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { useAppSelector } from '../../stores/hooks'; import type { Filter, FilterItem } from '../../types/filters'; const filters: Filter[] = [{ label: 'Name', title: 'name' }]; diff --git a/frontend/src/pages/permissions/permissions-new.tsx b/frontend/src/pages/permissions/permissions-new.tsx index 2a248df..913fc44 100644 --- a/frontend/src/pages/permissions/permissions-new.tsx +++ b/frontend/src/pages/permissions/permissions-new.tsx @@ -5,22 +5,22 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; -import { useCreatePermissionMutation } from '../../hooks/queries'; import { useRouter } from 'next/router'; +import { useCreatePermissionMutation } from '../../hooks/queries'; const initialValues = { name: '', diff --git a/frontend/src/pages/permissions/permissions-table.tsx b/frontend/src/pages/permissions/permissions-table.tsx index 0fba68d..82ca74b 100644 --- a/frontend/src/pages/permissions/permissions-table.tsx +++ b/frontend/src/pages/permissions/permissions-table.tsx @@ -1,22 +1,22 @@ import { mdiChartTimelineVariant } from '@mdi/js'; +import uniqueId from 'lodash/uniqueId'; import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; +import { ReactElement, useState } from 'react'; +import BaseButton from '../../components/BaseButton'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; +import CardBoxModal from '../../components/CardBoxModal'; +import DragDropFilePicker from '../../components/DragDropFilePicker'; +import TablePermissions from '../../components/Permissions/TablePermissions'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; -import TablePermissions from '../../components/Permissions/TablePermissions'; -import BaseButton from '../../components/BaseButton'; -import { useAppSelector } from '../../stores/hooks'; -import CardBoxModal from '../../components/CardBoxModal'; -import DragDropFilePicker from '../../components/DragDropFilePicker'; +import { hasPermission } from '../../helpers/userPermissions'; import { downloadPermissionsCsv, useUploadPermissionsCsvMutation, } from '../../hooks/queries'; -import { hasPermission } from '../../helpers/userPermissions'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { useAppSelector } from '../../stores/hooks'; import type { Filter, FilterItem } from '../../types/filters'; const PermissionsTablesPage = () => { diff --git a/frontend/src/pages/permissions/permissions-view.tsx b/frontend/src/pages/permissions/permissions-view.tsx index 4f55757..502343b 100644 --- a/frontend/src/pages/permissions/permissions-view.tsx +++ b/frontend/src/pages/permissions/permissions-view.tsx @@ -1,15 +1,15 @@ -import React, { ReactElement } from 'react'; +import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; import { useRouter } from 'next/router'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import { getPageTitle } from '../../config'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import SectionMain from '../../components/SectionMain'; -import CardBox from '../../components/CardBox'; +import { ReactElement } from 'react'; import BaseButton from '../../components/BaseButton'; import BaseDivider from '../../components/BaseDivider'; -import { mdiChartTimelineVariant } from '@mdi/js'; +import CardBox from '../../components/CardBox'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; import { usePermissionQuery } from '../../hooks/queries'; +import LayoutAuthenticated from '../../layouts/Authenticated'; const PermissionsView = () => { const router = useRouter(); diff --git a/frontend/src/pages/presigned_url_requests/[presigned_url_requestsId].tsx b/frontend/src/pages/presigned_url_requests/[presigned_url_requestsId].tsx index 6703396..d242747 100644 --- a/frontend/src/pages/presigned_url_requests/[presigned_url_requestsId].tsx +++ b/frontend/src/pages/presigned_url_requests/[presigned_url_requestsId].tsx @@ -1,29 +1,29 @@ import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; +import { ReactElement, useEffect, useState } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; -import { - update, - fetch, -} from '../../stores/presigned_url_requests/presigned_url_requestsSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { useRouter } from 'next/router'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { + fetch, + update, +} from '../../stores/presigned_url_requests/presigned_url_requestsSlice'; import type { PresignedUrlRequest } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/presigned_url_requests/presigned_url_requests-edit.tsx b/frontend/src/pages/presigned_url_requests/presigned_url_requests-edit.tsx index 8f119ff..fd54a45 100644 --- a/frontend/src/pages/presigned_url_requests/presigned_url_requests-edit.tsx +++ b/frontend/src/pages/presigned_url_requests/presigned_url_requests-edit.tsx @@ -3,32 +3,32 @@ */ import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; -import { - update, - fetch, -} from '../../stores/presigned_url_requests/presigned_url_requestsSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; import { useEditPageSync } from '../../hooks/useEditPageSync'; +import { useAppDispatch } from '../../stores/hooks'; +import { + fetch, + update, +} from '../../stores/presigned_url_requests/presigned_url_requestsSlice'; import type { PresignedUrlRequest } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/presigned_url_requests/presigned_url_requests-list.tsx b/frontend/src/pages/presigned_url_requests/presigned_url_requests-list.tsx index 959160b..4caee82 100644 --- a/frontend/src/pages/presigned_url_requests/presigned_url_requests-list.tsx +++ b/frontend/src/pages/presigned_url_requests/presigned_url_requests-list.tsx @@ -2,11 +2,11 @@ * Presigned URL Requests List Page */ -import { createListPage } from '../../factories/createListPage'; import TablePresigned_url_requests from '../../components/Presigned_url_requests/TablePresigned_url_requests'; +import { createListPage } from '../../factories/createListPage'; import { - uploadCsv, setRefetch, + uploadCsv, } from '../../stores/presigned_url_requests/presigned_url_requestsSlice'; import { Filter } from '../../types/filters'; diff --git a/frontend/src/pages/presigned_url_requests/presigned_url_requests-new.tsx b/frontend/src/pages/presigned_url_requests/presigned_url_requests-new.tsx index 1e6bdba..e1e0177 100644 --- a/frontend/src/pages/presigned_url_requests/presigned_url_requests-new.tsx +++ b/frontend/src/pages/presigned_url_requests/presigned_url_requests-new.tsx @@ -5,24 +5,24 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; -import { create } from '../../stores/presigned_url_requests/presigned_url_requestsSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; +import { useAppDispatch } from '../../stores/hooks'; +import { create } from '../../stores/presigned_url_requests/presigned_url_requestsSlice'; import type { PresignedUrlRequest } from '../../types/entities'; const initialValues = { diff --git a/frontend/src/pages/presigned_url_requests/presigned_url_requests-table.tsx b/frontend/src/pages/presigned_url_requests/presigned_url_requests-table.tsx index 057f857..84c63ac 100644 --- a/frontend/src/pages/presigned_url_requests/presigned_url_requests-table.tsx +++ b/frontend/src/pages/presigned_url_requests/presigned_url_requests-table.tsx @@ -1,32 +1,31 @@ import { mdiChartTimelineVariant } from '@mdi/js'; +import axios from 'axios'; +import uniqueId from 'lodash/uniqueId'; import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; +import Link from 'next/link'; +import { ReactElement, useState } from 'react'; +import BaseButton from '../../components/BaseButton'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; +import CardBoxModal from '../../components/CardBoxModal'; +import DragDropFilePicker from '../../components/DragDropFilePicker'; +import TablePresigned_url_requests from '../../components/Presigned_url_requests/TablePresigned_url_requests'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; -import TablePresigned_url_requests from '../../components/Presigned_url_requests/TablePresigned_url_requests'; -import BaseButton from '../../components/BaseButton'; -import axios from 'axios'; -import Link from 'next/link'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import CardBoxModal from '../../components/CardBoxModal'; -import DragDropFilePicker from '../../components/DragDropFilePicker'; import { setRefetch, uploadCsv, } from '../../stores/presigned_url_requests/presigned_url_requestsSlice'; import { hasPermission } from '../../helpers/userPermissions'; -import { Filter } from '../../types/filters'; +import { Filter, FilterItem } from '../../types/filters'; const Presigned_url_requestsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); + const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); const { currentUser } = useAppSelector((state) => state.auth); diff --git a/frontend/src/pages/presigned_url_requests/presigned_url_requests-view.tsx b/frontend/src/pages/presigned_url_requests/presigned_url_requests-view.tsx index 3cfac0f..45ca9e6 100644 --- a/frontend/src/pages/presigned_url_requests/presigned_url_requests-view.tsx +++ b/frontend/src/pages/presigned_url_requests/presigned_url_requests-view.tsx @@ -1,21 +1,21 @@ -import React, { ReactElement, useEffect } from 'react'; +import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; +import { useRouter } from 'next/router'; +import { ReactElement, useEffect } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { fetch } from '../../stores/presigned_url_requests/presigned_url_requestsSlice'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import { getPageTitle } from '../../config'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import SectionMain from '../../components/SectionMain'; -import CardBox from '../../components/CardBox'; import BaseButton from '../../components/BaseButton'; import BaseDivider from '../../components/BaseDivider'; -import { mdiChartTimelineVariant } from '@mdi/js'; +import CardBox from '../../components/CardBox'; import FormField from '../../components/FormField'; -import type { PresignedUrlRequest, Project, User } from '../../types/entities'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { fetch } from '../../stores/presigned_url_requests/presigned_url_requestsSlice'; +import type { Project, User } from '../../types/entities'; const Presigned_url_requestsView = () => { const router = useRouter(); diff --git a/frontend/src/pages/privacy-policy.tsx b/frontend/src/pages/privacy-policy.tsx index d72a9dc..7c5b026 100644 --- a/frontend/src/pages/privacy-policy.tsx +++ b/frontend/src/pages/privacy-policy.tsx @@ -1,8 +1,8 @@ -import React, { useEffect, useState } from 'react'; -import type { ReactElement } from 'react'; import Head from 'next/head'; -import LayoutGuest from '../layouts/Guest'; +import type { ReactElement } from 'react'; +import { useEffect, useState } from 'react'; import { getPageTitle } from '../config'; +import LayoutGuest from '../layouts/Guest'; export default function PrivacyPolicy() { const title = 'Shimahara Visual'; diff --git a/frontend/src/pages/profile.tsx b/frontend/src/pages/profile.tsx index d7d0389..7c393d9 100644 --- a/frontend/src/pages/profile.tsx +++ b/frontend/src/pages/profile.tsx @@ -1,48 +1,60 @@ import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; -import Image from 'next/image'; import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; -import { ToastContainer, toast } from 'react-toastify'; -import DatePicker from 'react-datepicker'; +import Image from 'next/image'; +import { ReactElement, useEffect, useMemo, useState } from 'react'; import 'react-datepicker/dist/react-datepicker.css'; +import { toast, type TypeOptions } from 'react-toastify'; import CardBox from '../components/CardBox'; -import LayoutAuthenticated from '../layouts/Authenticated'; import SectionMain from '../components/SectionMain'; import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; import { getPageTitle } from '../config'; +import LayoutAuthenticated from '../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../components/FormField'; -import BaseDivider from '../components/BaseDivider'; -import BaseButtons from '../components/BaseButtons'; import BaseButton from '../components/BaseButton'; -import FormCheckRadio from '../components/FormCheckRadio'; -import FormCheckRadioGroup from '../components/FormCheckRadioGroup'; +import BaseButtons from '../components/BaseButtons'; +import BaseDivider from '../components/BaseDivider'; +import FormField from '../components/FormField'; import FormImagePicker from '../components/FormImagePicker'; -import { SwitchField } from '../components/SwitchField'; import { SelectField } from '../components/SelectField'; +import { SwitchField } from '../components/SwitchField'; -import { update, fetch } from '../stores/users/usersSlice'; -import { useAppDispatch, useAppSelector } from '../stores/hooks'; import { useRouter } from 'next/router'; import { findMe } from '../stores/authSlice'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; +import { update } from '../stores/users/usersSlice'; +import type { ImageFile, User } from '../types/entities'; + +interface ProfileFormValues { + firstName: string; + lastName: string; + phoneNumber: string; + email: string; + app_role: string; + disabled: boolean; + avatar: ImageFile[]; + password: string; +} const EditUsers = () => { const { currentUser } = useAppSelector((state) => state.auth); const router = useRouter(); const dispatch = useAppDispatch(); - const notify = (type, msg) => toast(msg, { type }); - const initVals = { - firstName: '', - lastName: '', - phoneNumber: '', - email: '', - app_role: '', - disabled: false, - avatar: [], - password: '', - }; + const notify = (type: TypeOptions, msg: string) => toast(msg, { type }); + const initVals: ProfileFormValues = useMemo( + () => ({ + firstName: '', + lastName: '', + phoneNumber: '', + email: '', + app_role: '', + disabled: false, + avatar: [], + password: '', + }), + [], + ); const [initialValues, setInitialValues] = useState(initVals); const avatarUrl = Array.isArray(currentUser?.avatar) ? currentUser.avatar[0]?.publicUrl @@ -52,16 +64,25 @@ const EditUsers = () => { if (currentUser?.id && typeof currentUser === 'object') { const newInitialVal = { ...initVals }; - Object.keys(initVals).forEach( - (el) => (newInitialVal[el] = currentUser[el] ?? initVals[el]), - ); + newInitialVal.firstName = currentUser.firstName ?? initVals.firstName; + newInitialVal.lastName = currentUser.lastName ?? initVals.lastName; + newInitialVal.phoneNumber = + currentUser.phoneNumber ?? initVals.phoneNumber; + newInitialVal.email = currentUser.email ?? initVals.email; + newInitialVal.app_role = currentUser.app_role?.id ?? initVals.app_role; + newInitialVal.disabled = currentUser.disabled ?? initVals.disabled; + newInitialVal.avatar = currentUser.avatar ?? initVals.avatar; + newInitialVal.password = initVals.password; setInitialValues(newInitialVal); } - }, [currentUser]); + }, [currentUser, initVals]); - const handleSubmit = async (data) => { - await dispatch(update({ id: currentUser.id, data })); + const handleSubmit = async (data: typeof initVals) => { + if (!currentUser?.id) return; + await dispatch( + update({ id: currentUser.id, data: data as unknown as Partial }), + ); await dispatch(findMe()); await router.push('/users/users-list'); notify('success', 'Profile was updated!'); diff --git a/frontend/src/pages/project-element-defaults.tsx b/frontend/src/pages/project-element-defaults.tsx index b4bcb97..b22e0b8 100644 --- a/frontend/src/pages/project-element-defaults.tsx +++ b/frontend/src/pages/project-element-defaults.tsx @@ -3,13 +3,13 @@ import axios from 'axios'; import Head from 'next/head'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import React, { ReactElement, useCallback, useEffect, useState } from 'react'; +import { ReactElement, useCallback, useEffect, useState } from 'react'; import BaseButton from '../components/BaseButton'; import CardBox from '../components/CardBox'; -import LayoutAuthenticated from '../layouts/Authenticated'; import SectionMain from '../components/SectionMain'; import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; import { getPageTitle } from '../config'; +import LayoutAuthenticated from '../layouts/Authenticated'; import { logger } from '../lib/logger'; type ProjectElementDefault = { diff --git a/frontend/src/pages/project-element-defaults/[id].tsx b/frontend/src/pages/project-element-defaults/[id].tsx index 93b85ac..08788f0 100644 --- a/frontend/src/pages/project-element-defaults/[id].tsx +++ b/frontend/src/pages/project-element-defaults/[id].tsx @@ -1,26 +1,12 @@ -import { mdiContentSave, mdiCog, mdiSync } from '@mdi/js'; +import { mdiCog, mdiContentSave, mdiSync } from '@mdi/js'; import axios from 'axios'; import Head from 'next/head'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import React, { - ReactElement, - useCallback, - useEffect, - useMemo, - useState, -} from 'react'; +import { ReactElement, useCallback, useEffect, useMemo, useState } from 'react'; import BaseButton from '../../components/BaseButton'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import { logger } from '../../lib/logger'; -import type { - CanvasElementType, - ConstructorAsset, -} from '../../types/constructor'; +import { useElementSettingsForm } from '../../components/ElementSettings'; import { buildIconAssetOptions, buildImageAssetOptions, @@ -32,7 +18,15 @@ import { type ProjectElementDefault, } from '../../components/Projects/projectElementDefaultDetails.helpers'; import ProjectElementDefaultSettingsForm from '../../components/Projects/ProjectElementDefaultSettingsForm'; -import { useElementSettingsForm } from '../../components/ElementSettings'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { logger } from '../../lib/logger'; +import type { + CanvasElementType, + ConstructorAsset, +} from '../../types/constructor'; const ProjectElementDefaultDetailsPage = () => { const router = useRouter(); @@ -240,7 +234,7 @@ const ProjectElementDefaultDetailsPage = () => { (prop: string, value: string) => { setField(prop as keyof typeof form.state, value); }, - [setField], + [form, setField], ); // Handler for effects section changes @@ -257,7 +251,7 @@ const ProjectElementDefaultDetailsPage = () => { setField(prop as keyof typeof form.state, value as never); } }, - [setField], + [form, setField], ); // Handler for common section changes @@ -265,15 +259,15 @@ const ProjectElementDefaultDetailsPage = () => { (field: string, value: string) => { setField(field as keyof typeof form.state, value); }, - [setField], + [form, setField], ); // Handler for type-specific section changes const handleTypeChange = useCallback( - (field: string, value: string | boolean) => { + (field: string, value: string | number | boolean) => { setField(field as keyof typeof form.state, value as never); }, - [setField], + [form, setField], ); if (isLoading) { diff --git a/frontend/src/pages/project-ui-control-settings.tsx b/frontend/src/pages/project-ui-control-settings.tsx index b3dbf58..d0038bb 100644 --- a/frontend/src/pages/project-ui-control-settings.tsx +++ b/frontend/src/pages/project-ui-control-settings.tsx @@ -3,7 +3,7 @@ import axios from 'axios'; import Head from 'next/head'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import React, { ReactElement, useEffect, useMemo, useState } from 'react'; +import { ReactElement, useEffect, useMemo, useState } from 'react'; import { toast } from 'react-toastify'; import BaseButton from '../components/BaseButton'; import CardBox from '../components/CardBox'; @@ -13,8 +13,8 @@ import UiControlsSettingsForm from '../components/UiControls/UiControlsSettingsF import { getPageTitle } from '../config'; import LayoutAuthenticated from '../layouts/Authenticated'; import { logger } from '../lib/logger'; -import { useAppDispatch, useAppSelector } from '../stores/hooks'; import { fetch as fetchGlobalUiControlDefaults } from '../stores/global_ui_control_defaults/globalUiControlDefaultsSlice'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; import { deleteByProjectAndEnv, fetchByProjectAndEnv, diff --git a/frontend/src/pages/project_audio_tracks/[project_audio_tracksId].tsx b/frontend/src/pages/project_audio_tracks/[project_audio_tracksId].tsx index fdcab90..a47c598 100644 --- a/frontend/src/pages/project_audio_tracks/[project_audio_tracksId].tsx +++ b/frontend/src/pages/project_audio_tracks/[project_audio_tracksId].tsx @@ -1,27 +1,27 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; +import { ReactElement, useEffect, useState } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; import { SwitchField } from '../../components/SwitchField'; -import { - update, - fetch, -} from '../../stores/project_audio_tracks/project_audio_tracksSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { useRouter } from 'next/router'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { + fetch, + update, +} from '../../stores/project_audio_tracks/project_audio_tracksSlice'; import type { ProjectAudioTrack } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/project_audio_tracks/project_audio_tracks-edit.tsx b/frontend/src/pages/project_audio_tracks/project_audio_tracks-edit.tsx index 7ac5b52..4da0c53 100644 --- a/frontend/src/pages/project_audio_tracks/project_audio_tracks-edit.tsx +++ b/frontend/src/pages/project_audio_tracks/project_audio_tracks-edit.tsx @@ -1,28 +1,28 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; import { SwitchField } from '../../components/SwitchField'; -import { - update, - fetch, -} from '../../stores/project_audio_tracks/project_audio_tracksSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; import { useEditPageSync } from '../../hooks/useEditPageSync'; +import { useAppDispatch } from '../../stores/hooks'; +import { + fetch, + update, +} from '../../stores/project_audio_tracks/project_audio_tracksSlice'; import type { ProjectAudioTrack } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/project_audio_tracks/project_audio_tracks-list.tsx b/frontend/src/pages/project_audio_tracks/project_audio_tracks-list.tsx index fa03bd0..cc4978a 100644 --- a/frontend/src/pages/project_audio_tracks/project_audio_tracks-list.tsx +++ b/frontend/src/pages/project_audio_tracks/project_audio_tracks-list.tsx @@ -2,11 +2,11 @@ * Project Audio Tracks List Page */ -import { createListPage } from '../../factories/createListPage'; import TableProject_audio_tracks from '../../components/Project_audio_tracks/TableProject_audio_tracks'; +import { createListPage } from '../../factories/createListPage'; import { - uploadCsv, setRefetch, + uploadCsv, } from '../../stores/project_audio_tracks/project_audio_tracksSlice'; import { Filter } from '../../types/filters'; diff --git a/frontend/src/pages/project_audio_tracks/project_audio_tracks-new.tsx b/frontend/src/pages/project_audio_tracks/project_audio_tracks-new.tsx index 036dba6..13f3639 100644 --- a/frontend/src/pages/project_audio_tracks/project_audio_tracks-new.tsx +++ b/frontend/src/pages/project_audio_tracks/project_audio_tracks-new.tsx @@ -5,25 +5,25 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; -import { SwitchField } from '../../components/SwitchField'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; +import { SwitchField } from '../../components/SwitchField'; -import { create } from '../../stores/project_audio_tracks/project_audio_tracksSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; +import { useAppDispatch } from '../../stores/hooks'; +import { create } from '../../stores/project_audio_tracks/project_audio_tracksSlice'; import type { ProjectAudioTrack } from '../../types/entities'; const initialValues = { diff --git a/frontend/src/pages/project_audio_tracks/project_audio_tracks-table.tsx b/frontend/src/pages/project_audio_tracks/project_audio_tracks-table.tsx index 24f788e..48bf24f 100644 --- a/frontend/src/pages/project_audio_tracks/project_audio_tracks-table.tsx +++ b/frontend/src/pages/project_audio_tracks/project_audio_tracks-table.tsx @@ -1,32 +1,31 @@ import { mdiChartTimelineVariant } from '@mdi/js'; +import axios from 'axios'; +import uniqueId from 'lodash/uniqueId'; import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; +import Link from 'next/link'; +import { ReactElement, useState } from 'react'; +import BaseButton from '../../components/BaseButton'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; +import CardBoxModal from '../../components/CardBoxModal'; +import DragDropFilePicker from '../../components/DragDropFilePicker'; +import TableProject_audio_tracks from '../../components/Project_audio_tracks/TableProject_audio_tracks'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; -import TableProject_audio_tracks from '../../components/Project_audio_tracks/TableProject_audio_tracks'; -import BaseButton from '../../components/BaseButton'; -import axios from 'axios'; -import Link from 'next/link'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import CardBoxModal from '../../components/CardBoxModal'; -import DragDropFilePicker from '../../components/DragDropFilePicker'; import { setRefetch, uploadCsv, } from '../../stores/project_audio_tracks/project_audio_tracksSlice'; import { hasPermission } from '../../helpers/userPermissions'; -import { Filter } from '../../types/filters'; +import { Filter, FilterItem } from '../../types/filters'; const Project_audio_tracksTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); + const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); const { currentUser } = useAppSelector((state) => state.auth); diff --git a/frontend/src/pages/project_audio_tracks/project_audio_tracks-view.tsx b/frontend/src/pages/project_audio_tracks/project_audio_tracks-view.tsx index ab86e6e..3261ae0 100644 --- a/frontend/src/pages/project_audio_tracks/project_audio_tracks-view.tsx +++ b/frontend/src/pages/project_audio_tracks/project_audio_tracks-view.tsx @@ -1,19 +1,18 @@ -import React, { ReactElement, useEffect } from 'react'; +import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { useRouter } from 'next/router'; -import { fetch } from '../../stores/project_audio_tracks/project_audio_tracksSlice'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import { getPageTitle } from '../../config'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import SectionMain from '../../components/SectionMain'; -import CardBox from '../../components/CardBox'; +import { ReactElement, useEffect } from 'react'; import BaseButton from '../../components/BaseButton'; import BaseDivider from '../../components/BaseDivider'; -import { mdiChartTimelineVariant } from '@mdi/js'; -import { SwitchField } from '../../components/SwitchField'; +import CardBox from '../../components/CardBox'; import FormField from '../../components/FormField'; -import type { ProjectAudioTrack } from '../../types/entities'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { SwitchField } from '../../components/SwitchField'; +import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { fetch } from '../../stores/project_audio_tracks/project_audio_tracksSlice'; const Project_audio_tracksView = () => { const router = useRouter(); diff --git a/frontend/src/pages/project_memberships/[project_membershipsId].tsx b/frontend/src/pages/project_memberships/[project_membershipsId].tsx index 51a2376..3ccf328 100644 --- a/frontend/src/pages/project_memberships/[project_membershipsId].tsx +++ b/frontend/src/pages/project_memberships/[project_membershipsId].tsx @@ -1,30 +1,30 @@ import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; +import { ReactElement, useEffect, useState } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; import { SwitchField } from '../../components/SwitchField'; -import { - update, - fetch, -} from '../../stores/project_memberships/project_membershipsSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { useRouter } from 'next/router'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { + fetch, + update, +} from '../../stores/project_memberships/project_membershipsSlice'; import type { ProjectMembership } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/project_memberships/project_memberships-edit.tsx b/frontend/src/pages/project_memberships/project_memberships-edit.tsx index f7278da..8f9784f 100644 --- a/frontend/src/pages/project_memberships/project_memberships-edit.tsx +++ b/frontend/src/pages/project_memberships/project_memberships-edit.tsx @@ -3,33 +3,33 @@ */ import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; import { SwitchField } from '../../components/SwitchField'; -import { - update, - fetch, -} from '../../stores/project_memberships/project_membershipsSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; import { useEditPageSync } from '../../hooks/useEditPageSync'; +import { useAppDispatch } from '../../stores/hooks'; +import { + fetch, + update, +} from '../../stores/project_memberships/project_membershipsSlice'; import type { ProjectMembership } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/project_memberships/project_memberships-list.tsx b/frontend/src/pages/project_memberships/project_memberships-list.tsx index 55c7804..c9b07a2 100644 --- a/frontend/src/pages/project_memberships/project_memberships-list.tsx +++ b/frontend/src/pages/project_memberships/project_memberships-list.tsx @@ -2,11 +2,11 @@ * Project Memberships List Page */ -import { createListPage } from '../../factories/createListPage'; import TableProject_memberships from '../../components/Project_memberships/TableProject_memberships'; +import { createListPage } from '../../factories/createListPage'; import { - uploadCsv, setRefetch, + uploadCsv, } from '../../stores/project_memberships/project_membershipsSlice'; import { Filter } from '../../types/filters'; diff --git a/frontend/src/pages/project_memberships/project_memberships-new.tsx b/frontend/src/pages/project_memberships/project_memberships-new.tsx index 88869ab..0833b98 100644 --- a/frontend/src/pages/project_memberships/project_memberships-new.tsx +++ b/frontend/src/pages/project_memberships/project_memberships-new.tsx @@ -5,25 +5,25 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; -import { SwitchField } from '../../components/SwitchField'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; +import { SwitchField } from '../../components/SwitchField'; -import { create } from '../../stores/project_memberships/project_membershipsSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; +import { useAppDispatch } from '../../stores/hooks'; +import { create } from '../../stores/project_memberships/project_membershipsSlice'; import type { ProjectMembership } from '../../types/entities'; const initialValues = { diff --git a/frontend/src/pages/project_memberships/project_memberships-table.tsx b/frontend/src/pages/project_memberships/project_memberships-table.tsx index a8fe15c..cd6612d 100644 --- a/frontend/src/pages/project_memberships/project_memberships-table.tsx +++ b/frontend/src/pages/project_memberships/project_memberships-table.tsx @@ -1,32 +1,31 @@ import { mdiChartTimelineVariant } from '@mdi/js'; +import axios from 'axios'; +import uniqueId from 'lodash/uniqueId'; import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; +import Link from 'next/link'; +import { ReactElement, useState } from 'react'; +import BaseButton from '../../components/BaseButton'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; +import CardBoxModal from '../../components/CardBoxModal'; +import DragDropFilePicker from '../../components/DragDropFilePicker'; +import TableProject_memberships from '../../components/Project_memberships/TableProject_memberships'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; -import TableProject_memberships from '../../components/Project_memberships/TableProject_memberships'; -import BaseButton from '../../components/BaseButton'; -import axios from 'axios'; -import Link from 'next/link'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import CardBoxModal from '../../components/CardBoxModal'; -import DragDropFilePicker from '../../components/DragDropFilePicker'; import { setRefetch, uploadCsv, } from '../../stores/project_memberships/project_membershipsSlice'; import { hasPermission } from '../../helpers/userPermissions'; -import { Filter } from '../../types/filters'; +import { Filter, FilterItem } from '../../types/filters'; const Project_membershipsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); + const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); const { currentUser } = useAppSelector((state) => state.auth); diff --git a/frontend/src/pages/project_memberships/project_memberships-view.tsx b/frontend/src/pages/project_memberships/project_memberships-view.tsx index 971b879..a322907 100644 --- a/frontend/src/pages/project_memberships/project_memberships-view.tsx +++ b/frontend/src/pages/project_memberships/project_memberships-view.tsx @@ -1,22 +1,21 @@ -import React, { ReactElement, useEffect } from 'react'; +import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; +import { useRouter } from 'next/router'; +import { ReactElement, useEffect } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { fetch } from '../../stores/project_memberships/project_membershipsSlice'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import { getPageTitle } from '../../config'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import SectionMain from '../../components/SectionMain'; -import CardBox from '../../components/CardBox'; import BaseButton from '../../components/BaseButton'; import BaseDivider from '../../components/BaseDivider'; -import { mdiChartTimelineVariant } from '@mdi/js'; -import { SwitchField } from '../../components/SwitchField'; +import CardBox from '../../components/CardBox'; import FormField from '../../components/FormField'; -import type { ProjectMembership } from '../../types/entities'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { SwitchField } from '../../components/SwitchField'; +import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { fetch } from '../../stores/project_memberships/project_membershipsSlice'; const Project_membershipsView = () => { const router = useRouter(); diff --git a/frontend/src/pages/projects/[projectsId].tsx b/frontend/src/pages/projects/[projectsId].tsx index 9755846..e10298f 100644 --- a/frontend/src/pages/projects/[projectsId].tsx +++ b/frontend/src/pages/projects/[projectsId].tsx @@ -5,25 +5,24 @@ import { mdiFolderMultipleImage, mdiPencil, mdiViewDashboard, - mdiWidgets, } from '@mdi/js'; import axios from 'axios'; import Head from 'next/head'; import { useRouter } from 'next/router'; -import React, { ReactElement, useEffect, useMemo, useState } from 'react'; +import { ReactElement, useEffect, useMemo, useState } from 'react'; import { toast, ToastContainer } from 'react-toastify'; import BaseButton from '../../components/BaseButton'; import BaseButtons from '../../components/BaseButtons'; import CardBox from '../../components/CardBox'; import CardBoxModal from '../../components/CardBoxModal'; import FormField from '../../components/FormField'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; -import { logger } from '../../lib/logger'; -import { usePublishStatus } from '../../hooks/usePublishStatus'; import dataFormatter from '../../helpers/dataFormatter'; +import { usePublishStatus } from '../../hooks/usePublishStatus'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { logger } from '../../lib/logger'; type ProjectData = { id: string; diff --git a/frontend/src/pages/projects/projects-edit.tsx b/frontend/src/pages/projects/projects-edit.tsx index 5064411..8de0e4a 100644 --- a/frontend/src/pages/projects/projects-edit.tsx +++ b/frontend/src/pages/projects/projects-edit.tsx @@ -7,7 +7,7 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import axios from 'axios'; import Head from 'next/head'; import { useRouter } from 'next/router'; -import React, { ReactElement, useEffect, useState } from 'react'; +import { ReactElement, useEffect, useState } from 'react'; import { toast, ToastContainer } from 'react-toastify'; import CardBox from '../../components/CardBox'; import ProjectEditForm from '../../components/Projects/ProjectEditForm'; diff --git a/frontend/src/pages/projects/projects-list.tsx b/frontend/src/pages/projects/projects-list.tsx index 160869b..bbd93cb 100644 --- a/frontend/src/pages/projects/projects-list.tsx +++ b/frontend/src/pages/projects/projects-list.tsx @@ -2,21 +2,21 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import axios from 'axios'; import Head from 'next/head'; import { useRouter } from 'next/router'; -import React, { ReactElement, useEffect, useState } from 'react'; +import { ReactElement, useEffect, useMemo, useState } from 'react'; import { toast, ToastContainer } from 'react-toastify'; import BaseButton from '../../components/BaseButton'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { logger } from '../../lib/logger'; import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { - fetch as fetchProjects, create as createProject, + fetch as fetchProjects, } from '../../stores/projects/projectsSlice'; import type { Project } from '../../types/entities'; -import { logger } from '../../lib/logger'; const ProjectsListPage = () => { const router = useRouter(); @@ -25,11 +25,15 @@ const ProjectsListPage = () => { const projectsRaw = useAppSelector((state) => state.projects.data) as Project[] | Project | undefined; // Handle both array (from list fetch) and single object (after edit fetch) - const projects: Project[] = Array.isArray(projectsRaw) - ? projectsRaw - : projectsRaw - ? [projectsRaw] - : []; + const projects: Project[] = useMemo( + () => + Array.isArray(projectsRaw) + ? projectsRaw + : projectsRaw + ? [projectsRaw] + : [], + [projectsRaw], + ); const isLoading = useAppSelector((state) => state.projects.loading); const [isCreating, setIsCreating] = useState(false); diff --git a/frontend/src/pages/projects/projects-new.tsx b/frontend/src/pages/projects/projects-new.tsx index 0a5e411..1aafc4b 100644 --- a/frontend/src/pages/projects/projects-new.tsx +++ b/frontend/src/pages/projects/projects-new.tsx @@ -5,24 +5,24 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SwitchField } from '../../components/SwitchField'; -import { create } from '../../stores/projects/projectsSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; +import { useAppDispatch } from '../../stores/hooks'; +import { create } from '../../stores/projects/projectsSlice'; import type { Project } from '../../types/entities'; const initialValues = { diff --git a/frontend/src/pages/projects/projects-table.tsx b/frontend/src/pages/projects/projects-table.tsx index 805b0a3..2457d09 100644 --- a/frontend/src/pages/projects/projects-table.tsx +++ b/frontend/src/pages/projects/projects-table.tsx @@ -1,29 +1,28 @@ import { mdiChartTimelineVariant } from '@mdi/js'; +import axios from 'axios'; +import uniqueId from 'lodash/uniqueId'; import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; +import Link from 'next/link'; +import { ReactElement, useState } from 'react'; +import BaseButton from '../../components/BaseButton'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; +import CardBoxModal from '../../components/CardBoxModal'; +import DragDropFilePicker from '../../components/DragDropFilePicker'; +import TableProjects from '../../components/Projects/TableProjects'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; -import TableProjects from '../../components/Projects/TableProjects'; -import BaseButton from '../../components/BaseButton'; -import axios from 'axios'; -import Link from 'next/link'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import CardBoxModal from '../../components/CardBoxModal'; -import DragDropFilePicker from '../../components/DragDropFilePicker'; import { setRefetch, uploadCsv } from '../../stores/projects/projectsSlice'; import { hasPermission } from '../../helpers/userPermissions'; -import { Filter } from '../../types/filters'; +import { Filter, FilterItem } from '../../types/filters'; const ProjectsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); + const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); const { currentUser } = useAppSelector((state) => state.auth); diff --git a/frontend/src/pages/projects/projects-view.tsx b/frontend/src/pages/projects/projects-view.tsx index f966544..a21361b 100644 --- a/frontend/src/pages/projects/projects-view.tsx +++ b/frontend/src/pages/projects/projects-view.tsx @@ -1,24 +1,24 @@ -import React, { ReactElement, useEffect } from 'react'; +import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; +import { useRouter } from 'next/router'; +import { ReactElement, useEffect } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { fetch } from '../../stores/projects/projectsSlice'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import { getPageTitle } from '../../config'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import SectionMain from '../../components/SectionMain'; -import CardBox from '../../components/CardBox'; import BaseButton from '../../components/BaseButton'; import BaseDivider from '../../components/BaseDivider'; -import { mdiChartTimelineVariant } from '@mdi/js'; -import { SwitchField } from '../../components/SwitchField'; +import CardBox from '../../components/CardBox'; import FormField from '../../components/FormField'; import ProjectRelationTable from '../../components/Projects/ProjectRelationTable'; import { PROJECT_RELATION_SECTIONS } from '../../components/Projects/projectView.helpers'; import type { ProjectWithRelations } from '../../components/Projects/projectView.types'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { SwitchField } from '../../components/SwitchField'; +import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { fetch } from '../../stores/projects/projectsSlice'; const ProjectsView = () => { const router = useRouter(); diff --git a/frontend/src/pages/publish_events/[publish_eventsId].tsx b/frontend/src/pages/publish_events/[publish_eventsId].tsx index 52602ff..3cbbd50 100644 --- a/frontend/src/pages/publish_events/[publish_eventsId].tsx +++ b/frontend/src/pages/publish_events/[publish_eventsId].tsx @@ -1,26 +1,26 @@ import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; +import { ReactElement, useEffect, useState } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; -import { update, fetch } from '../../stores/publish_events/publish_eventsSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { useRouter } from 'next/router'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { fetch, update } from '../../stores/publish_events/publish_eventsSlice'; import type { PublishEvent } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/publish_events/publish_events-edit.tsx b/frontend/src/pages/publish_events/publish_events-edit.tsx index 5ad9dd0..1ff43cc 100644 --- a/frontend/src/pages/publish_events/publish_events-edit.tsx +++ b/frontend/src/pages/publish_events/publish_events-edit.tsx @@ -3,29 +3,29 @@ */ import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; -import { update, fetch } from '../../stores/publish_events/publish_eventsSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; import { useEditPageSync } from '../../hooks/useEditPageSync'; +import { useAppDispatch } from '../../stores/hooks'; +import { fetch, update } from '../../stores/publish_events/publish_eventsSlice'; import type { PublishEvent } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/publish_events/publish_events-list.tsx b/frontend/src/pages/publish_events/publish_events-list.tsx index 1bc7228..fe2a85b 100644 --- a/frontend/src/pages/publish_events/publish_events-list.tsx +++ b/frontend/src/pages/publish_events/publish_events-list.tsx @@ -2,15 +2,15 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import axios from 'axios'; import Head from 'next/head'; import { useRouter } from 'next/router'; -import React, { ReactElement, useEffect, useState } from 'react'; +import { ReactElement, useEffect, useState } from 'react'; import { toast } from 'react-toastify'; import CardBox from '../../components/CardBox'; import LoadingSpinner from '../../components/LoadingSpinner'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; import dataFormatter from '../../helpers/dataFormatter'; import LayoutAuthenticated from '../../layouts/Authenticated'; -import { getPageTitle } from '../../config'; import { logger } from '../../lib/logger'; type PublishEvent = { diff --git a/frontend/src/pages/publish_events/publish_events-new.tsx b/frontend/src/pages/publish_events/publish_events-new.tsx index 9919d77..1689d0c 100644 --- a/frontend/src/pages/publish_events/publish_events-new.tsx +++ b/frontend/src/pages/publish_events/publish_events-new.tsx @@ -5,24 +5,24 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; -import { create } from '../../stores/publish_events/publish_eventsSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; +import { useAppDispatch } from '../../stores/hooks'; +import { create } from '../../stores/publish_events/publish_eventsSlice'; import type { PublishEvent } from '../../types/entities'; const initialValues = { diff --git a/frontend/src/pages/publish_events/publish_events-table.tsx b/frontend/src/pages/publish_events/publish_events-table.tsx index 5229489..abc3a55 100644 --- a/frontend/src/pages/publish_events/publish_events-table.tsx +++ b/frontend/src/pages/publish_events/publish_events-table.tsx @@ -1,32 +1,31 @@ import { mdiChartTimelineVariant } from '@mdi/js'; +import axios from 'axios'; +import uniqueId from 'lodash/uniqueId'; import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; +import Link from 'next/link'; +import { ReactElement, useState } from 'react'; +import BaseButton from '../../components/BaseButton'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; +import CardBoxModal from '../../components/CardBoxModal'; +import DragDropFilePicker from '../../components/DragDropFilePicker'; +import TablePublish_events from '../../components/Publish_events/TablePublish_events'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; -import TablePublish_events from '../../components/Publish_events/TablePublish_events'; -import BaseButton from '../../components/BaseButton'; -import axios from 'axios'; -import Link from 'next/link'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import CardBoxModal from '../../components/CardBoxModal'; -import DragDropFilePicker from '../../components/DragDropFilePicker'; import { setRefetch, uploadCsv, } from '../../stores/publish_events/publish_eventsSlice'; import { hasPermission } from '../../helpers/userPermissions'; -import { Filter } from '../../types/filters'; +import { Filter, FilterItem } from '../../types/filters'; const Publish_eventsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); + const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); const { currentUser } = useAppSelector((state) => state.auth); diff --git a/frontend/src/pages/publish_events/publish_events-view.tsx b/frontend/src/pages/publish_events/publish_events-view.tsx index fa507f8..8c5d2ec 100644 --- a/frontend/src/pages/publish_events/publish_events-view.tsx +++ b/frontend/src/pages/publish_events/publish_events-view.tsx @@ -1,21 +1,20 @@ -import React, { ReactElement, useEffect } from 'react'; +import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; +import { useRouter } from 'next/router'; +import { ReactElement, useEffect } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { fetch } from '../../stores/publish_events/publish_eventsSlice'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import { getPageTitle } from '../../config'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import SectionMain from '../../components/SectionMain'; -import CardBox from '../../components/CardBox'; import BaseButton from '../../components/BaseButton'; import BaseDivider from '../../components/BaseDivider'; -import { mdiChartTimelineVariant } from '@mdi/js'; +import CardBox from '../../components/CardBox'; import FormField from '../../components/FormField'; -import type { PublishEvent } from '../../types/entities'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { fetch } from '../../stores/publish_events/publish_eventsSlice'; const Publish_eventsView = () => { const router = useRouter(); diff --git a/frontend/src/pages/pwa_caches/[pwa_cachesId].tsx b/frontend/src/pages/pwa_caches/[pwa_cachesId].tsx index 4ff0f3a..3ec9ddb 100644 --- a/frontend/src/pages/pwa_caches/[pwa_cachesId].tsx +++ b/frontend/src/pages/pwa_caches/[pwa_cachesId].tsx @@ -1,27 +1,27 @@ import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; +import { ReactElement, useEffect, useState } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; import { SwitchField } from '../../components/SwitchField'; -import { update, fetch } from '../../stores/pwa_caches/pwa_cachesSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { useRouter } from 'next/router'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { fetch, update } from '../../stores/pwa_caches/pwa_cachesSlice'; import type { PwaCache } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/pwa_caches/pwa_caches-edit.tsx b/frontend/src/pages/pwa_caches/pwa_caches-edit.tsx index a0065f8..b9c0f36 100644 --- a/frontend/src/pages/pwa_caches/pwa_caches-edit.tsx +++ b/frontend/src/pages/pwa_caches/pwa_caches-edit.tsx @@ -3,30 +3,30 @@ */ import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; import { SwitchField } from '../../components/SwitchField'; -import { update, fetch } from '../../stores/pwa_caches/pwa_cachesSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; import { useEditPageSync } from '../../hooks/useEditPageSync'; +import { useAppDispatch } from '../../stores/hooks'; +import { fetch, update } from '../../stores/pwa_caches/pwa_cachesSlice'; const initVals = { project: null as string | null, diff --git a/frontend/src/pages/pwa_caches/pwa_caches-list.tsx b/frontend/src/pages/pwa_caches/pwa_caches-list.tsx index cd5b8cb..36a612c 100644 --- a/frontend/src/pages/pwa_caches/pwa_caches-list.tsx +++ b/frontend/src/pages/pwa_caches/pwa_caches-list.tsx @@ -2,9 +2,9 @@ * PWA Caches List Page */ -import { createListPage } from '../../factories/createListPage'; import TablePwa_caches from '../../components/Pwa_caches/TablePwa_caches'; -import { uploadCsv, setRefetch } from '../../stores/pwa_caches/pwa_cachesSlice'; +import { createListPage } from '../../factories/createListPage'; +import { setRefetch, uploadCsv } from '../../stores/pwa_caches/pwa_cachesSlice'; import { Filter } from '../../types/filters'; const filters: Filter[] = [ diff --git a/frontend/src/pages/pwa_caches/pwa_caches-new.tsx b/frontend/src/pages/pwa_caches/pwa_caches-new.tsx index d98a46b..537b41b 100644 --- a/frontend/src/pages/pwa_caches/pwa_caches-new.tsx +++ b/frontend/src/pages/pwa_caches/pwa_caches-new.tsx @@ -5,25 +5,25 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; -import { SwitchField } from '../../components/SwitchField'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; +import { SwitchField } from '../../components/SwitchField'; -import { create } from '../../stores/pwa_caches/pwa_cachesSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; +import { useAppDispatch } from '../../stores/hooks'; +import { create } from '../../stores/pwa_caches/pwa_cachesSlice'; const initialValues = { project: '' as string | null, diff --git a/frontend/src/pages/pwa_caches/pwa_caches-table.tsx b/frontend/src/pages/pwa_caches/pwa_caches-table.tsx index 7de7d91..e575873 100644 --- a/frontend/src/pages/pwa_caches/pwa_caches-table.tsx +++ b/frontend/src/pages/pwa_caches/pwa_caches-table.tsx @@ -1,29 +1,28 @@ import { mdiChartTimelineVariant } from '@mdi/js'; +import axios from 'axios'; +import uniqueId from 'lodash/uniqueId'; import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; +import Link from 'next/link'; +import { ReactElement, useState } from 'react'; +import BaseButton from '../../components/BaseButton'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; +import CardBoxModal from '../../components/CardBoxModal'; +import DragDropFilePicker from '../../components/DragDropFilePicker'; +import TablePwa_caches from '../../components/Pwa_caches/TablePwa_caches'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; -import TablePwa_caches from '../../components/Pwa_caches/TablePwa_caches'; -import BaseButton from '../../components/BaseButton'; -import axios from 'axios'; -import Link from 'next/link'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import CardBoxModal from '../../components/CardBoxModal'; -import DragDropFilePicker from '../../components/DragDropFilePicker'; import { setRefetch, uploadCsv } from '../../stores/pwa_caches/pwa_cachesSlice'; import { hasPermission } from '../../helpers/userPermissions'; -import { Filter } from '../../types/filters'; +import { Filter, FilterItem } from '../../types/filters'; const Pwa_cachesTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); + const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); const { currentUser } = useAppSelector((state) => state.auth); diff --git a/frontend/src/pages/pwa_caches/pwa_caches-view.tsx b/frontend/src/pages/pwa_caches/pwa_caches-view.tsx index 59c92eb..b4bc0c5 100644 --- a/frontend/src/pages/pwa_caches/pwa_caches-view.tsx +++ b/frontend/src/pages/pwa_caches/pwa_caches-view.tsx @@ -1,22 +1,22 @@ -import React, { ReactElement, useEffect } from 'react'; +import { mdiChartTimelineVariant } from '@mdi/js'; +import dayjs from 'dayjs'; import Head from 'next/head'; +import { useRouter } from 'next/router'; +import { ReactElement, useEffect } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { fetch } from '../../stores/pwa_caches/pwa_cachesSlice'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import { getPageTitle } from '../../config'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import SectionMain from '../../components/SectionMain'; -import CardBox from '../../components/CardBox'; import BaseButton from '../../components/BaseButton'; import BaseDivider from '../../components/BaseDivider'; -import { mdiChartTimelineVariant } from '@mdi/js'; -import { SwitchField } from '../../components/SwitchField'; +import CardBox from '../../components/CardBox'; import FormField from '../../components/FormField'; -import type { PwaCache, Project } from '../../types/entities'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { SwitchField } from '../../components/SwitchField'; +import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { fetch } from '../../stores/pwa_caches/pwa_cachesSlice'; +import type { Project } from '../../types/entities'; const Pwa_cachesView = () => { const router = useRouter(); diff --git a/frontend/src/pages/roles/[rolesId].tsx b/frontend/src/pages/roles/[rolesId].tsx index 30d0c10..8e07832 100644 --- a/frontend/src/pages/roles/[rolesId].tsx +++ b/frontend/src/pages/roles/[rolesId].tsx @@ -1,23 +1,23 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; +import { ReactElement, useEffect, useState } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { update, fetch } from '../../stores/roles/rolesSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { useRouter } from 'next/router'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { fetch, update } from '../../stores/roles/rolesSlice'; import type { Role } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/roles/roles-edit.tsx b/frontend/src/pages/roles/roles-edit.tsx index 06ae413..5e3aa34 100644 --- a/frontend/src/pages/roles/roles-edit.tsx +++ b/frontend/src/pages/roles/roles-edit.tsx @@ -1,24 +1,24 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { update, fetch } from '../../stores/roles/rolesSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { useRouter } from 'next/router'; import { useEditPageSync } from '../../hooks/useEditPageSync'; +import { useAppDispatch } from '../../stores/hooks'; +import { fetch, update } from '../../stores/roles/rolesSlice'; const initVals = { name: '', diff --git a/frontend/src/pages/roles/roles-list.tsx b/frontend/src/pages/roles/roles-list.tsx index 63bd682..0d71a5b 100644 --- a/frontend/src/pages/roles/roles-list.tsx +++ b/frontend/src/pages/roles/roles-list.tsx @@ -2,9 +2,9 @@ * Roles List Page */ -import { createListPage } from '../../factories/createListPage'; import TableRoles from '../../components/Roles/TableRoles'; -import { uploadCsv, setRefetch } from '../../stores/roles/rolesSlice'; +import { createListPage } from '../../factories/createListPage'; +import { setRefetch, uploadCsv } from '../../stores/roles/rolesSlice'; const filters = [ { label: 'Name', title: 'name' }, diff --git a/frontend/src/pages/roles/roles-new.tsx b/frontend/src/pages/roles/roles-new.tsx index 9536793..0b95080 100644 --- a/frontend/src/pages/roles/roles-new.tsx +++ b/frontend/src/pages/roles/roles-new.tsx @@ -5,24 +5,24 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { create } from '../../stores/roles/rolesSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; +import { useAppDispatch } from '../../stores/hooks'; +import { create } from '../../stores/roles/rolesSlice'; import type { Role } from '../../types/entities'; const initialValues = { diff --git a/frontend/src/pages/roles/roles-table.tsx b/frontend/src/pages/roles/roles-table.tsx index f2a6e83..97e740e 100644 --- a/frontend/src/pages/roles/roles-table.tsx +++ b/frontend/src/pages/roles/roles-table.tsx @@ -1,28 +1,27 @@ import { mdiChartTimelineVariant } from '@mdi/js'; +import axios from 'axios'; +import uniqueId from 'lodash/uniqueId'; import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; +import { ReactElement, useState } from 'react'; +import BaseButton from '../../components/BaseButton'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; +import CardBoxModal from '../../components/CardBoxModal'; +import DragDropFilePicker from '../../components/DragDropFilePicker'; +import TableRoles from '../../components/Roles/TableRoles'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; -import TableRoles from '../../components/Roles/TableRoles'; -import BaseButton from '../../components/BaseButton'; -import axios from 'axios'; -import Link from 'next/link'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import CardBoxModal from '../../components/CardBoxModal'; -import DragDropFilePicker from '../../components/DragDropFilePicker'; import { setRefetch, uploadCsv } from '../../stores/roles/rolesSlice'; import { hasPermission } from '../../helpers/userPermissions'; +import type { FilterItem } from '../../types/filters'; const RolesTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); + const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); const { currentUser } = useAppSelector((state) => state.auth); diff --git a/frontend/src/pages/roles/roles-view.tsx b/frontend/src/pages/roles/roles-view.tsx index fb7eeac..2abb43e 100644 --- a/frontend/src/pages/roles/roles-view.tsx +++ b/frontend/src/pages/roles/roles-view.tsx @@ -1,18 +1,18 @@ -import React, { ReactElement, useEffect } from 'react'; +import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import 'react-datepicker/dist/react-datepicker.css'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { useRouter } from 'next/router'; -import { fetch } from '../../stores/roles/rolesSlice'; -import dataFormatter from '../../helpers/dataFormatter'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import { getPageTitle } from '../../config'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import SectionMain from '../../components/SectionMain'; -import CardBox from '../../components/CardBox'; +import { ReactElement, useEffect } from 'react'; +import 'react-datepicker/dist/react-datepicker.css'; import BaseButton from '../../components/BaseButton'; import BaseDivider from '../../components/BaseDivider'; -import { mdiChartTimelineVariant } from '@mdi/js'; +import CardBox from '../../components/CardBox'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; +import dataFormatter from '../../helpers/dataFormatter'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { fetch } from '../../stores/roles/rolesSlice'; import type { Role } from '../../types/entities'; // Extended Role type for view page with related entities diff --git a/frontend/src/pages/search.tsx b/frontend/src/pages/search.tsx index c06f7ae..e98689e 100644 --- a/frontend/src/pages/search.tsx +++ b/frontend/src/pages/search.tsx @@ -1,38 +1,36 @@ -import React, { ReactElement, useEffect, useState } from 'react'; import Head from 'next/head'; +import { ReactElement, useCallback, useEffect, useState } from 'react'; import 'react-datepicker/dist/react-datepicker.css'; -import { useAppDispatch } from '../stores/hooks'; +import { mdiChartTimelineVariant } from '@mdi/js'; +import axios from 'axios'; import { useRouter } from 'next/router'; -import LayoutAuthenticated from '../layouts/Authenticated'; -import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; -import SectionMain from '../components/SectionMain'; -import CardBox from '../components/CardBox'; -import SearchResults from '../components/SearchResults'; -import LoadingSpinner from '../components/LoadingSpinner'; import BaseButton from '../components/BaseButton'; import BaseDivider from '../components/BaseDivider'; -import { mdiChartTimelineVariant } from '@mdi/js'; -import { createAsyncThunk } from '@reduxjs/toolkit'; -import axios from 'axios'; +import CardBox from '../components/CardBox'; +import LoadingSpinner from '../components/LoadingSpinner'; +import type { + SearchResultRow, + SearchResultsByTable, +} from '../components/SearchResults'; +import SearchResults from '../components/SearchResults'; +import SectionMain from '../components/SectionMain'; +import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; +import LayoutAuthenticated from '../layouts/Authenticated'; import { logger } from '../lib/logger'; const SearchView = () => { const router = useRouter(); - const dispatch = useAppDispatch(); - const searchQuery = router.query.query; + const searchQuery = + typeof router.query.query === 'string' ? router.query.query : ''; const [loading, setLoading] = useState(false); - const [searchResults, setSearchResults] = useState([]); + const [searchResults, setSearchResults] = useState([]); - useEffect(() => { - dispatch(fetchData()); - }, [dispatch, searchQuery]); - - const fetchData = createAsyncThunk('/search', async () => { + const fetchData = useCallback(async () => { setLoading(true); try { const response = await axios.post('/search', { searchQuery }); - setSearchResults(response.data); + setSearchResults(response.data as SearchResultRow[]); setLoading(false); return response.data; } catch (error) { @@ -43,14 +41,23 @@ const SearchView = () => { setLoading(false); throw error; } - }); + }, [searchQuery]); - const groupedResults = searchResults.reduce((acc, item) => { - const { tableName } = item; - acc[tableName] = acc[tableName] || []; - acc[tableName].push(item); - return acc; - }, {}); + useEffect(() => { + fetchData(); + }, [fetchData]); + + const groupedResults = searchResults.reduce( + (acc, item) => { + const { tableName } = item; + if (typeof tableName === 'string') { + acc[tableName] = acc[tableName] || []; + acc[tableName].push(item); + } + return acc; + }, + {}, + ); return ( <> diff --git a/frontend/src/pages/terms-of-use.tsx b/frontend/src/pages/terms-of-use.tsx index 5946af5..659f500 100644 --- a/frontend/src/pages/terms-of-use.tsx +++ b/frontend/src/pages/terms-of-use.tsx @@ -1,16 +1,10 @@ -import React, { useEffect, useState } from 'react'; -import type { ReactElement } from 'react'; import Head from 'next/head'; -import LayoutGuest from '../layouts/Guest'; +import type { ReactElement } from 'react'; import { getPageTitle } from '../config'; +import LayoutGuest from '../layouts/Guest'; export default function PrivacyPolicy() { const title = 'Shimahara Visual'; - const [projectUrl, setProjectUrl] = useState(''); - - useEffect(() => { - setProjectUrl(location.origin); - }, []); const Information = () => { return ( diff --git a/frontend/src/pages/tour_pages/[tour_pagesId].tsx b/frontend/src/pages/tour_pages/[tour_pagesId].tsx index 1837ffc..172a400 100644 --- a/frontend/src/pages/tour_pages/[tour_pagesId].tsx +++ b/frontend/src/pages/tour_pages/[tour_pagesId].tsx @@ -1,24 +1,24 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; +import { ReactElement, useEffect, useState } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; import { SwitchField } from '../../components/SwitchField'; -import { update, fetch } from '../../stores/tour_pages/tour_pagesSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { useRouter } from 'next/router'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { fetch, update } from '../../stores/tour_pages/tour_pagesSlice'; import type { TourPage } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/tour_pages/tour_pages-edit.tsx b/frontend/src/pages/tour_pages/tour_pages-edit.tsx index 4aabb53..040ed4c 100644 --- a/frontend/src/pages/tour_pages/tour_pages-edit.tsx +++ b/frontend/src/pages/tour_pages/tour_pages-edit.tsx @@ -1,25 +1,25 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; import { SwitchField } from '../../components/SwitchField'; -import { update, fetch } from '../../stores/tour_pages/tour_pagesSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; import { useEditPageSync } from '../../hooks/useEditPageSync'; +import { useAppDispatch } from '../../stores/hooks'; +import { fetch, update } from '../../stores/tour_pages/tour_pagesSlice'; import type { TourPage } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/tour_pages/tour_pages-list.tsx b/frontend/src/pages/tour_pages/tour_pages-list.tsx index ec9c18e..642c62c 100644 --- a/frontend/src/pages/tour_pages/tour_pages-list.tsx +++ b/frontend/src/pages/tour_pages/tour_pages-list.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import TourFlowManager from '../../components/TourFlowManager'; import LayoutAuthenticated from '../../layouts/Authenticated'; diff --git a/frontend/src/pages/tour_pages/tour_pages-new.tsx b/frontend/src/pages/tour_pages/tour_pages-new.tsx index b05dd40..b3cb6f7 100644 --- a/frontend/src/pages/tour_pages/tour_pages-new.tsx +++ b/frontend/src/pages/tour_pages/tour_pages-new.tsx @@ -5,25 +5,25 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement } from 'react'; +import { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; -import { SwitchField } from '../../components/SwitchField'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import { SelectField } from '../../components/SelectField'; +import { SwitchField } from '../../components/SwitchField'; -import { create } from '../../stores/tour_pages/tour_pagesSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; +import { useAppDispatch } from '../../stores/hooks'; +import { create } from '../../stores/tour_pages/tour_pagesSlice'; import type { TourPage } from '../../types/entities'; const initialValues = { diff --git a/frontend/src/pages/tour_pages/tour_pages-table.tsx b/frontend/src/pages/tour_pages/tour_pages-table.tsx index e4c8c93..18119e5 100644 --- a/frontend/src/pages/tour_pages/tour_pages-table.tsx +++ b/frontend/src/pages/tour_pages/tour_pages-table.tsx @@ -1,29 +1,28 @@ import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import TableTour_pages from '../../components/Tour_pages/TableTour_pages'; -import BaseButton from '../../components/BaseButton'; import axios from 'axios'; +import uniqueId from 'lodash/uniqueId'; +import Head from 'next/head'; import Link from 'next/link'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { ReactElement, useState } from 'react'; +import BaseButton from '../../components/BaseButton'; +import CardBox from '../../components/CardBox'; import CardBoxModal from '../../components/CardBoxModal'; import DragDropFilePicker from '../../components/DragDropFilePicker'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import TableTour_pages from '../../components/Tour_pages/TableTour_pages'; +import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { setRefetch, uploadCsv } from '../../stores/tour_pages/tour_pagesSlice'; import { hasPermission } from '../../helpers/userPermissions'; -import { Filter } from '../../types/filters'; +import { Filter, FilterItem } from '../../types/filters'; const Tour_pagesTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); + const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); const { currentUser } = useAppSelector((state) => state.auth); diff --git a/frontend/src/pages/tour_pages/tour_pages-view.tsx b/frontend/src/pages/tour_pages/tour_pages-view.tsx index 7dcfb98..8fedfc9 100644 --- a/frontend/src/pages/tour_pages/tour_pages-view.tsx +++ b/frontend/src/pages/tour_pages/tour_pages-view.tsx @@ -1,20 +1,19 @@ -import React, { ReactElement, useEffect } from 'react'; +import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { useRouter } from 'next/router'; -import { fetch } from '../../stores/tour_pages/tour_pagesSlice'; -import dataFormatter from '../../helpers/dataFormatter'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import { getPageTitle } from '../../config'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import SectionMain from '../../components/SectionMain'; -import CardBox from '../../components/CardBox'; +import { ReactElement, useEffect } from 'react'; import BaseButton from '../../components/BaseButton'; import BaseDivider from '../../components/BaseDivider'; -import { mdiChartTimelineVariant } from '@mdi/js'; -import { SwitchField } from '../../components/SwitchField'; +import CardBox from '../../components/CardBox'; import FormField from '../../components/FormField'; -import type { TourPage, Project } from '../../types/entities'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { SwitchField } from '../../components/SwitchField'; +import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { fetch } from '../../stores/tour_pages/tour_pagesSlice'; +import type { Project, TourPage } from '../../types/entities'; // Extended type for view page with nested relations type TourPageWithRelations = TourPage & { diff --git a/frontend/src/pages/users/[usersId].tsx b/frontend/src/pages/users/[usersId].tsx index 4a84797..4d1cbc7 100644 --- a/frontend/src/pages/users/[usersId].tsx +++ b/frontend/src/pages/users/[usersId].tsx @@ -1,26 +1,26 @@ import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; +import { ReactElement, useEffect, useState } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import FormImagePicker from '../../components/FormImagePicker'; import { SelectField } from '../../components/SelectField'; import { SelectFieldMany } from '../../components/SelectFieldMany'; import { SwitchField } from '../../components/SwitchField'; -import { update, fetch } from '../../stores/users/usersSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { useRouter } from 'next/router'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { fetch, update } from '../../stores/users/usersSlice'; import type { User } from '../../types/entities'; const initVals = { diff --git a/frontend/src/pages/users/users-edit.tsx b/frontend/src/pages/users/users-edit.tsx index 2d96884..518af16 100644 --- a/frontend/src/pages/users/users-edit.tsx +++ b/frontend/src/pages/users/users-edit.tsx @@ -3,25 +3,25 @@ import Head from 'next/head'; import React, { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import FormImagePicker from '../../components/FormImagePicker'; -import { SelectField } from '../../components/SelectField'; +import { SelectField, type SelectOption } from '../../components/SelectField'; import { SelectFieldMany } from '../../components/SelectFieldMany'; import { SwitchField } from '../../components/SwitchField'; -import { update, fetch } from '../../stores/users/usersSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; import { useEditPageSync } from '../../hooks/useEditPageSync'; +import { useAppDispatch } from '../../stores/hooks'; +import { fetch, update } from '../../stores/users/usersSlice'; const initVals = { firstName: '', @@ -135,7 +135,7 @@ const EditUsersPage = () => { options={initialValues.app_role} itemRef={'roles'} showField={'name'} - onOptionChange={(option) => { + onOptionChange={(option: SelectOption | null) => { const label = option?.label || ''; setSelectedRoleLabel(label); if (label !== 'Public') { diff --git a/frontend/src/pages/users/users-list.tsx b/frontend/src/pages/users/users-list.tsx index c28d60c..a9f4c3d 100644 --- a/frontend/src/pages/users/users-list.tsx +++ b/frontend/src/pages/users/users-list.tsx @@ -2,9 +2,9 @@ * Users List Page */ -import { createListPage } from '../../factories/createListPage'; import TableUsers from '../../components/Users/TableUsers'; -import { uploadCsv, setRefetch } from '../../stores/users/usersSlice'; +import { createListPage } from '../../factories/createListPage'; +import { setRefetch, uploadCsv } from '../../stores/users/usersSlice'; const filters = [ { label: 'First Name', title: 'firstName' }, diff --git a/frontend/src/pages/users/users-new.tsx b/frontend/src/pages/users/users-new.tsx index 892513f..37d7247 100644 --- a/frontend/src/pages/users/users-new.tsx +++ b/frontend/src/pages/users/users-new.tsx @@ -8,24 +8,24 @@ import Head from 'next/head'; import React, { ReactElement } from 'react'; import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; import BaseButton from '../../components/BaseButton'; -import { SwitchField } from '../../components/SwitchField'; -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; +import BaseButtons from '../../components/BaseButtons'; +import BaseDivider from '../../components/BaseDivider'; +import FormField from '../../components/FormField'; import FormImagePicker from '../../components/FormImagePicker'; +import { SelectField, type SelectOption } from '../../components/SelectField'; +import { SelectFieldMany } from '../../components/SelectFieldMany'; +import { SwitchField } from '../../components/SwitchField'; -import { create } from '../../stores/users/usersSlice'; -import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; +import { useAppDispatch } from '../../stores/hooks'; +import { create } from '../../stores/users/usersSlice'; import type { User } from '../../types/entities'; const initialValues = { @@ -122,7 +122,7 @@ const UsersNew = () => { component={SelectField} options={[]} itemRef='roles' - onOptionChange={(option) => { + onOptionChange={(option: SelectOption | null) => { const label = option?.label || ''; setSelectedRoleLabel(label); if (label !== 'Public') { diff --git a/frontend/src/pages/users/users-table.tsx b/frontend/src/pages/users/users-table.tsx index 904768f..000cbe7 100644 --- a/frontend/src/pages/users/users-table.tsx +++ b/frontend/src/pages/users/users-table.tsx @@ -1,28 +1,27 @@ import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import TableUsers from '../../components/Users/TableUsers'; -import BaseButton from '../../components/BaseButton'; import axios from 'axios'; -import Link from 'next/link'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import uniqueId from 'lodash/uniqueId'; +import Head from 'next/head'; +import { ReactElement, useState } from 'react'; +import BaseButton from '../../components/BaseButton'; +import CardBox from '../../components/CardBox'; import CardBoxModal from '../../components/CardBoxModal'; import DragDropFilePicker from '../../components/DragDropFilePicker'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import TableUsers from '../../components/Users/TableUsers'; +import { getPageTitle } from '../../config'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { setRefetch, uploadCsv } from '../../stores/users/usersSlice'; import { hasPermission } from '../../helpers/userPermissions'; +import type { FilterItem } from '../../types/filters'; const UsersTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); + const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); const { currentUser } = useAppSelector((state) => state.auth); diff --git a/frontend/src/pages/users/users-view.tsx b/frontend/src/pages/users/users-view.tsx index d67bf0f..53e4bad 100644 --- a/frontend/src/pages/users/users-view.tsx +++ b/frontend/src/pages/users/users-view.tsx @@ -1,7 +1,7 @@ -import React, { ReactElement, useEffect } from 'react'; +import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; import { useRouter } from 'next/router'; -import { mdiChartTimelineVariant } from '@mdi/js'; +import { ReactElement, useEffect } from 'react'; import 'react-datepicker/dist/react-datepicker.css'; import BaseButton from '../../components/BaseButton'; import SectionMain from '../../components/SectionMain'; diff --git a/frontend/src/pages/verify-email.tsx b/frontend/src/pages/verify-email.tsx index 72fa9e2..81cda68 100644 --- a/frontend/src/pages/verify-email.tsx +++ b/frontend/src/pages/verify-email.tsx @@ -1,20 +1,20 @@ -import React from 'react'; -import type { ReactElement } from 'react'; -import { ToastContainer, toast } from 'react-toastify'; +import axios from 'axios'; import Head from 'next/head'; +import { useRouter } from 'next/router'; +import type { ReactElement } from 'react'; +import React from 'react'; +import { ToastContainer, toast, type TypeOptions } from 'react-toastify'; import CardBox from '../components/CardBox'; import SectionFullScreen from '../components/SectionFullScreen'; -import LayoutGuest from '../layouts/Guest'; -import { useRouter } from 'next/router'; import { getPageTitle } from '../config'; -import axios from 'axios'; +import LayoutGuest from '../layouts/Guest'; import { logger } from '../lib/logger'; export default function Verify() { const [loading, setLoading] = React.useState(false); const router = useRouter(); const { token } = router.query; - const notify = (type, msg) => toast(msg, { type }); + const notify = (type: TypeOptions, msg: string) => toast(msg, { type }); React.useEffect(() => { if (!token) { @@ -39,14 +39,19 @@ export default function Verify() { 'Email verification failed:', error instanceof Error ? error : { error }, ); - notify('error', error.response); + notify( + 'error', + axios.isAxiosError(error) && error.response + ? String(error.response.data?.message || error.response.status) + : 'Email verification failed', + ); }) .finally(async () => { await router.push('/login'); }); }; handleSubmit().then(); - }, [token]); + }, [router, token]); return ( <> diff --git a/frontend/src/schemas/index.ts b/frontend/src/schemas/index.ts index b36a391..6b37c78 100644 --- a/frontend/src/schemas/index.ts +++ b/frontend/src/schemas/index.ts @@ -2,8 +2,8 @@ * Schemas Index - Central export for all Zod validation schemas */ -export * from './userSchema'; -export * from './projectSchema'; export * from './assetSchema'; +export * from './projectSchema'; export * from './roleSchema'; export * from './tourPageSchema'; +export * from './userSchema'; diff --git a/frontend/src/stores/access_logs/access_logsSlice.ts b/frontend/src/stores/access_logs/access_logsSlice.ts index 71c7172..eec2902 100644 --- a/frontend/src/stores/access_logs/access_logsSlice.ts +++ b/frontend/src/stores/access_logs/access_logsSlice.ts @@ -2,8 +2,8 @@ * Access Logs Redux Slice */ -import { createEntitySlice } from '../createEntitySlice'; import type { AccessLog } from '../../types/entities'; +import { createEntitySlice } from '../createEntitySlice'; const { slice, actions, reducer } = createEntitySlice({ name: 'access_logs', diff --git a/frontend/src/stores/asset_variants/asset_variantsSlice.ts b/frontend/src/stores/asset_variants/asset_variantsSlice.ts index 2c5cb38..74e0dc0 100644 --- a/frontend/src/stores/asset_variants/asset_variantsSlice.ts +++ b/frontend/src/stores/asset_variants/asset_variantsSlice.ts @@ -2,8 +2,8 @@ * Asset Variants Redux Slice */ -import { createEntitySlice } from '../createEntitySlice'; import type { AssetVariant } from '../../types/entities'; +import { createEntitySlice } from '../createEntitySlice'; const { slice, actions, reducer } = createEntitySlice({ name: 'asset_variants', diff --git a/frontend/src/stores/assets/assetsSlice.ts b/frontend/src/stores/assets/assetsSlice.ts index dfdfaab..a227e55 100644 --- a/frontend/src/stores/assets/assetsSlice.ts +++ b/frontend/src/stores/assets/assetsSlice.ts @@ -2,8 +2,8 @@ * Assets Redux Slice */ -import { createEntitySlice } from '../createEntitySlice'; import type { Asset } from '../../types/entities'; +import { createEntitySlice } from '../createEntitySlice'; const { slice, actions, reducer } = createEntitySlice({ name: 'assets', diff --git a/frontend/src/stores/authSlice.ts b/frontend/src/stores/authSlice.ts index 86860d6..b3db771 100644 --- a/frontend/src/stores/authSlice.ts +++ b/frontend/src/stores/authSlice.ts @@ -1,7 +1,7 @@ -import { createSlice, createAsyncThunk, createAction } from '@reduxjs/toolkit'; +import { createAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import axios from 'axios'; -import type { AuthState } from '../types/redux'; import { decodeAuthToken } from '../lib/authToken'; +import type { AuthState } from '../types/redux'; const initialState: AuthState = { isFetching: false, @@ -24,7 +24,7 @@ export const loginUser = createAsyncThunk( const response = await axios.post('auth/signin/local', creds); return response.data; } catch (error) { - if (!error.response) { + if (!axios.isAxiosError(error) || !error.response) { throw error; } return rejectWithValue(error.response.data); @@ -44,7 +44,7 @@ export const passwordReset = createAsyncThunk( return response.data; } catch (error) { - if (!error.response) { + if (!axios.isAxiosError(error) || !error.response) { throw error; } @@ -113,7 +113,7 @@ export const authSlice = createSlice({ state.notify.textNotification = 'Password has been reset successfully'; }); - builder.addCase(resetAction, (state) => initialState); + builder.addCase(resetAction, (_state) => initialState); builder.addCase(passwordReset.rejected, (state) => { state.errorMessage = 'Something was wrong. Try again'; diff --git a/frontend/src/stores/constructor/constructorSlice.ts b/frontend/src/stores/constructor/constructorSlice.ts index b13e358..eb65417 100644 --- a/frontend/src/stores/constructor/constructorSlice.ts +++ b/frontend/src/stores/constructor/constructorSlice.ts @@ -11,9 +11,9 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import type { + ConstructorState, EditorMenuItem, EditorTab, - ConstructorState, } from '../../types/constructor'; // ============================================================================ diff --git a/frontend/src/stores/createEntitySlice.ts b/frontend/src/stores/createEntitySlice.ts index 6bfd83e..9cf800e 100644 --- a/frontend/src/stores/createEntitySlice.ts +++ b/frontend/src/stores/createEntitySlice.ts @@ -2,21 +2,16 @@ * Entity Slice Factory */ -import { - createSlice, - createAsyncThunk, - PayloadAction, - AsyncThunk, -} from '@reduxjs/toolkit'; +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; import axios, { AxiosError } from 'axios'; import { fulfilledNotify, rejectNotify, resetNotify, } from '../helpers/notifyStateHandler'; -import type { EntitySliceConfig, EntitySliceState } from '../types/redux'; -import type { PaginatedResponse, FetchParams, ApiError } from '../types/api'; +import type { ApiError, FetchParams, PaginatedResponse } from '../types/api'; import type { BaseEntity } from '../types/entities'; +import type { EntitySliceConfig, EntitySliceState } from '../types/redux'; // Use the standard EntitySliceState type to ensure type compatibility // with components that expect EntitySliceState diff --git a/frontend/src/stores/global_transition_defaults/globalTransitionDefaultsSlice.ts b/frontend/src/stores/global_transition_defaults/globalTransitionDefaultsSlice.ts index 62996dd..20f06d8 100644 --- a/frontend/src/stores/global_transition_defaults/globalTransitionDefaultsSlice.ts +++ b/frontend/src/stores/global_transition_defaults/globalTransitionDefaultsSlice.ts @@ -4,7 +4,7 @@ * Custom slice for singleton entity - always returns a single object, not an array. */ -import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; import axios, { AxiosError } from 'axios'; import { fulfilledNotify, diff --git a/frontend/src/stores/global_ui_control_defaults/globalUiControlDefaultsSlice.ts b/frontend/src/stores/global_ui_control_defaults/globalUiControlDefaultsSlice.ts index 2a3db78..e0fc874 100644 --- a/frontend/src/stores/global_ui_control_defaults/globalUiControlDefaultsSlice.ts +++ b/frontend/src/stores/global_ui_control_defaults/globalUiControlDefaultsSlice.ts @@ -1,4 +1,4 @@ -import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; import axios, { AxiosError } from 'axios'; import { fulfilledNotify, diff --git a/frontend/src/stores/hooks.ts b/frontend/src/stores/hooks.ts index beb95c0..78e4e96 100644 --- a/frontend/src/stores/hooks.ts +++ b/frontend/src/stores/hooks.ts @@ -1,5 +1,5 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; -import type { RootState, AppDispatch } from './store'; +import type { AppDispatch, RootState } from './store'; // Use throughout your app instead of plain `useDispatch` and `useSelector` export const useAppDispatch: () => AppDispatch = useDispatch; diff --git a/frontend/src/stores/presigned_url_requests/presigned_url_requestsSlice.ts b/frontend/src/stores/presigned_url_requests/presigned_url_requestsSlice.ts index c8416fa..ffa2d38 100644 --- a/frontend/src/stores/presigned_url_requests/presigned_url_requestsSlice.ts +++ b/frontend/src/stores/presigned_url_requests/presigned_url_requestsSlice.ts @@ -2,8 +2,8 @@ * Presigned URL Requests Redux Slice */ -import { createEntitySlice } from '../createEntitySlice'; import type { PresignedUrlRequest } from '../../types/entities'; +import { createEntitySlice } from '../createEntitySlice'; const { slice, actions, reducer } = createEntitySlice({ name: 'presigned_url_requests', diff --git a/frontend/src/stores/project_audio_tracks/project_audio_tracksSlice.ts b/frontend/src/stores/project_audio_tracks/project_audio_tracksSlice.ts index 00344fd..17a8fce 100644 --- a/frontend/src/stores/project_audio_tracks/project_audio_tracksSlice.ts +++ b/frontend/src/stores/project_audio_tracks/project_audio_tracksSlice.ts @@ -2,8 +2,8 @@ * Project Audio Tracks Redux Slice */ -import { createEntitySlice } from '../createEntitySlice'; import type { ProjectAudioTrack } from '../../types/entities'; +import { createEntitySlice } from '../createEntitySlice'; const { slice, actions, reducer } = createEntitySlice({ name: 'project_audio_tracks', diff --git a/frontend/src/stores/project_memberships/project_membershipsSlice.ts b/frontend/src/stores/project_memberships/project_membershipsSlice.ts index f4add8a..e8e0ccb 100644 --- a/frontend/src/stores/project_memberships/project_membershipsSlice.ts +++ b/frontend/src/stores/project_memberships/project_membershipsSlice.ts @@ -2,8 +2,8 @@ * Project Memberships Redux Slice */ -import { createEntitySlice } from '../createEntitySlice'; import type { ProjectMembership } from '../../types/entities'; +import { createEntitySlice } from '../createEntitySlice'; const { slice, actions, reducer } = createEntitySlice({ name: 'project_memberships', diff --git a/frontend/src/stores/project_transition_settings/projectTransitionSettingsSlice.ts b/frontend/src/stores/project_transition_settings/projectTransitionSettingsSlice.ts index 11708f5..10a50d9 100644 --- a/frontend/src/stores/project_transition_settings/projectTransitionSettingsSlice.ts +++ b/frontend/src/stores/project_transition_settings/projectTransitionSettingsSlice.ts @@ -5,7 +5,7 @@ * Provides actions for fetching/upserting by project + environment. */ -import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; import axios, { AxiosError } from 'axios'; import { fulfilledNotify, diff --git a/frontend/src/stores/project_ui_control_settings/projectUiControlSettingsSlice.ts b/frontend/src/stores/project_ui_control_settings/projectUiControlSettingsSlice.ts index 0ce816f..02bf456 100644 --- a/frontend/src/stores/project_ui_control_settings/projectUiControlSettingsSlice.ts +++ b/frontend/src/stores/project_ui_control_settings/projectUiControlSettingsSlice.ts @@ -1,4 +1,4 @@ -import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; import axios, { AxiosError } from 'axios'; import { fulfilledNotify, diff --git a/frontend/src/stores/projects/projectsSlice.ts b/frontend/src/stores/projects/projectsSlice.ts index 0d29ba8..1cfd457 100644 --- a/frontend/src/stores/projects/projectsSlice.ts +++ b/frontend/src/stores/projects/projectsSlice.ts @@ -2,8 +2,8 @@ * Projects Redux Slice */ -import { createEntitySlice } from '../createEntitySlice'; import type { Project } from '../../types/entities'; +import { createEntitySlice } from '../createEntitySlice'; const { slice, actions, reducer } = createEntitySlice({ name: 'projects', diff --git a/frontend/src/stores/publish_events/publish_eventsSlice.ts b/frontend/src/stores/publish_events/publish_eventsSlice.ts index dfc7358..58e2354 100644 --- a/frontend/src/stores/publish_events/publish_eventsSlice.ts +++ b/frontend/src/stores/publish_events/publish_eventsSlice.ts @@ -2,8 +2,8 @@ * Publish Events Redux Slice */ -import { createEntitySlice } from '../createEntitySlice'; import type { PublishEvent } from '../../types/entities'; +import { createEntitySlice } from '../createEntitySlice'; const { slice, actions, reducer } = createEntitySlice({ name: 'publish_events', diff --git a/frontend/src/stores/pwa_caches/pwa_cachesSlice.ts b/frontend/src/stores/pwa_caches/pwa_cachesSlice.ts index 7606777..4345e69 100644 --- a/frontend/src/stores/pwa_caches/pwa_cachesSlice.ts +++ b/frontend/src/stores/pwa_caches/pwa_cachesSlice.ts @@ -2,8 +2,8 @@ * PWA Caches Redux Slice */ -import { createEntitySlice } from '../createEntitySlice'; import type { PwaCache } from '../../types/entities'; +import { createEntitySlice } from '../createEntitySlice'; const { slice, actions, reducer } = createEntitySlice({ name: 'pwa_caches', diff --git a/frontend/src/stores/roles/rolesSlice.ts b/frontend/src/stores/roles/rolesSlice.ts index 320df8f..b9b3d6a 100644 --- a/frontend/src/stores/roles/rolesSlice.ts +++ b/frontend/src/stores/roles/rolesSlice.ts @@ -1,5 +1,5 @@ -import { createEntitySlice } from '../createEntitySlice'; import type { Role } from '../../types/entities'; +import { createEntitySlice } from '../createEntitySlice'; // Create base entity slice with standard CRUD operations const { slice, actions, reducer } = createEntitySlice({ diff --git a/frontend/src/stores/selectors.ts b/frontend/src/stores/selectors.ts index 33e9952..c03c061 100644 --- a/frontend/src/stores/selectors.ts +++ b/frontend/src/stores/selectors.ts @@ -53,7 +53,9 @@ export const selectUserPermissions = createSelector( * const { selectData, selectCount, selectLoading } = createEntitySelectors('assets'); * const assets = useAppSelector(selectData); */ -export function createEntitySelectors(sliceName: keyof RootState) { +export function createEntitySelectors( + sliceName: keyof RootState, +) { type EntityState = { data: T[]; loading: boolean; @@ -75,9 +77,7 @@ export function createEntitySelectors(sliceName: keyof RootState) { const selectFirstItem = createSelector([selectData], (data) => data[0]); const selectById = (id: string) => - createSelector([selectData], (data) => - data.find((item: T & { id: string }) => item.id === id), - ); + createSelector([selectData], (data) => data.find((item) => item.id === id)); return { selectSlice, @@ -94,7 +94,7 @@ export function createEntitySelectors(sliceName: keyof RootState) { // Pre-built Entity Selectors // ============================================================================ -import type { Asset, TourPage, Project, Role, User } from '../types/entities'; +import type { Asset, Project, Role, TourPage, User } from '../types/entities'; /** Asset selectors */ export const assetSelectors = createEntitySelectors('assets'); diff --git a/frontend/src/stores/store.ts b/frontend/src/stores/store.ts index 1ad0e2e..bad164c 100644 --- a/frontend/src/stores/store.ts +++ b/frontend/src/stores/store.ts @@ -1,25 +1,25 @@ import { configureStore } from '@reduxjs/toolkit'; -import styleReducer from './styleSlice'; -import mainReducer from './mainSlice'; import authSlice from './authSlice'; import constructorReducer from './constructor/constructorSlice'; +import mainReducer from './mainSlice'; +import styleReducer from './styleSlice'; -import usersSlice from './users/usersSlice'; -import rolesSlice from './roles/rolesSlice'; -import projectsSlice from './projects/projectsSlice'; -import project_membershipsSlice from './project_memberships/project_membershipsSlice'; -import assetsSlice from './assets/assetsSlice'; +import access_logsSlice from './access_logs/access_logsSlice'; import asset_variantsSlice from './asset_variants/asset_variantsSlice'; +import assetsSlice from './assets/assetsSlice'; +import globalTransitionDefaultsSlice from './global_transition_defaults/globalTransitionDefaultsSlice'; +import globalUiControlDefaultsSlice from './global_ui_control_defaults/globalUiControlDefaultsSlice'; import presigned_url_requestsSlice from './presigned_url_requests/presigned_url_requestsSlice'; -import tour_pagesSlice from './tour_pages/tour_pagesSlice'; import project_audio_tracksSlice from './project_audio_tracks/project_audio_tracksSlice'; +import project_membershipsSlice from './project_memberships/project_membershipsSlice'; +import projectTransitionSettingsSlice from './project_transition_settings/projectTransitionSettingsSlice'; +import projectUiControlSettingsSlice from './project_ui_control_settings/projectUiControlSettingsSlice'; +import projectsSlice from './projects/projectsSlice'; import publish_eventsSlice from './publish_events/publish_eventsSlice'; import pwa_cachesSlice from './pwa_caches/pwa_cachesSlice'; -import access_logsSlice from './access_logs/access_logsSlice'; -import globalTransitionDefaultsSlice from './global_transition_defaults/globalTransitionDefaultsSlice'; -import projectTransitionSettingsSlice from './project_transition_settings/projectTransitionSettingsSlice'; -import globalUiControlDefaultsSlice from './global_ui_control_defaults/globalUiControlDefaultsSlice'; -import projectUiControlSettingsSlice from './project_ui_control_settings/projectUiControlSettingsSlice'; +import rolesSlice from './roles/rolesSlice'; +import tour_pagesSlice from './tour_pages/tour_pagesSlice'; +import usersSlice from './users/usersSlice'; export const store = configureStore({ reducer: { diff --git a/frontend/src/stores/styleSlice.ts b/frontend/src/stores/styleSlice.ts index c773d86..1579b19 100644 --- a/frontend/src/stores/styleSlice.ts +++ b/frontend/src/stores/styleSlice.ts @@ -1,6 +1,6 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import * as styles from '../styles'; import { localStorageDarkModeKey, localStorageStyleKey } from '../config'; +import * as styles from '../styles'; import { StyleKey } from '../types/ui'; interface StyleState { @@ -95,7 +95,8 @@ export const styleSlice = createSlice({ const style = styles[action.payload]; for (const key in style) { - state[`${key}Style`] = style[key]; + const stateKey = `${key}Style` as keyof typeof state; + state[stateKey] = style[key as keyof typeof style] as never; } }, }, diff --git a/frontend/src/stores/tour_pages/tour_pagesSlice.ts b/frontend/src/stores/tour_pages/tour_pagesSlice.ts index ccc6eba..6bb899d 100644 --- a/frontend/src/stores/tour_pages/tour_pagesSlice.ts +++ b/frontend/src/stores/tour_pages/tour_pagesSlice.ts @@ -2,8 +2,8 @@ * Tour Pages Redux Slice */ -import { createEntitySlice } from '../createEntitySlice'; import type { TourPage } from '../../types/entities'; +import { createEntitySlice } from '../createEntitySlice'; const { slice, actions, reducer } = createEntitySlice({ name: 'tour_pages', diff --git a/frontend/src/stores/users/usersSlice.ts b/frontend/src/stores/users/usersSlice.ts index 0f84943..6396358 100644 --- a/frontend/src/stores/users/usersSlice.ts +++ b/frontend/src/stores/users/usersSlice.ts @@ -2,8 +2,8 @@ * Users Redux Slice */ -import { createEntitySlice } from '../createEntitySlice'; import type { User } from '../../types/entities'; +import { createEntitySlice } from '../createEntitySlice'; const { slice, actions, reducer } = createEntitySlice({ name: 'users', diff --git a/frontend/src/sw.ts b/frontend/src/sw.ts index 675a5bf..6fa365d 100644 --- a/frontend/src/sw.ts +++ b/frontend/src/sw.ts @@ -11,13 +11,7 @@ import { defaultCache } from '@serwist/next/worker'; import type { PrecacheEntry, SerwistGlobalConfig } from 'serwist'; -import { - CacheFirst, - NetworkFirst, - NetworkOnly, - Serwist, - StaleWhileRevalidate, -} from 'serwist'; +import { CacheFirst, NetworkFirst, NetworkOnly, Serwist } from 'serwist'; import { OFFLINE_CONFIG } from './config/offline.config'; // Service Worker type declarations diff --git a/frontend/src/types/constructor.ts b/frontend/src/types/constructor.ts index 0b62c78..20ae0ff 100644 --- a/frontend/src/types/constructor.ts +++ b/frontend/src/types/constructor.ts @@ -5,10 +5,10 @@ * Used in constructor.tsx, page_elements pages, and element-type-defaults pages. */ -import type { ElementStyleProperties } from '../lib/elementStyles'; import type { ElementEffectProperties } from '../lib/elementEffects'; +import type { ElementStyleProperties } from '../lib/elementStyles'; import type { InfoPanelSectionInstance } from './infoPanel'; -import type { TransitionType, EasingFunction } from './transition'; +import type { EasingFunction, TransitionType } from './transition'; /** * Element types available in the constructor canvas. @@ -516,12 +516,12 @@ export interface EditorElementProps { * Background settings props */ export interface EditorBackgroundProps { - backgroundImageUrl: string; - backgroundVideoUrl: string; - backgroundAudioUrl: string; - onBackgroundImageChange: (value: string) => void; - onBackgroundVideoChange: (value: string) => void; - onBackgroundAudioChange: (value: string) => void; + backgroundImageUrl: string | null; + backgroundVideoUrl: string | null; + backgroundAudioUrl: string | null; + onBackgroundImageChange: (value: string | null) => void; + onBackgroundVideoChange: (value: string | null) => void; + onBackgroundAudioChange: (value: string | null) => void; } /** @@ -530,8 +530,8 @@ export interface EditorBackgroundProps { export interface EditorDurationNotesProps { backgroundVideoDurationNote: string; backgroundAudioDurationNote: string; - selectedMediaDurationNote: string; - selectedTransitionDurationNote: string; + selectedMediaDurationNote: string | null; + selectedTransitionDurationNote: string | null; } /** @@ -557,7 +557,7 @@ export interface EditorNavigationProps { slug?: string; sort_order?: number; }>; - activePageId: string; + activePageId: string | null; normalizeNavigationType: ( element: CanvasElement, nextType: 'navigation_next' | 'navigation_prev', @@ -589,5 +589,5 @@ export interface EditorCollectionOpsProps { * Media utilities props */ export interface EditorMediaUtilsProps { - getDuration: (url: string) => number | undefined; + getDuration: (url: string | null) => number | null | undefined; } diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 9ef8e4f..454b365 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -2,19 +2,19 @@ * Types Index - Central export for all TypeScript types */ -export * from './entities'; export * from './api'; -export * from './redux'; -export * from './forms'; -export * from './filters'; -export * from './permissions'; -export * from './offline'; -export * from './preload'; -export * from './runtime'; -export * from './constructor'; -export * from './presentation'; -export * from './menu'; -export * from './ui'; -export * from './components'; export * from './charts'; +export * from './components'; +export * from './constructor'; +export * from './entities'; +export * from './filters'; +export * from './forms'; +export * from './menu'; +export * from './offline'; +export * from './permissions'; +export * from './preload'; +export * from './presentation'; +export * from './redux'; +export * from './runtime'; export * from './transition'; +export * from './ui'; diff --git a/frontend/src/types/redux.ts b/frontend/src/types/redux.ts index da51b0f..37d5175 100644 --- a/frontend/src/types/redux.ts +++ b/frontend/src/types/redux.ts @@ -2,6 +2,10 @@ * Redux Types */ +import type { AsyncThunk } from '@reduxjs/toolkit'; +import type { ApiError, FetchParams, PaginatedResponse } from './api'; +import type { BaseEntity } from './entities'; + // Notification state structure export interface NotificationState { showNotification: boolean; @@ -27,6 +31,36 @@ export interface EntitySliceConfig { singularName?: string; // For notification messages (e.g., "User" instead of "Users") } +export type EntityFetchThunk = AsyncThunk< + T | PaginatedResponse, + FetchParams, + { rejectValue: ApiError } +>; + +export type EntityUpdateThunk = AsyncThunk< + T, + { id: string; data: Partial }, + { rejectValue: ApiError } +>; + +export type EntityDeleteThunk = AsyncThunk< + void, + string, + { rejectValue: ApiError } +>; + +export type EntityDeleteByIdsThunk = AsyncThunk< + void, + string[], + { rejectValue: ApiError } +>; + +export type EntityUploadCsvThunk = AsyncThunk< + { imported: number }, + File, + { rejectValue: ApiError } +>; + // Auth state structure export interface AuthState { currentUser: import('./entities').User | null; diff --git a/frontend/src/types/runtime.ts b/frontend/src/types/runtime.ts index e2da2d9..e4df0b5 100644 --- a/frontend/src/types/runtime.ts +++ b/frontend/src/types/runtime.ts @@ -5,7 +5,7 @@ * These extend the minimal preload types with additional runtime-specific fields. */ -import type { PreloadPage, PreloadPageLink, PreloadElement } from './preload'; +import type { PreloadElement, PreloadPage, PreloadPageLink } from './preload'; import type { UiControlsSettings } from './uiControls'; /** diff --git a/frontend/tests/e2e/app-shell.spec.ts b/frontend/tests/e2e/app-shell.spec.ts index a511069..6832714 100644 --- a/frontend/tests/e2e/app-shell.spec.ts +++ b/frontend/tests/e2e/app-shell.spec.ts @@ -18,7 +18,9 @@ test('authenticated app shell loads dashboard and projects list', async ({ await page.goto('/dashboard'); await expect(page.getByRole('heading', { name: 'Overview' })).toBeVisible(); - await expect(page.getByRole('link', { name: /Projects/ }).first()).toBeVisible(); + await expect( + page.getByRole('link', { name: /Projects/ }).first(), + ).toBeVisible(); await page.goto('/projects/projects-list'); await expect(page.getByRole('heading', { name: 'Projects' })).toBeVisible(); diff --git a/frontend/tests/e2e/auth.spec.ts b/frontend/tests/e2e/auth.spec.ts index db51107..9ff138e 100644 --- a/frontend/tests/e2e/auth.spec.ts +++ b/frontend/tests/e2e/auth.spec.ts @@ -1,9 +1,5 @@ import { expect, test } from '@playwright/test'; -import { - collectConsoleFailures, - mockFrontendApi, - testUser, -} from './fixtures'; +import { collectConsoleFailures, mockFrontendApi, testUser } from './fixtures'; test.beforeEach(async ({ page }) => { await mockFrontendApi(page); @@ -15,7 +11,9 @@ test('protected pages redirect anonymous users to login', async ({ page }) => { await page.goto('/dashboard'); await expect(page).toHaveURL(/\/login/); - await expect(page.getByRole('heading', { name: 'Shimahara Visual' })).toBeVisible(); + await expect( + page.getByRole('heading', { name: 'Shimahara Visual' }), + ).toBeVisible(); await expect(page.locator('input[name="email"]')).toBeVisible(); await expect(page.locator('input[name="password"]')).toBeVisible(); consoleFailures.assertClean(); @@ -31,6 +29,8 @@ test('local login reaches authenticated dashboard shell', async ({ page }) => { await expect(page).toHaveURL(/\/dashboard/); await expect(page.getByRole('heading', { name: 'Overview' })).toBeVisible(); - await expect(page.getByRole('link', { name: /Projects/ }).first()).toBeVisible(); + await expect( + page.getByRole('link', { name: /Projects/ }).first(), + ).toBeVisible(); consoleFailures.assertClean(); }); diff --git a/frontend/tests/e2e/constructor.spec.ts b/frontend/tests/e2e/constructor.spec.ts index 67b23bd..1e18416 100644 --- a/frontend/tests/e2e/constructor.spec.ts +++ b/frontend/tests/e2e/constructor.spec.ts @@ -21,16 +21,26 @@ test('constructor loads project canvas, assets, and core controls', async ({ await expect(page).toHaveTitle(/Constructor/); await expect(page.getByText(testProject.name)).toBeVisible(); - await expect(page.getByRole('combobox', { name: 'Select constructor page' })).toBeVisible(); + await expect( + page.getByRole('combobox', { name: 'Select constructor page' }), + ).toBeVisible(); await expect(page.getByRole('button', { name: 'Edit mode' })).toBeVisible(); - await expect(page.getByRole('button', { name: 'Interact mode' })).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Interact mode' }), + ).toBeVisible(); await expect(page.getByRole('button', { name: 'Create page' })).toBeVisible(); - await expect(page.getByRole('button', { name: 'Background actions' })).toBeVisible(); - await expect(page.getByRole('button', { name: 'Element actions' })).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Background actions' }), + ).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Element actions' }), + ).toBeVisible(); await expect(page.getByRole('button', { name: /Save/ })).toBeVisible(); await expect(page.getByRole('img', { name: 'Background' })).toBeVisible(); await expect(page.getByRole('button', { name: 'Next' })).toBeVisible(); - await expect(page.getByRole('button', { name: 'Info' }).first()).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Info' }).first(), + ).toBeVisible(); const cacheKeys = await page.evaluate(async () => 'caches' in window ? caches.keys() : [], diff --git a/frontend/tests/e2e/fixtures.ts b/frontend/tests/e2e/fixtures.ts index 3a66d6d..bee4eb7 100644 --- a/frontend/tests/e2e/fixtures.ts +++ b/frontend/tests/e2e/fixtures.ts @@ -147,9 +147,7 @@ export const testAssets = [ function createUnsignedJwt(payload: Record) { const encode = (value: unknown) => - Buffer.from(JSON.stringify(value)) - .toString('base64url') - .replace(/=/g, ''); + Buffer.from(JSON.stringify(value)).toString('base64url').replace(/=/g, ''); return `${encode({ alg: 'none', typ: 'JWT' })}.${encode({ ...payload, @@ -216,7 +214,9 @@ export async function mockFrontendApi(page: Page) { } if (path === '/projects/autocomplete') { - return fulfillJson(route, [{ id: TEST_PROJECT_ID, label: testProject.name }]); + return fulfillJson(route, [ + { id: TEST_PROJECT_ID, label: testProject.name }, + ]); } if (path === '/projects') { @@ -235,7 +235,10 @@ export async function mockFrontendApi(page: Page) { request.headers()['x-runtime-environment'] === 'stage' ? 'stage' : 'production'; - return fulfillJson(route, rowsResponse(testRuntimePages(runtimeEnvironment))); + return fulfillJson( + route, + rowsResponse(testRuntimePages(runtimeEnvironment)), + ); } if (path === '/assets') { @@ -314,12 +317,12 @@ export async function mockFrontendApi(page: Page) { } if (path === '/file/presign') { - const urls = (( - request.postDataJSON() as { urls?: string[] } | null - )?.urls ?? []) as string[]; + const urls = ((request.postDataJSON() as { urls?: string[] } | null) + ?.urls ?? []) as string[]; const presignedUrls = urls.reduce>( (result, assetUrl) => { - result[assetUrl] = `/api/file/download?privateUrl=${encodeURIComponent(assetUrl)}`; + result[assetUrl] = + `/api/file/download?privateUrl=${encodeURIComponent(assetUrl)}`; return result; }, {}, diff --git a/frontend/tests/e2e/permissions.spec.ts b/frontend/tests/e2e/permissions.spec.ts index df38176..39d3b05 100644 --- a/frontend/tests/e2e/permissions.spec.ts +++ b/frontend/tests/e2e/permissions.spec.ts @@ -16,7 +16,9 @@ test('permissions list uses query-backed table filtering', async ({ page }) => { await page.goto('/permissions/permissions-list'); - await expect(page.getByRole('heading', { name: 'Permissions' })).toBeVisible(); + await expect( + page.getByRole('heading', { name: 'Permissions' }), + ).toBeVisible(); await expect(page.getByRole('link', { name: 'New Item' })).toBeVisible(); await expect(page.getByText(testPermissions[0].name)).toBeVisible(); @@ -48,7 +50,9 @@ test('permissions edit page submits through TanStack Query mutation', async ({ await page.goto(`/permissions/permissions-edit/?id=${permission.id}`); - await expect(page.getByRole('heading', { name: 'Edit permissions' })).toBeVisible(); + await expect( + page.getByRole('heading', { name: 'Edit permissions' }), + ).toBeVisible(); await expect(page.locator('input[name="name"]')).toHaveValue(permission.name); const updateRequest = page.waitForRequest((request) => { diff --git a/frontend/tests/e2e/runtime.spec.ts b/frontend/tests/e2e/runtime.spec.ts index 91ae53c..6c35cfe 100644 --- a/frontend/tests/e2e/runtime.spec.ts +++ b/frontend/tests/e2e/runtime.spec.ts @@ -10,7 +10,9 @@ test.beforeEach(async ({ page }) => { await mockFrontendApi(page); }); -test('production presentation loads without authentication', async ({ page }) => { +test('production presentation loads without authentication', async ({ + page, +}) => { const consoleFailures = collectConsoleFailures(page); await page.goto(`/p/${TEST_PROJECT_SLUG}`); @@ -18,9 +20,15 @@ test('production presentation loads without authentication', async ({ page }) => await expect(page).toHaveTitle(/Playwright Project/); await expect(page.getByRole('img', { name: 'Background' })).toBeVisible(); await expect(page.getByText('Next')).toBeVisible(); - await expect(page.getByRole('button', { name: 'Info' }).first()).toBeVisible(); - await expect(page.getByRole('button', { name: 'Download for offline' })).toBeVisible(); - await expect(page.getByRole('button', { name: 'Enter fullscreen' })).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Info' }).first(), + ).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Download for offline' }), + ).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Enter fullscreen' }), + ).toBeVisible(); consoleFailures.assertClean(); }); @@ -36,7 +44,9 @@ test('stage presentation uses authenticated minimal runtime shell', async ({ await expect(page).toHaveTitle(/Playwright Project/); await expect(page.getByRole('img', { name: 'Background' })).toBeVisible(); await expect(page.getByText('Next')).toBeVisible(); - await expect(page.getByRole('button', { name: 'Info' }).first()).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Info' }).first(), + ).toBeVisible(); await expect(page.getByRole('navigation')).toHaveCount(0); consoleFailures.assertClean(); diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index f612a2c..acdab01 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -8,7 +8,7 @@ ], "allowJs": true, "skipLibCheck": true, - "strict": false, + "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true,