From 58e7238c59c1c034d4c79ee3833066c06e402fcb Mon Sep 17 00:00:00 2001 From: 1aerostorm Date: Sun, 23 Jun 2024 20:13:44 +0300 Subject: [PATCH] HF 30 - Private message groups --- src/components/modules/CreateGroup.jsx | 1 - .../modules/groups/GroupSettings.jsx | 213 ++++++++++++++++-- .../modules/groups/GroupSettings.scss | 14 ++ src/locales/ru-RU.json | 9 +- src/redux/FetchDataSaga.js | 1 + src/redux/TransactionSaga.js | 57 +++++ 6 files changed, 271 insertions(+), 24 deletions(-) diff --git a/src/components/modules/CreateGroup.jsx b/src/components/modules/CreateGroup.jsx index fdda26a6..889e5047 100644 --- a/src/components/modules/CreateGroup.jsx +++ b/src/components/modules/CreateGroup.jsx @@ -146,7 +146,6 @@ class CreateGroup extends React.Component { } }) }, 'active') - } goNext = (e, setFieldValue) => { diff --git a/src/components/modules/groups/GroupSettings.jsx b/src/components/modules/groups/GroupSettings.jsx index 61f74904..410b905c 100644 --- a/src/components/modules/groups/GroupSettings.jsx +++ b/src/components/modules/groups/GroupSettings.jsx @@ -1,4 +1,5 @@ import React from 'react' +import DropZone from 'react-dropzone' import {connect} from 'react-redux' import { Formik, Form, Field, ErrorMessage, } from 'formik' import { Map } from 'immutable' @@ -15,7 +16,10 @@ import Icon from 'app/components/elements/Icon' import LoadingIndicator from 'app/components/elements/LoadingIndicator' import DialogManager from 'app/components/elements/common/DialogManager' import { showLoginDialog } from 'app/components/dialogs/LoginDialog' +import { validateLogoStep } from 'app/components/modules/groups/GroupLogo' +import { validateAdminStep } from 'app/components/modules/groups/GroupAdmin' import { getGroupLogo, getGroupMeta, getGroupTitle } from 'app/utils/groups' +import { proxifyImageUrlWithStrip } from 'app/utils/ProxifyUrl' class GroupSettings extends React.Component { constructor(props) { @@ -28,7 +32,7 @@ class GroupSettings extends React.Component { componentDidMount() { const { currentGroup } = this.props const group = currentGroup.toJS() - const { name, member_list, privacy, json_metadata } = group + const { name, member_list, privacy, json_metadata, is_encrypted } = group const meta = getGroupMeta(json_metadata) let admin for (const mem of member_list) { @@ -38,10 +42,12 @@ class GroupSettings extends React.Component { break } const initialValues = { + name, title: meta.title, logo: meta.logo, admin, - privacy + privacy, + is_encrypted, } this.setState({ initialValues @@ -66,12 +72,81 @@ class GroupSettings extends React.Component { applyFieldValue('admin', value) } - validate = async () => { + uploadLogo = (file, name, { applyFieldValue }) => { + const { uploadImage } = this.props + this.setState({ uploading: true }) + uploadImage(file, progress => { + if (progress.url) { + applyFieldValue('logo', progress.url) + } + if (progress.error) { + const { error } = progress; + notify(error, 10000) + } + this.setState({ uploading: false }) + }) + } + + onDrop = (acceptedFiles, rejectedFiles, { applyFieldValue }) => { + const file = acceptedFiles[0] + + if (!file) { + if (rejectedFiles.length) { + DialogManager.alert( + tt('post_editor.please_insert_only_image_files') + ) + } + return + } + + this.uploadLogo(file, file.name, { applyFieldValue }) + }; + onPrivacyChange = (e, { applyFieldValue }) => { + applyFieldValue('privacy', e.target.value) } - onSubmit = async () => { + validate = async (values) => { + const errors = {} + if (!values.title) { + errors.title = tt('g.required') + } else if (values.title.length < 3) { + errors.title = tt('create_group_jsx.group_min_length') + } + await validateLogoStep(values, errors) + await validateAdminStep(values, errors) + return errors + } + + _onSubmit = async (values, actions) => { + const { currentUser } = this.props + const creator = currentUser.get('username') + + this.setState({ + submitError: '' + }) + showLoginDialog(creator, (res) => { + const password = res && res.password + if (!password) { + actions.setSubmitting(false) + return + } + this.props.privateGroup({ + creator, + password, + ...values, + onSuccess: () => { + actions.setSubmitting(false) + const { closeMe } = this.props + if (closeMe) closeMe() + }, + onError: (err, errStr) => { + this.setState({ submitError: errStr }) + actions.setSubmitting(false) + } + }) + }, 'active', false) } closeMe = (e) => { @@ -79,6 +154,18 @@ class GroupSettings extends React.Component { this.props.closeMe() } + _renderPreview = ({ values, errors }) => { + let { logo } = values + if (logo && !errors.logo) { + const size = '75x75' // main size of Userpic + logo = proxifyImageUrlWithStrip(logo, size); + return + {tt('group_settings_jsx.preview')} + + } + return null + } + render() { const { currentGroup } = this.props const group = currentGroup.toJS() @@ -87,7 +174,7 @@ class GroupSettings extends React.Component { const meta = getGroupMeta(json_metadata) const title = getGroupTitle(meta, name) - const { initialValues } = this.state + const { initialValues, submitError } = this.state let form if (!initialValues) { @@ -104,7 +191,7 @@ class GroupSettings extends React.Component { {({ handleSubmit, isSubmitting, isValid, values, errors, setFieldValue, applyFieldValue, setFieldTouched, handleChange, }) => { - const disabled = !isValid + const disabled = !isValid || this.state.uploading return (
@@ -118,32 +205,43 @@ class GroupSettings extends React.Component { autoFocus validateOnBlur={false} /> +
-
+
{tt('create_group_jsx.name2')}
{tt('create_group_jsx.name3')}{name}
+
- {tt('create_group_jsx.logo')} -
- this.onLogoChange(e, { applyFieldValue })} - validateOnBlur={false} - /> - {tt('group_settings_jsx.upload')} -
+ {tt('create_group_jsx.logo')}{this._renderPreview({ values, errors })} + this.onDrop(af, rf, { applyFieldValue })} + > + {({getRootProps, getInputProps, open}) => (
+ + this.onLogoChange(e, { applyFieldValue })} + validateOnBlur={false} + /> + {tt('group_settings_jsx.upload')} +
)} +
+
-
+
{tt('create_group_jsx.admin')} this.onAdminChange(e, { applyFieldValue })} validateOnBlur={false} /> + +
+
+ +
+
+ {tt('create_group_jsx.access')} + + this.onPrivacyChange(e, { applyFieldValue })} + > + + + {values.is_encrypted && } + +
+ +
+
+ {tt('group_settings_jsx.encrypted')} + {values.is_encrypted ? tt('group_settings_jsx.encrypted2') : tt('group_settings_jsx.encrypted3')} +
+
+ + {submitError &&
{submitError}
}
-
@@ -190,5 +315,49 @@ export default connect( } }, dispatch => ({ + uploadImage: (file, progress) => { + dispatch({ + type: 'user/UPLOAD_IMAGE', + payload: {file, progress}, + }) + }, + privateGroup: ({ password, creator, name, title, logo, admin, is_encrypted, privacy, + onSuccess, onError }) => { + let json_metadata = { + app: 'golos-messenger', + version: 1, + title, + logo + } + json_metadata = JSON.stringify(json_metadata) + + const opData = { + creator, + name, + json_metadata, + admin: admin, + is_encrypted, + privacy, + extensions: [], + } + + const json = JSON.stringify(['private_group', opData]) + + dispatch(transaction.actions.broadcastOperation({ + type: 'custom_json', + operation: { + id: 'private_message', + required_auths: [creator], + json, + }, + username: creator, + password, + successCallback: onSuccess, + errorCallback: (err, errStr) => { + console.error(err) + if (onError) onError(err, errStr) + }, + })); + } }) )(GroupSettings) diff --git a/src/components/modules/groups/GroupSettings.scss b/src/components/modules/groups/GroupSettings.scss index 927dd851..e26d0186 100644 --- a/src/components/modules/groups/GroupSettings.scss +++ b/src/components/modules/groups/GroupSettings.scss @@ -2,4 +2,18 @@ h3 { padding-left: 0.75rem; } + .input-group-label.button { + margin-bottom: 0px; + } + .input-group { + margin-bottom: 0px; + } + .error { + margin-bottom: 0px; + margin-top: 5px; + } + .submit-error { + padding-left: 1rem; + padding-right: 1rem; + } } diff --git a/src/locales/ru-RU.json b/src/locales/ru-RU.json index a2a539b6..c2db4afc 100644 --- a/src/locales/ru-RU.json +++ b/src/locales/ru-RU.json @@ -152,7 +152,14 @@ "title_GROUP": "Группа %(GROUP)s", "submit": "Сохранить", "owner": "Владелец", - "upload": "Загрузить" + "upload": "Загрузить", + "name_cannot_change": "Ссылку на группу изменить нельзя.", + "preview": "(просмотр)", + "login_hint_GROUP": "(изменения группы \"%(GROUP)s\")", + "encrypted": "Сообщения в группе ", + "encrypted2": " шифруются.", + "encrypted3": " не шифруются.", + "encrypted_hint": "Изменить это нельзя." }, "emoji_i18n": { "categoriesLabel": "Категории", diff --git a/src/redux/FetchDataSaga.js b/src/redux/FetchDataSaga.js index caa8fb5f..b1ebcbff 100644 --- a/src/redux/FetchDataSaga.js +++ b/src/redux/FetchDataSaga.js @@ -126,6 +126,7 @@ export function* fetchMyGroups({ payload: { account } }) { accounts: [account] } }) + console.log('LOO', groups) yield put(g.actions.receiveMyGroups({ groups })) } catch (err) { diff --git a/src/redux/TransactionSaga.js b/src/redux/TransactionSaga.js index 127e65cc..e178f096 100644 --- a/src/redux/TransactionSaga.js +++ b/src/redux/TransactionSaga.js @@ -16,8 +16,56 @@ export function* watchForBroadcast() { const hook = { preBroadcast_custom_json, + accepted_custom_json, } +function* accepted_custom_json({operation}) { + const json = JSON.parse(operation.json) + if (operation.id === 'private_message') { + if (json[0] === 'private_group') { + yield put(g.actions.update({ + key: ['my_groups'], + notSet: List(), + updater: groups => { + const idx = groups.findIndex(i => i.get('name') === json[1].name) + if (idx === -1) { + const now = new Date().toISOString().split('.')[0] + groups = groups.insert(0, fromJS({ + owner: json[1].creator, + name: json[1].name, + json_metadata: json[1].json_metadata, + is_encrypted: json[1].is_encrypted, + privacy: json[1].privacy, + created: now, + admins: 0, + moders: 0, + members: 0, + pendings: 0, + member_list: [{ + account: json[1].creator, + group: json[1].name, + invited: json[1].creator, + joined: now, + json_metadata: '{}', + member_type: 'admin', + updated: now + }] + })) + } else { + groups = groups.update(idx, g => { + g = g.set('json_metadata', json[1].json_metadata); + return g; + }); + } + return groups + } + })) + } + } + return operation +} + + function* preBroadcast_custom_json({operation}) { const json = JSON.parse(operation.json) if (operation.id === 'private_message') { @@ -148,6 +196,15 @@ function* broadcastOperation( try { const res = yield golos.broadcast.sendAsync( tx, [password]) + for (const [type, operation] of operations) { + if (hook['accepted_' + type]) { + try { + yield call(hook['accepted_' + type], {operation}) + } catch (error) { + console.error(error) + } + } + } } catch (err) { console.error('Broadcast error', err) if (errorCallback) {