change(ui) - users list - form

This commit is contained in:
Shekar Siri 2022-05-04 16:41:29 +02:00
parent 424b071eaf
commit c97fe55cda
12 changed files with 275 additions and 20 deletions

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import UserList from './components/UserList';
import { PageTitle } from 'UI';
import { useStore } from 'App/mstore';
@ -6,8 +6,15 @@ import { useObserver } from 'mobx-react-lite';
import UserSearch from './components/UserSearch';
function UsersView(props) {
const { userStore } = useStore();
const { userStore, roleStore } = useStore();
const userCount = useObserver(() => userStore.list.length);
const roles = useObserver(() => roleStore.list);
useEffect(() => {
if (roles.length === 0) {
roleStore.fetchRoles();
}
}, []);
return (
<div>

View file

@ -0,0 +1,129 @@
import React from 'react';
import { Input, CopyButton, Button, Select } from 'UI'
import cn from 'classnames';
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
import { useModal } from 'App/components/Modal';
interface Props {
isSmtp?: boolean;
isEnterprise?: boolean;
}
function UserForm(props: Props) {
const { isSmtp = false, isEnterprise = false } = props;
const isSaving = false;
const { hideModal } = useModal();
const { userStore, roleStore } = useStore();
const user: any = useObserver(() => userStore.instance);
const roles = useObserver(() => roleStore.list.map(r => ({ label: r.name, value: r.roleId })));
console.log('roles', roles)
const onChangeCheckbox = (e: any) => {
user.updateKey('isAdmin', !user.isAdmin);
}
const onSave = () => {
}
const write = ({ target: { name, value } }) => {
user.updateKey(name, value);
}
return useObserver(() => (
<div className="bg-white h-screen p-6" style={{ width: '400px'}}>
<div className="">
<h1 className="text-2xl mb-4">{`${user.exists() ? 'Update' : 'Invite'} User`}</h1>
</div>
<form onSubmit={ onSave } >
<div className="form-group">
<label>{ 'Full Name' }</label>
<Input
name="name"
autoFocus
value={ user.name }
onChange={ write }
className="w-full"
id="name-field"
/>
</div>
<div className="form-group">
<label>{ 'Email Address' }</label>
<Input
disabled={user.exists()}
name="email"
value={ user.email }
onChange={ write }
className="w-full"
/>
</div>
{ !isSmtp &&
<div className={cn("mb-4 p-2 bg-yellow rounded")}>
SMTP is not configured (see <a className="link" href="https://docs.openreplay.com/configuration/configure-smtp" target="_blank">here</a> how to set it up). You can still add new users, but youd have to manually copy then send them the invitation link.
</div>
}
<div className="form-group">
<label className="flex items-start cursor-pointer">
<input
name="admin"
type="checkbox"
value={ user.isAdmin }
checked={ !!user.isAdmin }
onChange={ onChangeCheckbox }
disabled={user.superAdmin}
className="mt-1"
/>
<div className="ml-2 select-none">
<span>Admin Privileges</span>
<div className="text-sm color-gray-medium -mt-1">{ 'Can manage Projects and team members.' }</div>
</div>
</label>
</div>
{ !isEnterprise && (
<div className="form-group">
<label htmlFor="role">{ 'Role' }</label>
<Select
placeholder="Role"
selection
options={ roles }
name="roleId"
value={ user.roleId }
onChange={ write }
className="block"
/>
</div>
)}
</form>
<div className="flex items-center">
<div className="flex items-center mr-auto">
<Button
onClick={ onSave }
disabled={ !user.valid() }
loading={ isSaving }
primary
marginRight
>
{ user.exists() ? 'Update' : 'Invite' }
</Button>
<Button
data-hidden={ !user.exists() }
onClick={ hideModal }
outline
>
{ 'Cancel' }
</Button>
</div>
{ !user.isJoined && user.invitationLink &&
<CopyButton
content={user.invitationLink}
className="link"
btnText="Copy invite link"
/>
}
</div>
</div>
));
}
export default UserForm;

View file

@ -0,0 +1 @@
export { default } from './UserForm';

View file

@ -4,12 +4,15 @@ import React, { useEffect } from 'react';
import UserListItem from '../UserListItem';
import { sliceListPerPage, getRE } from 'App/utils';
import { Pagination, NoContent, Loader } from 'UI';
import { useModal } from 'App/components/Modal';
import UserForm from '../UserForm';
function UserList(props) {
const { userStore } = useStore();
const loading = useObserver(() => userStore.loading);
const users = useObserver(() => userStore.list);
const searchQuery = useObserver(() => userStore.searchQuery);
const { showModal } = useModal();
const filterList = (list) => {
const filterRE = getRE(searchQuery, 'i');
@ -24,11 +27,18 @@ function UserList(props) {
useEffect(() => {
userStore.fetchUsers();
editHandler(null);
}, []);
const editHandler = (user) => {
userStore.initUser(user).then(() => {
showModal(<UserForm />, { right: true });
});
}
return useObserver(() => (
<Loader loading={loading}>
<NoContent show={!loading && length === 0}>
<NoContent show={!loading && length === 0} animatedIcon="empty-state">
<div className="mt-3 rounded bg-white">
<div className="grid grid-cols-12 p-3 border-b font-medium">
<div className="col-span-5">Name</div>
@ -38,7 +48,7 @@ function UserList(props) {
{sliceListPerPage(list, userStore.page - 1, userStore.pageSize).map((user: any) => (
<div key={user.id} className="">
<UserListItem user={user} />
<UserListItem user={user} editHandler={() => editHandler(user)} />
</div>
))}
</div>

View file

@ -1,20 +1,24 @@
import React from 'react';
import { Icon } from 'UI';
interface Props {
user: any;
editHandler?: any;
}
function UserListItem(props: Props) {
const { user } = props;
const { user, editHandler = () => {} } = props;
return (
<div className="grid grid-cols-12 p-3 py-6 border-b select-none hover:bg-active-blue">
<div className="col-span-5">{user.email}</div>
<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-3">
<span className="px-2 py-1 bg-gray-lightest rounded border color-teal text-sm capitalize">
{user.roleName}
</span>
</div>
<div className="col-span-3">
<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>
</div>
);

View file

@ -2,6 +2,7 @@ import React from 'react';
import DashboardStore, { IDashboardSotre } from './dashboardStore';
import MetricStore, { IMetricStore } from './metricStore';
import UserStore from './userStore';
import RoleStore from './roleStore';
import APIClient from 'App/api_client';
import { dashboardService, metricService, sessionService, userService } from 'App/services';
import SettingsStore from './settingsStore';
@ -11,12 +12,14 @@ export class RootStore {
metricStore: IMetricStore;
settingsStore: SettingsStore;
userStore: UserStore;
roleStore: RoleStore;
constructor() {
this.dashboardStore = new DashboardStore();
this.metricStore = new MetricStore();
this.settingsStore = new SettingsStore();
this.userStore = new UserStore();
this.roleStore = new RoleStore();
}
initClient() {

View file

@ -0,0 +1,31 @@
import { makeAutoObservable, observable, action } from "mobx"
import { userService } from "App/services";
import Role, { IRole } from "./types/role";
export default class UserStore {
list: IRole[] = [];
loading: boolean = false;
constructor() {
makeAutoObservable(this, {
list: observable,
loading: observable,
})
}
fetchRoles(): Promise<any> {
this.loading = true;
return new Promise((resolve, reject) => {
userService.getRoles()
.then(response => {
this.list = response.map((role: any) => new Role().fromJson(role));
resolve(response);
}).catch(error => {
this.loading = false;
reject(error);
}).finally(() => {
this.loading = false;
});
});
}
}

View file

@ -0,0 +1,42 @@
import { makeAutoObservable, observable, runInAction } from "mobx";
export interface IRole {
roleId: string;
name: string;
description: string;
fromJson(json: any): IRole;
toJson(): any;
}
export default class Role implements IRole {
roleId: string = '';
name: string = '';
description: string = '';
constructor() {
makeAutoObservable(this, {
roleId: observable,
name: observable,
description: observable,
})
}
fromJson(json: any) {
runInAction(() => {
this.roleId = json.id;
this.name = json.name;
this.description = json.description;
})
return this;
}
toJson() {
return {
id: this.roleId,
name: this.name,
description: this.description,
}
}
}

View file

@ -1,5 +1,7 @@
import { runInAction, makeAutoObservable, observable } from 'mobx'
import { DateTime } from 'luxon';
import { validateEmail } from 'App/validate';
export interface IUser {
userId: string
email: string
@ -16,8 +18,9 @@ export interface IUser {
toJson(): any
}
export default class User {
export default class User implements IUser {
userId: string = '';
name: string = '';
email: string = '';
createdAt: string = '';
isAdmin: boolean = false;
@ -43,9 +46,17 @@ export default class User {
})
}
updateKey(key: string, value: any) {
console.log(key, value)
runInAction(() => {
this[key] = value
})
}
fromJson(json: any) {
runInAction(() => {
this.userId = json.id;
this.name = json.name;
this.email = json.email;
this.createdAt = json.createdAt && DateTime.fromISO(json.createdAt || 0)
this.isAdmin = json.admin
@ -67,4 +78,12 @@ export default class User {
roleId: this.roleId,
}
}
valid() {
return validateEmail(this.email) && !!this.roleId;
}
exists() {
return !!this.userId;
}
}

View file

@ -15,6 +15,18 @@ export default class UserStore {
instance: observable,
updateUser: action,
updateKey: action,
initUser: action,
})
}
initUser(user?: any ): Promise<void> {
return new Promise((resolve, reject) => {
if (user) {
this.instance = user;
} else {
this.instance = new User();
}
resolve();
})
}

View file

@ -42,4 +42,10 @@ export default class UserService {
.then(response => response.json())
.then(response => response.data || {});
}
getRoles() {
return this.client.get('/client/roles')
.then(response => response.json())
.then(response => response.data || []);
}
}

View file

@ -147,13 +147,4 @@
height: 100vh;
overflow-y: hidden;
padding-right: 15px;
}
/* .svg-map__location {
fill: #EEE !important;
cursor: pointer;
&:hover {
fill: #fff !important;
}
} */
}