diff --git a/.eslintrc.js b/.eslintrc.js index 5f09ba5f..4aaa0e49 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -34,6 +34,8 @@ module.exports = { props: true, ignorePropertyModificationsFor: ['accumulator', 'state', 'event'] } - ] + ], + 'no-shadow': 'off', + '@typescript-eslint/no-shadow': 'error' } }; diff --git a/package-lock.json b/package-lock.json index 8bfdfc37..c48f283c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@reduxjs/toolkit": "^1.9.2", "@sentry/browser": "^7.43.0", "@tinymce/tinymce-react": "^4.3.0", - "@zextras/carbonio-design-system": "^1.2.0", + "@zextras/carbonio-design-system": "^2.0.0", "@zextras/carbonio-ui-preview": "^1.1.0", "darkreader": "^4.9.58", "history": "^5.3.0", @@ -91,7 +91,7 @@ }, "peerDependencies": { "@reduxjs/toolkit": "^1 >=1.9", - "@zextras/carbonio-design-system": "^1", + "@zextras/carbonio-design-system": "^2", "@zextras/carbonio-ui-preview": "^1", "core-js": "^3.27.2", "lodash": "^4.17.21", @@ -5658,9 +5658,9 @@ "license": "Apache-2.0" }, "node_modules/@zextras/carbonio-design-system": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@zextras/carbonio-design-system/-/carbonio-design-system-1.2.0.tgz", - "integrity": "sha512-Fq/iDlC0eefyFS0FOUbwdBz86NIl1MN23dhU9qBZFpDfGyDKDVSoIymyOXdRpC00O5AjA/yWhwcFB/uY+JK3fA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@zextras/carbonio-design-system/-/carbonio-design-system-2.0.0.tgz", + "integrity": "sha512-8yW0d+o4PHpehOrgiAaFn2nlHEmx/zv1PBcSWxTT1VsqpFEXUsR7x4sIuI8p2sWk1AIt0aJzMJ7+GFaxzDvqeQ==", "dependencies": { "@popperjs/core": "2.11.6", "darkreader": "4.9.58", @@ -23101,9 +23101,9 @@ "devOptional": true }, "@zextras/carbonio-design-system": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@zextras/carbonio-design-system/-/carbonio-design-system-1.2.0.tgz", - "integrity": "sha512-Fq/iDlC0eefyFS0FOUbwdBz86NIl1MN23dhU9qBZFpDfGyDKDVSoIymyOXdRpC00O5AjA/yWhwcFB/uY+JK3fA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@zextras/carbonio-design-system/-/carbonio-design-system-2.0.0.tgz", + "integrity": "sha512-8yW0d+o4PHpehOrgiAaFn2nlHEmx/zv1PBcSWxTT1VsqpFEXUsR7x4sIuI8p2sWk1AIt0aJzMJ7+GFaxzDvqeQ==", "requires": { "@popperjs/core": "2.11.6", "darkreader": "4.9.58", diff --git a/package.json b/package.json index 2be88a98..46845648 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "@reduxjs/toolkit": "^1.9.2", "@sentry/browser": "^7.43.0", "@tinymce/tinymce-react": "^4.3.0", - "@zextras/carbonio-design-system": "^1.2.0", + "@zextras/carbonio-design-system": "^2.0.0", "@zextras/carbonio-ui-preview": "^1.1.0", "darkreader": "^4.9.58", "history": "^5.3.0", @@ -115,7 +115,7 @@ }, "peerDependencies": { "@reduxjs/toolkit": "^1 >=1.9", - "@zextras/carbonio-design-system": "^1", + "@zextras/carbonio-design-system": "^2", "@zextras/carbonio-ui-preview": "^1", "core-js": "^3.27.2", "lodash": "^4.17.21", diff --git a/src/constants/index.ts b/src/constants/index.ts index f1f7f4b3..3b99a1f5 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -126,3 +126,9 @@ export const DARK_READER_PROP_KEY = 'zappDarkreaderMode'; export const SENTRY_SHELL_DSN = 'https://0ce2448c05b94f0182c47ae52c7ff52c@feedback.zextras.tools/6'; export const SENTRY_FEEDBACK_DNS = 'https://1b6b3e2bbdc64a73bf45c72b725c56b4@feedback.zextras.tools/8'; + +export enum ResultLabelType { + NORMAL = 'normal', + WARNING = 'warning', + ERROR = 'error' +} diff --git a/src/keyboard-shortcuts/keyboard-shortcuts.tsx b/src/keyboard-shortcuts/keyboard-shortcuts.tsx index d7a3ca04..717f2314 100644 --- a/src/keyboard-shortcuts/keyboard-shortcuts.tsx +++ b/src/keyboard-shortcuts/keyboard-shortcuts.tsx @@ -4,11 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import React from 'react'; +import { type Action } from '../../types'; + type handleKeyboardShortcutsProps = { - primaryAction: any; - secondaryActions: any; - event: any; - inputRef: any; + primaryAction: Action; + secondaryActions: Action[]; + event: KeyboardEvent; + inputRef: React.RefObject; currentApp: string; }; @@ -18,17 +21,16 @@ const modifierKeysSecondTier = ['p']; let keySequence = ''; export const handleKeyboardShortcuts = (props: handleKeyboardShortcutsProps): void => { - const createEmail = props.secondaryActions?.filter((item: any) => item.id === 'create-mail')[0] - ?.click; + const createEmail = props.secondaryActions?.filter((item) => item.id === 'create-mail')[0] + ?.onClick; const createAppointment = props.secondaryActions?.filter( - (item: any) => item.id === 'new-appointment' - )[0]?.click; - const createContact = props.secondaryActions?.filter( - (item: any) => item.id === 'create-contact' - )[0]?.click; + (item) => item.id === 'new-appointment' + )[0]?.onClick; + const createContact = props.secondaryActions?.filter((item) => item.id === 'create-contact')[0] + ?.onClick; // will be used in future implementations - const ctrlModifierIsActive = props.event.ctrlKey || props.event.metaKey; + // const ctrlModifierIsActive = props.event.ctrlKey || props.event.metaKey; const consoleLogKeyCombination = (): void => { // console.log( @@ -41,45 +43,47 @@ export const handleKeyboardShortcuts = (props: handleKeyboardShortcutsProps): vo props.event.stopImmediatePropagation(); }; + const eventTargetElement = props.event.target instanceof HTMLElement ? props.event.target : null; const isGlobalContext = - props.event?.target?.isContentEditable === false && - props.event?.target?.nodeName !== 'INPUT' && - props.event.target.nodeName !== 'TEXTAREA'; + eventTargetElement && + !eventTargetElement.isContentEditable && + eventTargetElement.nodeName !== 'INPUT' && + eventTargetElement.nodeName !== 'TEXTAREA'; const callKeyboardShortcutAction = (): void => { switch (keySequence) { case 'n': if (props.primaryAction && isGlobalContext) { consoleLogKeyCombination(); - props.primaryAction.click(); + props.primaryAction.onClick?.(props.event); } break; case 'nm': if (isGlobalContext) { consoleLogKeyCombination(); - createEmail(); + createEmail?.(props.event); } break; case 'na': if (isGlobalContext) { consoleLogKeyCombination(); - createAppointment(); + createAppointment?.(props.event); } break; case 'nc': if (isGlobalContext) { consoleLogKeyCombination(); - createContact(); + createContact?.(props.event); } break; case 'c': if (isGlobalContext) { consoleLogKeyCombination(); - createContact(); + createContact?.(props.event); } break; @@ -87,12 +91,12 @@ export const handleKeyboardShortcuts = (props: handleKeyboardShortcutsProps): vo if (isGlobalContext) { props.event.preventDefault(); consoleLogKeyCombination(); - props.inputRef ? props.inputRef.current?.focus() : null; + props.inputRef.current?.focus(); } break; default: - null; + break; } keySequence = ''; }; @@ -115,6 +119,6 @@ export const handleKeyboardShortcuts = (props: handleKeyboardShortcutsProps): vo break; default: - null; + break; } }; diff --git a/src/network/fetch-locale.ts b/src/network/fetch-locale.ts index d07b55a7..e3a89c15 100644 --- a/src/network/fetch-locale.ts +++ b/src/network/fetch-locale.ts @@ -6,7 +6,6 @@ import { AvailableLocalesResponse } from '../../types'; import { SHELL_APP_ID } from '../constants'; -import { useAccountStore } from '../store/account'; import { getSoapFetch } from './fetch'; export const fetchLocales = (): Promise => diff --git a/src/reporting/feedback.tsx b/src/reporting/feedback.tsx index fe791f3c..cf6dd29c 100644 --- a/src/reporting/feedback.tsx +++ b/src/reporting/feedback.tsx @@ -4,22 +4,23 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { Event } from '@sentry/browser'; +import type { Event as SentryEvent } from '@sentry/browser'; import { ButtonOld as Button, Container, ContainerProps, Icon, + LabelFactoryProps, Row, Select, - SnackbarManagerContext, - Text + SelectItem, + Text, + useSnackbar } from '@zextras/carbonio-design-system'; import { filter, find, map } from 'lodash'; import React, { - FC, + TextareaHTMLAttributes, useCallback, - useContext, useEffect, useMemo, useReducer, @@ -33,6 +34,7 @@ import { closeBoard } from '../store/boards'; import { getT } from '../store/i18n'; import { feedback } from './functions'; +// TODO: replace with DS TextArea? const TextArea = styled.textarea<{ size?: keyof DefaultTheme['sizes']['font'] }>` width: 100%; min-height: 8rem; @@ -94,7 +96,7 @@ const LabelContainer = styled(Container)` disabled ? theme.palette.error.regular : theme.palette.gray2.regular}; `; -const emptyEvent: Event = { +const emptyEvent: SentryEvent = { message: '', level: 'info', release: 'unknown', @@ -105,7 +107,14 @@ const emptyEvent: Event = { user: {} }; -function reducer(state: Event, { type, payload }: { type: string; payload: any }): Event { +type SentryEventReducer = + | { type: 'set-user'; payload: SentryEvent['user'] } + | { type: 'reset'; payload: never } + | { type: 'set-message'; payload: SentryEvent['message'] } + | { type: 'select-app'; payload: { version: SentryEvent['release']; app: string } } + | { type: 'select-topic'; payload: string }; + +function reducer(state: SentryEvent, { type, payload }: SentryEventReducer): SentryEvent { switch (type) { case 'set-user': return { ...state, user: payload }; @@ -126,27 +135,37 @@ function reducer(state: Event, { type, payload }: { type: string; payload: any } } } -const getTopics = (t: TFunction): Array<{ label: string; value: string }> => [ +type Topic = { label: string; value: string }; + +const getTopics = (t: TFunction): Array => [ { label: t('feedback.user_interface', 'User interface'), value: 'UserInterface' }, { label: t('feedback.behaviors', 'Behaviors'), value: 'Behaviors' }, { label: t('feedback.missing_features', 'Missing features'), value: 'MissingFeatures' }, { label: t('feedback.other', 'Other'), value: 'Other' } ]; -const ModuleLabelFactory: FC<{ - selected: Array<{ label: string; value: string }>; +interface ModuleLabelFactory { + selected: SelectItem[]; label?: string; open: boolean; focus: boolean; disabled: boolean; -}> = ({ selected, label, open, focus, disabled }) => ( +} + +const ModuleLabelFactory = ({ + selected, + label, + open, + focus, + disabled +}: ModuleLabelFactory): JSX.Element => ( ); -const _LabelFactory: FC<{ - selected: Array<{ label: string; value: string }>; - label: string; - open: boolean; - focus: boolean; +interface FeedbackLabelFactoryProps extends LabelFactoryProps { showErr: boolean; -}> = ({ selected, label, open, focus, showErr }) => ( +} + +const FeedbackLabelFactory = ({ + selected, + label, + open, + focus, + showErr +}: FeedbackLabelFactoryProps): JSX.Element => ( {showErr ? ( - {' '} {selected.length > 0 ? selected[0].label : label} ) : ( @@ -216,7 +238,7 @@ const _LabelFactory: FC<{ ); -const Feedback: FC = () => { +const Feedback = (): JSX.Element => { const t = getT(); const allApps = useAppList(); const apps = useMemo( @@ -225,15 +247,18 @@ const Feedback: FC = () => { [allApps] ); const appItems = useMemo( - () => - map(apps, (app) => ({ - label: app.display, - value: app.name - })), + (): SelectItem[] => + map( + apps, + (app): SelectItem => ({ + label: app.display, + value: app.name + }) + ), [apps] ); - const acct = useUserAccount(); - const [event, dispatch] = useReducer(reducer, emptyEvent); + const account = useUserAccount(); + const [sentryEvent, dispatch] = useReducer(reducer, emptyEvent); const [showErr, setShowErr] = useState(false); const [limit, setLimit] = useState(0); @@ -254,34 +279,39 @@ const Feedback: FC = () => { dispatch({ type: 'select-topic', payload: ev }); }, []); - const onInputChange = useCallback((ev) => { + const onInputChange = useCallback< + NonNullable['onChange']> + >((event) => { // eslint-disable-next-line no-param-reassign - ev.target.style.height = 'auto'; + event.target.style.height = 'auto'; // eslint-disable-next-line no-param-reassign - ev.target.style.height = `${25 + ev.target.scrollHeight}px`; - if (ev.target.value.length <= 500) { - setLimit(ev.target.value.length); - dispatch({ type: 'set-message', payload: ev.target.value }); + event.target.style.height = `${25 + event.target.scrollHeight}px`; + if (event.target.value.length <= 500) { + setLimit(event.target.value.length); + dispatch({ type: 'set-message', payload: event.target.value }); } }, []); - const checkTopicSelect = useCallback( - (ev) => { - if (event.extra?.topic === '0') setShowErr(true); - else setShowErr(false); - if (ev.keyCode === 8) { - if (event.message?.length === 0) { - setShowErr(false); - } + const checkTopicSelect = useCallback< + NonNullable['onKeyUp']> + >( + (event) => { + if (sentryEvent.extra?.topic === '0') { + setShowErr(true); + } else { + setShowErr(false); + } + if (event.key === 'Backspace' && sentryEvent.message?.length === 0) { + setShowErr(false); } }, - [setShowErr, event] + [setShowErr, sentryEvent] ); - const createSnackbar = useContext(SnackbarManagerContext) as (snackbar: any) => void; + const createSnackbar = useSnackbar(); const confirmHandler = useCallback(() => { - const feedbackId = feedback(event); + const feedbackId = feedback(sentryEvent); createSnackbar( feedbackId ? { type: 'success', label: t('feedback.success', 'Thank you for your feedback') } @@ -291,28 +321,36 @@ const Feedback: FC = () => { } ); closeBoard('feedback'); - }, [event, createSnackbar, t]); + }, [sentryEvent, createSnackbar, t]); useEffect(() => { dispatch({ type: 'set-user', - payload: { id: acct.id, name: acct.displayName ?? acct.name } + payload: { id: account.id, name: account.displayName ?? account.name } }); - }, [acct]); + }, [account]); const disabledSend = useMemo( () => - (event?.message?.length ?? 0) <= 0 || event.extra?.topic === '0' || event.extra?.app === '0', - [event.message, event.extra?.topic, event.extra?.app] + (sentryEvent?.message?.length ?? 0) <= 0 || + sentryEvent.extra?.topic === '0' || + sentryEvent.extra?.app === '0', + [sentryEvent.message, sentryEvent.extra?.topic, sentryEvent.extra?.app] ); const LabelFactory = useCallback( - (props) => <_LabelFactory {...props} showErr={showErr} />, + (props: LabelFactoryProps) => , [showErr] ); const topics = useMemo(() => getTopics(t), [t]); + const defaultTopic = useMemo( + (): Topic => + find(topics, (topic) => topic.value === sentryEvent.extra?.topic) ?? { label: '', value: '' }, + [sentryEvent.extra?.topic, topics] + ); + return ( @@ -372,9 +410,7 @@ const Feedback: FC = () => {