Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';
import prettier from 'eslint-config-prettier';

export default tseslint.config(
{ ignores: ['dist', 'build', 'android', 'ios', 'node_modules', 'playwright-report', 'test-results'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2022,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
},
},
{
// Playwright resolves fixtures by exact destructured name, so dependency
// fixtures must be declared even when their value is unused.
files: ['tests/**/*.{ts,tsx}'],
rules: {
'@typescript-eslint/no-unused-vars': ['error', { args: 'none' }],
},
},
prettier
);
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"dev": "vite",
"dev:local": "vite --config vite.config.local.mts",
"check-type": "tsc --noEmit",
"lint": "eslint .",
"build": "tsc && NODE_OPTIONS=--max-old-space-size=3072 vite build",
"build-app": "tsc && vite build --mode app",
"build-single": "vite build --mode single",
Expand Down Expand Up @@ -74,16 +75,23 @@
},
"devDependencies": {
"@capacitor/cli": "^8.2.0",
"@eslint/js": "^10.0.1",
"@playwright/test": "^1.55.1",
"@types/jest": "^29.5.3",
"@types/node": "^22.7.5",
"@types/react": "^18.2.18",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^10.9.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.6",
"globals": "^17.12.0",
"knip": "^5.33.2",
"mariadb": "^3.5.3",
"prettier": "^3.0.0",
"typescript": "^5.6.3",
"typescript-eslint": "^8.69.0",
"vite": "^5.4.20",
"vite-plugin-svgr": "^4.2.0"
},
Expand Down
2 changes: 1 addition & 1 deletion src/components/AppIcon/AppIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ import {
LiaTshirtSolid,
} from 'react-icons/lia';
import { MdOutlineLockReset } from 'react-icons/md';
import { LuSigma, LuMails } from 'react-icons/lu';
import { LuSigma } from 'react-icons/lu';
import { PiCookingPotLight, PiShareNetwork } from 'react-icons/pi';
import CircleIcon from './circle.svg?react';
import VotingIcon from './voting.svg?react';
Expand Down
3 changes: 1 addition & 2 deletions src/components/Buttons/AddCategories/AddCategoriesButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
ListItemButton,
ListItemText,
Skeleton,
Typography,
} from '@mui/material';
import { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from 'react';
import { useTranslation } from 'react-i18next';
Expand All @@ -35,7 +34,7 @@ const AddCategoryButton = forwardRef<AddCategoryRefProps, Props>(

const [open, setOpen] = useState(false);
const [isLoading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [, setError] = useState<string | null>(null);
const [categories, setCategories] = useState<CategoryType[]>([]);
const [selectedCategory, setSelectedCategory] = useState<CategoryType>();
const [originalCategory, setOriginalCategory] = useState<CategoryType>();
Expand Down
5 changes: 2 additions & 3 deletions src/components/Buttons/AddIdeas/AddIdeasButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import {
ListItemButton,
ListItemText,
Skeleton,
Typography,
} from '@mui/material';
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
import { forwardRef, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';

Expand All @@ -29,7 +28,7 @@ const AddIdeasButton = forwardRef<HTMLButtonElement, Props>(({ ideas = [], onClo

const [open, setOpen] = useState(false);
const [isLoading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [, setError] = useState<string | null>(null);
const [roomIdeas, setIdeas] = useState<IdeaType[]>([]);
const [selectedIdeas, setSelectedIdeas] = useState<IdeaType[]>(ideas);

Expand Down
3 changes: 1 addition & 2 deletions src/components/Buttons/AddRooms/AddRoomsButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
ListItemButton,
ListItemText,
Skeleton,
Typography,
} from '@mui/material';
import { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from 'react';
import { useTranslation } from 'react-i18next';
Expand All @@ -36,7 +35,7 @@ const AddRoomButton = forwardRef<AddRoomRefProps, Props>(({ users = [], disabled
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [isLoading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [, setError] = useState<string | null>(null);
const [rooms, setRooms] = useState<RoomType[]>([]);
const [selectedRooms, setSelectedRooms] = useState<string[]>([]);
const [indeterminateRooms, setIndeterminateRooms] = useState<string[]>([]);
Expand Down
2 changes: 1 addition & 1 deletion src/components/Buttons/CodeButton/CodeButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import AppIcon from '@/components/AppIcon';
import { getRuntimeConfig } from '@/config';
import { useAppStore } from '@/store';
import { announceToScreenReader, localStorageGet } from '@/utils';
import { Button, ButtonProps, Divider } from '@mui/material';
import { Button, ButtonProps } from '@mui/material';
import { FC } from 'react';
import { useTranslation } from 'react-i18next';

Expand Down
3 changes: 1 addition & 2 deletions src/components/Buttons/DeleteButton/DeleteButton.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { ConfirmDialog } from '@/components';
import AppIconButton from '@/components/AppIconButton';
import { SettingNamesType } from '@/types/SettingsTypes';
import { WarningAmber } from '@mui/icons-material';
import { IconButtonProps, Stack, Typography } from '@mui/material';
import { forwardRef, useRef, useState } from 'react';
import { forwardRef, useState } from 'react';
import { useTranslation } from 'react-i18next';

interface Props extends IconButtonProps {
Expand Down
4 changes: 2 additions & 2 deletions src/components/Buttons/ShareButton/ShareButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ const ShareButton = forwardRef<HTMLButtonElement, Props>(({ idea, disabled }, re
await navigator.clipboard.writeText(absoluteUrl);
successAlert(t('clipboard.linkCopied'), dispatch);
}
} catch (e) {
} catch {
// fallback to copying even if Web Share API fails mid-way
try {
await navigator.clipboard.writeText(absoluteUrl);
successAlert(t('clipboard.linkCopied'), dispatch);
} catch (_) {
} catch {
/* ignore */
}
}
Expand Down
28 changes: 0 additions & 28 deletions src/components/ChangePassword/ChangePassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { useForm, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import * as yup from 'yup';
import AppIconButton from '../AppIconButton';
import AppIcon from '../AppIcon';
import { useNavigate } from 'react-router-dom';
import { useAppStore } from '@/store';

Expand All @@ -39,10 +38,6 @@ interface Props {
passwordComplexity?: PasswordComplexity;
}

interface ChangePasswordMethods {
displayMessage: (isSuccess: boolean) => void;
}

/**
* Renders User info with Avatar
* @component ChangePassword
Expand Down Expand Up @@ -70,29 +65,6 @@ const ChangePassword: React.FC<Props> = ({
newPassword: false,
});

const createPasswordValidation = () => {
let validation = yup.string().required(t('forms.validation.required'));

validation = validation.min(
passwordComplexity.minLength,
t('forms.validation.minLength', { var: passwordComplexity.minLength })
);

if (passwordComplexity.requireUppercase) {
validation = validation.matches(/[A-Z]/, t('forms.validation.passwordRequireUppercase'));
}

if (passwordComplexity.requireNumber) {
validation = validation.matches(/[0-9]/, t('forms.validation.passwordRequireNumber'));
}

if (passwordComplexity.requireSymbol) {
validation = validation.matches(/[^A-Za-z0-9]/, t('forms.validation.passwordRequireSymbol'));
}

return validation.max(64, t('forms.validation.maxLength', { var: 64 }));
};

const schema = yup
.object({
oldPassword: yup
Expand Down
2 changes: 1 addition & 1 deletion src/components/DataFields/GroupField.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getGroups } from '@/services/groups';
import { SelectOptionsType } from '@/types/SettingsTypes';
import { Autocomplete, BaseTextFieldProps, CircularProgress, TextField } from '@mui/material';
import { Autocomplete, BaseTextFieldProps, TextField } from '@mui/material';
import { useEffect, useState } from 'react';
import { Control, Controller } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
Expand Down
2 changes: 1 addition & 1 deletion src/components/DataFields/MarkdownEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ const MarkdownEditor: React.FC<Props> = ({
if (mdxEditorRef.current) {
try {
mdxEditorRef.current.focus();
} catch (error) {
} catch {
// Fallback: try to find and focus the content area directly
const contentArea = containerRef.current?.querySelector(
'.cm-content, .mdxeditor-root-contenteditable, [contenteditable="true"]'
Expand Down
2 changes: 1 addition & 1 deletion src/components/DataFields/RoomRolesField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const RoomRolesField: React.FC<Props> = ({ user, rooms, defaultLevel, disabled =

const [open, setOpen] = useState(false);
const [isLoading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [, setError] = useState<string | null>(null);
const [schoolRooms, setRooms] = useState<RoomType[]>([]);

const [userRoles, setUserRoles] = useState<{ room: string; role: RoleTypes }[]>(JSON.parse(user?.roles || '[]'));
Expand Down
2 changes: 1 addition & 1 deletion src/components/DataFields/SelectField.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PossibleFields } from '@/types/Scopes';
import { SelectOptionsType } from '@/types/SettingsTypes';
import { FormControl, MenuItem, Paper, TextField } from '@mui/material';
import { FormControl, MenuItem, TextField } from '@mui/material';
import { Control, Controller } from 'react-hook-form-mui';
import { useTranslation } from 'react-i18next';

Expand Down
2 changes: 1 addition & 1 deletion src/components/DataFields/StatusField.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { STATUS, statusOptions } from '@/utils';
import { STATUS } from '@/utils';
import { MenuItem, TextField, Typography } from '@mui/material';
import { amber, blueGrey, green, red } from '@mui/material/colors';
import { Control, Controller } from 'react-hook-form-mui';
Expand Down
2 changes: 1 addition & 1 deletion src/components/DataForms/GroupForms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ const GroupForms: React.FC<GroupFormsProps> = ({ defaultValues, onClose }) => {
const userIds = response.data.map((user) => user.hash_id);
setExistingUsers(userIds);
}
} catch (error) {
} catch {
// Error fetching group users
} finally {
setIsLoading(false);
Expand Down
4 changes: 2 additions & 2 deletions src/components/DataForms/IdeaForms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ const IdeaForms: React.FC<IdeaFormsProps> = ({ defaultValues, onClose }) => {
try {
await setIdeaCategory(response.data.hash_id);
return true;
} catch (error) {
} catch {
setError('root', {
type: 'manual',
message: t('errors.default'),
Expand Down Expand Up @@ -191,7 +191,7 @@ const IdeaForms: React.FC<IdeaFormsProps> = ({ defaultValues, onClose }) => {
await setIdeaBox(defaultValues?.hash_id);
await setIdeaCategory(defaultValues?.hash_id);
return true;
} catch (error) {
} catch {
setError('root', {
type: 'manual',
message: t('errors.default'),
Expand Down
2 changes: 0 additions & 2 deletions src/components/DataForms/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import AnnouncementForms from './AnnouncementForms';
import BoxForms from './BoxForms';
import BugForms from './BugForms';
import CategoryForms from './CategoryForms';
import CommentForms from './CommentForms';
import GroupForms from './GroupForms';
import IdeaForms from './IdeaForms';
import MessageForms from './MessageForms';
import ReportForms from './ReportForms';
Expand Down
6 changes: 0 additions & 6 deletions src/components/DataTable/ToolBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import AppIcon from '@/components/AppIcon';
import { StatusTypes } from '@/types/Generics';
import { PossibleFields } from '@/types/Scopes';
import { SettingNamesType } from '@/types/SettingsTypes';
import { announceToScreenReader, checkPermissions } from '@/utils';
import { WarningAmber } from '@mui/icons-material';
Expand All @@ -17,10 +15,6 @@ import {
import React, { useRef, useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';

interface FilterOptionsType {
status: StatusTypes;
filter: [keyof PossibleFields, string];
}

type Props = {
extraTools?: ({ items }: { items: Array<string> }) => JSX.Element;
Expand Down
3 changes: 1 addition & 2 deletions src/components/DelegateVote/DelegateVote.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useAppStore } from '@/store';
import { localStorageGet, parseJwt } from '@/utils';
import { Button, FilledInput, Slide, Stack, Typography } from '@mui/material';
import { grey } from '@mui/material/colors';
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import { ChangeEvent, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import UserAvatar from '../UserAvatar';
Expand All @@ -29,7 +29,6 @@ const DelegateVote = ({ open, delegate, onClose, triggerRef }: Props) => {
const jwt_token = localStorageGet('token');
const jwt_payload = parseJwt(jwt_token);
const [, dispatch] = useAppStore();
const dialogRef = useRef<HTMLDivElement>(null);

const [users, setUsers] = useState<DelegateType[]>([]);
const [selected, setSelected] = useState<DelegateType>();
Expand Down
1 change: 0 additions & 1 deletion src/components/FilterBar/FilterBar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { TEST_IDS } from '@/test-ids';
import { StatusTypes } from '@/types/Generics';
import { PossibleFields } from '@/types/Scopes';
import { SettingNamesType } from '@/types/SettingsTypes';
Expand Down
2 changes: 1 addition & 1 deletion src/components/Idea/IdeaBubble/IdeaBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import ChatBubble from '@/components/ChatBubble';
import MarkdownReader from '@/components/MarkdownReader';
import MoreOptions from '@/components/MoreOptions';
import { IdeaType } from '@/types/Scopes';
import { checkPermissions, phases } from '@/utils';
import { checkPermissions } from '@/utils';
import { Stack, StackProps, Typography } from '@mui/material';
import { ReactNode, useState } from 'react';
import { useTranslation } from 'react-i18next';
Expand Down
4 changes: 2 additions & 2 deletions src/components/Idea/IdeaCard/IdeaCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ const IdeaCard = ({ idea, phase, sx, quorum, disabled = false, ...restOfProps }:
const { t } = useTranslation();
const theme = useTheme();

const [isLoading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [, setLoading] = useState(true);
const [, setError] = useState<string | null>(null);
const [vote, setVote] = useState<number | null>();

const [icon, setIcon] = useState<CategoryIconType>();
Expand Down
3 changes: 1 addition & 2 deletions src/components/Idea/VotingCard/VotingCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import AppIcon from '@/components/AppIcon';
import DelegateButton from '@/components/Buttons/DelegateButton';
import { getDelegations } from '@/services/users';
import { addVote, getVote } from '@/services/vote';
import { checkPermissions, Vote, votingOptions } from '@/utils';
Expand All @@ -21,7 +20,7 @@ const VotingCard = ({ onChange }: Props) => {
const { idea_id, box_id } = useParams();

const [isLoading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [, setError] = useState<string | null>(null);
const [vote, setVote] = useState<Vote>();
const [hasVoted, setHasVoted] = useState(false);
const [hasDelegate, setHasDelegate] = useState(false);
Expand Down
4 changes: 2 additions & 2 deletions src/components/Idea/VotingResults/VotingResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ interface Props {
const VotingResults: React.FC<Props> = ({ idea, quorum, onReload }) => {
const { t } = useTranslation();

const [isLoading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [, setLoading] = useState(true);
const [, setError] = useState<string | null>(null);
const [vote, setVote] = useState<Vote | null>();

const fetchVote = useCallback(async () => {
Expand Down
4 changes: 0 additions & 4 deletions src/components/ImageEditor/ImageEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,6 @@ const ImageEditor: React.FC<Props> = ({ id, width = 200, height = 200, rounded =
// Clear the canvas
ctx.clearRect(0, 0, width, height);

// Calculate the scaled dimensions
const scaledWidth = img.width * zoom;
const scaledHeight = img.height * zoom;

// Calculate source (crop) coordinates
const sourceX = -crop.x / zoom;
const sourceY = -crop.y / zoom;
Expand Down
2 changes: 1 addition & 1 deletion src/components/LocaleSwitch/LocaleSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
* @component LocaleSwitch
*/
const LocaleSwitch = ({ ...restOfProps }) => {
const { t, i18n } = useTranslation();
const { i18n } = useTranslation();

const handleChange = (event: SelectChangeEvent) => {
event.stopPropagation();
Expand Down
Loading