change(ui) - user limit check and other fixes
This commit is contained in:
parent
918f7e9d86
commit
dfce25709a
9 changed files with 121 additions and 26 deletions
|
|
@ -168,8 +168,11 @@ class Sites extends React.PureComponent {
|
|||
</div>
|
||||
|
||||
<div className={ stl.list }>
|
||||
<div key={ _site.key } className="grid grid-cols-12 gap-2 w-full group hover:bg-active-blue items-center border-b px-2 py-3">
|
||||
<div>Name</div>
|
||||
<div className="grid grid-cols-12 gap-2 w-full items-center border-b px-2 py-3 font-medium">
|
||||
<div className="col-span-4">Name</div>
|
||||
<div className="col-span-4">Key</div>
|
||||
<div className="col-span-4"></div>
|
||||
|
||||
</div>
|
||||
{
|
||||
sites.map(_site => (
|
||||
|
|
@ -191,7 +194,7 @@ class Sites extends React.PureComponent {
|
|||
</div>
|
||||
</div>
|
||||
<div className="col-span-4">
|
||||
<span className="px-2 py-1 bg-gray-lightest rounded border color-teal text-sm">{_site.projectKey}</span>
|
||||
<span className="px-2 py-1 bg-gray-lightest rounded border text-sm">{_site.projectKey}</span>
|
||||
</div>
|
||||
{/* <div className="ml-3 flex items-center">
|
||||
<div>{ _site.host }</div>
|
||||
|
|
|
|||
|
|
@ -8,23 +8,26 @@ import { useModal } from 'App/components/Modal';
|
|||
import UserForm from './components/UserForm';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const PERMISSION_WARNING = 'You don’t have the permissions to perform this action.';
|
||||
const LIMIT_WARNING = 'You have reached users limit.';
|
||||
interface Props {
|
||||
account: any;
|
||||
isEnterprise: boolean;
|
||||
limits: any;
|
||||
}
|
||||
function UsersView(props: Props) {
|
||||
const { account, isEnterprise } = props;
|
||||
const { account, limits, isEnterprise } = props;
|
||||
const { userStore, roleStore } = useStore();
|
||||
const userCount = useObserver(() => userStore.list.length);
|
||||
const roles = useObserver(() => roleStore.list);
|
||||
const { showModal } = useModal();
|
||||
|
||||
const reachedLimit = (limits.remaining + userStore.modifiedCount) <= 0;
|
||||
const isAdmin = account.admin || account.superAdmin;
|
||||
// const canAddUsers = isAdmin && userCount !== 0; // TODO fetch limits and disable button if limit reached
|
||||
|
||||
const editHandler = (user = null) => {
|
||||
userStore.initUser(user).then(() => {
|
||||
showModal(<UserForm />, { right: true });
|
||||
showModal(<UserForm />, {});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -34,6 +37,8 @@ function UsersView(props: Props) {
|
|||
}
|
||||
}, []);
|
||||
|
||||
console.log('remaining', limits, reachedLimit)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
|
|
@ -45,7 +50,7 @@ function UsersView(props: Props) {
|
|||
<div>
|
||||
<IconButton
|
||||
id="add-button"
|
||||
// disabled={ !canAddUsers }
|
||||
disabled={ reachedLimit || !isAdmin }
|
||||
circle
|
||||
icon="plus"
|
||||
outline
|
||||
|
|
@ -54,8 +59,7 @@ function UsersView(props: Props) {
|
|||
/>
|
||||
</div>
|
||||
}
|
||||
// disabled={ canAddUsers }
|
||||
// content={ `${ !canAddUsers ? (!isAdmin ? PERMISSION_WARNING : LIMIT_WARNING) : 'Add team member' }` }
|
||||
content={ `${ !isAdmin ? PERMISSION_WARNING : (reachedLimit ? LIMIT_WARNING : 'Add team member') }` }
|
||||
size="tiny"
|
||||
inverted
|
||||
position="top left"
|
||||
|
|
@ -74,4 +78,6 @@ function UsersView(props: Props) {
|
|||
export default connect(state => ({
|
||||
account: state.getIn([ 'user', 'account' ]),
|
||||
isEnterprise: state.getIn([ 'user', 'client', 'edition' ]) === 'ee',
|
||||
limits: state.getIn([ 'user', 'account', 'limits', 'teamMember' ]),
|
||||
// remaining: this.props.account.limits.teamMember.remaining
|
||||
}))(UsersView);
|
||||
|
|
@ -12,9 +12,9 @@ interface Props {
|
|||
}
|
||||
function UserForm(props: Props) {
|
||||
const { isSmtp = false, isEnterprise = false } = props;
|
||||
const isSaving = false;
|
||||
const { hideModal } = useModal();
|
||||
const { userStore, roleStore } = useStore();
|
||||
const isSaving = useObserver(() => userStore.saving);
|
||||
const user: any = useObserver(() => userStore.instance);
|
||||
const roles = useObserver(() => roleStore.list.filter(r => r.isProtected ? user.isSuperAdmin : true).map(r => ({ label: r.name, value: r.roleId })));
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ function UserForm(props: Props) {
|
|||
<div className="form-group">
|
||||
<label htmlFor="role">{ 'Role' }</label>
|
||||
<Select
|
||||
placeholder="Role"
|
||||
placeholder="Selct Role"
|
||||
selection
|
||||
options={ roles }
|
||||
name="roleId"
|
||||
|
|
@ -115,12 +115,12 @@ function UserForm(props: Props) {
|
|||
<div className="flex items-center mr-auto">
|
||||
<Button
|
||||
onClick={ onSave }
|
||||
disabled={ !user.valid() }
|
||||
disabled={ !user.valid() || isSaving }
|
||||
loading={ isSaving }
|
||||
primary
|
||||
marginRight
|
||||
>
|
||||
{ user.exists() ? 'Update' : 'Invite' }
|
||||
{ user.exists() ? 'Update' : 'Invite' }
|
||||
</Button>
|
||||
<Button
|
||||
data-hidden={ !user.exists() }
|
||||
|
|
@ -143,7 +143,7 @@ function UserForm(props: Props) {
|
|||
{ !user.isJoined && user.invitationLink &&
|
||||
<CopyButton
|
||||
content={user.invitationLink}
|
||||
className="link"
|
||||
className="link mt-4"
|
||||
btnText="Copy invite link"
|
||||
/>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ function UserList(props) {
|
|||
|
||||
const editHandler = (user) => {
|
||||
userStore.initUser(user).then(() => {
|
||||
showModal(<UserForm />, { right: true });
|
||||
showModal(<UserForm />, { });
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -42,12 +42,18 @@ function UserList(props) {
|
|||
<div className="grid grid-cols-12 p-3 border-b font-medium">
|
||||
<div className="col-span-5">Name</div>
|
||||
<div className="col-span-3">Role</div>
|
||||
<div className="col-span-"></div>
|
||||
<div className="col-span-2">Created On</div>
|
||||
<div className="col-span-2"></div>
|
||||
</div>
|
||||
|
||||
{sliceListPerPage(list, userStore.page - 1, userStore.pageSize).map((user: any) => (
|
||||
<div key={user.id} className="">
|
||||
<UserListItem user={user} editHandler={() => editHandler(user)} />
|
||||
<UserListItem
|
||||
user={user}
|
||||
editHandler={() => editHandler(user)}
|
||||
generateInvite={() => userStore.generateInviteCode(user.userId)}
|
||||
copyInviteCode={() => userStore.copyInviteCode(user.userId)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,52 @@
|
|||
import React from 'react';
|
||||
import { Icon } from 'UI';
|
||||
import { Icon, CopyButton } from 'UI';
|
||||
import { checkForRecent } from 'App/date';
|
||||
|
||||
interface Props {
|
||||
user: any;
|
||||
editHandler?: any;
|
||||
generateInvite?: any;
|
||||
copyInviteCode?: any;
|
||||
}
|
||||
function UserListItem(props: Props) {
|
||||
const { user, editHandler = () => {} } = props;
|
||||
const {
|
||||
user,
|
||||
editHandler = () => {},
|
||||
generateInvite = () => {},
|
||||
copyInviteCode = () => {},
|
||||
} = props;
|
||||
return (
|
||||
<div className="grid grid-cols-12 p-3 py-4 border-b items-center select-none hover:bg-active-blue group">
|
||||
<div className="col-span-5">{user.name}</div>
|
||||
<div className="col-span-5">
|
||||
{user.name}
|
||||
{user.isAdmin && <span className="ml-2 px-2 py-1 bg-gray-lightest rounded border text-sm capitalize">Admin</span>}
|
||||
{user.isSuperAdmin && <span className="ml-2 px-2 py-1 bg-gray-lightest rounded border text-sm capitalize">Owner</span>}
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
<span className="px-2 py-1 bg-gray-lightest rounded border color-teal text-sm capitalize">
|
||||
<span className="px-2 py-1 bg-gray-lightest rounded border text-sm capitalize">
|
||||
{user.roleName}
|
||||
</span>
|
||||
</div>
|
||||
<div className="col-span-4 justify-self-end invisible group-hover:visible">
|
||||
<button className='' onClick={editHandler}>
|
||||
<Icon name="pencil" color="teal" size="16" />
|
||||
</button>
|
||||
<div className="col-span-2">
|
||||
<span>{user.createdAt && checkForRecent(user.createdAt, 'LLL dd, yyyy, hh:mm a')}</span>
|
||||
</div>
|
||||
{/* invisible */}
|
||||
<div className="col-span-2 justify-self-end group-hover:visible">
|
||||
<div className="grid grid-cols-2 gap-3 items-center justify-end">
|
||||
{!user.isJoined && user.invitationLink ? (
|
||||
<button className='' onClick={copyInviteCode}>
|
||||
<Icon name="link-45deg" size="16" color="teal"/>
|
||||
</button>
|
||||
) : <div/>}
|
||||
{!user.isJoined && user.isExpiredInvite && (
|
||||
<button className='' onClick={generateInvite}>
|
||||
<Icon name="link-45deg" size="16" color="red"/>
|
||||
</button>
|
||||
)}
|
||||
<button className='' onClick={editHandler}>
|
||||
<Icon name="pencil" color="teal" size="16" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export default function({ plain = false, options, isSearchable = false, defaultV
|
|||
return { ...provided, opacity, transition };
|
||||
}
|
||||
}
|
||||
const defaultSelected = defaultValue ? options.find(x => x.value === defaultValue) : options[0];
|
||||
const defaultSelected = defaultValue ? options.find(x => x.value === defaultValue) : null;
|
||||
return (
|
||||
<Select
|
||||
options={options}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ export interface IUser {
|
|||
roleName: string
|
||||
invitationLink: string
|
||||
|
||||
|
||||
updateKey(key: string, value: any): void
|
||||
fromJson(json: any): IUser
|
||||
toJson(): any
|
||||
toSave(): any
|
||||
|
|
@ -79,6 +81,9 @@ export default class User implements IUser {
|
|||
admin: this.isAdmin,
|
||||
superAdmin: this.isSuperAdmin,
|
||||
roleId: this.roleId,
|
||||
joined: this.isJoined,
|
||||
invitationLink: this.invitationLink,
|
||||
expiredInvitation: this.isExpiredInvite,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { makeAutoObservable, observable, action } from "mobx"
|
|||
import User, { IUser } from "./types/user";
|
||||
import { userService } from "App/services";
|
||||
import { toast } from 'react-toastify';
|
||||
import copy from 'copy-to-clipboard';
|
||||
|
||||
export default class UserStore {
|
||||
list: IUser[] = [];
|
||||
|
|
@ -9,6 +10,7 @@ export default class UserStore {
|
|||
page: number = 1;
|
||||
pageSize: number = 10;
|
||||
searchQuery: string = "";
|
||||
modifiedCount: number = 0;
|
||||
|
||||
loading: boolean = false;
|
||||
saving: boolean = false;
|
||||
|
|
@ -87,6 +89,7 @@ export default class UserStore {
|
|||
userService.save(user).then(response => {
|
||||
const newUser = new User().fromJson(response);
|
||||
if (wasCreating) {
|
||||
this.modifiedCount -= 1;
|
||||
this.list.push(new User().fromJson(newUser));
|
||||
toast.success('User created successfully');
|
||||
} else {
|
||||
|
|
@ -108,6 +111,7 @@ export default class UserStore {
|
|||
return new Promise((resolve, reject) => {
|
||||
userService.delete(userId)
|
||||
.then(response => {
|
||||
this.modifiedCount += 1;
|
||||
this.list = this.list.filter(user => user.userId !== userId);
|
||||
resolve(response);
|
||||
}).catch(error => {
|
||||
|
|
@ -118,4 +122,41 @@ export default class UserStore {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
copyInviteCode(userId: string): void {
|
||||
const content = this.list.find(u => u.userId === userId)?.invitationLink;
|
||||
if (content) {
|
||||
copy(content);
|
||||
toast.success('Invite code copied successfully');
|
||||
} else {
|
||||
toast.error('Invite code not found');
|
||||
}
|
||||
}
|
||||
|
||||
generateInviteCode(userId: string): Promise<any> {
|
||||
this.saving = true;
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
userService.generateInviteCode(userId)
|
||||
.then(response => {
|
||||
const index = this.list.findIndex(u => u.userId === userId);
|
||||
if (index > -1) {
|
||||
this.list[index].updateKey('isExpiredInvite', false);
|
||||
this.list[index].updateKey('invitationLink', response.invitationLink);
|
||||
}
|
||||
resolve(response);
|
||||
}).catch(error => {
|
||||
this.saving = false;
|
||||
reject(error);
|
||||
}).finally(() => {
|
||||
this.saving = false;
|
||||
});
|
||||
});
|
||||
|
||||
toast.promise(promise, {
|
||||
pending: 'Generating an invite code...',
|
||||
success: 'Invite code generated successfully',
|
||||
})
|
||||
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,12 @@ export default class UserService {
|
|||
}
|
||||
}
|
||||
|
||||
generateInviteCode(userId: any): Promise<any> {
|
||||
return this.client.get(`/client/members/${userId}/reset`)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
}
|
||||
|
||||
delete(userId: string) {
|
||||
return this.client.delete('/client/members/' + userId)
|
||||
.then(response => response.json())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue