Skip to content

feat: Create ReadOnly user components #7610

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/app/public/static/locales/en_US/admin.json
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,7 @@
},
"user_table": {
"administrator": "Administrator",
"read_only_user": "Read Only User",
"edit_menu": "Edit menu",
"reset_password": "Reset password",
"administrator_menu": "Administrator Menu",
Expand All @@ -734,6 +735,8 @@
"remove_admin_access": "Remove admin access",
"cannot_remove": "You cannot remove yourself from administrator",
"give_admin_access": "Give admin access",
"remove_read_only_access": "Remove read only access",
"give_read_only_access": "Give read only access",
"send_invitation_email": "Send invitation email",
"resend_invitation_email": "Resend invitation email"
},
Expand Down Expand Up @@ -1024,6 +1027,8 @@
"toaster": {
"give_user_admin": "Succeeded to give {{username}} admin",
"remove_user_admin": "Succeeded to remove {{username}} admin",
"give_user_read_only": "Succeeded to give {{username}} read only",
"remove_user_read_only": "Succeeded to remove {{username}} read only",
"activate_user_success": "Succeeded to activating {{username}}",
"deactivate_user_success": "Succeeded to deactivate {{username}}",
"remove_user_success": "Succeeded to removing {{username}}",
Expand Down
5 changes: 5 additions & 0 deletions apps/app/public/static/locales/ja_JP/admin.json
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,7 @@
},
"user_table": {
"administrator": "管理者",
"read_only_user": "閲覧ユーザー",
"edit_menu": "編集メニュー",
"reset_password": "パスワードの再発行",
"administrator_menu": "管理者メニュー",
Expand All @@ -742,6 +743,8 @@
"remove_admin_access": "管理者から外す",
"cannot_remove": "自分自身を管理者から外すことはできません",
"give_admin_access": "管理者にする",
"remove_read_only_access": "閲覧ユーザーから外す",
"give_read_only_access": "閲覧ユーザーにする",
"send_invitation_email": "招待メールの送信",
"resend_invitation_email": "招待メールの再送信"
},
Expand Down Expand Up @@ -1032,6 +1035,8 @@
"toaster": {
"give_user_admin": "{{username}}を管理者に設定しました",
"remove_user_admin": "{{username}}を管理者から外しました",
"give_user_read_only": "{{username}}を閲覧ユーザーに設定しました",
"remove_user_read_only": "{{username}}を閲覧ユーザーから外しました",
"activate_user_success": "{{username}}を有効化しました",
"deactivate_user_success": "{{username}}を無効化しました",
"remove_user_success": "{{username}}を削除しました",
Expand Down
7 changes: 6 additions & 1 deletion apps/app/public/static/locales/zh_CN/admin.json
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,7 @@
},
"user_table": {
"administrator": "管理员",
"read_only_user": "浏览的用户",
"edit_menu": "编辑菜单",
"reset_password": "重置密码",
"administrator_menu": "管理员菜单",
Expand All @@ -742,6 +743,8 @@
"remove_admin_access": "删除管理员访问权限",
"cannot_remove": "您不能从管理员中删除自己",
"give_admin_access": "授予管理员访问权限",
"remove_read_only_access": "删除一个用户作为浏览用户",
"give_read_only_access": "使一个用户成为阅读用户",
"send_invitation_email": "发送邀请邮件",
"resend_invitation_email": "重发邀请函"
},
Expand Down Expand Up @@ -1031,7 +1034,9 @@
},
"toaster": {
"give_user_admin": "Succeeded to give {{username}} admin",
"remove_user_admin": "Succeeded to remove {{username}} admin ",
"remove_user_admin": "Succeeded to remove {{username}} admin",
"give_user_read_only": "Succeeded to give {{username}} read only",
"remove_user_read_only": "Succeeded to remove {{username}} read only",
"activate_user_success": "Succeeded to activating {{username}}",
"deactivate_user_success": "Succeeded to deactivate {{username}}",
"remove_user_success": "Succeeded to removing {{username}}",
Expand Down
26 changes: 26 additions & 0 deletions apps/app/src/client/services/AdminUsersContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,32 @@ export default class AdminUsersContainer extends Container {
return username;
}

/**
* Give user read only
* @memberOf AdminUsersContainer
* @param {string} userId
* @return {string} username
*/
async giveUserReadOnly(userId) {
const response = await apiv3Put(`/users/${userId}/give-read-only`);
const { username } = response.data.userData;
await this.retrieveUsersByPagingNum(this.state.activePage);
return username;
}

/**
* Remove user read only
* @memberOf AdminUsersContainer
* @param {string} userId
* @return {string} username
*/
async removeUserReadOnly(userId) {
const response = await apiv3Put(`/users/${userId}/remove-read-only`);
const { username } = response.data.userData;
await this.retrieveUsersByPagingNum(this.state.activePage);
return username;
}

/**
* Activate user
* @memberOf AdminUsersContainer
Expand Down
40 changes: 40 additions & 0 deletions apps/app/src/components/Admin/Users/GiveReadOnlyButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { useCallback } from 'react';

import type { IUserHasId } from '@growi/core';
import { useTranslation } from 'next-i18next';

import AdminUsersContainer from '~/client/services/AdminUsersContainer';
import { toastSuccess, toastError } from '~/client/util/toastr';

import { withUnstatedContainers } from '../../UnstatedUtils';

const GiveReadOnlyButton: React.FC<{
adminUsersContainer: AdminUsersContainer,
user: IUserHasId,
}> = ({ adminUsersContainer, user }): JSX.Element => {
const { t } = useTranslation('admin');

const onClickGiveReadOnlyBtnHandler = useCallback(async() => {
try {
const username = await adminUsersContainer.giveUserReadOnly(user._id);
toastSuccess(t('toaster.give_user_read_only', { username }));
}
catch (err) {
toastError(err);
}
}, [adminUsersContainer, t, user._id]);

return (
<button className="dropdown-item" type="button" onClick={onClickGiveReadOnlyBtnHandler}>
<i className="icon-fw icon-user-following"></i> {t('user_management.user_table.give_read_only_access')}
</button>
);
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

別に管理者自身も ROM にできていいと思うよ

Copy link
Contributor Author

@jam411 jam411 Apr 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

管理者も ROM にできるように修正しました。

細かいですが、
Read Only Member でなく Read Only User (日本語では”閲覧ユーザー")として作ってしまったのですが、
Read Only Member (日本語では "閲覧メンバー" )の方がよいでしょうか。


/**
* Wrapper component for using unstated
*/
// eslint-disable-next-line max-len
const GiveReadOnlyButtonWrapper: React.ForwardRefExoticComponent<Pick<any, string | number | symbol> & React.RefAttributes<any>> = withUnstatedContainers(GiveReadOnlyButton, [AdminUsersContainer]);

export default GiveReadOnlyButtonWrapper;
40 changes: 40 additions & 0 deletions apps/app/src/components/Admin/Users/RemoveReadOnlyMenuItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { useCallback } from 'react';

import type { IUserHasId } from '@growi/core';
import { useTranslation } from 'next-i18next';

import AdminUsersContainer from '~/client/services/AdminUsersContainer';
import { toastSuccess, toastError } from '~/client/util/toastr';

import { withUnstatedContainers } from '../../UnstatedUtils';

const RemoveReadOnlyMenuItem: React.FC<{
adminUsersContainer: AdminUsersContainer,
user: IUserHasId,
}> = ({ adminUsersContainer, user }): JSX.Element => {
const { t } = useTranslation('admin');

const clickRemoveReadOnlyBtnHandler = useCallback(async() => {
try {
const username = await adminUsersContainer.removeUserReadOnly(user._id);
toastSuccess(t('toaster.remove_user_read_only', { username }));
}
catch (err) {
toastError(err);
}
}, [adminUsersContainer, t, user._id]);

return (
<button className="dropdown-item" type="button" onClick={clickRemoveReadOnlyBtnHandler}>
<i className="icon-fw icon-user-unfollow"></i> {t('user_management.user_table.remove_read_only_access')}
</button>
);
};

/**
* Wrapper component for using unstated
*/
// eslint-disable-next-line max-len
const RemoveReadOnlyMenuItemWrapper: React.ForwardRefExoticComponent<Pick<any, string | number | symbol> & React.RefAttributes<any>> = withUnstatedContainers(RemoveReadOnlyMenuItem, [AdminUsersContainer]);

export default RemoveReadOnlyMenuItemWrapper;
8 changes: 6 additions & 2 deletions apps/app/src/components/Admin/Users/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import AdminUsersContainer from '~/client/services/AdminUsersContainer';
import { withUnstatedContainers } from '../../UnstatedUtils';

import GiveAdminButton from './GiveAdminButton';
import GiveReadOnlyButton from './GiveReadOnlyButton';
import RemoveAdminMenuItem from './RemoveAdminMenuItem';
import RemoveReadOnlyMenuItem from './RemoveReadOnlyMenuItem';
import SendInvitationEmailButton from './SendInvitationEmailButton';
import StatusActivateButton from './StatusActivateButton';
import StatusSuspendedMenuItem from './StatusSuspendMenuItem';
Expand Down Expand Up @@ -81,8 +83,10 @@ const UserMenu = (props: UserMenuProps) => {
<li className="dropdown-divider pl-0"></li>
<li className="dropdown-header">{t('user_management.user_table.administrator_menu')}</li>
<li>
{user.admin === true && <RemoveAdminMenuItem user={user} />}
{user.admin === false && <GiveAdminButton user={user} />}
{user.admin ? <RemoveAdminMenuItem user={user} /> : <GiveAdminButton user={user} />}
</li>
<li>
{user.readOnly ? <RemoveReadOnlyMenuItem user={user} /> : <GiveReadOnlyButton user={user} />}
</li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

前バージョンからアップグレードしたシステムだと user.readOnly が undefined というケースがある。その場合両方のメニューが表示されないことになってしまうので、三項演算子の方がよさそう。

一方 admin は必ず T/F のどちらかが入っているが、可読性を考慮して書式を揃えてもよい。

</>
);
Expand Down
5 changes: 5 additions & 0 deletions apps/app/src/components/Admin/Users/UserTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ const UserTable = (props: UserTableProps) => {
{t('admin:user_management.user_table.administrator')}
</span>
)}
{(user.readOnly) && (
<span className="badge badge-light badge-pill ml-2">
{t('admin:user_management.user_table.read_only_user')}
</span>
)}
</td>
<td>
<strong>{user.username}</strong>
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/interfaces/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type IUser = {
imageUrlCached: string,
isGravatarEnabled: boolean,
admin: boolean,
readOnly: boolean,
apiToken?: string,
isEmailPublished: boolean,
isInvitationEmailSended: boolean,
Expand Down