Compare commits

..
8 Commits
Author SHA1 Message Date
Dominik Dancs ba850801c5 feat: Add Slovak translation (#265) 2022-06-17 20:16:15 +05:00
Maksim Eltyshev c9b23652d1 feat: Add ability to edit users
Closes #60
2022-06-15 14:13:22 +02:00
Maksim Eltyshev 8490629e4c fix: Fix password edit popup styles 2022-06-13 17:33:26 +02:00
ejo090 76fcd79203 fix: Fix markdown line breaks
Closes #257
2022-06-06 17:37:43 +05:00
Maksim Eltyshev 3ce1bc0419 fix: Fix dependencies installation 2022-05-21 23:52:12 +05:00
Maksim Eltyshev 540fbfceb5 chore: Update version 2022-05-21 21:49:36 +05:00
Maksim Eltyshev 48a56648e2 chore: Update version 2022-05-21 21:30:38 +05:00
Maksim Eltyshev 6d002c311c fix: Fix Swedish language code 2022-05-21 21:29:33 +05:00
43 changed files with 1067 additions and 227 deletions
+2 -6
View File
@@ -16,13 +16,9 @@ WORKDIR /app
COPY server/.npmrc server/package.json server/package-lock.json ./
RUN apk add \
g++ make python3 \
--virtual node-pre-gyp-dependencies \
--no-cache \
&& npm install npm@latest --global \
RUN npm install npm@latest --global \
&& npm install --production \
&& apk del node-pre-gyp-dependencies --purge
&& apk del vips-dependencies --purge
COPY docker-start.sh start.sh
COPY server .
+1 -2
View File
@@ -24,5 +24,4 @@ RUN apk -U upgrade \
&& ./configure \
&& make \
&& make install-strip \
&& rm -rf /tmp/vips-${VIPS_VERSION} \
&& apk del vips-dependencies --purge
&& rm -rf /tmp/vips-${VIPS_VERSION}
+25
View File
@@ -34,6 +34,7 @@
"redux-logger": "^3.0.6",
"redux-orm": "^0.16.2",
"redux-saga": "^1.1.3",
"remark-breaks": "^3.0.2",
"remark-gfm": "^3.0.1",
"reselect": "^4.1.5",
"sails.io.js": "^1.2.1",
@@ -18824,6 +18825,20 @@
"node": ">= 0.10"
}
},
"node_modules/remark-breaks": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-3.0.2.tgz",
"integrity": "sha512-x96YDJ9X+Ry0/JNZFKfr1hpcAKvGYWfUTszxY9RbxKEqq6uzPPoLCuHdZsLPZZUdAv3nCROyc7FPrQLWr2rxyw==",
"dependencies": {
"@types/mdast": "^3.0.0",
"unified": "^10.0.0",
"unist-util-visit": "^4.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/remark-gfm": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz",
@@ -36761,6 +36776,16 @@
"resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
"integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk="
},
"remark-breaks": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-3.0.2.tgz",
"integrity": "sha512-x96YDJ9X+Ry0/JNZFKfr1hpcAKvGYWfUTszxY9RbxKEqq6uzPPoLCuHdZsLPZZUdAv3nCROyc7FPrQLWr2rxyw==",
"requires": {
"@types/mdast": "^3.0.0",
"unified": "^10.0.0",
"unist-util-visit": "^4.0.0"
}
},
"remark-gfm": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz",
+1
View File
@@ -91,6 +91,7 @@
"redux-logger": "^3.0.6",
"redux-orm": "^0.16.2",
"redux-saga": "^1.1.3",
"remark-breaks": "^3.0.2",
"remark-gfm": "^3.0.1",
"reselect": "^4.1.5",
"sails.io.js": "^1.2.1",
+45
View File
@@ -41,6 +41,14 @@ export const handleUserUpdate = (user) => ({
},
});
export const updateUserEmail = (id, data) => ({
type: EntryActionTypes.USER_EMAIL_UPDATE,
payload: {
id,
data,
},
});
export const updateCurrentUserEmail = (data) => ({
type: EntryActionTypes.CURRENT_USER_EMAIL_UPDATE,
payload: {
@@ -48,11 +56,26 @@ export const updateCurrentUserEmail = (data) => ({
},
});
export const clearUserEmailUpdateError = (id) => ({
type: EntryActionTypes.USER_EMAIL_UPDATE_ERROR_CLEAR,
payload: {
id,
},
});
export const clearCurrentUserEmailUpdateError = () => ({
type: EntryActionTypes.CURRENT_USER_EMAIL_UPDATE_ERROR_CLEAR,
payload: {},
});
export const updateUserPassword = (id, data) => ({
type: EntryActionTypes.USER_PASSWORD_UPDATE,
payload: {
id,
data,
},
});
export const updateCurrentUserPassword = (data) => ({
type: EntryActionTypes.CURRENT_USER_PASSWORD_UPDATE,
payload: {
@@ -60,11 +83,26 @@ export const updateCurrentUserPassword = (data) => ({
},
});
export const clearUserPasswordUpdateError = (id) => ({
type: EntryActionTypes.USER_PASSWORD_UPDATE_ERROR_CLEAR,
payload: {
id,
},
});
export const clearCurrentUserPasswordUpdateError = () => ({
type: EntryActionTypes.CURRENT_USER_PASSWORD_UPDATE_ERROR_CLEAR,
payload: {},
});
export const updateUserUsername = (id, data) => ({
type: EntryActionTypes.USER_USERNAME_UPDATE,
payload: {
id,
data,
},
});
export const updateCurrentUserUsername = (data) => ({
type: EntryActionTypes.CURRENT_USER_USERNAME_UPDATE,
payload: {
@@ -72,6 +110,13 @@ export const updateCurrentUserUsername = (data) => ({
},
});
export const clearUserUsernameUpdateError = (id) => ({
type: EntryActionTypes.USER_USERNAME_UPDATE_ERROR_CLEAR,
payload: {
id,
},
});
export const clearCurrentUserUsernameUpdateError = () => ({
type: EntryActionTypes.CURRENT_USER_USERNAME_UPDATE_ERROR_CLEAR,
payload: {},
@@ -0,0 +1,5 @@
import { withPopup } from '../lib/popup';
import UserEmailEditStep from './UserEmailEditStep';
export default withPopup(UserEmailEditStep);
@@ -1,15 +1,15 @@
import omit from 'lodash/omit';
import isEmail from 'validator/lib/isEmail';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, Form, Message } from 'semantic-ui-react';
import { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks';
import { withPopup } from '../../../lib/popup';
import { Input, Popup } from '../../../lib/custom-ui';
import { useDidUpdate, usePrevious, useToggle } from '../../lib/hooks';
import { Input, Popup } from '../../lib/custom-ui';
import { useForm } from '../../../hooks';
import { useForm } from '../../hooks';
import styles from './EmailEditPopup.module.scss';
import styles from './UserEmailEditStep.module.scss';
const createMessage = (error) => {
if (!error) {
@@ -35,8 +35,18 @@ const createMessage = (error) => {
}
};
const EmailEditStep = React.memo(
({ defaultData, email, isSubmitting, error, onUpdate, onMessageDismiss, onClose }) => {
const UserEmailEditStep = React.memo(
({
defaultData,
email,
isSubmitting,
error,
usePasswordConfirmation,
onUpdate,
onMessageDismiss,
onBack,
onClose,
}) => {
const [t] = useTranslation();
const wasSubmitting = usePrevious(isSubmitting);
@@ -68,13 +78,13 @@ const EmailEditStep = React.memo(
return;
}
if (!cleanData.currentPassword) {
if (usePasswordConfirmation && !cleanData.currentPassword) {
currentPasswordField.current.focus();
return;
}
onUpdate(cleanData);
}, [email, onUpdate, onClose, data]);
onUpdate(usePasswordConfirmation ? cleanData : omit(cleanData, 'currentPassword'));
}, [email, usePasswordConfirmation, onUpdate, onClose, data]);
useEffect(() => {
emailField.current.select();
@@ -110,7 +120,7 @@ const EmailEditStep = React.memo(
return (
<>
<Popup.Header>
<Popup.Header onBack={onBack}>
{t('common.editEmail', {
context: 'title',
})}
@@ -138,6 +148,8 @@ const EmailEditStep = React.memo(
className={styles.field}
onChange={handleFieldChange}
/>
{usePasswordConfirmation && (
<>
<div className={styles.text}>{t('common.currentPassword')}</div>
<Input.Password
fluid
@@ -147,6 +159,8 @@ const EmailEditStep = React.memo(
className={styles.field}
onChange={handleFieldChange}
/>
</>
)}
<Button
positive
content={t('action.save')}
@@ -160,18 +174,22 @@ const EmailEditStep = React.memo(
},
);
EmailEditStep.propTypes = {
UserEmailEditStep.propTypes = {
defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
email: PropTypes.string.isRequired,
isSubmitting: PropTypes.bool.isRequired,
error: PropTypes.object, // eslint-disable-line react/forbid-prop-types
usePasswordConfirmation: PropTypes.bool,
onUpdate: PropTypes.func.isRequired,
onMessageDismiss: PropTypes.func.isRequired,
onBack: PropTypes.func,
onClose: PropTypes.func.isRequired,
};
EmailEditStep.defaultProps = {
UserEmailEditStep.defaultProps = {
error: undefined,
usePasswordConfirmation: false,
onBack: undefined,
};
export default withPopup(EmailEditStep);
export default UserEmailEditStep;
@@ -0,0 +1,3 @@
import UserEmailEditStep from './UserEmailEditStep';
export default UserEmailEditStep;
@@ -5,11 +5,11 @@ import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, Form, Input } from 'semantic-ui-react';
import { useForm } from '../../../hooks';
import { useForm } from '../../hooks';
import styles from './InformationEdit.module.scss';
import styles from './UserInformationEdit.module.scss';
const InformationEdit = React.memo(({ defaultData, onUpdate }) => {
const UserInformationEdit = React.memo(({ defaultData, onUpdate }) => {
const [t] = useTranslation();
const [data, handleFieldChange] = useForm(() => ({
@@ -72,9 +72,9 @@ const InformationEdit = React.memo(({ defaultData, onUpdate }) => {
);
});
InformationEdit.propTypes = {
UserInformationEdit.propTypes = {
defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
onUpdate: PropTypes.func.isRequired,
};
export default InformationEdit;
export default UserInformationEdit;
@@ -0,0 +1,3 @@
import UserInformationEdit from './UserInformationEdit';
export default UserInformationEdit;
@@ -0,0 +1,44 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Popup } from '../lib/custom-ui';
import UserInformationEdit from './UserInformationEdit';
const UserInformationEditStep = React.memo(({ defaultData, onUpdate, onBack, onClose }) => {
const [t] = useTranslation();
const handleUpdate = useCallback(
(data) => {
onUpdate(data);
onClose();
},
[onUpdate, onClose],
);
return (
<>
<Popup.Header onBack={onBack}>
{t('common.editInformation', {
context: 'title',
})}
</Popup.Header>
<Popup.Content>
<UserInformationEdit defaultData={defaultData} onUpdate={handleUpdate} />
</Popup.Content>
</>
);
});
UserInformationEditStep.propTypes = {
defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
onUpdate: PropTypes.func.isRequired,
onBack: PropTypes.func,
onClose: PropTypes.func.isRequired,
};
UserInformationEditStep.defaultProps = {
onBack: undefined,
};
export default UserInformationEditStep;
@@ -0,0 +1,5 @@
import { withPopup } from '../lib/popup';
import UserPasswordEditStep from './UserPasswordEditStep';
export default withPopup(UserPasswordEditStep);
@@ -1,14 +1,14 @@
import omit from 'lodash/omit';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, Form, Message } from 'semantic-ui-react';
import { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks';
import { withPopup } from '../../../lib/popup';
import { Input, Popup } from '../../../lib/custom-ui';
import { useDidUpdate, usePrevious, useToggle } from '../../lib/hooks';
import { Input, Popup } from '../../lib/custom-ui';
import { useForm } from '../../../hooks';
import { useForm } from '../../hooks';
import styles from './PasswordEditPopup.module.scss';
import styles from './UserPasswordEditStep.module.scss';
const createMessage = (error) => {
if (!error) {
@@ -29,8 +29,17 @@ const createMessage = (error) => {
}
};
const PasswordEditStep = React.memo(
({ defaultData, isSubmitting, error, onUpdate, onMessageDismiss, onClose }) => {
const UserPasswordEditStep = React.memo(
({
defaultData,
isSubmitting,
error,
usePasswordConfirmation,
onUpdate,
onMessageDismiss,
onBack,
onClose,
}) => {
const [t] = useTranslation();
const wasSubmitting = usePrevious(isSubmitting);
@@ -52,13 +61,13 @@ const PasswordEditStep = React.memo(
return;
}
if (!data.currentPassword) {
if (usePasswordConfirmation && !data.currentPassword) {
currentPasswordField.current.focus();
return;
}
onUpdate(data);
}, [onUpdate, data]);
onUpdate(usePasswordConfirmation ? data : omit(data, 'currentPassword'));
}, [usePasswordConfirmation, onUpdate, data]);
useEffect(() => {
passwordField.current.select();
@@ -84,7 +93,7 @@ const PasswordEditStep = React.memo(
return (
<>
<Popup.Header>
<Popup.Header onBack={onBack}>
{t('common.editPassword', {
context: 'title',
})}
@@ -111,6 +120,8 @@ const PasswordEditStep = React.memo(
className={styles.field}
onChange={handleFieldChange}
/>
{usePasswordConfirmation && (
<>
<div className={styles.text}>{t('common.currentPassword')}</div>
<Input.Password
fluid
@@ -120,6 +131,8 @@ const PasswordEditStep = React.memo(
className={styles.field}
onChange={handleFieldChange}
/>
</>
)}
<Button
positive
content={t('action.save')}
@@ -133,17 +146,21 @@ const PasswordEditStep = React.memo(
},
);
PasswordEditStep.propTypes = {
UserPasswordEditStep.propTypes = {
defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
isSubmitting: PropTypes.bool.isRequired,
error: PropTypes.object, // eslint-disable-line react/forbid-prop-types
usePasswordConfirmation: PropTypes.bool,
onUpdate: PropTypes.func.isRequired,
onMessageDismiss: PropTypes.func.isRequired,
onBack: PropTypes.func,
onClose: PropTypes.func.isRequired,
};
PasswordEditStep.defaultProps = {
UserPasswordEditStep.defaultProps = {
error: undefined,
usePasswordConfirmation: false,
onBack: undefined,
};
export default withPopup(PasswordEditStep);
export default UserPasswordEditStep;
@@ -0,0 +1,3 @@
import UserPasswordEditStep from './UserPasswordEditStep';
export default UserPasswordEditStep;
@@ -3,12 +3,12 @@ import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, Divider, Header, Tab } from 'semantic-ui-react';
import InformationEdit from './InformationEdit';
import AvatarEditPopup from './AvatarEditPopup';
import UsernameEditPopup from './UsernameEditPopup';
import EmailEditPopup from './EmailEditPopup';
import PasswordEditPopup from './PasswordEditPopup';
import User from '../../User';
import UserInformationEdit from '../../UserInformationEdit';
import UserUsernameEditPopup from '../../UserUsernameEditPopup';
import UserEmailEditPopup from '../../UserEmailEditPopup';
import UserPasswordEditPopup from '../../UserPasswordEditPopup';
import styles from './AccountPane.module.scss';
@@ -52,7 +52,7 @@ const AccountPane = React.memo(
</AvatarEditPopup>
<br />
<br />
<InformationEdit
<UserInformationEdit
defaultData={{
name,
phone,
@@ -68,7 +68,8 @@ const AccountPane = React.memo(
</Header>
</Divider>
<div className={styles.action}>
<UsernameEditPopup
<UserUsernameEditPopup
usePasswordConfirmation
defaultData={usernameUpdateForm.data}
username={username}
isSubmitting={usernameUpdateForm.isSubmitting}
@@ -81,10 +82,11 @@ const AccountPane = React.memo(
context: 'title',
})}
</Button>
</UsernameEditPopup>
</UserUsernameEditPopup>
</div>
<div className={styles.action}>
<EmailEditPopup
<UserEmailEditPopup
usePasswordConfirmation
defaultData={emailUpdateForm.data}
email={email}
isSubmitting={emailUpdateForm.isSubmitting}
@@ -97,10 +99,11 @@ const AccountPane = React.memo(
context: 'title',
})}
</Button>
</EmailEditPopup>
</UserEmailEditPopup>
</div>
<div className={styles.action}>
<PasswordEditPopup
<UserPasswordEditPopup
usePasswordConfirmation
defaultData={passwordUpdateForm.data}
isSubmitting={passwordUpdateForm.isSubmitting}
error={passwordUpdateForm.error}
@@ -112,7 +115,7 @@ const AccountPane = React.memo(
context: 'title',
})}
</Button>
</PasswordEditPopup>
</UserPasswordEditPopup>
</div>
</Tab.Pane>
);
@@ -1,5 +0,0 @@
:global(#app) {
.field {
margin-bottom: 8px;
}
}
@@ -0,0 +1,5 @@
import { withPopup } from '../lib/popup';
import UserUsernameEditStep from './UserUsernameEditStep';
export default withPopup(UserUsernameEditStep);
@@ -1,15 +1,15 @@
import omit from 'lodash/omit';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, Form, Message } from 'semantic-ui-react';
import { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks';
import { withPopup } from '../../../lib/popup';
import { Input, Popup } from '../../../lib/custom-ui';
import { useDidUpdate, usePrevious, useToggle } from '../../lib/hooks';
import { Input, Popup } from '../../lib/custom-ui';
import { useForm } from '../../../hooks';
import { isUsername } from '../../../utils/validator';
import { useForm } from '../../hooks';
import { isUsername } from '../../utils/validator';
import styles from './UsernameEditPopup.module.scss';
import styles from './UserUsernameEditStep.module.scss';
const createMessage = (error) => {
if (!error) {
@@ -35,8 +35,18 @@ const createMessage = (error) => {
}
};
const UsernameEditStep = React.memo(
({ defaultData, username, isSubmitting, error, onUpdate, onMessageDismiss, onClose }) => {
const UserUsernameEditStep = React.memo(
({
defaultData,
username,
isSubmitting,
error,
usePasswordConfirmation,
onUpdate,
onMessageDismiss,
onBack,
onClose,
}) => {
const [t] = useTranslation();
const wasSubmitting = usePrevious(isSubmitting);
@@ -58,7 +68,7 @@ const UsernameEditStep = React.memo(
username: data.username.trim() || null,
};
if (cleanData.username && !isUsername(cleanData.username)) {
if (!cleanData.username || !isUsername(cleanData.username)) {
usernameField.current.select();
return;
}
@@ -68,13 +78,13 @@ const UsernameEditStep = React.memo(
return;
}
if (!cleanData.currentPassword) {
if (usePasswordConfirmation && !cleanData.currentPassword) {
currentPasswordField.current.focus();
return;
}
onUpdate(cleanData);
}, [username, onUpdate, onClose, data]);
onUpdate(usePasswordConfirmation ? cleanData : omit(cleanData, 'currentPassword'));
}, [username, usePasswordConfirmation, onUpdate, onClose, data]);
useEffect(() => {
usernameField.current.select();
@@ -110,7 +120,7 @@ const UsernameEditStep = React.memo(
return (
<>
<Popup.Header>
<Popup.Header onBack={onBack}>
{t('common.editUsername', {
context: 'title',
})}
@@ -138,6 +148,8 @@ const UsernameEditStep = React.memo(
className={styles.field}
onChange={handleFieldChange}
/>
{usePasswordConfirmation && (
<>
<div className={styles.text}>{t('common.currentPassword')}</div>
<Input.Password
fluid
@@ -147,6 +159,8 @@ const UsernameEditStep = React.memo(
className={styles.field}
onChange={handleFieldChange}
/>
</>
)}
<Button
positive
content={t('action.save')}
@@ -160,19 +174,23 @@ const UsernameEditStep = React.memo(
},
);
UsernameEditStep.propTypes = {
UserUsernameEditStep.propTypes = {
defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
username: PropTypes.string,
isSubmitting: PropTypes.bool.isRequired,
error: PropTypes.object, // eslint-disable-line react/forbid-prop-types
usePasswordConfirmation: PropTypes.bool,
onUpdate: PropTypes.func.isRequired,
onMessageDismiss: PropTypes.func.isRequired,
onBack: PropTypes.func,
onClose: PropTypes.func.isRequired,
};
UsernameEditStep.defaultProps = {
UserUsernameEditStep.defaultProps = {
username: undefined,
error: undefined,
usePasswordConfirmation: false,
onBack: undefined,
};
export default withPopup(UsernameEditStep);
export default UserUsernameEditStep;
@@ -0,0 +1,12 @@
:global(#app) {
.field {
margin-bottom: 8px;
}
.text {
color: #444444;
font-size: 12px;
font-weight: bold;
padding-bottom: 6px;
}
}
@@ -0,0 +1,3 @@
import UserUsernameEditStep from './UserUsernameEditStep';
export default UserUsernameEditStep;
-56
View File
@@ -1,56 +0,0 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, Radio, Table } from 'semantic-ui-react';
import DeletePopup from '../DeletePopup';
import styles from './Item.module.scss';
const Item = React.memo(({ name, username, email, isAdmin, onUpdate, onDelete }) => {
const [t] = useTranslation();
const handleIsAdminChange = useCallback(() => {
onUpdate({
isAdmin: !isAdmin,
});
}, [isAdmin, onUpdate]);
return (
<Table.Row>
<Table.Cell>{name}</Table.Cell>
<Table.Cell>{username || '-'}</Table.Cell>
<Table.Cell>{email}</Table.Cell>
<Table.Cell collapsing>
<Radio toggle checked={isAdmin} onChange={handleIsAdminChange} />
</Table.Cell>
<Table.Cell collapsing>
<DeletePopup
title={t('common.deleteUser', {
context: 'title',
})}
content={t('common.areYouSureYouWantToDeleteThisUser')}
buttonContent={t('action.deleteUser')}
onConfirm={onDelete}
>
<Button content={t('action.delete')} className={styles.button} />
</DeletePopup>
</Table.Cell>
</Table.Row>
);
});
Item.propTypes = {
name: PropTypes.string.isRequired,
username: PropTypes.string,
email: PropTypes.string.isRequired,
isAdmin: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
};
Item.defaultProps = {
username: undefined,
};
export default Item;
@@ -0,0 +1,181 @@
import pick from 'lodash/pick';
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Menu } from 'semantic-ui-react';
import { withPopup } from '../../../lib/popup';
import { Popup } from '../../../lib/custom-ui';
import { useSteps } from '../../../hooks';
import UserInformationEditStep from '../../UserInformationEditStep';
import UserUsernameEditStep from '../../UserUsernameEditStep';
import UserEmailEditStep from '../../UserEmailEditStep';
import UserPasswordEditStep from '../../UserPasswordEditStep';
import DeleteStep from '../../DeleteStep';
import styles from './ActionsPopup.module.scss';
const StepTypes = {
EDIT_INFORMATION: 'EDIT_INFORMATION',
EDIT_USERNAME: 'EDIT_USERNAME',
EDIT_EMAIL: 'EDIT_EMAIL',
EDIT_PASSWORD: 'EDIT_PASSWORD',
DELETE: 'DELETE',
};
const ActionsStep = React.memo(
({
user,
onUpdate,
onUsernameUpdate,
onUsernameUpdateMessageDismiss,
onEmailUpdate,
onEmailUpdateMessageDismiss,
onPasswordUpdate,
onPasswordUpdateMessageDismiss,
onDelete,
onClose,
}) => {
const [t] = useTranslation();
const [step, openStep, handleBack] = useSteps();
const handleEditInformationClick = useCallback(() => {
openStep(StepTypes.EDIT_INFORMATION);
}, [openStep]);
const handleEditUsernameClick = useCallback(() => {
openStep(StepTypes.EDIT_USERNAME);
}, [openStep]);
const handleEditEmailClick = useCallback(() => {
openStep(StepTypes.EDIT_EMAIL);
}, [openStep]);
const handleEditPasswordClick = useCallback(() => {
openStep(StepTypes.EDIT_PASSWORD);
}, [openStep]);
const handleDeleteClick = useCallback(() => {
openStep(StepTypes.DELETE);
}, [openStep]);
if (step) {
switch (step.type) {
case StepTypes.EDIT_INFORMATION:
return (
<UserInformationEditStep
defaultData={pick(user, ['name', 'phone', 'organization'])}
onUpdate={onUpdate}
onBack={handleBack}
onClose={onClose}
/>
);
case StepTypes.EDIT_USERNAME:
return (
<UserUsernameEditStep
defaultData={user.usernameUpdateForm.data}
username={user.username}
isSubmitting={user.usernameUpdateForm.isSubmitting}
error={user.usernameUpdateForm.error}
onUpdate={onUsernameUpdate}
onMessageDismiss={onUsernameUpdateMessageDismiss}
onBack={handleBack}
onClose={onClose}
/>
);
case StepTypes.EDIT_EMAIL:
return (
<UserEmailEditStep
defaultData={user.emailUpdateForm.data}
email={user.email}
isSubmitting={user.emailUpdateForm.isSubmitting}
error={user.emailUpdateForm.error}
onUpdate={onEmailUpdate}
onMessageDismiss={onEmailUpdateMessageDismiss}
onBack={handleBack}
onClose={onClose}
/>
);
case StepTypes.EDIT_PASSWORD:
return (
<UserPasswordEditStep
defaultData={user.passwordUpdateForm.data}
isSubmitting={user.passwordUpdateForm.isSubmitting}
error={user.emailUpdateForm.error}
onUpdate={onPasswordUpdate}
onMessageDismiss={onPasswordUpdateMessageDismiss}
onBack={handleBack}
onClose={onClose}
/>
);
case StepTypes.DELETE:
return (
<DeleteStep
title={t('common.deleteUser', {
context: 'title',
})}
content={t('common.areYouSureYouWantToDeleteThisUser')}
buttonContent={t('action.deleteUser')}
onConfirm={onDelete}
onBack={handleBack}
/>
);
default:
}
}
return (
<>
<Popup.Header>
{t('common.userActions', {
context: 'title',
})}
</Popup.Header>
<Popup.Content>
<Menu secondary vertical className={styles.menu}>
<Menu.Item className={styles.menuItem} onClick={handleEditInformationClick}>
{t('action.editInformation', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleEditUsernameClick}>
{t('action.editUsername', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleEditEmailClick}>
{t('action.editEmail', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleEditPasswordClick}>
{t('action.editPassword', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
{t('action.deleteUser', {
context: 'title',
})}
</Menu.Item>
</Menu>
</Popup.Content>
</>
);
},
);
ActionsStep.propTypes = {
user: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
onUpdate: PropTypes.func.isRequired,
onUsernameUpdate: PropTypes.func.isRequired,
onUsernameUpdateMessageDismiss: PropTypes.func.isRequired,
onEmailUpdate: PropTypes.func.isRequired,
onEmailUpdateMessageDismiss: PropTypes.func.isRequired,
onPasswordUpdate: PropTypes.func.isRequired,
onPasswordUpdateMessageDismiss: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};
export default withPopup(ActionsStep);
@@ -0,0 +1,11 @@
:global(#app) {
.menu {
margin: -7px -12px -5px;
width: calc(100% + 24px);
}
.menuItem {
margin: 0;
padding-left: 14px;
}
}
+103
View File
@@ -0,0 +1,103 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { Button, Icon, Radio, Table } from 'semantic-ui-react';
import ActionsPopup from './ActionsPopup';
import styles from './Item.module.scss';
const Item = React.memo(
({
email,
username,
name,
organization,
phone,
isAdmin,
emailUpdateForm,
passwordUpdateForm,
usernameUpdateForm,
onUpdate,
onUsernameUpdate,
onUsernameUpdateMessageDismiss,
onEmailUpdate,
onEmailUpdateMessageDismiss,
onPasswordUpdate,
onPasswordUpdateMessageDismiss,
onDelete,
}) => {
const handleIsAdminChange = useCallback(() => {
onUpdate({
isAdmin: !isAdmin,
});
}, [isAdmin, onUpdate]);
return (
<Table.Row>
<Table.Cell>{name}</Table.Cell>
<Table.Cell>{username || '-'}</Table.Cell>
<Table.Cell>{email}</Table.Cell>
<Table.Cell collapsing>
<Radio toggle checked={isAdmin} onChange={handleIsAdminChange} />
</Table.Cell>
<Table.Cell collapsing>
<ActionsPopup
user={{
email,
username,
name,
organization,
phone,
isAdmin,
emailUpdateForm,
passwordUpdateForm,
usernameUpdateForm,
}}
onUpdate={onUpdate}
onUsernameUpdate={onUsernameUpdate}
onUsernameUpdateMessageDismiss={onUsernameUpdateMessageDismiss}
onEmailUpdate={onEmailUpdate}
onEmailUpdateMessageDismiss={onEmailUpdateMessageDismiss}
onPasswordUpdate={onPasswordUpdate}
onPasswordUpdateMessageDismiss={onPasswordUpdateMessageDismiss}
onDelete={onDelete}
>
<Button className={styles.button}>
<Icon fitted name="pencil" />
</Button>
</ActionsPopup>
</Table.Cell>
</Table.Row>
);
},
);
Item.propTypes = {
email: PropTypes.string.isRequired,
username: PropTypes.string,
name: PropTypes.string.isRequired,
organization: PropTypes.string,
phone: PropTypes.string,
isAdmin: PropTypes.bool.isRequired,
/* eslint-disable react/forbid-prop-types */
emailUpdateForm: PropTypes.object.isRequired,
passwordUpdateForm: PropTypes.object.isRequired,
usernameUpdateForm: PropTypes.object.isRequired,
/* eslint-enable react/forbid-prop-types */
onUpdate: PropTypes.func.isRequired,
onUsernameUpdate: PropTypes.func.isRequired,
onUsernameUpdateMessageDismiss: PropTypes.func.isRequired,
onEmailUpdate: PropTypes.func.isRequired,
onEmailUpdateMessageDismiss: PropTypes.func.isRequired,
onPasswordUpdate: PropTypes.func.isRequired,
onPasswordUpdateMessageDismiss: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
};
Item.defaultProps = {
username: undefined,
organization: undefined,
phone: undefined,
};
export default Item;
@@ -0,0 +1,3 @@
import Item from './Item';
export default Item;
@@ -6,7 +6,19 @@ import { Button, Modal, Table } from 'semantic-ui-react';
import UserAddPopupContainer from '../../containers/UserAddPopupContainer';
import Item from './Item';
const UsersModal = React.memo(({ items, onUpdate, onDelete, onClose }) => {
const UsersModal = React.memo(
({
items,
onUpdate,
onUsernameUpdate,
onUsernameUpdateMessageDismiss,
onEmailUpdate,
onEmailUpdateMessageDismiss,
onPasswordUpdate,
onPasswordUpdateMessageDismiss,
onDelete,
onClose,
}) => {
const [t] = useTranslation();
const handleUpdate = useCallback(
@@ -16,6 +28,48 @@ const UsersModal = React.memo(({ items, onUpdate, onDelete, onClose }) => {
[onUpdate],
);
const handleUsernameUpdate = useCallback(
(id, data) => {
onUsernameUpdate(id, data);
},
[onUsernameUpdate],
);
const handleUsernameUpdateMessageDismiss = useCallback(
(id) => {
onUsernameUpdateMessageDismiss(id);
},
[onUsernameUpdateMessageDismiss],
);
const handleEmailUpdate = useCallback(
(id, data) => {
onEmailUpdate(id, data);
},
[onEmailUpdate],
);
const handleEmailUpdateMessageDismiss = useCallback(
(id) => {
onEmailUpdateMessageDismiss(id);
},
[onEmailUpdateMessageDismiss],
);
const handlePasswordUpdate = useCallback(
(id, data) => {
onPasswordUpdate(id, data);
},
[onPasswordUpdate],
);
const handlePasswordUpdateMessageDismiss = useCallback(
(id) => {
onPasswordUpdateMessageDismiss(id);
},
[onPasswordUpdateMessageDismiss],
);
const handleDelete = useCallback(
(id) => {
onDelete(id);
@@ -45,11 +99,22 @@ const UsersModal = React.memo(({ items, onUpdate, onDelete, onClose }) => {
{items.map((item) => (
<Item
key={item.id}
name={item.name}
username={item.username}
email={item.email}
username={item.username}
name={item.name}
organization={item.organization}
phone={item.phone}
isAdmin={item.isAdmin}
emailUpdateForm={item.emailUpdateForm}
passwordUpdateForm={item.passwordUpdateForm}
usernameUpdateForm={item.usernameUpdateForm}
onUpdate={(data) => handleUpdate(item.id, data)}
onUsernameUpdate={(data) => handleUsernameUpdate(item.id, data)}
onUsernameUpdateMessageDismiss={() => handleUsernameUpdateMessageDismiss(item.id)}
onEmailUpdate={(data) => handleEmailUpdate(item.id, data)}
onEmailUpdateMessageDismiss={() => handleEmailUpdateMessageDismiss(item.id)}
onPasswordUpdate={(data) => handlePasswordUpdate(item.id, data)}
onPasswordUpdateMessageDismiss={() => handlePasswordUpdateMessageDismiss(item.id)}
onDelete={() => handleDelete(item.id)}
/>
))}
@@ -63,11 +128,18 @@ const UsersModal = React.memo(({ items, onUpdate, onDelete, onClose }) => {
</Modal.Actions>
</Modal>
);
});
},
);
UsersModal.propTypes = {
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
onUpdate: PropTypes.func.isRequired,
onUsernameUpdate: PropTypes.func.isRequired,
onUsernameUpdateMessageDismiss: PropTypes.func.isRequired,
onEmailUpdate: PropTypes.func.isRequired,
onEmailUpdateMessageDismiss: PropTypes.func.isRequired,
onPasswordUpdate: PropTypes.func.isRequired,
onPasswordUpdateMessageDismiss: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};
+6
View File
@@ -31,11 +31,17 @@ export default {
USER_UPDATE: `${PREFIX}/USER_UPDATE`,
CURRENT_USER_UPDATE: `${PREFIX}/CURRENT_USER_UPDATE`,
USER_UPDATE_HANDLE: `${PREFIX}/USER_UPDATE_HANDLE`,
USER_EMAIL_UPDATE: `${PREFIX}/USER_EMAIL_UPDATE`,
CURRENT_USER_EMAIL_UPDATE: `${PREFIX}/CURRENT_USER_EMAIL_UPDATE`,
USER_EMAIL_UPDATE_ERROR_CLEAR: `${PREFIX}/USER_EMAIL_UPDATE_ERROR_CLEAR`,
CURRENT_USER_EMAIL_UPDATE_ERROR_CLEAR: `${PREFIX}/CURRENT_USER_EMAIL_UPDATE_ERROR_CLEAR`,
USER_PASSWORD_UPDATE: `${PREFIX}/USER_PASSWORD_UPDATE`,
CURRENT_USER_PASSWORD_UPDATE: `${PREFIX}/CURRENT_USER_PASSWORD_UPDATE`,
USER_PASSWORD_UPDATE_ERROR_CLEAR: `${PREFIX}/USER_PASSWORD_UPDATE_ERROR_CLEAR`,
CURRENT_USER_PASSWORD_UPDATE_ERROR_CLEAR: `${PREFIX}/CURRENT_USER_PASSWORD_UPDATE_ERROR_CLEAR`,
USER_USERNAME_UPDATE: `${PREFIX}/USER_USERNAME_UPDATE`,
CURRENT_USER_USERNAME_UPDATE: `${PREFIX}/CURRENT_USER_USERNAME_UPDATE`,
USER_USERNAME_UPDATE_ERROR_CLEAR: `${PREFIX}/USER_USERNAME_UPDATE_ERROR_CLEAR`,
CURRENT_USER_USERNAME_UPDATE_ERROR_CLEAR: `${PREFIX}/CURRENT_USER_USERNAME_UPDATE_ERROR_CLEAR`,
CURRENT_USER_AVATAR_UPDATE: `${PREFIX}/CURRENT_USER_AVATAR_UPDATE`,
USER_DELETE: `${PREFIX}/USER_DELETE`,
+17 -1
View File
@@ -2,7 +2,17 @@ import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { usersExceptCurrentSelector } from '../selectors';
import { closeModal, deleteUser, updateUser } from '../actions/entry';
import {
clearUserEmailUpdateError,
clearUserPasswordUpdateError,
clearUserUsernameUpdateError,
closeModal,
deleteUser,
updateUser,
updateUserEmail,
updateUserPassword,
updateUserUsername,
} from '../actions/entry';
import UsersModal from '../components/UsersModal';
const mapStateToProps = (state) => {
@@ -17,6 +27,12 @@ const mapDispatchToProps = (dispatch) =>
bindActionCreators(
{
onUpdate: updateUser,
onUsernameUpdate: updateUserUsername,
onUsernameUpdateMessageDismiss: clearUserUsernameUpdateError,
onEmailUpdate: updateUserEmail,
onEmailUpdateMessageDismiss: clearUserEmailUpdateError,
onPasswordUpdate: updateUserPassword,
onPasswordUpdateMessageDismiss: clearUserPasswordUpdateError,
onDelete: deleteUser,
onClose: closeModal,
},
@@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import remarkBreaks from 'remark-breaks';
import './Markdown.module.scss'; // FIXME: import as styles?
@@ -35,7 +36,7 @@ const Markdown = React.memo(({ linkStopPropagation, ...props }) => {
<ReactMarkdown
{...props}
components={components}
remarkPlugins={[remarkGfm]}
remarkPlugins={[remarkGfm, remarkBreaks]}
className="markdown-body"
/>
/* eslint-enable react/jsx-props-no-spreading */
+2
View File
@@ -70,6 +70,7 @@ export default {
editBoard_title: 'Edit Board',
editDueDate_title: 'Edit Due Date',
editEmail_title: 'Edit E-mail',
editInformation_title: 'Edit Information',
editLabel_title: 'Edit Label',
editPassword_title: 'Edit Password',
editTimer_title: 'Edit Timer',
@@ -178,6 +179,7 @@ export default {
editDueDate_title: 'Edit Due Date',
editDescription_title: 'Edit Description',
editEmail_title: 'Edit E-mail',
editInformation_title: 'Edit Information',
editPassword_title: 'Edit Password',
editTimer_title: 'Edit Timer',
editTitle_title: 'Edit Title',
+4 -2
View File
@@ -11,7 +11,8 @@ import frLogin from './fr/login';
import jaLogin from './ja/login';
import plLogin from './pl/login';
import ruLogin from './ru/login';
import seLogin from './se/login';
import skLogin from './sk/login';
import svLogin from './sv/login';
import uzLogin from './uz/login';
import zhLogin from './zh/login';
@@ -25,7 +26,8 @@ const localePairs = [
['ja', jaLogin],
['pl', plLogin],
['ru', ruLogin],
['se', seLogin],
['sk', skLogin],
['sv', svLogin],
['uz', uzLogin],
['zh', zhLogin],
];
+215
View File
@@ -0,0 +1,215 @@
import dateFns from 'date-fns/locale/sk';
export default {
dateFns,
format: {
date: 'd.M.yyyy',
time: 'p',
dateTime: '$t(format:date) $t(format:time)',
longDate: 'd MMM',
longDateTime: "d MMMM 'v' p",
},
translation: {
common: {
account: 'Účet',
actions: 'Akcia',
addAttachment_title: 'Pridať prílohu',
addComment: 'Pridať komentár',
addManager_title: 'Pridať správcu',
addMember_title: 'Pridať člena',
addUser_title: 'Pridať používateľa',
administrator: 'Administrátor',
all: 'Všetko',
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'Všetky zmeny budú automaticky uložené<br />po obnovení spojenia',
areYouSureYouWantToDeleteThisAttachment: 'Naozaj chcete zmazať túto prílohu?',
areYouSureYouWantToDeleteThisBoard: 'Naozaj chcete zmazať túto tabuľu?',
areYouSureYouWantToDeleteThisCard: 'Naozaj chcete zmazať túto kartu?',
areYouSureYouWantToDeleteThisComment: 'Naozaj chcete zmazať tento komentár?',
areYouSureYouWantToDeleteThisLabel: 'Naozaj chcete zmazať tento štítok?',
areYouSureYouWantToDeleteThisList: 'Naozaj chcete zmazať tento zoznam?',
areYouSureYouWantToDeleteThisProject: 'Naozaj chcete zmazať tento projekt?',
areYouSureYouWantToDeleteThisTask: 'Naozaj chcete zmazať túto úlohu?',
areYouSureYouWantToDeleteThisUser: 'Naozaj chcete zmazať tohoto používateľa?',
areYouSureYouWantToLeaveBoard: 'Naozaj chcete opustiť túto tabuľu?',
areYouSureYouWantToLeaveProject: 'Naozaj chcete opustiť tento projekt?',
areYouSureYouWantToRemoveThisManagerFromProject:
'Naozaj chcete zmazať daného správcu tohto projektu?',
areYouSureYouWantToRemoveThisMemberFromBoard:
'Naozaj chcete odstrániť tohoto člena z tabule?',
attachment: 'Príloha',
attachments: 'Prílohy',
authentication: 'Overenie',
background: 'Pozadie',
board: 'Tabuľa',
boardNotFound_title: 'Tabuľa neexistuje',
cardActions_title: 'Akcie na karte',
cardNotFound_title: 'Karta neexistuje',
cardOrActionAreDeleted: 'Karta alebo akcia sú zmazané',
color: 'Farba',
createBoard_title: 'Vytvoriť tabuľu',
createLabel_title: 'Vytvoriť štítok',
createNewOneOrSelectExistingOne: 'Vytvoriť nový alebo vyberte<br />už existujúci',
createProject_title: 'Vytvoriť projekt',
createTextFile_title: 'Vytvoriť textový súbor',
currentPassword: 'Aktuálne heslo',
dangerZone_title: 'Nebezpečná zóna',
date: 'Dátum',
dueDate_title: 'Termín do',
deleteAttachment_title: 'Zmazať prílohu',
deleteBoard_title: 'Zmazať tabuľu',
deleteCard_title: 'Zmazať kartu',
deleteComment_title: 'Zmazať komentár',
deleteLabel_title: 'Zmazať štítok',
deleteList_title: 'Zmazať zoznam',
deleteProject_title: 'Zmazať projekt',
deleteTask_title: 'Zmazať úlohu',
deleteUser_title: 'Zmazať používateľa',
description: 'Popis',
dropFileToUpload: 'Potiahnutím nahraj súbor',
editAttachment_title: 'Upraviť prílohu',
editAvatar_title: 'Upraviť avatar',
editBoard_title: 'Upraviť tabuľu',
editDueDate_title: 'Upraviť Termín do',
editEmail_title: 'Upraviť e-mail',
editInformation_title: 'Upraviť informácie',
editLabel_title: 'Upraviť štítok',
editPassword_title: 'Upraviť heslo',
editTimer_title: 'Upraviť časovač',
editUsername_title: 'Upraviť používateľské meno',
email: 'E-mail',
emailAlreadyInUse: 'E-mail je už použitý',
enterCardTitle: 'Vlož názov karty...',
enterDescription: 'Vlož popis...',
enterFilename: 'Vlož názov súboru',
enterListTitle: 'Vlož názov zoznamu...',
enterProjectTitle: 'Vlož názov projektu',
enterTaskDescription: 'Vlož popis úlohy...',
filterByLabels_title: 'Filtruj podľa štítku',
filterByMembers_title: 'Filtruj podľa člena',
fromComputer_title: 'Z počítača',
general: 'Všeobecné',
hours: 'Hodiny',
invalidCurrentPassword: 'Neplatné aktuálne heslo',
labels: 'Štítky',
leaveBoard_title: 'Opustiť tabuľu',
leaveProject_title: 'Opustiť projekt',
list: 'Zoznam',
listActions_title: 'Zoznam akcií',
managers: 'Správcovia',
members: 'Členovia',
minutes: 'Minúty',
moveCard_title: 'Presunúť kartu',
name: 'Meno',
newEmail: 'Nový e-mail',
newPassword: 'Nové heslo',
newUsername: 'Nové používateľské meno',
noConnectionToServer: 'Nie je spojenie k serveru',
noBoards: 'Žiadne tabule',
noLists: 'Žiadne zoznamy',
noProjects: 'Žiadne projekty',
notifications: 'Oznámenia',
noUnreadNotifications: 'Žiadne neprečítané oznámenia',
openBoard_title: 'Otvoriť tabuľu',
optional_inline: 'voliteľné',
organization: 'Spoločnosť',
phone: 'Telefón',
preferences: 'Voľby',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: stlačte Ctrl-V (Cmd-V na Mac) pre vloženie prílohy zo schránky.',
project: 'Projekt',
projectNotFound_title: 'Projekt neexistuje',
removeManager_title: 'Odstrániť správcu',
removeMember_title: 'Odstrániť člena',
seconds: 'Sekúnd',
selectBoard: 'Vybrať tabuľu',
selectList: 'Vybrať zoznam',
selectProject: 'Vybrať projekt',
settings: 'Nastavenia',
subscribeToMyOwnCardsByDefault: 'Predvolene odoberať vlastné karty',
taskActions_title: 'Akcie na úlohe',
tasks: 'Úlohy',
time: 'Čas',
timer: 'Časovač',
title: 'Názov',
userActions_title: 'Akcie na používateľovi',
userAddedThisCardToList: '<0>{{user}}</0><1> pridal kartu do {{list}}</1>',
userLeftNewCommentToCard: '{{user}} zanechal nový komentár «{{comment}}» k <2>{{card}}</2>',
userMovedCardFromListToList: '{{user}} presunul <2>{{card}}</2> z {{fromList}} do {{toList}}',
userMovedThisCardFromListToList:
'<0>{{user}}</0><1> presunul túto kartu z {{fromList}} do {{toList}}</1>',
username: 'Používateľské meno',
usernameAlreadyInUse: 'Používateľské meno je zabrané',
users: 'Používatelia',
writeComment: 'Napísať komentár...',
},
action: {
addAnotherCard: 'Pridať ďalšiu kartu',
addAnotherList: 'Pridať ďalší zoznam',
addAnotherTask: 'Pridať ďalšiu úlohu',
addCard: 'Pridať kartu',
addCard_title: 'Pridať kartu',
addComment: 'Pridať komentár',
addList: 'Pridať zoznam',
addMoreDetailedDescription: 'Pridať ďalší detailný popis',
addTask: 'Pridať úlohu',
addToCard: 'Pridať na kartu',
addUser: 'Pridať používateľa',
createBoard: 'Vytvoriť tabuľu',
createFile: 'Vytvoriť súbor',
createLabel: 'Vytvoriť štítok',
createNewLabel: 'Vytvoriť nový štítok',
createProject: 'Vytvoriť projekt',
delete: 'Zmazať',
deleteAttachment: 'Zmazať prílohu',
deleteAvatar: 'Zmazať avatar',
deleteBoard: 'Zmazať tabuľu',
deleteCard: 'Zmazať kartu',
deleteCard_title: 'Zmazať kartu',
deleteComment: 'Zmazať komentár',
deleteImage: 'Zmazať obrázok',
deleteLabel: 'Zmazať štítok',
deleteList: 'Zmazať zoznam',
deleteList_title: 'Zmazať zoznam',
deleteProject: 'Zmazať projekt',
deleteProject_title: 'Zmazať projekt',
deleteTask: 'Zmazať úlohu',
deleteTask_title: 'Zmazať úlohu',
deleteUser: 'Zmazať používateľa',
edit: 'Upraviť',
editDueDate_title: 'Upraviť termín do',
editDescription_title: 'Upraviť popis',
editEmail_title: 'Upraviť e-mail',
editInformation_title: 'Upraviť informácie',
editPassword_title: 'Upraviť heslo',
editTimer_title: 'Upraviť časovač',
editTitle_title: 'Upraviť názov',
editUsername_title: 'Upraviť používateľské meno',
leaveBoard: 'Opustiť tabuľu',
leaveProject: 'Opustiť projekt',
logOut_title: 'Odhlásiť sa',
makeCover_title: 'Vytvoriť obal',
move: 'Presunúť',
moveCard_title: 'Presunúť kartu',
remove: 'Odstrániť',
removeBackground: 'Odstrániť pozadie',
removeCover_title: 'Odstrániť obal',
removeFromBoard: 'Odstrániť z tabule',
removeFromProject: 'Odstrániť z projektu',
removeManager: 'Odstrániť správcu',
removeMember: 'Odstrániť člena',
save: 'Uložiť',
showAllAttachments: 'Zozbraziť všetky prílohy ({{hidden}} skryté)',
showFewerAttachments: 'Zobraziť menej príloh',
start: 'Start',
stop: 'Stop',
subscribe: 'Odoberať',
unsubscribe: 'Neodoberať',
uploadNewAvatar: 'Nahrať nový avatar',
uploadNewImage: 'Nahrať nový obrázok',
},
},
};
+20
View File
@@ -0,0 +1,20 @@
export default {
translation: {
common: {
emailOrUsername: 'E-mail alebo používateľské meno',
invalidEmailOrUsername: 'Nesprávny e-mail alebo používateľské meno',
invalidPassword: 'Nesprávne heslo',
logInToPlanka: 'Prihlásiť sa do Planka',
noInternetConnection: 'Bez pripojenia k internetu',
pageNotFound_title: 'Stránka neexistuje',
password: 'Heslo',
projectManagement: 'Správa projektu',
serverConnectionFailed: 'Pripojenie k serveru zlyhalo',
unknownError: 'Neznáma chyba, skúste to neskôr',
},
action: {
logIn: 'Prihlásiť sa',
},
},
};
@@ -1,4 +1,4 @@
import dateFns from 'date-fns/locale/se';
import dateFns from 'date-fns/locale/sv';
export default {
dateFns,
@@ -24,10 +24,12 @@ export default {
all: 'Alla',
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'Alla ändringar kommer att sparas automatiskt<br />så fort anslutningen är återställd',
areYouSureYouWantToDeleteThisAttachment: 'Är du säker på att du vill ta bort den här bilagan?',
areYouSureYouWantToDeleteThisAttachment:
'Är du säker på att du vill ta bort den här bilagan?',
areYouSureYouWantToDeleteThisBoard: 'Är du säker på att du vill ta bort den här tavlan?',
areYouSureYouWantToDeleteThisCard: 'Är du säker på att du vill ta bort det här kortet?',
areYouSureYouWantToDeleteThisComment: 'Är du säker på att du vill ta bort den här kommentaren?',
areYouSureYouWantToDeleteThisComment:
'Är du säker på att du vill ta bort den här kommentaren?',
areYouSureYouWantToDeleteThisLabel: 'Är du säker på att du vill ta bort den här etiketten?',
areYouSureYouWantToDeleteThisList: 'Är du säker på att du vill ta bort den här listan?',
areYouSureYouWantToDeleteThisProject: 'Är du säker på att du vill ta bort det här projektet?',
@@ -136,7 +138,8 @@ export default {
userActions_title: 'Användaråtgärder',
userAddedThisCardToList: '<0>{{user}}</0><1> lade till detta kort i {{list}}</1>',
userLeftNewCommentToCard: '{{user}} lämnade en ny kommentar «{{comment}}» på <2>{{card}}</2>',
userMovedCardFromListToList: '{{user}} flyttade <2>{{card}}</2> från {{fromList}} till {{toList}}',
userMovedCardFromListToList:
'{{user}} flyttade <2>{{card}}</2> från {{fromList}} till {{toList}}',
userMovedThisCardFromListToList:
'<0>{{user}}</0><1> flyttade detta kort från {{fromList}} till {{toList}}</1>',
username: 'Användarnamn',
+37
View File
@@ -35,6 +35,7 @@ export default class extends Model {
static fields = {
id: attr(),
email: attr(),
username: attr(),
name: attr(),
avatarUrl: attr(),
phone: attr(),
@@ -140,6 +141,18 @@ export default class extends Model {
break;
}
case ActionTypes.USER_EMAIL_UPDATE_ERROR_CLEAR: {
const userModel = User.withId(payload.id);
userModel.update({
emailUpdateForm: {
...userModel.emailUpdateForm,
error: null,
},
});
break;
}
case ActionTypes.USER_PASSWORD_UPDATE: {
const userModel = User.withId(payload.id);
@@ -174,6 +187,18 @@ export default class extends Model {
break;
}
case ActionTypes.USER_PASSWORD_UPDATE_ERROR_CLEAR: {
const userModel = User.withId(payload.id);
userModel.update({
passwordUpdateForm: {
...userModel.passwordUpdateForm,
error: null,
},
});
break;
}
case ActionTypes.USER_USERNAME_UPDATE: {
const userModel = User.withId(payload.id);
@@ -208,6 +233,18 @@ export default class extends Model {
break;
}
case ActionTypes.USER_USERNAME_UPDATE_ERROR_CLEAR: {
const userModel = User.withId(payload.id);
userModel.update({
usernameUpdateForm: {
...userModel.usernameUpdateForm,
error: null,
},
});
break;
}
case ActionTypes.USER_AVATAR_UPDATE:
User.withId(payload.id).update({
isAvatarUpdating: true,
+29 -5
View File
@@ -8,8 +8,16 @@ import {
clearCurrentUserPasswordUpdateErrorService,
clearCurrentUserUsernameUpdateErrorService,
clearUserCreateErrorService,
clearUserEmailUpdateErrorService,
clearUserPasswordUpdateErrorService,
clearUserUsernameUpdateErrorService,
createUserService,
deleteUserService,
handleUserCreateService,
handleUserDeleteService,
handleUserFromCardRemoveService,
handleUserToCardAddService,
handleUserUpdateService,
removeUserFromCardService,
removeUserFromCurrentCardService,
removeUserFromFilterInCurrentBoardService,
@@ -19,11 +27,9 @@ import {
updateCurrentUserPasswordService,
updateCurrentUserService,
updateCurrentUserUsernameService,
handleUserCreateService,
handleUserUpdateService,
handleUserDeleteService,
handleUserToCardAddService,
handleUserFromCardRemoveService,
updateUserEmailService,
updateUserPasswordService,
updateUserUsernameService,
} from '../services';
import EntryActionTypes from '../../../constants/EntryActionTypes';
@@ -43,21 +49,39 @@ export default function* userWatchers() {
takeEvery(EntryActionTypes.USER_UPDATE_HANDLE, ({ payload: { user } }) =>
handleUserUpdateService(user),
),
takeEvery(EntryActionTypes.USER_EMAIL_UPDATE, ({ payload: { id, data } }) =>
updateUserEmailService(id, data),
),
takeEvery(EntryActionTypes.CURRENT_USER_EMAIL_UPDATE, ({ payload: { data } }) =>
updateCurrentUserEmailService(data),
),
takeEvery(EntryActionTypes.USER_EMAIL_UPDATE_ERROR_CLEAR, ({ payload: { id } }) =>
clearUserEmailUpdateErrorService(id),
),
takeEvery(EntryActionTypes.CURRENT_USER_EMAIL_UPDATE_ERROR_CLEAR, () =>
clearCurrentUserEmailUpdateErrorService(),
),
takeEvery(EntryActionTypes.USER_PASSWORD_UPDATE, ({ payload: { id, data } }) =>
updateUserPasswordService(id, data),
),
takeEvery(EntryActionTypes.CURRENT_USER_PASSWORD_UPDATE, ({ payload: { data } }) =>
updateCurrentUserPasswordService(data),
),
takeEvery(EntryActionTypes.USER_PASSWORD_UPDATE_ERROR_CLEAR, ({ payload: { id } }) =>
clearUserPasswordUpdateErrorService(id),
),
takeEvery(EntryActionTypes.CURRENT_USER_PASSWORD_UPDATE_ERROR_CLEAR, () =>
clearCurrentUserPasswordUpdateErrorService(),
),
takeEvery(EntryActionTypes.USER_USERNAME_UPDATE, ({ payload: { id, data } }) =>
updateUserUsernameService(id, data),
),
takeEvery(EntryActionTypes.CURRENT_USER_USERNAME_UPDATE, ({ payload: { data } }) =>
updateCurrentUserUsernameService(data),
),
takeEvery(EntryActionTypes.USER_USERNAME_UPDATE_ERROR_CLEAR, ({ payload: { id } }) =>
clearUserUsernameUpdateErrorService(id),
),
takeEvery(EntryActionTypes.CURRENT_USER_USERNAME_UPDATE_ERROR_CLEAR, () =>
clearCurrentUserUsernameUpdateErrorService(),
),
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "planka",
"version": "0.1.0",
"version": "1.1.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "planka",
"version": "0.1.0",
"version": "1.1.5",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "planka",
"version": "1.1.3",
"version": "1.1.5",
"private": true,
"homepage": "https://plankanban.github.io/planka",
"repository": {