changes(ui) - user invite link and assist changes (#116)
* change(ui) - assist installation link in onboarding * change(ui) - invite link * change(ui) - reset params * change(ui) - unused component * feature(ui) - user changes icon * changes(ui) - invite link, and assist changes * fix(ui) - smtp flag
This commit is contained in:
parent
d158824c07
commit
a98cbe883c
18 changed files with 300 additions and 90 deletions
BIN
frontend/app/assets/img/live-sessions.png
Normal file
BIN
frontend/app/assets/img/live-sessions.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 151 KiB |
|
|
@ -35,8 +35,8 @@ function LiveSessionList(props: Props) {
|
|||
<div>
|
||||
<NoContent
|
||||
title={"No live sessions!"}
|
||||
subtext="Please try changing your search parameters."
|
||||
icon="exclamation-circle"
|
||||
// subtext="Please try changing your search parameters."
|
||||
image={<img src="/img/live-sessions.png" style={{ width: '70%', marginBottom: '30px' }}/>}
|
||||
show={ !loading && list && list.size === 0}
|
||||
>
|
||||
<Loader loading={ loading }>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
import Highlight from 'react-highlight'
|
||||
import ToggleContent from 'Shared/ToggleContent'
|
||||
import DocLink from 'Shared/DocLink/DocLink';
|
||||
|
||||
const AssistDoc = (props) => {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div>OpenReplay Assist allows you to support your users by seeing their live screen and instantly hopping on call (WebRTC) with them without requiring any 3rd-party screen sharing software.</div>
|
||||
|
||||
<div className="font-bold my-2">Installation</div>
|
||||
<Highlight className="js">
|
||||
{`npm i @openreplay/tracker-assist`}
|
||||
</Highlight>
|
||||
|
||||
<div className="font-bold my-2">Usage</div>
|
||||
<p>Initialize the tracker then load the @openreplay/tracker-assist plugin.</p>
|
||||
<div className="py-3" />
|
||||
|
||||
<div className="font-bold my-2">Usage</div>
|
||||
<ToggleContent
|
||||
label="Is SSR?"
|
||||
first={
|
||||
<Highlight className="js">
|
||||
{`import Tracker from '@openreplay/tracker';
|
||||
import trackerAssist from '@openreplay/tracker-assist';
|
||||
const tracker = new Tracker({
|
||||
projectKey: PROJECT_KEY,
|
||||
});
|
||||
tracker.start();
|
||||
tracker.use(trackerAssist(options)); // check the list of available options below`}
|
||||
</Highlight>
|
||||
}
|
||||
second={
|
||||
<Highlight className="js">
|
||||
{`import OpenReplay from '@openreplay/tracker/cjs';
|
||||
import trackerFetch from '@openreplay/tracker-assist/cjs';
|
||||
const tracker = new OpenReplay({
|
||||
projectKey: PROJECT_KEY
|
||||
});
|
||||
const trackerAssist = tracker.use(trackerAssist(options)); // check the list of available options below
|
||||
//...
|
||||
function MyApp() {
|
||||
useEffect(() => { // use componentDidMount in case of React Class Component
|
||||
tracker.start();
|
||||
}, [])
|
||||
//...
|
||||
}`}
|
||||
</Highlight>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="font-bold my-2">Options</div>
|
||||
<Highlight className="js">
|
||||
{`trackerAssist({
|
||||
confirmText: string;
|
||||
})`}
|
||||
</Highlight>
|
||||
|
||||
<DocLink className="mt-4" label="Install Assist" url="https://docs.openreplay.com/installation/assist" />
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
AssistDoc.displayName = "AssistDoc";
|
||||
|
||||
export default AssistDoc;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './AssistDoc'
|
||||
|
|
@ -28,6 +28,7 @@ import SlackAddForm from './SlackAddForm';
|
|||
import FetchDoc from './FetchDoc';
|
||||
import MobxDoc from './MobxDoc';
|
||||
import ProfilerDoc from './ProfilerDoc';
|
||||
import AssistDoc from './AssistDoc';
|
||||
|
||||
const NONE = -1;
|
||||
const SENTRY = 0;
|
||||
|
|
@ -49,6 +50,7 @@ const SLACK = 15;
|
|||
const FETCH = 16;
|
||||
const MOBX = 17;
|
||||
const PROFILER = 18;
|
||||
const ASSIST = 19;
|
||||
|
||||
const TITLE = {
|
||||
[ SENTRY ]: 'Sentry',
|
||||
|
|
@ -70,6 +72,7 @@ const TITLE = {
|
|||
[ FETCH ] : 'Fetch',
|
||||
[ MOBX ] : 'MobX',
|
||||
[ PROFILER ] : 'Profiler',
|
||||
[ ASSIST ] : 'Assist',
|
||||
}
|
||||
|
||||
const DOCS = [REDUX, VUE, GRAPHQL, NGRX, FETCH, MOBX, PROFILER]
|
||||
|
|
@ -182,6 +185,8 @@ export default class Integrations extends React.PureComponent {
|
|||
return <MobxDoc onClose={ this.closeModal } />
|
||||
case PROFILER:
|
||||
return <ProfilerDoc onClose={ this.closeModal } />
|
||||
case ASSIST:
|
||||
return <AssistDoc onClose={ this.closeModal } />
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
@ -253,7 +258,7 @@ export default class Integrations extends React.PureComponent {
|
|||
{plugins && (
|
||||
<div className="" >
|
||||
<div className="mb-4">Use plugins to better debug your application's store, monitor queries and track performance issues.</div>
|
||||
<div className="flex">
|
||||
<div className="flex flex-wrap">
|
||||
<IntegrationItem
|
||||
title="Redux"
|
||||
icon="integrations/redux"
|
||||
|
|
@ -313,6 +318,14 @@ export default class Integrations extends React.PureComponent {
|
|||
onClick={ () => this.showIntegrationConfig(PROFILER) }
|
||||
// integrated={ sentryIntegrated }
|
||||
/>
|
||||
<IntegrationItem
|
||||
title="Assist"
|
||||
icon="integrations/assist"
|
||||
url={ null }
|
||||
dockLink="https://docs.openreplay.com/installation/assist"
|
||||
onClick={ () => this.showIntegrationConfig(ASSIST) }
|
||||
// integrated={ sentryIntegrated }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { connect } from 'react-redux';
|
||||
import cn from 'classnames';
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
import { IconButton, SlideModal, Input, Button, Loader, NoContent, Popup } from 'UI';
|
||||
import { init, save, edit, remove as deleteMember, fetchList } from 'Duck/member';
|
||||
import { IconButton, SlideModal, Input, Button, Loader, NoContent, Popup, CopyButton } from 'UI';
|
||||
import { init, save, edit, remove as deleteMember, fetchList, generateInviteLink } from 'Duck/member';
|
||||
import styles from './manageUsers.css';
|
||||
import UserItem from './UserItem';
|
||||
import { confirm } from 'UI/Confirmation';
|
||||
|
|
@ -24,11 +24,12 @@ const LIMIT_WARNING = 'You have reached users limit.';
|
|||
save,
|
||||
edit,
|
||||
deleteMember,
|
||||
fetchList
|
||||
fetchList,
|
||||
generateInviteLink
|
||||
})
|
||||
@withPageTitle('Manage Users - OpenReplay Preferences')
|
||||
class ManageUsers extends React.PureComponent {
|
||||
state = { showModal: false, remaining: this.props.account.limits.teamMember.remaining }
|
||||
state = { showModal: false, remaining: this.props.account.limits.teamMember.remaining, invited: false }
|
||||
|
||||
onChange = (e, { name, value }) => this.props.edit({ [ name ]: value });
|
||||
onChangeCheckbox = ({ target: { checked, name } }) => this.props.edit({ [ name ]: checked });
|
||||
|
|
@ -70,11 +71,12 @@ class ManageUsers extends React.PureComponent {
|
|||
toast.error(e);
|
||||
})
|
||||
}
|
||||
this.closeModal()
|
||||
this.setState({ invited: true })
|
||||
// this.closeModal()
|
||||
});
|
||||
}
|
||||
|
||||
formContent = member => (
|
||||
|
||||
formContent = (member, account) => (
|
||||
<div className={ styles.form }>
|
||||
<form onSubmit={ this.save } >
|
||||
<div className={ styles.formGroup }>
|
||||
|
|
@ -99,7 +101,11 @@ class ManageUsers extends React.PureComponent {
|
|||
className={ styles.input }
|
||||
/>
|
||||
</div>
|
||||
|
||||
{ !account.smtp &&
|
||||
<div className={cn("mb-4 p-2", styles.smtpMessage)}>
|
||||
SMTP is not configured, <a className="link" href="https://docs.openreplay.com/configuration/configure-smtp" target="_blank">setup SMTP</a>
|
||||
</div>
|
||||
}
|
||||
<div className={ styles.formGroup }>
|
||||
<label className={ styles.checkbox }>
|
||||
<input
|
||||
|
|
@ -111,26 +117,37 @@ class ManageUsers extends React.PureComponent {
|
|||
/>
|
||||
<span>{ 'Admin' }</span>
|
||||
</label>
|
||||
<div className={ styles.adminInfo }>{ 'Can manage Projects and Users.' }</div>
|
||||
<div className={ styles.adminInfo }>{ 'Can manage Projects and team members.' }</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<Button
|
||||
onClick={ this.save }
|
||||
disabled={ !member.validate() }
|
||||
loading={ this.props.saving }
|
||||
primary
|
||||
marginRight
|
||||
>
|
||||
{ member.exists() ? 'Update' : 'Invite' }
|
||||
</Button>
|
||||
<Button
|
||||
data-hidden={ !member.exists() }
|
||||
onClick={ this.closeModal }
|
||||
outline
|
||||
>
|
||||
{ 'Cancel' }
|
||||
</Button>
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center mr-auto">
|
||||
<Button
|
||||
onClick={ this.save }
|
||||
disabled={ !member.validate() }
|
||||
loading={ this.props.saving }
|
||||
primary
|
||||
marginRight
|
||||
>
|
||||
{ member.exists() ? 'Update' : 'Invite' }
|
||||
</Button>
|
||||
<Button
|
||||
data-hidden={ !member.exists() }
|
||||
onClick={ this.closeModal }
|
||||
outline
|
||||
>
|
||||
{ 'Cancel' }
|
||||
</Button>
|
||||
</div>
|
||||
{ !member.joined && member.invitationLink &&
|
||||
<CopyButton
|
||||
content={member.invitationLink}
|
||||
className="link"
|
||||
btnText="Copy invite link"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
|
|
@ -144,7 +161,7 @@ class ManageUsers extends React.PureComponent {
|
|||
const {
|
||||
members, member, loading, account, hideHeader = false,
|
||||
} = this.props;
|
||||
const { showModal, remaining } = this.state;
|
||||
const { showModal, remaining, invited } = this.state;
|
||||
const isAdmin = account.admin || account.superAdmin;
|
||||
const canAddUsers = isAdmin && remaining !== 0;
|
||||
|
||||
|
|
@ -155,7 +172,7 @@ class ManageUsers extends React.PureComponent {
|
|||
title="Inivte People"
|
||||
size="small"
|
||||
isDisplayed={ showModal }
|
||||
content={ this.formContent(member) }
|
||||
content={ this.formContent(member, account) }
|
||||
onClose={ this.closeModal }
|
||||
/>
|
||||
<div className={ styles.wrapper }>
|
||||
|
|
@ -202,6 +219,7 @@ class ManageUsers extends React.PureComponent {
|
|||
{
|
||||
members.map(user => (
|
||||
<UserItem
|
||||
generateInviteLink={this.props.generateInviteLink}
|
||||
key={ user.id }
|
||||
user={ user }
|
||||
adminLabel={ this.adminLabel(user) }
|
||||
|
|
|
|||
|
|
@ -1,13 +1,43 @@
|
|||
import React from 'react';
|
||||
import { Icon } from 'UI';
|
||||
import { Icon, CopyButton, Popup } from 'UI';
|
||||
import styles from './userItem.css';
|
||||
|
||||
const UserItem = ({ user, adminLabel, deleteHandler, editHandler }) => (
|
||||
const UserItem = ({ user, adminLabel, deleteHandler, editHandler, generateInviteLink }) => (
|
||||
<div className={ styles.wrapper } id="user-row">
|
||||
<Icon name="user-alt" size="16" marginRight="10" />
|
||||
<div id="user-name">{ user.name || user.email }</div>
|
||||
{ adminLabel && <div className={ styles.adminLabel }>{ adminLabel }</div>}
|
||||
<div className={ styles.actions }>
|
||||
{ user.expiredInvitation && !user.joined &&
|
||||
<Popup
|
||||
trigger={
|
||||
<div className={ styles.button } onClick={ () => generateInviteLink(user) } id="trash">
|
||||
<Icon name="link-45deg" size="16" color="red"/>
|
||||
</div>
|
||||
}
|
||||
content={ `Generate Invitation Link` }
|
||||
size="tiny"
|
||||
inverted
|
||||
position="top center"
|
||||
/>
|
||||
}
|
||||
{ !user.expiredInvitation && !user.joined && user.invitationLink &&
|
||||
<Popup
|
||||
trigger={
|
||||
<div className={ styles.button }>
|
||||
<CopyButton
|
||||
content={user.invitationLink}
|
||||
className="link"
|
||||
btnText={<Icon name="link-45deg" size="16" color="teal"/>}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
content={ `Copy Invitation Link` }
|
||||
size="tiny"
|
||||
inverted
|
||||
position="top center"
|
||||
/>
|
||||
}
|
||||
{ !!deleteHandler &&
|
||||
<div className={ styles.button } onClick={ () => deleteHandler(user) } id="trash">
|
||||
<Icon name="trash" size="16" color="teal"/>
|
||||
|
|
|
|||
|
|
@ -34,4 +34,9 @@
|
|||
.adminInfo {
|
||||
font-size: 12px;
|
||||
color: $gray-medium;
|
||||
}
|
||||
|
||||
.smtpMessage {
|
||||
background-color: #faf6e0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
|
@ -35,6 +35,9 @@
|
|||
padding: 5px;
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&:hover {
|
||||
& svg {
|
||||
fill: $teal-dark;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import withPageTitle from 'HOCs/withPageTitle';
|
|||
import { Loader, Button, Link, Icon, Message } from 'UI';
|
||||
import { requestResetPassword, resetPassword } from 'Duck/user';
|
||||
import { login as loginRoute } from 'App/routes';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { validateEmail } from 'App/validate';
|
||||
import cn from 'classnames';
|
||||
import stl from './forgotPassword.css';
|
||||
|
||||
|
|
@ -17,14 +19,16 @@ const checkDontMatch = (newPassword, newPasswordRepeat) =>
|
|||
newPasswordRepeat.length > 0 && newPasswordRepeat !== newPassword;
|
||||
|
||||
@connect(
|
||||
state => ({
|
||||
(state, props) => ({
|
||||
errors: state.getIn([ 'user', 'requestResetPassowrd', 'errors' ]),
|
||||
resetErrors: state.getIn([ 'user', 'resetPassword', 'errors' ]),
|
||||
loading: state.getIn([ 'user', 'requestResetPassowrd', 'loading' ]),
|
||||
params: new URLSearchParams(props.location.search)
|
||||
}),
|
||||
{ requestResetPassword, resetPassword },
|
||||
)
|
||||
@withPageTitle("Password Reset - OpenReplay")
|
||||
@withRouter
|
||||
export default class ForgotPassword extends React.PureComponent {
|
||||
state = {
|
||||
email: '',
|
||||
|
|
@ -37,15 +41,20 @@ export default class ForgotPassword extends React.PureComponent {
|
|||
|
||||
handleSubmit = (token) => {
|
||||
const { email, requested, code, password } = this.state;
|
||||
const { params } = this.props;
|
||||
|
||||
if (!requested) {
|
||||
const pass = params.get('pass')
|
||||
const invitation = params.get('invitation')
|
||||
const resetting = pass && invitation
|
||||
|
||||
if (!resetting) {
|
||||
this.props.requestResetPassword({ email: email.trim(), 'g-recaptcha-response': token }).then(() => {
|
||||
const { errors } = this.props;
|
||||
if (!errors) this.setState({ requested: true });
|
||||
});
|
||||
} else {
|
||||
if (this.isSubmitDisabled()) return;
|
||||
this.props.resetPassword({ email: email.trim(), code, password }).then(() => {
|
||||
this.props.resetPassword({ email: email.trim(), invitation, pass, password }).then(() => {
|
||||
const { resetErrors } = this.props;
|
||||
if (!resetErrors) this.setState({ updated: true });
|
||||
});
|
||||
|
|
@ -78,9 +87,14 @@ export default class ForgotPassword extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { errors, loading } = this.props;
|
||||
const { requested, updated, password, passwordRepeat, code } = this.state;
|
||||
const dontMatch = checkDontMatch(password, passwordRepeat);
|
||||
const { errors, loading, params } = this.props;
|
||||
const { requested, updated, password, passwordRepeat, email } = this.state;
|
||||
const dontMatch = checkDontMatch(password, passwordRepeat);
|
||||
|
||||
const pass = params.get('pass')
|
||||
const invitation = params.get('invitation')
|
||||
const resetting = pass && invitation
|
||||
const validEmail = validateEmail(email)
|
||||
|
||||
return (
|
||||
<div className="flex" style={{ height: '100vh'}}>
|
||||
|
|
@ -113,7 +127,7 @@ export default class ForgotPassword extends React.PureComponent {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{ !requested ?
|
||||
{ !resetting && !requested &&
|
||||
<div className={ stl.inputWithIcon }>
|
||||
<i className={ stl.inputIconUser } />
|
||||
<input
|
||||
|
|
@ -125,47 +139,57 @@ export default class ForgotPassword extends React.PureComponent {
|
|||
onChange={ this.write }
|
||||
className={ stl.input }
|
||||
/>
|
||||
</div>
|
||||
:
|
||||
<React.Fragment>
|
||||
<div className={ stl.inputWithIcon } >
|
||||
<i className={ stl.inputIconPassword } />
|
||||
<input
|
||||
autocomplete="new-password"
|
||||
type="text"
|
||||
placeholder="Code"
|
||||
name="code"
|
||||
onChange={ this.write }
|
||||
className={ stl.input }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className={ stl.inputWithIcon } >
|
||||
<i className={ stl.inputIconPassword } />
|
||||
<input
|
||||
autocomplete="new-password"
|
||||
type="password"
|
||||
placeholder="New Password"
|
||||
name="password"
|
||||
onChange={ this.write }
|
||||
className={ stl.input }
|
||||
/>
|
||||
</div>
|
||||
<div className={ stl.passwordPolicy } data-hidden={ !this.shouldShouwPolicy() }>
|
||||
{ PASSWORD_POLICY }
|
||||
</div>
|
||||
<div className={ stl.inputWithIcon } >
|
||||
<i className={ stl.inputIconPassword } />
|
||||
<input
|
||||
autocomplete="new-password"
|
||||
type="password"
|
||||
placeholder="Repeat New Password"
|
||||
name="passwordRepeat"
|
||||
onChange={ this.write }
|
||||
className={ stl.input }
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
{
|
||||
requested && (
|
||||
<div>Reset password link has been sent to your email.</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
resetting && (
|
||||
<React.Fragment>
|
||||
{/* <div className={ stl.inputWithIcon } >
|
||||
<i className={ stl.inputIconPassword } />
|
||||
<input
|
||||
autocomplete="new-password"
|
||||
type="text"
|
||||
placeholder="Code"
|
||||
name="code"
|
||||
onChange={ this.write }
|
||||
className={ stl.input }
|
||||
/>
|
||||
</div> */}
|
||||
|
||||
<div className={ stl.inputWithIcon } >
|
||||
<i className={ stl.inputIconPassword } />
|
||||
<input
|
||||
autocomplete="new-password"
|
||||
type="password"
|
||||
placeholder="New Password"
|
||||
name="password"
|
||||
onChange={ this.write }
|
||||
className={ stl.input }
|
||||
/>
|
||||
</div>
|
||||
<div className={ stl.passwordPolicy } data-hidden={ !this.shouldShouwPolicy() }>
|
||||
{ PASSWORD_POLICY }
|
||||
</div>
|
||||
<div className={ stl.inputWithIcon } >
|
||||
<i className={ stl.inputIconPassword } />
|
||||
<input
|
||||
autocomplete="new-password"
|
||||
type="password"
|
||||
placeholder="Repeat New Password"
|
||||
name="passwordRepeat"
|
||||
onChange={ this.write }
|
||||
className={ stl.input }
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
<Message error hidden={ !dontMatch }>
|
||||
|
|
@ -185,7 +209,13 @@ export default class ForgotPassword extends React.PureComponent {
|
|||
</div>
|
||||
</div>
|
||||
<div className={ stl.formFooter }>
|
||||
<Button data-hidden={ updated } type="submit" primary >{ 'Reset' }</Button>
|
||||
<Button
|
||||
data-hidden={ updated || requested }
|
||||
type="submit" primary
|
||||
disabled={ (resetting && this.isSubmitDisabled()) || (!resetting && !validEmail)}
|
||||
>
|
||||
{ 'Reset' }
|
||||
</Button>
|
||||
|
||||
<div className={ stl.links }>
|
||||
<Link to={ LOGIN }>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||
import { useState } from 'react';
|
||||
import copy from 'copy-to-clipboard';
|
||||
|
||||
function CopyButton({ content, className }) {
|
||||
function CopyButton({ content, className, btnText = 'copy' }) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const copyHandler = () => {
|
||||
|
|
@ -17,7 +17,7 @@ function CopyButton({ content, className }) {
|
|||
className={ className }
|
||||
onClick={ copyHandler }
|
||||
>
|
||||
{ copied ? 'copied' : 'copy' }
|
||||
{ copied ? 'copied' : btnText }
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,12 @@ export default ({
|
|||
show = true,
|
||||
children = null,
|
||||
empty = false,
|
||||
image = null
|
||||
}) => (!show ? children :
|
||||
<div className={ `${ styles.wrapper } ${ size && styles[ size ] }` }>
|
||||
{
|
||||
image && image
|
||||
}
|
||||
{
|
||||
icon && <div className={ empty ? styles.emptyIcon : styles.icon } />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,48 @@
|
|||
import { Map } from 'immutable';
|
||||
import Member from 'Types/member';
|
||||
import crudDuckGenerator from './tools/crudDuck';
|
||||
import withRequestState, { RequestTypes } from 'Duck/requestStateCreator';
|
||||
import { reduceDucks } from 'Duck/tools';
|
||||
|
||||
const GENERATE_LINK = new RequestTypes('member/GENERATE_LINK');
|
||||
|
||||
const crudDuck = crudDuckGenerator('client/member', Member, { idKey: 'id' });
|
||||
export const {
|
||||
fetchList, init, edit, remove,
|
||||
} = crudDuck.actions;
|
||||
export const { fetchList, init, edit, remove, } = crudDuck.actions;
|
||||
|
||||
const initialState = Map({
|
||||
definedPercent: 0,
|
||||
});
|
||||
|
||||
const reducer = (state = initialState, action = {}) => {
|
||||
switch (action.type) {
|
||||
case GENERATE_LINK.SUCCESS:
|
||||
return state.update(
|
||||
'list',
|
||||
list => list
|
||||
.map(member => {
|
||||
if(member.id === action.id) {
|
||||
return Member({...member.toJS(), invitationLink: action.data.invitationLink })
|
||||
}
|
||||
return member
|
||||
})
|
||||
);
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export function save(instance) {
|
||||
return {
|
||||
types: crudDuck.actionTypes.SAVE.toArray(),
|
||||
call: client => client.put( instance.id ? `/client/members/${ instance.id }` : '/client/members', instance.toData()),
|
||||
call: client => client.put( instance.id ? `/client/members/${ instance.id }` : '/client/members', instance.toData()),
|
||||
};
|
||||
}
|
||||
|
||||
export default crudDuck.reducer;
|
||||
export function generateInviteLink(instance) {
|
||||
return {
|
||||
types: GENERATE_LINK.toArray(),
|
||||
call: client => client.get(`/client/members/${ instance.id }/reset`),
|
||||
id: instance.id
|
||||
};
|
||||
}
|
||||
|
||||
export default reduceDucks(crudDuck, { initialState, reducer }).reducer;
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ const reducer = (state = initialState, action = {}) => {
|
|||
case UPDATE_PASSWORD.SUCCESS:
|
||||
case LOGIN.SUCCESS:
|
||||
return setClient(
|
||||
state.set('account', Account(action.data.user)),
|
||||
state.set('account', Account({...action.data.user, smtp: action.data.client.smtp })),
|
||||
action.data.client,
|
||||
);
|
||||
case SIGNUP.SUCCESS:
|
||||
|
|
|
|||
|
|
@ -125,9 +125,9 @@ export default class AssistManager {
|
|||
this.md.setMessagesLoading(false);
|
||||
}
|
||||
if (status === ConnectionStatus.Connected) {
|
||||
this.md.display(true);
|
||||
// this.md.display(true);
|
||||
} else {
|
||||
this.md.display(false);
|
||||
// this.md.display(false);
|
||||
}
|
||||
update({ peerConnectionStatus: status });
|
||||
}
|
||||
|
|
|
|||
1
frontend/app/svg/icons/integrations/assist.svg
Normal file
1
frontend/app/svg/icons/integrations/assist.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#d7e2e2;}.cls-3{fill:#9da0a0;}.cls-4{fill:#fab29a;}.cls-5{fill:#222;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M114,0H6A6,6,0,0,0,0,6V114a6,6,0,0,0,6,6H114a6,6,0,0,0,6-6V6A6,6,0,0,0,114,0Z"/><path class="cls-2" d="M28,108.75H91a8,8,0,0,0,8-8v-64a8,8,0,0,0-8-8H55.67L35,12V28.81H28a8,8,0,0,0-8,8v64a8,8,0,0,0,8,8Z"/><path class="cls-3" d="M54.11,79.63H64.89A13.25,13.25,0,0,1,78.15,92.88v7.2H40.85v-7.2A13.25,13.25,0,0,1,54.11,79.63Z"/><path class="cls-4" d="M46.18,53.82H72.82V66.3a13.32,13.32,0,1,1-26.64,0Z"/><path class="cls-5" d="M76.15,55v6.93a65,65,0,0,1-22.58-4.94,14.93,14.93,0,0,1-10.72,5V55a16.67,16.67,0,0,1,33.33,0Z"/><path d="M59.67,41.83A13.55,13.55,0,0,0,46.11,55.39V58.1h2.71a2.72,2.72,0,0,1,1.92.8,2.75,2.75,0,0,1,.79,1.91V69a2.75,2.75,0,0,1-.79,1.92,2.71,2.71,0,0,1-1.92.79H46.11A2.71,2.71,0,0,1,43.39,69V55.39a16.23,16.23,0,0,1,4.77-11.5,16.26,16.26,0,0,1,23,0,16.23,16.23,0,0,1,4.77,11.5V71.66a6.78,6.78,0,0,1-6.78,6.78H63.37A2.68,2.68,0,0,1,61,79.8H58.31a2.72,2.72,0,0,1-1.92-.8,2.67,2.67,0,0,1-.79-1.91,2.71,2.71,0,0,1,2.71-2.72H61a2.7,2.7,0,0,1,1.36.37,2.76,2.76,0,0,1,1,1h5.79a4.08,4.08,0,0,0,4.07-4.07H70.51a2.67,2.67,0,0,1-1.91-.79A2.72,2.72,0,0,1,67.8,69V60.81a2.71,2.71,0,0,1,.8-1.91,2.68,2.68,0,0,1,1.91-.8h2.72V55.39a13.61,13.61,0,0,0-4-9.59,13.44,13.44,0,0,0-4.39-2.94,13.61,13.61,0,0,0-5.19-1Z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
4
frontend/app/svg/icons/link-45deg.svg
Normal file
4
frontend/app/svg/icons/link-45deg.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-link-45deg" viewBox="0 0 16 16">
|
||||
<path d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1.002 1.002 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4.018 4.018 0 0 1-.128-1.287z"/>
|
||||
<path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243L6.586 4.672z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 503 B |
|
|
@ -9,6 +9,9 @@ export default Record({
|
|||
createdAt: undefined,
|
||||
admin: false,
|
||||
superAdmin: false,
|
||||
joined: false,
|
||||
expiredInvitation: false,
|
||||
invitationLink: ''
|
||||
}, {
|
||||
idKey: 'id',
|
||||
methods: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue