Compare commits

...
Author SHA1 Message Date
Maksim Eltyshev c8d732d936 chore: Update version 2024-05-03 12:44:51 +02:00
Maksim Eltyshev 5a32b6327c fix: Fix actions with members
Closes #737
2024-05-03 12:42:51 +02:00
Emmanuel Guyot 8488105810 feat: Search in card descriptions (#729) 2024-04-26 21:52:57 +02:00
Niccolò Pedrini 57ebfa51d8 fix: Update Italian translation (#727) 2024-04-24 18:49:27 +02:00
Maksim Eltyshev 40170fdf90 chore: Update version 2024-04-23 15:57:14 +02:00
leroyloren 1629f62d09 fix: Update Czech translation (#725) 2024-04-23 15:53:10 +02:00
Maksim Eltyshev fff0552081 chore: Update dependencies
Closes #726
2024-04-23 15:45:47 +02:00
19 changed files with 11445 additions and 14462 deletions
+2 -2
View File
@@ -15,13 +15,13 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.27
version: 0.1.29
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.17.0"
appVersion: "1.17.2"
dependencies:
- alias: postgresql
+1 -1
View File
@@ -1 +1 @@
REACT_APP_VERSION=1.17.0
REACT_APP_VERSION=1.17.2
+9538 -3053
View File
File diff suppressed because it is too large Load Diff
+17 -17
View File
@@ -60,27 +60,27 @@
},
"dependencies": {
"@juggle/resize-observer": "^3.4.0",
"classnames": "^2.3.2",
"classnames": "^2.5.1",
"date-fns": "^2.30.0",
"dequal": "^2.0.3",
"easymde": "^2.18.0",
"history": "^5.3.0",
"i18next": "^23.7.6",
"i18next-browser-languagedetector": "^7.2.0",
"i18next": "^23.11.2",
"i18next-browser-languagedetector": "^7.2.1",
"initials": "^3.1.2",
"js-cookie": "^3.0.5",
"jwt-decode": "^4.0.0",
"linkify-react": "^4.1.3",
"linkifyjs": "^4.1.3",
"lodash": "^4.17.21",
"nanoid": "^5.0.3",
"nanoid": "^5.0.7",
"node-sass": "^9.0.0",
"photoswipe": "^5.4.2",
"photoswipe": "^5.4.3",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-app-rewired": "^2.2.1",
"react-beautiful-dnd": "^13.1.1",
"react-datepicker": "^4.21.0",
"react-datepicker": "^4.25.0",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-i18next": "^13.5.0",
@@ -88,35 +88,35 @@
"react-markdown": "^8.0.7",
"react-photoswipe-gallery": "^2.2.7",
"react-redux": "^8.1.3",
"react-router-dom": "^6.19.0",
"react-router-dom": "^6.22.3",
"react-scripts": "5.0.1",
"react-simplemde-editor": "^5.2.0",
"react-textarea-autosize": "^8.5.3",
"redux": "^4.2.1",
"redux-logger": "^3.0.6",
"redux-orm": "^0.16.2",
"redux-saga": "^1.2.3",
"redux-saga": "^1.3.0",
"remark-breaks": "^4.0.0",
"remark-gfm": "^3.0.1",
"reselect": "^4.1.8",
"sails.io.js": "^1.2.1",
"semantic-ui-react": "^2.1.4",
"semantic-ui-react": "^2.1.5",
"socket.io-client": "^2.5.0",
"validator": "^13.11.0",
"whatwg-fetch": "^3.6.19",
"whatwg-fetch": "^3.6.20",
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^14.1.0",
"@testing-library/user-event": "^14.5.1",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.3.1",
"@testing-library/user-event": "^14.5.2",
"babel-preset-airbnb": "^5.0.0",
"chai": "^4.3.10",
"eslint": "^8.53.0",
"chai": "^4.4.1",
"eslint": "^8.57.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"react-test-renderer": "^18.2.0"
}
@@ -3,6 +3,7 @@ import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button } from 'semantic-ui-react';
import { Popup } from '../../lib/custom-ui';
import { useSteps } from '../../hooks';
import User from '../User';
@@ -19,6 +20,7 @@ const ActionsStep = React.memo(
({
membership,
permissionsSelectStep,
title,
leaveButtonContent,
leaveConfirmationTitle,
leaveConfirmationContent,
@@ -31,6 +33,7 @@ const ActionsStep = React.memo(
canLeave,
onUpdate,
onDelete,
onBack,
onClose,
}) => {
const [t] = useTranslation();
@@ -53,6 +56,11 @@ const ActionsStep = React.memo(
[onUpdate],
);
const handleDeleteConfirm = useCallback(() => {
onDelete();
onClose();
}, [onDelete, onClose]);
if (step) {
switch (step.type) {
case StepTypes.EDIT_PERMISSIONS: {
@@ -81,7 +89,7 @@ const ActionsStep = React.memo(
? leaveConfirmationButtonContent
: deleteConfirmationButtonContent
}
onConfirm={onDelete}
onConfirm={handleDeleteConfirm}
onBack={handleBack}
/>
);
@@ -89,7 +97,7 @@ const ActionsStep = React.memo(
}
}
return (
const contentNode = (
<>
<span className={styles.user}>
<User name={membership.user.name} avatarUrl={membership.user.avatarUrl} size="large" />
@@ -125,12 +133,26 @@ const ActionsStep = React.memo(
)}
</>
);
return onBack ? (
<>
<Popup.Header onBack={onBack}>
{t(title, {
context: 'title',
})}
</Popup.Header>
<Popup.Content>{contentNode}</Popup.Content>
</>
) : (
contentNode
);
},
);
ActionsStep.propTypes = {
membership: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
permissionsSelectStep: PropTypes.elementType,
title: PropTypes.string,
leaveButtonContent: PropTypes.string,
leaveConfirmationTitle: PropTypes.string,
leaveConfirmationContent: PropTypes.string,
@@ -143,11 +165,13 @@ ActionsStep.propTypes = {
canLeave: PropTypes.bool.isRequired,
onUpdate: PropTypes.func,
onDelete: PropTypes.func.isRequired,
onBack: PropTypes.func,
onClose: PropTypes.func.isRequired,
};
ActionsStep.defaultProps = {
permissionsSelectStep: undefined,
title: 'common.memberActions',
leaveButtonContent: 'action.leaveBoard',
leaveConfirmationTitle: 'common.leaveBoard',
leaveConfirmationContent: 'common.areYouSureYouWantToLeaveBoard',
@@ -157,6 +181,7 @@ ActionsStep.defaultProps = {
deleteConfirmationContent: 'common.areYouSureYouWantToRemoveThisMemberFromBoard',
deleteConfirmationButtonContent: 'action.removeMember',
onUpdate: undefined,
onBack: undefined,
};
export default ActionsStep;
@@ -5,10 +5,10 @@ import { usePopup } from '../../lib/popup';
import AddStep from './AddStep';
import ActionsStep from './ActionsStep';
import MembershipsStep from './MembershipsStep';
import User from '../User';
import styles from './Memberships.module.scss';
import MembershipsStep from './MembershipsStep';
const MAX_MEMBERS = 6;
@@ -17,7 +17,9 @@ const Memberships = React.memo(
items,
allUsers,
permissionsSelectStep,
title,
addTitle,
actionsTitle,
leaveButtonContent,
leaveConfirmationTitle,
leaveConfirmationContent,
@@ -73,6 +75,8 @@ const Memberships = React.memo(
<MembershipsPopup
items={items}
permissionsSelectStep={permissionsSelectStep}
title={title}
actionsTitle={actionsTitle}
leaveButtonContent={leaveButtonContent}
leaveConfirmationTitle={leaveConfirmationTitle}
leaveConfirmationContent={leaveConfirmationContent}
@@ -87,7 +91,7 @@ const Memberships = React.memo(
onDelete={onDelete}
>
<Button icon className={styles.addUser}>
+ {remainMembersCount < 99 ? remainMembersCount : 99}
+{remainMembersCount < 99 ? remainMembersCount : 99}
</Button>
</MembershipsPopup>
)}
@@ -113,7 +117,9 @@ Memberships.propTypes = {
allUsers: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */
permissionsSelectStep: PropTypes.elementType,
title: PropTypes.string,
addTitle: PropTypes.string,
actionsTitle: PropTypes.string,
leaveButtonContent: PropTypes.string,
leaveConfirmationTitle: PropTypes.string,
leaveConfirmationContent: PropTypes.string,
@@ -131,7 +137,9 @@ Memberships.propTypes = {
Memberships.defaultProps = {
permissionsSelectStep: undefined,
title: undefined,
addTitle: undefined,
actionsTitle: undefined,
leaveButtonContent: undefined,
leaveConfirmationTitle: undefined,
leaveConfirmationContent: undefined,
@@ -1,15 +1,20 @@
import React, { useCallback, useState } from 'react';
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import BoardMembershipsStep from '../BoardMembershipsStep/BoardMembershipsStep';
import { useSteps } from '../../hooks';
import ActionsStep from './ActionsStep';
import { Popup } from '../../lib/custom-ui';
import BoardMembershipsStep from '../BoardMembershipsStep';
const StepTypes = {
EDIT: 'EDIT',
};
const MembershipsStep = React.memo(
({
items,
permissionsSelectStep,
title,
actionsTitle,
leaveButtonContent,
leaveConfirmationTitle,
leaveConfirmationContent,
@@ -24,49 +29,54 @@ const MembershipsStep = React.memo(
onDelete,
onClose,
}) => {
const [t] = useTranslation();
const [step, openStep, handleBack] = useSteps();
const [editingItem, setEditingItem] = useState();
const handleUserClick = useCallback(
(id) => {
setEditingItem(items.find((item) => item.user.id === id));
const handleUserSelect = useCallback(
(userId) => {
openStep(StepTypes.EDIT, {
userId,
});
},
[setEditingItem, items],
[openStep],
);
if (editingItem) {
return (
<>
<Popup.Header onBack={() => setEditingItem(null)}>{t('common.memberInfo')}</Popup.Header>
<Popup.Content>
<ActionsStep
membership={editingItem}
permissionsSelectStep={permissionsSelectStep}
leaveButtonContent={leaveButtonContent}
leaveConfirmationTitle={leaveConfirmationTitle}
leaveConfirmationContent={leaveConfirmationContent}
leaveConfirmationButtonContent={leaveConfirmationButtonContent}
deleteButtonContent={deleteButtonContent}
deleteConfirmationTitle={deleteConfirmationTitle}
deleteConfirmationContent={deleteConfirmationContent}
deleteConfirmationButtonContent={deleteConfirmationButtonContent}
canEdit={canEdit}
canLeave={canLeave}
onUpdate={onUpdate}
onDelete={onDelete}
onClose={onClose}
/>
</Popup.Content>
</>
);
if (step && step.type === StepTypes.EDIT) {
const currentItem = items.find((item) => item.userId === step.params.userId);
if (currentItem) {
return (
<ActionsStep
membership={currentItem}
permissionsSelectStep={permissionsSelectStep}
title={actionsTitle}
leaveButtonContent={leaveButtonContent}
leaveConfirmationTitle={leaveConfirmationTitle}
leaveConfirmationContent={leaveConfirmationContent}
leaveConfirmationButtonContent={leaveConfirmationButtonContent}
deleteButtonContent={deleteButtonContent}
deleteConfirmationTitle={deleteConfirmationTitle}
deleteConfirmationContent={deleteConfirmationContent}
deleteConfirmationButtonContent={deleteConfirmationButtonContent}
canEdit={canEdit}
canLeave={canLeave}
onUpdate={(data) => onUpdate(currentItem.id, data)}
onDelete={() => onDelete(currentItem.id)}
onBack={handleBack}
onClose={onClose}
/>
);
}
openStep(null);
}
return (
// FIXME: hack
<BoardMembershipsStep
items={items}
currentUserIds={[]}
onUserSelect={handleUserClick}
title={title}
onUserSelect={handleUserSelect}
onUserDeselect={() => {}}
/>
);
@@ -76,6 +86,8 @@ const MembershipsStep = React.memo(
MembershipsStep.propTypes = {
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
permissionsSelectStep: PropTypes.elementType,
title: PropTypes.string,
actionsTitle: PropTypes.string,
leaveButtonContent: PropTypes.string,
leaveConfirmationTitle: PropTypes.string,
leaveConfirmationContent: PropTypes.string,
@@ -93,14 +105,16 @@ MembershipsStep.propTypes = {
MembershipsStep.defaultProps = {
permissionsSelectStep: undefined,
leaveButtonContent: 'action.leaveBoard',
leaveConfirmationTitle: 'common.leaveBoard',
leaveConfirmationContent: 'common.areYouSureYouWantToLeaveBoard',
leaveConfirmationButtonContent: 'action.leaveBoard',
deleteButtonContent: 'action.removeFromBoard',
deleteConfirmationTitle: 'common.removeMember',
deleteConfirmationContent: 'common.areYouSureYouWantToRemoveThisMemberFromBoard',
deleteConfirmationButtonContent: 'action.removeMember',
title: undefined,
actionsTitle: undefined,
leaveButtonContent: undefined,
leaveConfirmationTitle: undefined,
leaveConfirmationContent: undefined,
leaveConfirmationButtonContent: undefined,
deleteButtonContent: undefined,
deleteConfirmationTitle: undefined,
deleteConfirmationContent: undefined,
deleteConfirmationButtonContent: undefined,
onUpdate: undefined,
};
@@ -12,7 +12,9 @@ const ManagersPane = React.memo(({ items, allUsers, onCreate, onDelete }) => {
<Memberships
items={items}
allUsers={allUsers}
title="common.managers"
addTitle="common.addManager"
actionsTitle="common.managerActions"
leaveButtonContent="action.leaveProject"
leaveConfirmationTitle="common.leaveProject"
leaveConfirmationContent="common.areYouSureYouWantToLeaveProject"
+9 -3
View File
@@ -64,7 +64,8 @@ export default {
currentPassword: 'Aktuální heslo',
dangerZone_title: 'Nebezpečná zóna',
date: 'Datum',
dueDate_title: 'Termín do',
dueDate: 'Termín',
dueDate_title: 'Termín',
deleteAttachment_title: 'Smazat přílohu',
deleteBoard_title: 'Smazat tabuli',
deleteCard_title: 'Smazat kartu',
@@ -81,7 +82,7 @@ export default {
editAttachment_title: 'Upravit přílohu',
editAvatar_title: 'Upravit avatar',
editBoard_title: 'Upravit tabuli',
editDueDate_title: 'Upravit Termín do',
editDueDate_title: 'Upravit termín',
editEmail_title: 'Upravit e-mail',
editInformation_title: 'Upravit informace',
editLabel_title: 'Upravit štítek',
@@ -116,6 +117,7 @@ export default {
minutes: 'Minuty',
moveCard_title: 'Přesunout kartu',
name: 'Jméno',
newestFirst: 'Nejnovější',
newEmail: 'Nový e-mail',
newPassword: 'Nové heslo',
newUsername: 'Nové uživatelské jméno',
@@ -125,6 +127,7 @@ export default {
noProjects: 'Žádné projekty',
notifications: 'Oznámení',
noUnreadNotifications: 'Žádné nepřečtené oznámení',
oldestFirst: 'Nejstarší',
openBoard_title: 'Otevřít tabuli',
optional_inline: 'volitelné',
organization: 'Společnost',
@@ -139,12 +142,14 @@ export default {
searchLabels: 'Hledat štítky...',
searchMembers: 'Hledat členy...',
searchUsers: 'Hledat uživatele...',
searchCards: 'Hledat karty...',
seconds: 'Vteřin',
selectBoard: 'Vybrat tabuli',
selectList: 'Vybrat seznam',
selectPermissions_title: 'Vybrat oprávnění',
selectProject: 'Vybrat projekt',
settings: 'Nastavení',
sortList_title: 'Řadit podle',
stopwatch: 'Časovač',
subscribeToMyOwnCardsByDefault: 'Ve výchozím nastavení odebírat vlastní karty',
taskActions_title: 'Akce na úkolu',
@@ -203,7 +208,7 @@ export default {
duplicate: 'Duplikovat',
duplicateCard_title: 'Duplikovat kartu',
edit: 'Upravit',
editDueDate_title: 'Upravit termín do',
editDueDate_title: 'Upravit termín',
editDescription_title: 'Upravit popis',
editEmail_title: 'Upravit e-mail',
editInformation_title: 'Upravit informace',
@@ -231,6 +236,7 @@ export default {
showAllAttachments: 'Zozbrazit všechny přílohy ({{hidden}} skryté)',
showDetails: 'Zobrazit detaily',
showFewerAttachments: 'Zobrazit méně příloh',
sortList_title: 'Řadit podle',
start: 'Start',
stop: 'Stop',
subscribe: 'Odebírat',
+2 -2
View File
@@ -1,8 +1,8 @@
export default {
translation: {
common: {
emailOrUsername: 'Email nebo uživatelské jméno',
invalidEmailOrUsername: 'Nesprávný email nebo uživatelské jméno',
emailOrUsername: 'E-mail nebo uživatelské jméno',
invalidEmailOrUsername: 'Nesprávný e-mail nebo uživatelské jméno',
invalidPassword: 'Nesprávné heslo',
logInToPlanka: 'Přihlásit se do Planka',
noInternetConnection: 'Bez připojení k internetu',
+2 -1
View File
@@ -109,8 +109,9 @@ export default {
list: 'List',
listActions_title: 'List Actions',
managers: 'Managers',
memberInfo: 'Member Info',
managerActions_title: 'Manager Actions',
members: 'Members',
memberActions_title: 'Member Actions',
minutes: 'Minutes',
moveCard_title: 'Move Card',
name: 'Name',
+6
View File
@@ -59,6 +59,7 @@ export default {
currentPassword: 'Password corrente',
dangerZone_title: 'Zona pericolosa',
date: 'Data',
dueDate: 'Data di scadenza',
dueDate_title: 'Data di scadenza',
deleteAttachment_title: 'Elimina Allegato',
deleteBoard_title: 'Elimina Bacheca',
@@ -111,6 +112,7 @@ export default {
minutes: 'Minuti',
moveCard_title: 'Sposta card',
name: 'Nome',
newestFirst: 'Dal più recente',
newEmail: 'Nuova e-mail',
newPassword: 'Nuova password',
newUsername: 'Nuovo username',
@@ -120,6 +122,7 @@ export default {
noProjects: 'No progetti',
notifications: 'Notifiche',
noUnreadNotifications: 'Nessuna notifica non letta.',
oldestFirst: 'Dal meno recente',
openBoard_title: 'Bacheca aperta',
optional_inline: 'opzionale',
organization: 'Organizazzione',
@@ -134,12 +137,14 @@ export default {
searchLabels: 'Cerca etichette...',
searchMembers: 'Cerca membri...',
searchUsers: 'Cerca utenti...',
searchCards: 'Cerca cards...',
seconds: 'Secondi',
selectBoard: 'Seleziona bacheca',
selectList: 'Seleziona lista',
selectPermissions_title: 'Seleziona Permessi',
selectProject: 'Seleziona progetto',
settings: 'Impostazioni',
sortList_title: 'Ordina',
stopwatch: 'Timer',
subscribeToMyOwnCardsByDefault: 'Abbonati alle mie card per impostazione predefinita',
taskActions_title: 'Task Azioni',
@@ -227,6 +232,7 @@ export default {
showAllAttachments: 'Mostra tutti gli allegati ({{hidden}} hidden)',
showDetails: 'Mostra dettagli',
showFewerAttachments: 'Mostra meno allegati',
sortList_title: 'Ordina',
start: 'Inizio',
stop: 'Ferma',
subscribe: 'Iscriviti',
-1
View File
@@ -111,7 +111,6 @@ export default {
list: 'Lista',
listActions_title: 'Ações da Lista',
managers: 'Gerentes',
memberInfo: 'Informações do Membro',
members: 'Membros',
minutes: 'Minutos',
moveCard_title: 'Mover Cartão',
+6 -2
View File
@@ -101,7 +101,9 @@ export default class extends BaseModel {
}
let doRegularSearch = true;
if (re) {
cardModels = cardModels.filter((cardModel) => re.test(cardModel.name));
cardModels = cardModels.filter(
(cardModel) => re.test(cardModel.name) || re.test(cardModel?.description),
);
doRegularSearch = false;
} else if (filterText.startsWith('!') && posSpace > 0) {
const creatorUserId = User.findUsersFromText(
@@ -116,7 +118,9 @@ export default class extends BaseModel {
if (doRegularSearch) {
const lowerCasedFilter = filterText.toLocaleLowerCase();
cardModels = cardModels.filter(
(cardModel) => cardModel.name.toLocaleLowerCase().indexOf(lowerCasedFilter) >= 0,
(cardModel) =>
cardModel.name.toLocaleLowerCase().indexOf(lowerCasedFilter) >= 0 ||
cardModel.description?.toLocaleLowerCase().indexOf(lowerCasedFilter) >= 0,
);
}
}
+203 -2217
View File
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "planka",
"version": "1.17.0",
"version": "1.17.2",
"private": true,
"homepage": "https://plankanban.github.io/planka",
"repository": {
@@ -58,12 +58,12 @@
"dependencies": {
"concurrently": "^8.2.2",
"husky": "^8.0.3",
"lint-staged": "^15.1.0"
"lint-staged": "^15.2.2"
},
"devDependencies": {
"eslint": "^8.53.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.1",
"prettier": "^3.1.0"
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"prettier": "^3.2.5"
}
}
+1 -1
View File
@@ -46,7 +46,7 @@ module.exports.datastores = {
*
*/
adapter: 'sails-postgresql-redacted',
adapter: 'sails-postgresql',
url: process.env.DATABASE_URL,
},
};
+1537 -9089
View File
File diff suppressed because it is too large Load Diff
+17 -18
View File
@@ -28,36 +28,35 @@
},
"dependencies": {
"bcrypt": "^5.1.1",
"dotenv": "^16.3.1",
"dotenv-cli": "^7.3.0",
"dotenv": "^16.4.5",
"dotenv-cli": "^7.4.1",
"filenamify": "^4.3.0",
"jsonwebtoken": "^9.0.2",
"knex": "^3.0.1",
"knex": "^3.1.0",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"moment": "^2.30.1",
"move-file": "^2.1.0",
"nodemailer": "^6.9.12",
"openid-client": "^5.6.1",
"nodemailer": "^6.9.13",
"openid-client": "^5.6.5",
"rimraf": "^5.0.5",
"sails": "^1.5.7",
"sails-hook-orm": "^4.0.2",
"sails-hook-sockets": "^2.0.4",
"sails-postgresql-redacted": "^1.0.2-9",
"sharp": "^0.33.2",
"sails": "^1.5.10",
"sails-hook-orm": "^4.0.3",
"sails-hook-sockets": "^3.0.0",
"sails-postgresql": "^5.0.1",
"sharp": "^0.33.3",
"stream-to-array": "^2.3.0",
"uuid": "^9.0.1",
"validator": "^13.11.0",
"winston": "^3.11.0",
"winston": "^3.13.0",
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"chai": "^4.3.10",
"eslint": "^8.53.0",
"chai": "^4.4.1",
"eslint": "^8.57.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.29.0",
"mocha": "^10.2.0",
"nodemon": "^3.0.1",
"supertest": "^6.3.3"
"eslint-plugin-import": "^2.29.1",
"mocha": "^10.4.0",
"nodemon": "^3.1.0"
},
"engines": {
"node": ">=18"