feat: Read-only users (#1955)
* Introduce isViewer field * Update policies * Make users read-only feature * Remove not demoting current user validation * Update tests * Catch the unhandled promise rejection * Hide unnecessary ui elements for read-only user * Update app/scenes/Settings/People.js Co-authored-by: Tom Moor <tom.moor@gmail.com> * Remove redundant logic for admin only policies * Use can logic * Update snapshot * Remove lint error * Update snapshot * Minor fix * Update app/menus/UserMenu.js Co-authored-by: Tom Moor <tom.moor@gmail.com> * Update server/api/users.js Co-authored-by: Tom Moor <tom.moor@gmail.com> * Update app/components/DocumentListItem.js Co-authored-by: Tom Moor <tom.moor@gmail.com> * Update app/stores/UsersStore.js Co-authored-by: Tom Moor <tom.moor@gmail.com> * Use useCurrentTeam hook in functional component * Update translation * Update ternary * Remove punctuation * Move the functions to User model * Update share policy and shareMenu * Rename makeAdmin to promote * Create updateCounts function and Rank enum * Update tests * Remove enum * Use async await, remove enum and create computed accessor * Remove unused variable * Fix lint issues * Hide templates * Create shared/types and use rank type from it * Delete shared/utils/rank type file Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import invariant from "invariant";
|
||||
import { filter, orderBy } from "lodash";
|
||||
import { observable, computed, action, runInAction } from "mobx";
|
||||
import type { Rank } from "shared/types";
|
||||
import User from "models/User";
|
||||
import BaseStore from "./BaseStore";
|
||||
import RootStore from "./RootStore";
|
||||
@@ -14,6 +15,7 @@ export default class UsersStore extends BaseStore<User> {
|
||||
all: number,
|
||||
invited: number,
|
||||
suspended: number,
|
||||
viewers: number,
|
||||
} = {};
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
@@ -48,6 +50,11 @@ export default class UsersStore extends BaseStore<User> {
|
||||
return filter(this.orderedData, (user) => user.isAdmin);
|
||||
}
|
||||
|
||||
@computed
|
||||
get viewers(): User[] {
|
||||
return filter(this.orderedData, (user) => user.isViewer);
|
||||
}
|
||||
|
||||
@computed
|
||||
get all(): User[] {
|
||||
return filter(this.orderedData, (user) => user.lastActiveAt);
|
||||
@@ -59,27 +66,47 @@ export default class UsersStore extends BaseStore<User> {
|
||||
}
|
||||
|
||||
@action
|
||||
promote = (user: User) => {
|
||||
this.counts.admins += 1;
|
||||
return this.actionOnUser("promote", user);
|
||||
promote = async (user: User) => {
|
||||
try {
|
||||
this.updateCounts("Admin", user.rank);
|
||||
await this.actionOnUser("promote", user);
|
||||
} catch {
|
||||
this.updateCounts(user.rank, "Admin");
|
||||
}
|
||||
};
|
||||
|
||||
@action
|
||||
demote = (user: User) => {
|
||||
this.counts.admins -= 1;
|
||||
return this.actionOnUser("demote", user);
|
||||
demote = async (user: User, to: Rank) => {
|
||||
try {
|
||||
this.updateCounts(to, user.rank);
|
||||
await this.actionOnUser("demote", user, to);
|
||||
} catch {
|
||||
this.updateCounts(user.rank, to);
|
||||
}
|
||||
};
|
||||
|
||||
@action
|
||||
suspend = (user: User) => {
|
||||
this.counts.suspended += 1;
|
||||
return this.actionOnUser("suspend", user);
|
||||
suspend = async (user: User) => {
|
||||
try {
|
||||
this.counts.suspended += 1;
|
||||
this.counts.active -= 1;
|
||||
await this.actionOnUser("suspend", user);
|
||||
} catch {
|
||||
this.counts.suspended -= 1;
|
||||
this.counts.active += 1;
|
||||
}
|
||||
};
|
||||
|
||||
@action
|
||||
activate = (user: User) => {
|
||||
this.counts.suspended -= 1;
|
||||
return this.actionOnUser("activate", user);
|
||||
activate = async (user: User) => {
|
||||
try {
|
||||
this.counts.suspended -= 1;
|
||||
this.counts.active += 1;
|
||||
await this.actionOnUser("activate", user);
|
||||
} catch {
|
||||
this.counts.suspended += 1;
|
||||
this.counts.active -= 1;
|
||||
}
|
||||
};
|
||||
|
||||
@action
|
||||
@@ -118,9 +145,36 @@ export default class UsersStore extends BaseStore<User> {
|
||||
if (user.isSuspended) {
|
||||
this.counts.suspended -= 1;
|
||||
}
|
||||
if (user.isViewer) {
|
||||
this.counts.viewers -= 1;
|
||||
}
|
||||
this.counts.all -= 1;
|
||||
}
|
||||
|
||||
@action
|
||||
updateCounts = (to: Rank, from: Rank) => {
|
||||
if (to === "Admin") {
|
||||
this.counts.admins += 1;
|
||||
if (from === "Viewer") {
|
||||
this.counts.viewers -= 1;
|
||||
}
|
||||
}
|
||||
if (to === "Viewer") {
|
||||
this.counts.viewers += 1;
|
||||
if (from === "Admin") {
|
||||
this.counts.admins -= 1;
|
||||
}
|
||||
}
|
||||
if (to === "Member") {
|
||||
if (from === "Viewer") {
|
||||
this.counts.viewers -= 1;
|
||||
}
|
||||
if (from === "Admin") {
|
||||
this.counts.admins -= 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
notInCollection = (collectionId: string, query: string = "") => {
|
||||
const memberships = filter(
|
||||
this.rootStore.memberships.orderedData,
|
||||
@@ -179,9 +233,10 @@ export default class UsersStore extends BaseStore<User> {
|
||||
return queriedUsers(users, query);
|
||||
};
|
||||
|
||||
actionOnUser = async (action: string, user: User) => {
|
||||
actionOnUser = async (action: string, user: User, to?: Rank) => {
|
||||
const res = await client.post(`/users.${action}`, {
|
||||
id: user.id,
|
||||
to,
|
||||
});
|
||||
invariant(res && res.data, "Data should be available");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user