From 7d08e32d25572942998b097b0d5eefe095172e7c Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Wed, 4 May 2022 12:27:44 +0200 Subject: [PATCH] change(ui) - users list --- frontend/app/components/Client/Client.js | 3 +- .../app/components/Client/Users/UsersView.tsx | 30 ++++++ .../Users/components/UserList/UserList.tsx | 47 +++++++++ .../Client/Users/components/UserList/index.ts | 1 + .../components/UserListItem/UserListItem.tsx | 23 +++++ .../Users/components/UserListItem/index.ts | 1 + .../components/UserSearch/UserSearch.tsx | 35 +++++++ .../Users/components/UserSearch/index.ts | 1 + frontend/app/mstore/index.tsx | 6 +- frontend/app/mstore/types/user.ts | 70 +++++++++++++ frontend/app/mstore/userStore.ts | 98 +++++++++++++++++++ frontend/app/services/UserService.ts | 45 +++++++++ frontend/app/services/index.ts | 4 +- 13 files changed, 361 insertions(+), 3 deletions(-) create mode 100644 frontend/app/components/Client/Users/UsersView.tsx create mode 100644 frontend/app/components/Client/Users/components/UserList/UserList.tsx create mode 100644 frontend/app/components/Client/Users/components/UserList/index.ts create mode 100644 frontend/app/components/Client/Users/components/UserListItem/UserListItem.tsx create mode 100644 frontend/app/components/Client/Users/components/UserListItem/index.ts create mode 100644 frontend/app/components/Client/Users/components/UserSearch/UserSearch.tsx create mode 100644 frontend/app/components/Client/Users/components/UserSearch/index.ts create mode 100644 frontend/app/mstore/types/user.ts create mode 100644 frontend/app/mstore/userStore.ts create mode 100644 frontend/app/services/UserService.ts diff --git a/frontend/app/components/Client/Client.js b/frontend/app/components/Client/Client.js index cb55d933d..bb702f9ed 100644 --- a/frontend/app/components/Client/Client.js +++ b/frontend/app/components/Client/Client.js @@ -7,6 +7,7 @@ import { fetchList as fetchMemberList } from 'Duck/member'; import ProfileSettings from './ProfileSettings'; import Integrations from './Integrations'; import ManageUsers from './ManageUsers'; +import UserView from './Users/UsersView'; import Sites from './Sites'; import CustomFields from './CustomFields'; import Webhooks from './Webhooks'; @@ -36,7 +37,7 @@ export default class Client extends React.PureComponent { - + diff --git a/frontend/app/components/Client/Users/UsersView.tsx b/frontend/app/components/Client/Users/UsersView.tsx new file mode 100644 index 000000000..cb57a9ec6 --- /dev/null +++ b/frontend/app/components/Client/Users/UsersView.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import UserList from './components/UserList'; +import { PageTitle } from 'UI'; +import { useStore } from 'App/mstore'; +import { useObserver } from 'mobx-react-lite'; +import UserSearch from './components/UserSearch'; + +function UsersView(props) { + const { userStore } = useStore(); + const userCount = useObserver(() => userStore.list.length); + + return ( +
+
+ Team {userCount}
} + actionButton={( +
test
+ )} + /> +
+ +
+
+ + + ); +} + +export default UsersView; \ No newline at end of file diff --git a/frontend/app/components/Client/Users/components/UserList/UserList.tsx b/frontend/app/components/Client/Users/components/UserList/UserList.tsx new file mode 100644 index 000000000..c9c557b22 --- /dev/null +++ b/frontend/app/components/Client/Users/components/UserList/UserList.tsx @@ -0,0 +1,47 @@ +import { useStore } from 'App/mstore'; +import { useObserver } from 'mobx-react-lite'; +import React, { useEffect } from 'react'; +import UserListItem from '../UserListItem'; +import { sliceListPerPage } from 'App/utils'; +import { Pagination } from 'UI'; + +function UserList(props) { + const { userStore } = useStore(); + const list = useObserver(() => userStore.list); + const length = list.length; + + useEffect(() => { + userStore.fetchUsers(); + }, []); + + return useObserver(() => ( +
+
+
+
Name
+
Role
+
+
+ + {sliceListPerPage(list, userStore.page - 1, userStore.pageSize).map((user: any) => ( +
+ +
+ ))} +
+ +
+ userStore.updateKey('page', page)} + limit={userStore.pageSize} + debounceRequest={100} + /> +
+ +
+ )); +} + +export default UserList; \ No newline at end of file diff --git a/frontend/app/components/Client/Users/components/UserList/index.ts b/frontend/app/components/Client/Users/components/UserList/index.ts new file mode 100644 index 000000000..129ddae35 --- /dev/null +++ b/frontend/app/components/Client/Users/components/UserList/index.ts @@ -0,0 +1 @@ +export { default } from './UserList' \ No newline at end of file diff --git a/frontend/app/components/Client/Users/components/UserListItem/UserListItem.tsx b/frontend/app/components/Client/Users/components/UserListItem/UserListItem.tsx new file mode 100644 index 000000000..ad991da9a --- /dev/null +++ b/frontend/app/components/Client/Users/components/UserListItem/UserListItem.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +interface Props { + user: any; +} +function UserListItem(props: Props) { + const { user } = props; + return ( +
+
{user.email}
+
+ + {user.roleName} + +
+
+ +
+
+ ); +} + +export default UserListItem; \ No newline at end of file diff --git a/frontend/app/components/Client/Users/components/UserListItem/index.ts b/frontend/app/components/Client/Users/components/UserListItem/index.ts new file mode 100644 index 000000000..f0e2ae7e3 --- /dev/null +++ b/frontend/app/components/Client/Users/components/UserListItem/index.ts @@ -0,0 +1 @@ +export { default } from './UserListItem'; \ No newline at end of file diff --git a/frontend/app/components/Client/Users/components/UserSearch/UserSearch.tsx b/frontend/app/components/Client/Users/components/UserSearch/UserSearch.tsx new file mode 100644 index 000000000..3b2f5d754 --- /dev/null +++ b/frontend/app/components/Client/Users/components/UserSearch/UserSearch.tsx @@ -0,0 +1,35 @@ +import { useObserver } from 'mobx-react-lite'; +import React, { useEffect, useState } from 'react'; +import { useStore } from 'App/mstore'; +import { Icon } from 'UI'; +import { debounce } from 'App/utils'; + +let debounceUpdate: any = () => {} +function UserSearch(props) { + const { userStore } = useStore(); + const [query, setQuery] = useState(userStore.searchQuery); + + useEffect(() => { + debounceUpdate = debounce((key, value) => userStore.updateKey(key, value), 500); + }, []) + + const write = ({ target: { name, value } }) => { + setQuery(value); + debounceUpdate(name, value); + } + + return useObserver(() => ( +
+ + +
+ )); +} + +export default UserSearch; \ No newline at end of file diff --git a/frontend/app/components/Client/Users/components/UserSearch/index.ts b/frontend/app/components/Client/Users/components/UserSearch/index.ts new file mode 100644 index 000000000..3810a4030 --- /dev/null +++ b/frontend/app/components/Client/Users/components/UserSearch/index.ts @@ -0,0 +1 @@ +export { default } from './UserSearch'; \ No newline at end of file diff --git a/frontend/app/mstore/index.tsx b/frontend/app/mstore/index.tsx index f72972121..dc6560c93 100644 --- a/frontend/app/mstore/index.tsx +++ b/frontend/app/mstore/index.tsx @@ -1,19 +1,22 @@ import React from 'react'; import DashboardStore, { IDashboardSotre } from './dashboardStore'; import MetricStore, { IMetricStore } from './metricStore'; +import UserStore from './userStore'; import APIClient from 'App/api_client'; -import { dashboardService, metricService, sessionService } from 'App/services'; +import { dashboardService, metricService, sessionService, userService } from 'App/services'; import SettingsStore from './settingsStore'; export class RootStore { dashboardStore: IDashboardSotre; metricStore: IMetricStore; settingsStore: SettingsStore; + userStore: UserStore; constructor() { this.dashboardStore = new DashboardStore(); this.metricStore = new MetricStore(); this.settingsStore = new SettingsStore(); + this.userStore = new UserStore(); } initClient() { @@ -21,6 +24,7 @@ export class RootStore { dashboardService.initClient(client) metricService.initClient(client) sessionService.initClient(client) + userService.initClient(client) } } diff --git a/frontend/app/mstore/types/user.ts b/frontend/app/mstore/types/user.ts new file mode 100644 index 000000000..88b168624 --- /dev/null +++ b/frontend/app/mstore/types/user.ts @@ -0,0 +1,70 @@ +import { runInAction, makeAutoObservable, observable } from 'mobx' +import { DateTime } from 'luxon'; +export interface IUser { + userId: string + email: string + createdAt: string + isAdmin: boolean + isSuperAdmin: boolean + isJoined: boolean + isExpiredInvite: boolean + roleId: string + roleName: string + invitationLink: string + + fromJson(json: any): IUser + toJson(): any +} + +export default class User { + userId: string = ''; + email: string = ''; + createdAt: string = ''; + isAdmin: boolean = false; + isSuperAdmin: boolean = false; + isJoined: boolean = false; + isExpiredInvite: boolean = false; + roleId: string = ''; + roleName: string = ''; + invitationLink: string = ''; + + constructor() { + makeAutoObservable(this, { + userId: observable, + email: observable, + createdAt: observable, + isAdmin: observable, + isSuperAdmin: observable, + isJoined: observable, + isExpiredInvite: observable, + roleId: observable, + roleName: observable, + invitationLink: observable, + }) + } + + fromJson(json: any) { + runInAction(() => { + this.userId = json.id; + this.email = json.email; + this.createdAt = json.createdAt && DateTime.fromISO(json.createdAt || 0) + this.isAdmin = json.admin + this.isSuperAdmin = json.superAdmin + this.isJoined = json.joined + this.isExpiredInvite = json.expiredInvitation + this.roleId = json.roleId + this.roleName = json.roleName + this.invitationLink = json.invitationLink + }) + return this; + } + + toJson() { + return { + email: this.email, + isAdmin: this.isAdmin, + isSuperAdmin: this.isSuperAdmin, + roleId: this.roleId, + } + } +} \ No newline at end of file diff --git a/frontend/app/mstore/userStore.ts b/frontend/app/mstore/userStore.ts new file mode 100644 index 000000000..a0c004f82 --- /dev/null +++ b/frontend/app/mstore/userStore.ts @@ -0,0 +1,98 @@ +import { makeAutoObservable, observable, action } from "mobx" +import User, { IUser } from "./types/user"; +import { userService } from "App/services"; + +export default class UserStore { + list: IUser[] = []; + instance: IUser|null = null; + loading: boolean = false; + page: number = 1; + pageSize: number = 5; + searchQuery: string = ""; + + constructor() { + makeAutoObservable(this, { + instance: observable, + updateUser: action, + updateKey: action, + }) + } + + updateKey(key: string, value: any) { + this[key] = value + } + + updateUser(user: any) { + Object.assign(this.instance, user); + } + + fetchUser(userId: string): Promise { + this.loading = true; + return new Promise((resolve, reject) => { + userService.one(userId) + .then(response => { + this.instance = new User().fromJson(response.data); + resolve(response); + }).catch(error => { + this.loading = false; + reject(error); + }).finally(() => { + this.loading = false; + }); + }); + } + + fetchUsers(): Promise { + this.loading = true; + return new Promise((resolve, reject) => { + userService.all() + .then(response => { + console.log('list', response); + this.list = response.map(user => new User().fromJson(user)); + resolve(response); + }).catch(error => { + this.loading = false; + reject(error); + }).finally(() => { + this.loading = false; + }); + }); + } + + saveUser(user: IUser): Promise { + this.loading = true; + const wasCreating = !user.userId; + return new Promise((resolve, reject) => { + userService.save(user) + .then(response => { + if (wasCreating) { + this.list.push(new User().fromJson(response.data)); + } else { + this.updateUser(response.data); + } + resolve(response); + }).catch(error => { + this.loading = false; + reject(error); + }).finally(() => { + this.loading = false; + }); + }); + } + + deleteUser(userId: string): Promise { + this.loading = true; + return new Promise((resolve, reject) => { + userService.delete(userId) + .then(response => { + this.instance = null; + resolve(response); + }).catch(error => { + this.loading = false; + reject(error); + }).finally(() => { + this.loading = false; + }); + }); + } +} \ No newline at end of file diff --git a/frontend/app/services/UserService.ts b/frontend/app/services/UserService.ts new file mode 100644 index 000000000..3cdead002 --- /dev/null +++ b/frontend/app/services/UserService.ts @@ -0,0 +1,45 @@ +import APIClient from 'App/api_client'; +import { IUser } from 'App/mstore/types/user' + +export default class UserService { + private client: APIClient; + + constructor(client?: APIClient) { + this.client = client ? client : new APIClient(); + } + + initClient(client?: APIClient) { + this.client = client || new APIClient(); + } + + all() { + return this.client.get('/client/members') + .then(response => response.json()) + .then(response => response.data || []); + } + + one(userId: string) { + return this.client.get('/users/' + userId) + .then(response => response.json()) + .then(response => response.data || {}); + } + + save(user: IUser) { + const data = user.toJson(); + if (user.userId) { + return this.client.put('/users/' + user.userId, data) + .then(response => response.json()) + .then(response => response.data || {}); + } else { + return this.client.post('/users', data) + .then(response => response.json()) + .then(response => response.data || {}); + } + } + + delete(userId: string) { + return this.client.delete('/users/' + userId) + .then(response => response.json()) + .then(response => response.data || {}); + } +} \ No newline at end of file diff --git a/frontend/app/services/index.ts b/frontend/app/services/index.ts index e721e0f10..3cb64a7ad 100644 --- a/frontend/app/services/index.ts +++ b/frontend/app/services/index.ts @@ -1,7 +1,9 @@ import DashboardService, { IDashboardService } from "./DashboardService"; import MetricService, { IMetricService } from "./MetricService"; import SessionSerivce from "./SessionService"; +import UserService from "./UserService"; export const dashboardService: IDashboardService = new DashboardService(); export const metricService: IMetricService = new MetricService(); -export const sessionService: SessionSerivce = new SessionSerivce(); \ No newline at end of file +export const sessionService: SessionSerivce = new SessionSerivce(); +export const userService: UserService = new UserService(); \ No newline at end of file