{tt('loginform_jsx.is_is_for_operation')}
+ {hint}
+ .
-
diff --git a/src/components/modules/groups/MyGroups.jsx b/src/components/modules/groups/MyGroups.jsx
new file mode 100644
index 00000000..a87169f4
--- /dev/null
+++ b/src/components/modules/groups/MyGroups.jsx
@@ -0,0 +1,213 @@
+import React from 'react'
+import {connect} from 'react-redux'
+import { Formik, Form, Field, ErrorMessage, } from 'formik'
+import { Map } from 'immutable'
+import { api, formatter } from 'golos-lib-js'
+import { Asset, Price, AssetEditor } from 'golos-lib-js/lib/utils'
+import tt from 'counterpart'
+
+import g from 'app/redux/GlobalReducer'
+import transaction from 'app/redux/TransactionReducer'
+import user from 'app/redux/UserReducer'
+import { session } from 'app/redux/UserSaga'
+import DropdownMenu from 'app/components/elements/DropdownMenu'
+import ExtLink from 'app/components/elements/ExtLink'
+import Icon from 'app/components/elements/Icon'
+import LoadingIndicator from 'app/components/elements/LoadingIndicator'
+import FormikAgent from 'app/components/elements/donate/FormikUtils'
+import DialogManager from 'app/components/elements/common/DialogManager'
+import { showLoginDialog } from 'app/components/dialogs/LoginDialog'
+import { getGroupLogo, getGroupMeta } from 'app/utils/groups'
+
+class MyGroups extends React.Component {
+ constructor(props) {
+ super(props)
+ this.state = {
+ loaded: false
+ }
+ }
+
+ refetch = () => {
+ const { currentUser } = this.props
+ this.props.fetchMyGroups(currentUser)
+ }
+
+ componentDidMount = async () => {
+ this.refetch()
+ }
+
+ createGroup = (e) => {
+ e.preventDefault()
+ this.props.showCreateGroup()
+ }
+
+ _renderGroupLogo = (group, meta) => {
+ const { json_metadata } = group
+
+ const logo = getGroupLogo(json_metadata)
+ return
+
+ |
+ }
+
+ deleteGroup = (e, group, title) => {
+ e.preventDefault()
+ showLoginDialog(group.owner, (res) => {
+ const password = res && res.password
+ if (!password) {
+ return
+ }
+ this.props.deleteGroup({
+ owner: group.owner,
+ name: group.name,
+ password,
+ onSuccess: () => {
+ this.refetch()
+ },
+ onError: (err, errStr) => {
+ alert(errStr)
+ }
+ })
+ }, 'active', false, tt('my_groups_jsx.login_hint_GROUP', {
+ GROUP: title
+ }))
+ }
+
+ _renderGroup = (group) => {
+ const { name, json_metadata } = group
+
+ const meta = getGroupMeta(json_metadata)
+
+ let title = meta.title || name
+ let titleShr = title
+ if (titleShr.length > 20) {
+ titleShr = titleShr.substring(0, 17) + '...'
+ }
+
+ const kebabItems = []
+
+ kebabItems.push({ link: '#', onClick: e => {
+ this.deleteGroup(e, group, titleShr)
+ }, value: tt('g.delete') })
+
+ return
+
+ {this._renderGroupLogo(group, meta)}
+
+ {titleShr}
+ |
+ {
+ e.preventDefault()
+ }}>
+
+ {tt('my_groups_jsx.edit')}
+
+
+ {tt('g.delete')}
+
+ {kebabItems.length ?
+
+ : null}
+ |
+
+
+ }
+
+ render() {
+ let groups, hasGroups
+
+ let { my_groups } = this.props
+
+ if (!my_groups) {
+ groups =
+ } else {
+ my_groups = my_groups.toJS()
+
+ if (!my_groups.length) {
+ groups =
+ } else {
+ hasGroups = true
+ groups = []
+ for (const g of my_groups) {
+ groups.push(this._renderGroup(g))
+ }
+ groups =
+ }
+ }
+
+ let button
+ if (hasGroups) {
+ button =
+ {tt('my_groups_jsx.create_more')}
+
+ }
+
+ return
+
+
{tt('my_groups_jsx.title')}
+
+ {button}
+ {groups}
+
+ }
+}
+
+export default connect(
+ (state, ownProps) => {
+ const currentUser = state.user.getIn(['current'])
+ const currentAccount = currentUser && state.global.getIn(['accounts', currentUser.get('username')])
+ const my_groups = state.global.get('my_groups')
+
+ return { ...ownProps,
+ currentUser,
+ currentAccount,
+ my_groups,
+ }
+ },
+ dispatch => ({
+ fetchMyGroups: (currentUser) => {
+ if (!currentUser) return
+ const account = currentUser.get('username')
+ dispatch(g.actions.fetchMyGroups({ account }))
+ },
+ showCreateGroup() {
+ dispatch(user.actions.showCreateGroup({ redirectAfter: false }))
+ },
+ deleteGroup: ({ owner, name, password,
+ onSuccess, onError }) => {
+ const opData = {
+ owner,
+ name,
+ extensions: [],
+ }
+
+ const json = JSON.stringify(['private_group_delete', opData])
+
+ dispatch(transaction.actions.broadcastOperation({
+ type: 'custom_json',
+ operation: {
+ id: 'private_message',
+ required_auths: [owner],
+ json,
+ },
+ username: owner,
+ password,
+ successCallback: onSuccess,
+ errorCallback: (err, errStr) => {
+ console.error(err)
+ if (onError) onError(err, errStr)
+ },
+ }));
+ }
+ })
+)(MyGroups)
diff --git a/src/components/modules/groups/MyGroups.scss b/src/components/modules/groups/MyGroups.scss
new file mode 100644
index 00000000..2bfb29ea
--- /dev/null
+++ b/src/components/modules/groups/MyGroups.scss
@@ -0,0 +1,27 @@
+.MyGroups {
+ .group-logo {
+ width: 67px;
+ img {
+ width: 48px;
+ height: 48px;
+ }
+ }
+ .group-title {
+ font-weight: bold;
+ font-size: 110%;
+ @include themify($themes) {
+ color: themed('textColorPrimary');
+ }
+ }
+ .group-buttons {
+ float: right;
+ padding-top: 0.8rem;
+ .button {
+ vertical-align: middle;
+ margin-bottom: 0px;
+ }
+ .DropdownMenu.show > .VerticalMenu {
+ transform: translateX(-100%);
+ }
+ }
+}
diff --git a/src/components/pages/Messages.jsx b/src/components/pages/Messages.jsx
index b46ef4e0..b4bc6e77 100644
--- a/src/components/pages/Messages.jsx
+++ b/src/components/pages/Messages.jsx
@@ -801,8 +801,13 @@ class Messages extends React.Component {
this.props.logout(username)
}
+ const openMyGroups = (e) => {
+ e.preventDefault()
+ this.props.showMyGroups()
+ }
+
let user_menu = [
- {link: accountLink, extLink: 'blogs', icon: 'voters', value: tt('g.groups') + (isSmall ? (' @' + username) : ''), addon: },
+ {link: '#', onClick: openMyGroups, icon: 'voters', value: tt('g.groups') + (isSmall ? (' @' + username) : '') },
{link: accountLink, extLink: 'blogs', icon: 'new/blogging', value: tt('g.blog'), addon: },
{link: mentionsLink, extLink: 'blogs', icon: 'new/mention', value: tt('g.mentions'), addon: },
{link: donatesLink, extLink: 'wallet', icon: 'editor/coin', value: tt('g.rewards'), addon: },
@@ -1023,6 +1028,9 @@ export default withRouter(connect(
}
return true;
},
+
+ showMyGroups: () => dispatch(user.actions.showMyGroups()),
+
fetchState: (to) => {
const pathname = '/' + (to ? ('@' + to) : '');
dispatch({type: 'FETCH_STATE', payload: {
diff --git a/src/locales/en.json b/src/locales/en.json
index 3356a2e2..75f6df4d 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -134,6 +134,12 @@
"image_wrong": "Cannot load this image.",
"image_timeout": "Cannot load this image, it is loading too long..."
},
+ "my_groups_jsx": {
+ "title": "My Groups",
+ "empty": "You have not any groups yet. ",
+ "empty2": "You can ",
+ "create": "create your own group"
+ },
"emoji_i18n": {
"categoriesLabel": "Категории",
"emojiUnsupportedMessage": "Ваш браузер не поддерживает эмодзи.",
diff --git a/src/locales/ru-RU.json b/src/locales/ru-RU.json
index 5e9db9e1..3e5003cb 100644
--- a/src/locales/ru-RU.json
+++ b/src/locales/ru-RU.json
@@ -48,7 +48,7 @@
"login_to_comment": "Пожалуйста, авторизуйтесь для выполнения следующих действий",
"login_to_message": "Пожалуйста, авторизуйтесь с memo-ключом или паролем",
"login_active": "Введите пароль (или активный ключ)",
- "is_is_for_operation": "Это нужно для отправки операции.",
+ "is_is_for_operation": "Это нужно для отправки операции",
"login_with_active_key_USERNAME": "Пожалуйста, авторизуйтесь Вашим активным ключом\n\nИмя:\n%(USERNAME)s\n\nActive-ключ:\n",
"login_to_your_steem_account": "Войти в свой Голос аккаунт",
"posting": "Постинг",
@@ -136,6 +136,15 @@
"image_wrong": "Не удается загрузить картинку.",
"image_timeout": "Не удается загрузить картинку, она загружается слишком долго..."
},
+ "my_groups_jsx": {
+ "title": "Мои группы",
+ "empty": "У вас пока нет групп. ",
+ "empty2": "Вы можете ",
+ "create": "создать свою группу",
+ "create_more": "Создать еще группу",
+ "edit": "Изменить",
+ "login_hint_GROUP": "(удаления группы \"%(GROUP)s\")"
+ },
"emoji_i18n": {
"categoriesLabel": "Категории",
"emojiUnsupportedMessage": "Ваш браузер не поддерживает эмодзи.",
@@ -256,6 +265,7 @@
"settings": "Настройки",
"sign_up": "Регистрация",
"username_does_not_exist": "Такого имени не существует",
- "wallet": "Кошелек"
+ "wallet": "Кошелек",
+ "wait": "Ждите..."
}
}
\ No newline at end of file
diff --git a/src/redux/FetchDataSaga.js b/src/redux/FetchDataSaga.js
index 9a947b96..caa8fb5f 100644
--- a/src/redux/FetchDataSaga.js
+++ b/src/redux/FetchDataSaga.js
@@ -8,6 +8,7 @@ export function* fetchDataWatches () {
yield fork(watchLocationChange)
yield fork(watchFetchState)
yield fork(watchFetchUiaBalances)
+ yield fork(watchFetchMyGroups)
}
export function* watchLocationChange() {
@@ -109,3 +110,25 @@ export function* fetchUiaBalances({ payload: { account } }) {
console.error('fetchUiaBalances', err)
}
}
+
+export function* watchFetchMyGroups() {
+ yield takeLatest('global/FETCH_MY_GROUPS', fetchMyGroups)
+}
+
+export function* fetchMyGroups({ payload: { account } }) {
+ try {
+ const groups = yield call([api, api.getGroupsAsync], {
+ member: account,
+ member_types: ['pending', 'member', 'moder', 'admin'],
+ start_group: '',
+ limit: 100,
+ with_members: {
+ accounts: [account]
+ }
+ })
+
+ yield put(g.actions.receiveMyGroups({ groups }))
+ } catch (err) {
+ console.error('fetchMyGroups', err)
+ }
+}
diff --git a/src/redux/GlobalReducer.js b/src/redux/GlobalReducer.js
index e9845952..9ab16b7d 100644
--- a/src/redux/GlobalReducer.js
+++ b/src/redux/GlobalReducer.js
@@ -264,5 +264,15 @@ export default createModule({
return state.set('assets', fromJS(assets))
},
},
+ {
+ action: 'FETCH_MY_GROUPS',
+ reducer: state => state
+ },
+ {
+ action: 'RECEIVE_MY_GROUPS',
+ reducer: (state, { payload: { groups } }) => {
+ return state.set('my_groups', fromJS(groups))
+ },
+ },
],
})
diff --git a/src/redux/UserReducer.js b/src/redux/UserReducer.js
index e60902e3..ead90c9b 100644
--- a/src/redux/UserReducer.js
+++ b/src/redux/UserReducer.js
@@ -6,6 +6,7 @@ const defaultState = fromJS({
show_login_modal: false,
show_donate_modal: false,
show_create_group_modal: false,
+ show_my_groups_modal: false,
show_app_download_modal: false,
loginLoading: false,
pub_keys_used: null,
@@ -129,8 +130,14 @@ export default createModule({
{ action: 'HIDE_CONNECTION_ERROR_MODAL', reducer: state => state.set('hide_connection_error_modal', true) },
{ action: 'SHOW_DONATE', reducer: state => state.set('show_donate_modal', true) },
{ action: 'HIDE_DONATE', reducer: state => state.set('show_donate_modal', false) },
- { action: 'SHOW_CREATE_GROUP', reducer: state => state.set('show_create_group_modal', true) },
+ { action: 'SHOW_CREATE_GROUP', reducer: (state, { payload: { redirectAfter }}) => {
+ state = state.set('show_create_group_modal', true)
+ state = state.set('create_group_redirect_after', redirectAfter)
+ return state
+ }},
{ action: 'HIDE_CREATE_GROUP', reducer: state => state.set('show_create_group_modal', false) },
+ { action: 'SHOW_MY_GROUPS', reducer: state => state.set('show_my_groups_modal', true) },
+ { action: 'HIDE_MY_GROUPS', reducer: state => state.set('show_my_groups_modal', false) },
{ action: 'SHOW_APP_DOWNLOAD', reducer: state => state.set('show_app_download_modal', true) },
{ action: 'HIDE_APP_DOWNLOAD', reducer: state => state.set('show_app_download_modal', false) },
{ action: 'SET_DONATE_DEFAULTS', reducer: (state, {payload}) => state.set('donate_defaults', fromJS(payload)) },
diff --git a/src/utils/groups.js b/src/utils/groups.js
new file mode 100644
index 00000000..2de29c40
--- /dev/null
+++ b/src/utils/groups.js
@@ -0,0 +1,28 @@
+import { proxifyImageUrlWithStrip } from 'app/utils/ProxifyUrl'
+
+const getGroupMeta = (json_metadata) => {
+ let meta
+ if (json_metadata) {
+ meta = JSON.parse(json_metadata)
+ }
+ meta = meta || {} // node allows null, object, array... or empty json_metadata
+ return meta
+}
+
+const getGroupLogo = (json_metadata) => {
+ const meta = getGroupMeta(json_metadata)
+
+ let { logo } = meta
+ if (logo && /^(https?:)\/\//.test(logo)) {
+ const size = '75x75'
+ logo = proxifyImageUrlWithStrip(logo, size)
+ } else {
+ logo = require('app/assets/images/user.png')
+ }
+ return logo
+}
+
+export {
+ getGroupMeta,
+ getGroupLogo
+}