diff --git a/src/App.jsx b/src/App.jsx index 5ad2a894a..19d04cb32 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -18,6 +18,7 @@ import AppSettings, { openAppSettings } from 'app/components/pages/app/AppSettin import Themifier from 'app/Themifier' import Translator from 'app/Translator' import initConfig from 'app/utils/initConfig' +import RenderError from 'app/utils/RenderError' import { getShortcutIntent, onShortcutIntent } from 'app/utils/app/ShortcutUtils' import 'app/App.scss' @@ -68,6 +69,20 @@ class App extends React.Component { }) } + componentDidCatch(err, info) { + console.error('Render error:', err, info) + const errStr = (err && err.toString()) ? err.toString() : JSON.stringify(err) + const infoStr = (info && info.componentStack) || JSON.stringify(info) + this.setState({ + fatalErr: { + errStr, + infoStr + } + }) + //alert(';( Ошибка рендеринга\n\n' + errStr + '\n' + infoStr) + //throw err + } + showAppReminder = () => { if (process.env.MOBILE_APP || process.env.DESKTOP_APP) { return @@ -87,10 +102,11 @@ class App extends React.Component { } const reminder = this.showAppReminder() ? : null + const { fatalErr } = this.state return ( - + {fatalErr ? : @@ -110,7 +126,7 @@ class App extends React.Component { } - + } ) diff --git a/src/app/default_cfg.js b/src/app/default_cfg.js index 09a45400d..037c39fac 100644 --- a/src/app/default_cfg.js +++ b/src/app/default_cfg.js @@ -1,8 +1,17 @@ module.exports = { - "app_version": "1.0.0", + "app_version": "1.0.1", "nodes": [ { "address": "wss://apibeta.golos.today/ws" + }, + { + "address": "wss://api.golos.id/ws" + }, + { + "address": "wss://api.aleksw.space/ws" + }, + { + "address": "wss://api-golos.blckchnd.com/ws" } ], "images": { @@ -16,12 +25,16 @@ module.exports = { "custom_client": "blogs" }, "notify_service": { - "host": "https://devnotify.golos.app" + "host": "https://devnotify.golos.app", + "host_ws": "wss://devnotify.golos.app/ws" }, "blogs_service": { "host": "https://beta.golos.today" }, + "wallet_service": { + "host": "https://devwallet.golos.today" + }, "app_updater": { - "host": "https://files.golos.app" + "host": "https://devfiles.golos.app" } } \ No newline at end of file diff --git a/src/components/elements/MarkNotificationRead.jsx b/src/components/elements/MarkNotificationRead.jsx index 10a398c67..e75d64200 100644 --- a/src/components/elements/MarkNotificationRead.jsx +++ b/src/components/elements/MarkNotificationRead.jsx @@ -11,6 +11,7 @@ class MarkNotificationRead extends React.Component { account: PropTypes.string, update: PropTypes.func, interval: PropTypes.number, + delay: PropTypes.number, }; shouldComponentUpdate(nextProps) { @@ -37,12 +38,19 @@ class MarkNotificationRead extends React.Component { } componentDidMount() { - const { account, fields, update, interval } = this.props; + const { account, fields, update, interval, delay } = this.props; this.fields_array = fields.replace(/\s/g,'').split(','); - if (interval) + const firstMark = () => { + markNotificationRead(account, this.fields_array).then(nc => update(nc)) + } + if (delay) { + setTimeout(firstMark, delay) + } + if (interval) { this._activateInterval(interval); - else - markNotificationRead(account, this.fields_array).then(nc => update(nc)); + } else if (!delay) { + firstMark() + } } componentDidUpdate() { diff --git a/src/components/elements/VerticalMenu.jsx b/src/components/elements/VerticalMenu.jsx index a0537a36e..b5a6b20d1 100644 --- a/src/components/elements/VerticalMenu.jsx +++ b/src/components/elements/VerticalMenu.jsx @@ -39,7 +39,7 @@ export default class VerticalMenu extends React.Component { const LinkType = i.extLink ? ExtLink : Link const service = i.extLink || undefined return
  • - {i.link ? + {i.link ? {i.icon && }{i.label ? i.label : i.value} {i.data && {i.data}}   {i.addon} diff --git a/src/components/elements/common/AccountName/index.jsx b/src/components/elements/common/AccountName/index.jsx index fa9e578ec..6e0c1936e 100644 --- a/src/components/elements/common/AccountName/index.jsx +++ b/src/components/elements/common/AccountName/index.jsx @@ -10,7 +10,7 @@ class AccountName extends React.Component { super(props) this.state = { defaultOptions: [], - isLoading: true, + isLoading: false, } this.ref = React.createRef() } diff --git a/src/components/elements/groups/GroupMember.jsx b/src/components/elements/groups/GroupMember.jsx index 30a7855d6..c4e89e9a7 100644 --- a/src/components/elements/groups/GroupMember.jsx +++ b/src/components/elements/groups/GroupMember.jsx @@ -1,4 +1,5 @@ import React from 'react' +import { Link } from 'react-router-dom' import tt from 'counterpart' import cn from 'classnames' @@ -53,7 +54,7 @@ class GroupMember extends React.Component { } render() { - const { member, username, currentGroup } = this.props + const { member, username, currentGroup, linkClick } = this.props const { account, member_type, joined } = member const { creatingNew, } = currentGroup @@ -101,12 +102,12 @@ class GroupMember extends React.Component { return - + {account} - + {!isSmall && !creatingNew && } diff --git a/src/components/elements/messages/ConversationListItem/ConversationListItem.css b/src/components/elements/messages/ConversationListItem/ConversationListItem.css index 11346a85f..cd6555601 100644 --- a/src/components/elements/messages/ConversationListItem/ConversationListItem.css +++ b/src/components/elements/messages/ConversationListItem/ConversationListItem.css @@ -33,7 +33,7 @@ } .conversation-info { - width: 100%; + width: calc(100% - 60px); } .conversation-snippet { diff --git a/src/components/elements/messages/Message/Message.css b/src/components/elements/messages/Message/Message.css index c235da713..b9b92b7c0 100644 --- a/src/components/elements/messages/Message/Message.css +++ b/src/components/elements/messages/Message/Message.css @@ -179,3 +179,17 @@ .msgs-message.mine .bubble-container.selected .quote-from { color: white; } + +.msgs-adds { + display: flex; +} + +@media screen and (max-width: 39.9375em) { + .msgs-adds { + display: block; + + .msgs-donating { + margin-left: 3px; + } + } +} diff --git a/src/components/elements/messages/Message/index.jsx b/src/components/elements/messages/Message/index.jsx index 37c1d8a7e..c02ad2fc4 100644 --- a/src/components/elements/messages/Message/index.jsx +++ b/src/components/elements/messages/Message/index.jsx @@ -2,6 +2,7 @@ import React from 'react'; import {connect} from 'react-redux' import { Fade } from 'react-foundation-components/lib/global/fade' import { LinkWithDropdown } from 'react-foundation-components/lib/global/dropdown' +import { withRouter } from 'react-router' import { Link } from 'react-router-dom' import tt from 'counterpart'; import cn from 'classnames' @@ -33,6 +34,30 @@ class Message extends React.Component { event.stopPropagation(); }; + linkClicked = (event) => { + this.doNotSelectMessage(event) + if (process.env.MOBILE_APP) { + event.preventDefault() + let node, href + do { + node = node ? node.parentNode : event.target + if (!node) break + href = node.href + } while (!href) + try { + let url = new URL(href) + if (url.host === location.host) { + const { history } = this.props + history.push(url.pathname) + return + } + } catch (err) { + console.error(err) + } + window.open(href, '_system') + } + } + render() { let username @@ -61,7 +86,7 @@ class Message extends React.Component { const previewWidth = message.previewWidth ? message.previewWidth + 'px' : 'auto'; const previewHeight = message.previewHeight ? message.previewHeight + 'px' : 'auto'; - content = ( + content = ( {src} ); } else { @@ -77,7 +102,7 @@ class Message extends React.Component { if (!href.startsWith('http://') && !href.startsWith('https://')) { href = 'http://' + href; } - spans.push({word}); + spans.push({word}); spans.push(' '); } else if (word.length <= 2 && /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/.test(word)) { spans.push({word}); @@ -130,12 +155,14 @@ class Message extends React.Component { if (startsSequence) { author =
    { - e.preventDefault() - e.stopPropagation() - this.dropdown.current.click() - }}> - {from} + })}> + { + e.preventDefault() + e.stopPropagation() + this.dropdown.current.click() + }}> + {from} +
    avatar = - {!isMine ? adds : null} + {!isMine ?
    {adds}
    : null} ); } } -export default connect( +export default withRouter(connect( (state, ownProps) => { const accounts = state.global.get('accounts') @@ -199,4 +226,4 @@ export default connect( }, dispatch => ({ }), -)(Message) +)(Message)) diff --git a/src/components/modules/MessagesTopCenter.jsx b/src/components/modules/MessagesTopCenter.jsx index a4744ff6d..23201bd25 100644 --- a/src/components/modules/MessagesTopCenter.jsx +++ b/src/components/modules/MessagesTopCenter.jsx @@ -248,10 +248,13 @@ class MessagesTopCenter extends React.Component { this.setState({ refreshing: true }) e.preventDefault() e.stopPropagation() - this.props.fetchState(this.props.to) + this.props.loginUser() setTimeout(() => { - this.setState({ refreshing: false }) - }, 500) + this.props.fetchState(this.props.to) + setTimeout(() => { + this.setState({ refreshing: false }) + }, 500) + }, 1000) } render() { diff --git a/src/components/modules/MessagesTopCenter.scss b/src/components/modules/MessagesTopCenter.scss index d1302f4fc..e1f10b667 100644 --- a/src/components/modules/MessagesTopCenter.scss +++ b/src/components/modules/MessagesTopCenter.scss @@ -48,6 +48,7 @@ @media screen and (max-width: 39.9375em) { .GroupDropdown { left: 10px !important; + position: fixed; } } diff --git a/src/components/modules/Modals.jsx b/src/components/modules/Modals.jsx index f4bbcba82..4243c734f 100644 --- a/src/components/modules/Modals.jsx +++ b/src/components/modules/Modals.jsx @@ -16,6 +16,7 @@ import LoginForm from 'app/components/modules/LoginForm'; import AppDownload from 'app/components/modules/app/AppDownload' import user from 'app/redux/UserReducer' //import tr from 'app/redux/Transaction'; +import isScreenSmall from 'app/utils/isScreenSmall' let keyIndex = 0; @@ -76,10 +77,15 @@ class Modals extends React.Component { return n; }) : []; - const modalStyle = { - borderRadius: '8px', - boxShadow: '0 0 19px 3px rgba(0,0,0, 0.2)', - overflow: 'hidden', + let modalStyle = { + overflowX: 'hidden', + } + if (!isScreenSmall()) { + modalStyle = { + borderRadius: '8px', + boxShadow: '0 0 19px 3px rgba(0,0,0, 0.2)', + ...modalStyle, + } } const doHideLogin = (e) => { diff --git a/src/components/modules/groups/GroupMembers.jsx b/src/components/modules/groups/GroupMembers.jsx index 3bbde6f79..18e450340 100644 --- a/src/components/modules/groups/GroupMembers.jsx +++ b/src/components/modules/groups/GroupMembers.jsx @@ -1,5 +1,6 @@ import React from 'react' import { connect } from 'react-redux' +import { Link } from 'react-router-dom' import { Field, ErrorMessage, } from 'formik' import tt from 'counterpart' import { validateAccountName } from 'golos-lib-js/lib/utils' @@ -11,8 +12,8 @@ import AccountName from 'app/components/elements/common/AccountName' import Input from 'app/components/elements/common/Input'; import GroupMember from 'app/components/elements/groups/GroupMember' import LoadingIndicator from 'app/components/elements/LoadingIndicator' -import MarkNotificationRead from 'app/components/elements/MarkNotificationRead' import { getRoleInGroup, getGroupMeta, getGroupTitle } from 'app/utils/groups' +import isScreenSmall from 'app/utils/isScreenSmall' export async function validateMembersStep(values, errors) { // nothing yet... @@ -149,7 +150,7 @@ class GroupMembers extends React.Component { } render() { - const { currentGroup, group, username } = this.props + const { currentGroup, group, username, closeMe } = this.props const loading = this.isLoading() let members = group && group.get('members') if (members) members = members.get('data') @@ -162,6 +163,12 @@ class GroupMembers extends React.Component { amModer = true } + const isSmall = isScreenSmall() + + const linkClick = () => { + if (closeMe) closeMe() + } + let mems if (loading) { mems =
    @@ -178,6 +185,7 @@ class GroupMembers extends React.Component { mems.push() } @@ -197,7 +205,7 @@ class GroupMembers extends React.Component { {amModer ?
    { @@ -222,7 +230,7 @@ class GroupMembers extends React.Component {
    } else { - const { name, json_metadata, pendings, banneds, } = currentGroup + const { name, owner, json_metadata, pendings, banneds, } = currentGroup const meta = getGroupMeta(json_metadata) let title = getGroupTitle(meta, name) @@ -231,12 +239,31 @@ class GroupMembers extends React.Component { const { showPendings, showBanneds } = this.state + let ownerRight, ownerRow + let ownerBlock = + {tt('group_settings_jsx.owner') + ' - '} + {amOwner ? {tt('g.you')} : + {('@' + owner)}} + + if (isSmall) { + ownerRow =
    +
    + {ownerBlock} +
    +
    + } else { + ownerRight =
    + {ownerBlock} +
    + } + header =

    {title}

    + {ownerRow} {amModer ?
    + {ownerRight}
    :
    {this._renderMemberTypeSwitch()} + {ownerRight}
    } - {(username && showPendings) ? : null}
    } diff --git a/src/components/modules/groups/GroupName.jsx b/src/components/modules/groups/GroupName.jsx index 46c467025..856fc5679 100644 --- a/src/components/modules/groups/GroupName.jsx +++ b/src/components/modules/groups/GroupName.jsx @@ -19,10 +19,10 @@ export async function validateNameStep(values, errors) { for (let i = 0; i < 3; ++i) { try { console.time('group_exists') - group = await api.getGroupsAsync({ + group = (await api.getGroupsAsync({ start_group: values.name, limit: 1 - }) + })).groups console.timeEnd('group_exists') break } catch (err) { @@ -30,7 +30,7 @@ export async function validateNameStep(values, errors) { errors.name = 'Blockchain unavailable :(' } } - if (group && group[0] && group[0].name === values.name) { + if (group[0] && group[0].name === values.name) { errors.name = tt('create_group_jsx.group_already_exists') } } diff --git a/src/components/modules/groups/MyGroups.jsx b/src/components/modules/groups/MyGroups.jsx index 9de07e978..6b69b1f0a 100644 --- a/src/components/modules/groups/MyGroups.jsx +++ b/src/components/modules/groups/MyGroups.jsx @@ -14,6 +14,7 @@ import DropdownMenu from 'app/components/elements/DropdownMenu' import Icon from 'app/components/elements/Icon' import LoadingIndicator from 'app/components/elements/LoadingIndicator' import MarkNotificationRead from 'app/components/elements/MarkNotificationRead' +import NotifiCounter from 'app/components/elements/NotifiCounter' import { showLoginDialog } from 'app/components/dialogs/LoginDialog' import { getGroupLogo, getGroupMeta, getRoleInGroup } from 'app/utils/groups' import isScreenSmall from 'app/utils/isScreenSmall' @@ -28,7 +29,11 @@ class MyGroups extends React.Component { refetch = () => { const { currentUser } = this.props - this.props.fetchMyGroups(currentUser) + this.setState({ + currentTab: null, + }, () => { + this.props.fetchMyGroups(currentUser) + }) } componentDidMount = async () => { @@ -124,7 +129,7 @@ class MyGroups extends React.Component { } _renderGroup = (group) => { - const { name, json_metadata, pendings } = group + const { name, json_metadata, pendings, members, moders, } = group const meta = getGroupMeta(json_metadata) @@ -156,6 +161,9 @@ class MyGroups extends React.Component { }, value: tt('msgs_group_dropdown.retire') }) } + + const noMembers = !pendings && !members && !moders + return {this._renderGroupLogo(group, meta)} @@ -175,7 +183,7 @@ class MyGroups extends React.Component { {amPending ? tt('msgs_group_dropdown.cancel') : tt('msgs_group_dropdown.retire')} : null} - {(amModer && pendings) ? : null}
    } else { hasGroups = true + + const { stat, } = this.props + let { currentTab } = this.state + currentTab = currentTab || stat.current + groups = [] for (const g of my_groups) { + if (currentTab && g.my_role !== currentTab) continue groups.push(this._renderGroup(g)) } - groups = - - {groups} - -
    + groups = + {this._renderGroupTypeSwitch()} + + + {groups} + +
    +
    } } let button if (hasGroups) { + const isSmall = isScreenSmall() button =
    } @@ -276,11 +329,13 @@ export default connect( const currentUser = state.user.getIn(['current']) const username = currentUser && currentUser.get('username') const my_groups = state.global.get('my_groups') + const my_groups_stat = state.global.get('my_groups_stat') return { ...ownProps, currentUser, username, my_groups, + stat: my_groups_stat ? my_groups_stat.toJS() : {}, } }, dispatch => ({ diff --git a/src/components/modules/groups/MyGroups.scss b/src/components/modules/groups/MyGroups.scss index d0cfc5749..5b12e5721 100644 --- a/src/components/modules/groups/MyGroups.scss +++ b/src/components/modules/groups/MyGroups.scss @@ -19,6 +19,8 @@ } .more-group { float: right; + padding-top: 0.725em; + padding-bottom: 0.725em; &:not(:hover) { border-color: transparent; } @@ -44,4 +46,40 @@ display: none; } } + .button.force-white { + color: #fefefe !important; + fill: #fefefe !important; + } + .label { + padding: 0.5rem; + padding-top: 0.6rem; + padding-bottom: 0.4rem; + + transition: all .1s ease-in; + user-select: none; + + margin-right: 0.5rem; + margin-bottom: 0.5rem; + + .label-text { + display: inline-block; + height: 16px; + } + .NotifiCounter { + margin-left: 0.35rem; + vertical-align: top; + } + + &:not(.disabled) { + cursor: pointer; + } + &:not(.checked) { + background: #f4f4f8; + color: #333333; + } + &:hover:not(.disabled) { + background: #0078C4; + color: #fefefe; + } + } } diff --git a/src/components/modules/groups/TopGroups.scss b/src/components/modules/groups/TopGroups.scss index a0f5e79b7..578d8727e 100644 --- a/src/components/modules/groups/TopGroups.scss +++ b/src/components/modules/groups/TopGroups.scss @@ -16,6 +16,9 @@ vertical-align: middle; img { border-radius: 50%; + width: 50px; + height: 50px; + object-fit: cover; } } .group-stats { diff --git a/src/components/pages/Messages.jsx b/src/components/pages/Messages.jsx index 6b0c40241..df75c236a 100644 --- a/src/components/pages/Messages.jsx +++ b/src/components/pages/Messages.jsx @@ -34,7 +34,7 @@ import { getProfileImage, } from 'app/utils/NormalizeProfile'; import { normalizeContacts, normalizeMessages, cacheMyOwnMsg } from 'app/utils/Normalizators'; import { fitToPreview } from 'app/utils/ImageUtils'; import { notificationSubscribe, notificationSubscribeWs, notifyWsPing, - notificationShallowUnsubscribe, notificationTake, queueWatch, sendOffchainMessage, notifyWsHost, notifyUrl } from 'app/utils/NotifyApiClient'; + notificationShallowUnsubscribe, notificationTake, queueWatchWs, sendOffchainMessage, notifyWsHost, notifyUrl } from 'app/utils/NotifyApiClient'; import { flash, unflash } from 'app/components/elements/messages/FlashTitle'; import { addShortcut } from 'app/utils/app/ShortcutUtils' import { hideSplash } from 'app/utils/app/SplashUtils' @@ -252,15 +252,17 @@ class Messages extends React.Component { const {username} = this.props if (!username) { - console.log('watchGroup -', to, ' - no username') + console.log('watchGroupWs -', to, ' - no username') return false } try { - await queueWatch(username, to) - console.log('watchGroup - ', to) + window.errorLogs.push({ details: { watchGroup: to } }) + await queueWatchWs(username, to) + console.log('watchGroupWs - ', to) + window.errorLogs.push({ details: { watchGroup: 'ok' } }) return true } catch (err) { - console.error('watchGroup - ', to, err) + console.error('watchGroupWs - ', to, err) this.notifyErrorsInc(30, err, {watchGroup: notifyUrl()}) } return false @@ -323,6 +325,7 @@ class Messages extends React.Component { this.notifyErrorsClear() const ping = async (firstCall = false) => { if (!firstCall) { + window.errorLogs.push({ details: { ping: Date.now() } }) try { await notifyWsPing() if (this.state.notifyErrors) { @@ -340,6 +343,7 @@ class Messages extends React.Component { } setTimeout(ping, 10000) } + ping(true) this.watchGroup(this.props.to) } @@ -878,11 +882,11 @@ class Messages extends React.Component { > e.preventDefault()}>
    -
    +
    - +
    @@ -903,7 +907,7 @@ class Messages extends React.Component { }; _renderMessagesTopCenter = ({ isSmall }) => { - const { fetchState, to } = this.props + const { fetchState, loginUser, to } = this.props const toAcc = this.getToAcc() const { notifyErrors, } = this.state @@ -914,6 +918,7 @@ class Messages extends React.Component { notifyErrors={notifyErrors} errorLogs={window.errorLogs} fetchState={fetchState} + loginUser={loginUser} /> }; @@ -940,7 +945,7 @@ class Messages extends React.Component { } let user_menu = [ - {link: '#', onClick: openMyGroups, icon: 'voters', value: tt('g.groups') + (isSmall ? (' @' + username) : ''), addon: }, + {link: '#', onClick: openMyGroups, icon: 'voters', value: tt('g.groups') + (isSmall ? (' @' + username) : ''), addon: }, {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: }, @@ -956,7 +961,25 @@ class Messages extends React.Component { ] if (process.env.MOBILE_APP) { - user_menu.push({link: '#', onClick: this.props.openSettings, icon: 'new/setting', value: tt('g.settings')}) + user_menu.push({link: '#', onClick: this.props.openSettings, icon: 'new/setting', value: tt('g.settings'), + onTouchStart: (e) => { + window.settingsTouch = setTimeout(() => { + try { + const { errorLogs } = window + let msg = '' + for (const err of errorLogs) { + msg += (err.err ? err.err.toString() : '') + '\n' + JSON.stringify(err.details) + '\n\n' + } + alert(msg) + } catch (err) { + alert('Cannot display error logs, due to: ' + (err && err.toString())) + } + }, 3000) + }, + onTouchEnd: (e) => { + clearTimeout(window.settingsTouch) + } + }) } user_menu.push({link: '#', icon: 'new/logout', onClick: logout, value: tt('g.logout')}) @@ -982,7 +1005,7 @@ class Messages extends React.Component {
    - +
    {!isSmall ?
    diff --git a/src/locales/en.json b/src/locales/en.json index 3f0dbb4e7..aba3e3c07 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -181,7 +181,8 @@ "members_list": "Members:", "image_wrong": "Cannot load this image.", "image_timeout": "Cannot load this image, it is loading too long...", - "add_member": "+ Add Member..." + "add_member": "+ Add Member...", + "add_member2": "+ Add..." }, "my_groups_jsx": { "title": "My Groups", @@ -192,12 +193,19 @@ "create": "create", "create2": "your own one", "create_more": "+ Create a group", + "create_more2": "+ Create...", "more_groups": "More groups...", "edit": "Edit", "login_hint_GROUP": "(delete \"%(GROUP)s\" group)", "members": "Members", "cancel_pending": "Cancel request", - "are_you_sure_cancel": "Do you sure you don't want to join" + "are_you_sure_cancel": "Do you sure you don't want to join", + "tab_pending": "Pending", + "tab_member": "Member", + "tab_moder": "Moder", + "tab_own": "Owner", + "tabs_title": "Your status in groups", + "total_MEMBERS": "Total: %(MEMBERS)s member(-s)." }, "top_groups_jsx": { "title": "Popular Groups", @@ -265,6 +273,9 @@ "flags": "Флаги" } }, + "fatal_error_jsx": { + "render_error": ";( Render Error" + }, "stub_jsx": { "read_only": "Only members can post messages in this group.", "private_group": "This group is private. To read and write messages, you should be a member.", @@ -389,6 +400,7 @@ "submit": "Submit", "unblock": "Unblock", "username_does_not_exist": "Username does not exist", - "wallet": "Wallet" + "wallet": "Wallet", + "you": "you" } } diff --git a/src/locales/ru-RU.json b/src/locales/ru-RU.json index 910190bc4..0e8d495c1 100644 --- a/src/locales/ru-RU.json +++ b/src/locales/ru-RU.json @@ -189,6 +189,7 @@ "image_wrong": "Не удается загрузить картинку.", "image_timeout": "Не удается загрузить картинку, она загружается слишком долго...", "add_member": "+ Добавить участника...", + "add_member2": "+ Добавить...", "cannot_set_members": "Группа создана успешно. Но, к сожалению, не получилось задать участников из-за ошибки.", "cannot_set_members2": "Вы можете попытаться сделать это заново в настройках группы." }, @@ -200,13 +201,20 @@ "find2": "интересную для себя группу", "create": "создать", "create2": "свою собственную", - "create_more": "+ Создать группу", + "create_more": "+ Создать группу", + "create_more2": "+ Создать...", "more_groups": "Еще группы...", "edit": "Изменить", "login_hint_GROUP": "(удаления группы \"%(GROUP)s\")", "members": "Участники", "cancel_pending": "Отменить заявку", - "are_you_sure_cancel": "Вы уверены, что хотите отказаться от вступления в группу" + "are_you_sure_cancel": "Вы уверены, что хотите отказаться от вступления в группу", + "tab_pending": "Заявки", + "tab_member": "Участник", + "tab_moder": "Модератор", + "tab_own": "Владелец", + "tabs_title": "Ваш статус в группах", + "total_MEMBERS": "Всего %(MEMBERS)s участник(-ов)." }, "top_groups_jsx": { "title": "Топ популярных групп", @@ -281,8 +289,11 @@ "flags": "Флаги" } }, + "fatal_error_jsx": { + "render_error": ";( Ошибка рендеринга" + }, "stub_jsx": { - "read_only": "Писать сообщения в этой группе могут лишь ее члены.", + "read_only": "Писать сообщения могут только члены группы.", "private_group": "Это закрытая группа. Чтобы видеть сообщения и общаться в ней, надо стать ее членом.", "pending": "Вы подали заявку на вступление в группу.", "banned": "Вы забанены в этой группе.", @@ -407,6 +418,7 @@ "unblock": "Разблокировать", "username_does_not_exist": "Такого имени не существует", "wallet": "Кошелек", - "wait": "Ждите..." + "wait": "Ждите...", + "you": "вы" } } \ No newline at end of file diff --git a/src/redux/FetchDataSaga.js b/src/redux/FetchDataSaga.js index e27de9470..639f3ec55 100644 --- a/src/redux/FetchDataSaga.js +++ b/src/redux/FetchDataSaga.js @@ -3,6 +3,7 @@ import golos, { api, auth } from 'golos-lib-js' import tt from 'counterpart' import g from 'app/redux/GlobalReducer' +import { getRoleInGroup } from 'app/utils/groups' import { getSpaceInCache, saveToCache } from 'app/utils/Normalizators' export function* fetchDataWatches () { @@ -129,8 +130,8 @@ export function* fetchState(location_change_action) { } }) if (hasErr) return - if (the_group[0] && the_group[0].name === path) { - the_group = the_group[0] + if (the_group && the_group.groups && the_group.groups[0] && the_group.groups[0].name === path) { + the_group = the_group.groups[0] } else { the_group = null } @@ -226,7 +227,13 @@ export function* watchFetchMyGroups() { export function* fetchMyGroups({ payload: { account } }) { try { - const groupsOwn = yield call([api, api.getGroupsAsync], { + const stat = { + pending: 0, + member: 0, + moder: 0, + own: 0, + } + const groupsOwn = (yield call([api, api.getGroupsAsync], { member: account, member_types: [], start_group: '', @@ -234,8 +241,9 @@ export function* fetchMyGroups({ payload: { account } }) { with_members: { accounts: [account] } - }) - let groups = yield call([api, api.getGroupsAsync], { + })).groups + + let groups = (yield call([api, api.getGroupsAsync], { member: account, member_types: ['pending', 'member', 'moder'], start_group: '', @@ -243,13 +251,39 @@ export function* fetchMyGroups({ payload: { account } }) { with_members: { accounts: [account] } - }) + })).groups + groups = [...groupsOwn, ...groups] + for (const group of groups) { + const { amPending, amMember, amModer, amOwner } = getRoleInGroup(group, account) + if (amOwner) { + group.my_role = 'own' + stat.own++ + } else if (amPending) { + group.my_role = 'pending' + stat.pending++ + } else if (amMember) { + group.my_role = 'member' + stat.member++ + } else if (amModer) { + group.my_role = 'moder' + stat.moder++ + } + } groups.sort((a, b) => { return b.pendings - a.pendings }) - yield put(g.actions.receiveMyGroups({ groups })) + let current = 'member' + if (stat.pending) { + current = 'pending' + } else { + if (stat.moder > stat[current]) current = 'moder' + if (stat.own > stat[current]) current = 'own' + } + stat.current = current + + yield put(g.actions.receiveMyGroups({ groups, stat })) } catch (err) { console.error('fetchMyGroups', err) } @@ -269,7 +303,7 @@ export function* fetchTopGroups({ payload: { account } }) { groupsWithoutMe.pop() } - const groups = yield call([api, api.getGroupsAsync], { + const { groups } = yield call([api, api.getGroupsAsync], { sort: 'by_popularity', start_group, limit: 100, @@ -310,7 +344,7 @@ export function* fetchGroupMembers({ payload: { group, creatingNew, memberTypes, yield put(g.actions.receiveGroupMembers({ group, loading: true })) - const members = yield call([api, api.getGroupMembersAsync], { + const { members } = yield call([api, api.getGroupMembersAsync], { group, member_types: memberTypes, sort_conditions: sortConditions, diff --git a/src/redux/GlobalReducer.js b/src/redux/GlobalReducer.js index 3e421ce00..0952f21fd 100644 --- a/src/redux/GlobalReducer.js +++ b/src/redux/GlobalReducer.js @@ -70,6 +70,7 @@ export default createModule({ }) let new_state = state.set('messages', List()); new_state = new_state.set('contacts', List()); + new_state = new_state.delete('the_group'); new_state = new_state.mergeDeep(payload) return new_state }, @@ -366,8 +367,10 @@ export default createModule({ }, { action: 'RECEIVE_MY_GROUPS', - reducer: (state, { payload: { groups } }) => { - return state.set('my_groups', fromJS(groups)) + reducer: (state, { payload: { groups, stat } }) => { + let new_state = state.set('my_groups', fromJS(groups)) + new_state = new_state.set('my_groups_stat', fromJS(stat)) + return new_state }, }, { diff --git a/src/utils/NotifyApiClient.js b/src/utils/NotifyApiClient.js index 602892e90..28d722e7f 100644 --- a/src/utils/NotifyApiClient.js +++ b/src/utils/NotifyApiClient.js @@ -39,6 +39,10 @@ function saveSession(response) { } } if (!session) return; + if (window.errorLogs) { + let xSess = session && session.substring && (session.substring(0, 5) + '...') + window.errorLogs.push({ details: { xsession: xSess } }) + } localStorage.setItem('X-Session', session); } @@ -330,9 +334,10 @@ export async function queueWatchWs(account, group, sidKey = '__subscriber_id') { if (!notifyWsHost()) return null const xSession = notifySession() return await new Promise(async (resolve, reject) => { - await notifyWsSend('queues/subscribe', { + await notifyWsSend('queues/watch', { account, 'X-Session': xSession, + subscriber_id: window[sidKey], objects: { [group]: { type: 'group', diff --git a/src/utils/RenderError.jsx b/src/utils/RenderError.jsx new file mode 100644 index 000000000..ed7b65324 --- /dev/null +++ b/src/utils/RenderError.jsx @@ -0,0 +1,28 @@ +import React from 'react' + +import tt from 'counterpart' + +class RenderError extends React.Component { + refreshIt = () => { + if (window.location.pathname === '/') { + window.location.reload() + return + } + window.location.href = '/' + } + + render() { + const { error } = this.props + const refreshBtn = + const { errStr, infoStr } = error + return
    +

    {tt('fatal_error_jsx.render_error')}


    + {refreshBtn}
    + {errStr}
    +
    {infoStr}

    + {refreshBtn} +
    + } +} + +export default RenderError diff --git a/src/utils/ServerApiClient.js b/src/utils/ServerApiClient.js index 8d9fff818..7e8d04c8b 100644 --- a/src/utils/ServerApiClient.js +++ b/src/utils/ServerApiClient.js @@ -3,7 +3,7 @@ import { fetchEx } from 'golos-lib-js/lib/utils' export function getHost() { const { location, } = window; if (process.env.NODE_ENV === 'development') { - return location.protocol + '//'+ location.hostname + ':8080'; + return location.protocol + '//'+ location.hostname + ':8088'; } return location.origin; }