mirror of
https://github.com/astuto/astuto.git
synced 2025-12-16 11:47:56 +01:00
Add users management to site settings (#126)
This commit is contained in:
committed by
GitHub
parent
bc15140512
commit
37fb99a868
60
app/javascript/actions/User/requestUsers.ts
Normal file
60
app/javascript/actions/User/requestUsers.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Action } from 'redux';
|
||||
import { ThunkAction } from 'redux-thunk';
|
||||
|
||||
import IUserJSON from '../../interfaces/json/IUser';
|
||||
|
||||
import { State } from '../../reducers/rootReducer';
|
||||
|
||||
export const USERS_REQUEST_START = 'USERS_REQUEST_START';
|
||||
interface UsersRequestStartAction {
|
||||
type: typeof USERS_REQUEST_START;
|
||||
}
|
||||
|
||||
export const USERS_REQUEST_SUCCESS = 'USERS_REQUEST_SUCCESS';
|
||||
interface UsersRequestSuccessAction {
|
||||
type: typeof USERS_REQUEST_SUCCESS;
|
||||
users: Array<IUserJSON>;
|
||||
}
|
||||
|
||||
export const USERS_REQUEST_FAILURE = 'USERS_REQUEST_FAILURE';
|
||||
interface UsersRequestFailureAction {
|
||||
type: typeof USERS_REQUEST_FAILURE;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export type UsersRequestActionTypes =
|
||||
UsersRequestStartAction |
|
||||
UsersRequestSuccessAction |
|
||||
UsersRequestFailureAction;
|
||||
|
||||
|
||||
const usersRequestStart = (): UsersRequestActionTypes => ({
|
||||
type: USERS_REQUEST_START,
|
||||
});
|
||||
|
||||
const usersRequestSuccess = (
|
||||
users: Array<IUserJSON>
|
||||
): UsersRequestActionTypes => ({
|
||||
type: USERS_REQUEST_SUCCESS,
|
||||
users,
|
||||
});
|
||||
|
||||
const usersRequestFailure = (error: string): UsersRequestActionTypes => ({
|
||||
type: USERS_REQUEST_FAILURE,
|
||||
error,
|
||||
});
|
||||
|
||||
export const requestUsers = (): ThunkAction<void, State, null, Action<string>> => (
|
||||
async (dispatch) => {
|
||||
dispatch(usersRequestStart());
|
||||
|
||||
try {
|
||||
const response = await fetch('/users');
|
||||
const json = await response.json();
|
||||
|
||||
dispatch(usersRequestSuccess(json));
|
||||
} catch (e) {
|
||||
dispatch(usersRequestFailure(e));
|
||||
}
|
||||
}
|
||||
)
|
||||
87
app/javascript/actions/User/updateUser.ts
Normal file
87
app/javascript/actions/User/updateUser.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { Action } from "redux";
|
||||
import { ThunkAction } from "redux-thunk";
|
||||
|
||||
import HttpStatus from "../../constants/http_status";
|
||||
import buildRequestHeaders from "../../helpers/buildRequestHeaders";
|
||||
import IUserJSON from "../../interfaces/json/IUser";
|
||||
import { State } from "../../reducers/rootReducer";
|
||||
|
||||
export const USER_UPDATE_START = 'USER_UPDATE_START';
|
||||
interface UserUpdateStartAction {
|
||||
type: typeof USER_UPDATE_START;
|
||||
}
|
||||
|
||||
export const USER_UPDATE_SUCCESS = 'USER_UPDATE_SUCCESS';
|
||||
interface UserUpdateSuccessAction {
|
||||
type: typeof USER_UPDATE_SUCCESS;
|
||||
user: IUserJSON;
|
||||
}
|
||||
|
||||
export const USER_UPDATE_FAILURE = 'USER_UPDATE_FAILURE';
|
||||
interface UserUpdateFailureAction {
|
||||
type: typeof USER_UPDATE_FAILURE;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export type UserUpdateActionTypes =
|
||||
UserUpdateStartAction |
|
||||
UserUpdateSuccessAction |
|
||||
UserUpdateFailureAction;
|
||||
|
||||
const userUpdateStart = (): UserUpdateStartAction => ({
|
||||
type: USER_UPDATE_START,
|
||||
});
|
||||
|
||||
const userUpdateSuccess = (
|
||||
userJSON: IUserJSON,
|
||||
): UserUpdateSuccessAction => ({
|
||||
type: USER_UPDATE_SUCCESS,
|
||||
user: userJSON,
|
||||
});
|
||||
|
||||
const userUpdateFailure = (error: string): UserUpdateFailureAction => ({
|
||||
type: USER_UPDATE_FAILURE,
|
||||
error,
|
||||
});
|
||||
|
||||
interface UpdateUserParams {
|
||||
id: number;
|
||||
role?: string;
|
||||
status?: string;
|
||||
authenticityToken: string;
|
||||
}
|
||||
|
||||
export const updateUser = ({
|
||||
id,
|
||||
role = null,
|
||||
status = null,
|
||||
authenticityToken,
|
||||
}: UpdateUserParams): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
|
||||
dispatch(userUpdateStart());
|
||||
|
||||
const user = Object.assign({},
|
||||
role !== null ? { role } : null,
|
||||
status !== null ? { status } : null,
|
||||
);
|
||||
|
||||
try {
|
||||
const res = await fetch(`/users/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: buildRequestHeaders(authenticityToken),
|
||||
body: JSON.stringify({ user }),
|
||||
});
|
||||
const json = await res.json();
|
||||
|
||||
if (res.status === HttpStatus.OK) {
|
||||
dispatch(userUpdateSuccess(json));
|
||||
} else {
|
||||
dispatch(userUpdateFailure(json.error));
|
||||
}
|
||||
|
||||
return Promise.resolve(res);
|
||||
} catch (e) {
|
||||
dispatch(userUpdateFailure(e));
|
||||
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
};
|
||||
@@ -92,7 +92,7 @@ class BoardsSiteSettingsP extends React.Component<Props> {
|
||||
return (
|
||||
<>
|
||||
<Box>
|
||||
<h2>{I18n.t('site_settings.boards.title')}</h2>
|
||||
<h2>{ I18n.t('site_settings.boards.title') }</h2>
|
||||
|
||||
{
|
||||
boards.items.length > 0 ?
|
||||
|
||||
157
app/javascript/components/SiteSettings/Users/UserEditable.tsx
Normal file
157
app/javascript/components/SiteSettings/Users/UserEditable.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import * as React from "react";
|
||||
import Gravatar from 'react-gravatar';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
import IUser, { UserRoles, USER_ROLE_ADMIN, USER_ROLE_USER, USER_STATUS_ACTIVE, USER_STATUS_BLOCKED, USER_STATUS_DELETED } from "../../../interfaces/IUser";
|
||||
import Separator from "../../common/Separator";
|
||||
import UserForm from "./UserForm";
|
||||
import { MutedText } from "../../common/CustomTexts";
|
||||
|
||||
interface Props {
|
||||
user: IUser;
|
||||
updateUserRole(
|
||||
id: number,
|
||||
role: UserRoles,
|
||||
closeEditMode: Function,
|
||||
): void;
|
||||
updateUserStatus(
|
||||
id: number,
|
||||
status: typeof USER_STATUS_ACTIVE | typeof USER_STATUS_BLOCKED,
|
||||
): void;
|
||||
|
||||
currentUserRole: UserRoles;
|
||||
currentUserEmail: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
editMode: boolean;
|
||||
}
|
||||
|
||||
class UserEditable extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = { editMode: false };
|
||||
|
||||
this.toggleEditMode = this.toggleEditMode.bind(this);
|
||||
this._handleUpdateUserRole = this._handleUpdateUserRole.bind(this);
|
||||
this._handleUpdateUserStatus = this._handleUpdateUserStatus.bind(this);
|
||||
}
|
||||
|
||||
toggleEditMode() {
|
||||
this.setState({ editMode: !this.state.editMode });
|
||||
}
|
||||
|
||||
_handleUpdateUserRole(newRole: UserRoles) {
|
||||
this.props.updateUserRole(
|
||||
this.props.user.id,
|
||||
newRole,
|
||||
this.toggleEditMode,
|
||||
);
|
||||
}
|
||||
|
||||
_handleUpdateUserStatus() {
|
||||
const { user } = this.props;
|
||||
const currentStatus = user.status;
|
||||
let newStatus: typeof USER_STATUS_ACTIVE | typeof USER_STATUS_BLOCKED;
|
||||
|
||||
if (currentStatus === 'deleted') return;
|
||||
|
||||
if (currentStatus === 'active') newStatus = 'blocked';
|
||||
else newStatus = 'active';
|
||||
|
||||
const confirmationMessage =
|
||||
newStatus === 'blocked' ?
|
||||
I18n.t('site_settings.users.block_confirmation', { name: user.fullName })
|
||||
:
|
||||
I18n.t('site_settings.users.unblock_confirmation', { name: user.fullName });
|
||||
|
||||
const confirmationResponse = confirm(confirmationMessage);
|
||||
|
||||
if (confirmationResponse) {
|
||||
this.props.updateUserStatus(user.id, newStatus);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { user, currentUserRole, currentUserEmail } = this.props;
|
||||
const { editMode } = this.state;
|
||||
|
||||
const editEnabled =
|
||||
user.status === USER_STATUS_ACTIVE &&
|
||||
currentUserRole === USER_ROLE_ADMIN &&
|
||||
currentUserEmail !== user.email;
|
||||
|
||||
const blockEnabled =
|
||||
user.status !== USER_STATUS_DELETED &&
|
||||
(currentUserRole === USER_ROLE_ADMIN || user.role === USER_ROLE_USER) &&
|
||||
currentUserEmail !== user.email;
|
||||
|
||||
return (
|
||||
<li className="userEditable">
|
||||
{
|
||||
editMode === false ?
|
||||
<>
|
||||
<div className="userInfo">
|
||||
<Gravatar email={user.email} size={42} className="gravatar userGravatar" />
|
||||
|
||||
<div className="userFullNameRoleStatus">
|
||||
<span className="userFullName">{ user.fullName }</span>
|
||||
|
||||
<div className="userRoleStatus">
|
||||
<span>
|
||||
<MutedText>{ I18n.t(`site_settings.users.role_${user.role}`) }</MutedText>
|
||||
</span>
|
||||
|
||||
{
|
||||
user.status !== USER_STATUS_ACTIVE ?
|
||||
<>
|
||||
<Separator />
|
||||
<span className={`userStatus userStatus${user.status}`}>
|
||||
{ I18n.t(`site_settings.users.status_${user.status}`) }
|
||||
</span>
|
||||
</>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="userEditableActions">
|
||||
<a
|
||||
onClick={() => editEnabled && this.toggleEditMode()}
|
||||
className={editEnabled ? '' : 'actionDisabled'}
|
||||
>
|
||||
{ I18n.t('common.buttons.edit') }
|
||||
</a>
|
||||
|
||||
<Separator />
|
||||
|
||||
<a
|
||||
onClick={() => blockEnabled && this._handleUpdateUserStatus()}
|
||||
className={blockEnabled ? '' : 'actionDisabled'}
|
||||
>
|
||||
{
|
||||
user.status !== USER_STATUS_BLOCKED ?
|
||||
I18n.t('site_settings.users.block')
|
||||
:
|
||||
I18n.t('site_settings.users.unblock')
|
||||
}
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
:
|
||||
<>
|
||||
<UserForm user={user} updateUserRole={this._handleUpdateUserRole} />
|
||||
<a onClick={this.toggleEditMode} className="userEditCancelButton">
|
||||
{ I18n.t('common.buttons.cancel') }
|
||||
</a>
|
||||
</>
|
||||
}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UserEditable;
|
||||
79
app/javascript/components/SiteSettings/Users/UserForm.tsx
Normal file
79
app/javascript/components/SiteSettings/Users/UserForm.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import * as React from 'react';
|
||||
import Gravatar from 'react-gravatar';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
import Button from '../../common/Button';
|
||||
import IUser, { UserRoles, USER_ROLE_ADMIN, USER_ROLE_MODERATOR, USER_ROLE_USER } from '../../../interfaces/IUser';
|
||||
|
||||
interface Props {
|
||||
user: IUser;
|
||||
updateUserRole(newRole: UserRoles): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
role: UserRoles;
|
||||
}
|
||||
|
||||
class UserForm extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = { role: this.props.user.role };
|
||||
|
||||
this._handleUpdateUserRole = this._handleUpdateUserRole.bind(this);
|
||||
}
|
||||
|
||||
_handleUpdateUserRole(selectedRole: UserRoles) {
|
||||
const { user, updateUserRole } = this.props;
|
||||
let confirmation = true;
|
||||
|
||||
if (selectedRole === 'admin') {
|
||||
confirmation = confirm(I18n.t('site_settings.users.role_to_admin_confirmation', { name: user.fullName }));
|
||||
}
|
||||
|
||||
if (confirmation) updateUserRole(selectedRole);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { user } = this.props;
|
||||
const selectedRole = this.state.role;
|
||||
|
||||
return (
|
||||
<div className="userForm">
|
||||
<Gravatar email={user.email} size={42} className="gravatar userGravatar" />
|
||||
|
||||
<div className="userFullNameRoleForm">
|
||||
<span className="userFullName">{ user.fullName }</span>
|
||||
|
||||
<select
|
||||
value={selectedRole || 'Loading...'}
|
||||
onChange={
|
||||
(e: React.FormEvent) => {
|
||||
this.setState({role: (e.target as HTMLSelectElement).value as UserRoles});
|
||||
}}
|
||||
id="selectPickerUserRole"
|
||||
className="selectPicker"
|
||||
>
|
||||
<optgroup label="Roles">
|
||||
<option value={USER_ROLE_USER}>
|
||||
{ I18n.t(`site_settings.users.role_${USER_ROLE_USER}`) }
|
||||
</option>
|
||||
<option value={USER_ROLE_MODERATOR}>
|
||||
{ I18n.t(`site_settings.users.role_${USER_ROLE_MODERATOR}`) }
|
||||
</option>
|
||||
<option value={USER_ROLE_ADMIN}>
|
||||
{ I18n.t(`site_settings.users.role_${USER_ROLE_ADMIN}`) }
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<Button onClick={() => this._handleUpdateUserRole(selectedRole)} className="updateUserButton">
|
||||
{ I18n.t('common.buttons.update') }
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UserForm;
|
||||
@@ -0,0 +1,103 @@
|
||||
import * as React from 'react';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
import UserEditable from './UserEditable';
|
||||
import Box from '../../common/Box';
|
||||
import SiteSettingsInfoBox from '../../common/SiteSettingsInfoBox';
|
||||
|
||||
import { UsersState } from '../../../reducers/usersReducer';
|
||||
import { UserRoles, USER_STATUS_ACTIVE, USER_STATUS_BLOCKED } from '../../../interfaces/IUser';
|
||||
import HttpStatus from '../../../constants/http_status';
|
||||
|
||||
interface Props {
|
||||
users: UsersState;
|
||||
settingsAreUpdating: boolean;
|
||||
settingsError: string;
|
||||
|
||||
requestUsers(): void;
|
||||
updateUserRole(
|
||||
id: number,
|
||||
role: UserRoles,
|
||||
authenticityToken: string,
|
||||
): Promise<any>;
|
||||
updateUserStatus(
|
||||
id: number,
|
||||
status: typeof USER_STATUS_ACTIVE | typeof USER_STATUS_BLOCKED,
|
||||
authenticityToken: string,
|
||||
): void;
|
||||
|
||||
currentUserEmail: string;
|
||||
currentUserRole: UserRoles;
|
||||
authenticityToken: string;
|
||||
}
|
||||
|
||||
class UsersSiteSettingsP extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._handleUpdateUserRole = this._handleUpdateUserRole.bind(this);
|
||||
this._handleUpdateUserStatus = this._handleUpdateUserStatus.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.requestUsers();
|
||||
}
|
||||
|
||||
_handleUpdateUserRole(id: number, role: UserRoles, closeEditMode: Function) {
|
||||
this.props.updateUserRole(
|
||||
id,
|
||||
role,
|
||||
this.props.authenticityToken,
|
||||
).then(res => {
|
||||
if (res?.status !== HttpStatus.OK) return;
|
||||
|
||||
closeEditMode();
|
||||
});
|
||||
}
|
||||
|
||||
_handleUpdateUserStatus(id: number, status: typeof USER_STATUS_ACTIVE | typeof USER_STATUS_BLOCKED) {
|
||||
this.props.updateUserStatus(
|
||||
id,
|
||||
status,
|
||||
this.props.authenticityToken,
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
users,
|
||||
settingsAreUpdating,
|
||||
settingsError,
|
||||
currentUserRole,
|
||||
currentUserEmail,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box>
|
||||
<h2>{ I18n.t('site_settings.users.title') }</h2>
|
||||
|
||||
<ul className="usersList">
|
||||
{
|
||||
users.items.map((user, i) => (
|
||||
<UserEditable
|
||||
user={user}
|
||||
updateUserRole={this._handleUpdateUserRole}
|
||||
updateUserStatus={this._handleUpdateUserStatus}
|
||||
|
||||
currentUserEmail={currentUserEmail}
|
||||
currentUserRole={currentUserRole}
|
||||
key={i}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</Box>
|
||||
|
||||
<SiteSettingsInfoBox areUpdating={settingsAreUpdating || users.areLoading} error={settingsError} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UsersSiteSettingsP;
|
||||
39
app/javascript/components/SiteSettings/Users/index.tsx
Normal file
39
app/javascript/components/SiteSettings/Users/index.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import * as React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Store } from 'redux';
|
||||
|
||||
import UsersSiteSettings from '../../../containers/UsersSiteSettings';
|
||||
|
||||
import createStoreHelper from '../../../helpers/createStore';
|
||||
import { UserRoles } from '../../../interfaces/IUser';
|
||||
import { State } from '../../../reducers/rootReducer';
|
||||
|
||||
interface Props {
|
||||
currentUserEmail: string;
|
||||
currentUserRole: UserRoles;
|
||||
authenticityToken: string;
|
||||
}
|
||||
|
||||
class UsersSiteSettingsRoot extends React.Component<Props> {
|
||||
store: Store<State, any>;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.store = createStoreHelper();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Provider store={this.store}>
|
||||
<UsersSiteSettings
|
||||
currentUserEmail={this.props.currentUserEmail}
|
||||
currentUserRole={this.props.currentUserRole}
|
||||
authenticityToken={this.props.authenticityToken}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UsersSiteSettingsRoot;
|
||||
49
app/javascript/containers/UsersSiteSettings.tsx
Normal file
49
app/javascript/containers/UsersSiteSettings.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import UsersSiteSettingsP from "../components/SiteSettings/Users/UsersSiteSettingsP";
|
||||
|
||||
import { requestUsers } from "../actions/User/requestUsers";
|
||||
import { updateUser } from "../actions/User/updateUser";
|
||||
import { UserRoles, USER_STATUS_ACTIVE, USER_STATUS_BLOCKED } from "../interfaces/IUser";
|
||||
import { State } from "../reducers/rootReducer";
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
users: state.users,
|
||||
settingsAreUpdating: state.siteSettings.users.areUpdating,
|
||||
settingsError: state.siteSettings.users.error,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: any) => ({
|
||||
requestUsers() {
|
||||
dispatch(requestUsers());
|
||||
},
|
||||
|
||||
updateUserRole(
|
||||
id: number,
|
||||
role: UserRoles,
|
||||
authenticityToken: string,
|
||||
): Promise<any> {
|
||||
return dispatch(updateUser({
|
||||
id,
|
||||
role,
|
||||
authenticityToken,
|
||||
}));
|
||||
},
|
||||
|
||||
updateUserStatus(
|
||||
id: number,
|
||||
status: typeof USER_STATUS_ACTIVE | typeof USER_STATUS_BLOCKED,
|
||||
authenticityToken: string,
|
||||
) {
|
||||
dispatch(updateUser({
|
||||
id,
|
||||
status,
|
||||
authenticityToken,
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(UsersSiteSettingsP);
|
||||
29
app/javascript/interfaces/IUser.ts
Normal file
29
app/javascript/interfaces/IUser.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
// Roles
|
||||
export const USER_ROLE_USER = 'user';
|
||||
export const USER_ROLE_MODERATOR = 'moderator';
|
||||
export const USER_ROLE_ADMIN = 'admin';
|
||||
|
||||
export type UserRoles =
|
||||
typeof USER_ROLE_USER |
|
||||
typeof USER_ROLE_MODERATOR |
|
||||
typeof USER_ROLE_ADMIN;
|
||||
|
||||
// Statuses
|
||||
export const USER_STATUS_ACTIVE = 'active';
|
||||
export const USER_STATUS_BLOCKED = 'blocked';
|
||||
export const USER_STATUS_DELETED = 'deleted';
|
||||
|
||||
export type UserStatuses =
|
||||
typeof USER_STATUS_ACTIVE |
|
||||
typeof USER_STATUS_BLOCKED |
|
||||
typeof USER_STATUS_DELETED;
|
||||
|
||||
interface IUser {
|
||||
id: number;
|
||||
email: string;
|
||||
fullName: string;
|
||||
role: UserRoles;
|
||||
status: UserStatuses;
|
||||
}
|
||||
|
||||
export default IUser;
|
||||
9
app/javascript/interfaces/json/IUser.ts
Normal file
9
app/javascript/interfaces/json/IUser.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
interface IUserJSON {
|
||||
id: number;
|
||||
email: string;
|
||||
full_name: string;
|
||||
role: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export default IUserJSON;
|
||||
58
app/javascript/reducers/SiteSettings/usersReducer.ts
Normal file
58
app/javascript/reducers/SiteSettings/usersReducer.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
UsersRequestActionTypes,
|
||||
USERS_REQUEST_START,
|
||||
USERS_REQUEST_SUCCESS,
|
||||
USERS_REQUEST_FAILURE,
|
||||
} from '../../actions/User/requestUsers';
|
||||
|
||||
import {
|
||||
UserUpdateActionTypes,
|
||||
USER_UPDATE_START,
|
||||
USER_UPDATE_SUCCESS,
|
||||
USER_UPDATE_FAILURE,
|
||||
} from '../../actions/User/updateUser';
|
||||
|
||||
export interface SiteSettingsUsersState {
|
||||
areUpdating: boolean;
|
||||
error: string;
|
||||
}
|
||||
|
||||
const initialState: SiteSettingsUsersState = {
|
||||
areUpdating: false,
|
||||
error: '',
|
||||
};
|
||||
|
||||
const siteSettingsUsersReducer = (
|
||||
state = initialState,
|
||||
action: UsersRequestActionTypes | UserUpdateActionTypes,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case USERS_REQUEST_START:
|
||||
case USER_UPDATE_START:
|
||||
return {
|
||||
...state,
|
||||
areUpdating: true,
|
||||
};
|
||||
|
||||
case USERS_REQUEST_SUCCESS:
|
||||
case USER_UPDATE_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
areUpdating: false,
|
||||
error: '',
|
||||
};
|
||||
|
||||
case USERS_REQUEST_FAILURE:
|
||||
case USER_UPDATE_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
areUpdating: false,
|
||||
error: action.error,
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default siteSettingsUsersReducer;
|
||||
@@ -38,7 +38,7 @@ const initialState: BoardsState = {
|
||||
items: [],
|
||||
areLoading: false,
|
||||
error: '',
|
||||
}
|
||||
};
|
||||
|
||||
const boardsReducer = (
|
||||
state = initialState,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { combineReducers } from 'redux';
|
||||
import postsReducer from './postsReducer';
|
||||
import boardsReducer from './boardsReducer';
|
||||
import postStatusesReducer from './postStatusesReducer';
|
||||
import usersReducer from './usersReducer';
|
||||
import currentPostReducer from './currentPostReducer';
|
||||
import siteSettingsReducer from './siteSettingsReducer';
|
||||
|
||||
@@ -10,6 +11,7 @@ const rootReducer = combineReducers({
|
||||
posts: postsReducer,
|
||||
boards: boardsReducer,
|
||||
postStatuses: postStatusesReducer,
|
||||
users: usersReducer,
|
||||
currentPost: currentPostReducer,
|
||||
siteSettings: siteSettingsReducer,
|
||||
});
|
||||
|
||||
@@ -61,20 +61,37 @@ import {
|
||||
POSTSTATUS_UPDATE_FAILURE,
|
||||
} from '../actions/PostStatus/updatePostStatus';
|
||||
|
||||
import {
|
||||
UsersRequestActionTypes,
|
||||
USERS_REQUEST_START,
|
||||
USERS_REQUEST_SUCCESS,
|
||||
USERS_REQUEST_FAILURE,
|
||||
} from '../actions/User/requestUsers';
|
||||
|
||||
import {
|
||||
UserUpdateActionTypes,
|
||||
USER_UPDATE_START,
|
||||
USER_UPDATE_SUCCESS,
|
||||
USER_UPDATE_FAILURE,
|
||||
} from '../actions/User/updateUser';
|
||||
|
||||
import siteSettingsBoardsReducer, { SiteSettingsBoardsState } from './SiteSettings/boardsReducer';
|
||||
import siteSettingsPostStatusesReducer, { SiteSettingsPostStatusesState } from './SiteSettings/postStatusesReducer';
|
||||
import siteSettingsRoadmapReducer, { SiteSettingsRoadmapState } from './SiteSettings/roadmapReducer';
|
||||
import siteSettingsUsersReducer, { SiteSettingsUsersState } from './SiteSettings/usersReducer';
|
||||
|
||||
interface SiteSettingsState {
|
||||
boards: SiteSettingsBoardsState;
|
||||
postStatuses: SiteSettingsPostStatusesState;
|
||||
roadmap: SiteSettingsRoadmapState;
|
||||
users: SiteSettingsUsersState;
|
||||
}
|
||||
|
||||
const initialState: SiteSettingsState = {
|
||||
boards: siteSettingsBoardsReducer(undefined, {} as any),
|
||||
postStatuses: siteSettingsPostStatusesReducer(undefined, {} as any),
|
||||
roadmap: siteSettingsRoadmapReducer(undefined, {} as any),
|
||||
users: siteSettingsUsersReducer(undefined, {} as any),
|
||||
};
|
||||
|
||||
const siteSettingsReducer = (
|
||||
@@ -88,7 +105,9 @@ const siteSettingsReducer = (
|
||||
PostStatusOrderUpdateActionTypes |
|
||||
PostStatusDeleteActionTypes |
|
||||
PostStatusSubmitActionTypes |
|
||||
PostStatusUpdateActionTypes
|
||||
PostStatusUpdateActionTypes |
|
||||
UsersRequestActionTypes |
|
||||
UserUpdateActionTypes
|
||||
): SiteSettingsState => {
|
||||
switch (action.type) {
|
||||
case BOARDS_REQUEST_START:
|
||||
@@ -134,6 +153,17 @@ const siteSettingsReducer = (
|
||||
roadmap: siteSettingsRoadmapReducer(state.roadmap, action),
|
||||
};
|
||||
|
||||
case USERS_REQUEST_START:
|
||||
case USERS_REQUEST_SUCCESS:
|
||||
case USERS_REQUEST_FAILURE:
|
||||
case USER_UPDATE_START:
|
||||
case USER_UPDATE_SUCCESS:
|
||||
case USER_UPDATE_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
users: siteSettingsUsersReducer(state.users, action),
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
75
app/javascript/reducers/usersReducer.ts
Normal file
75
app/javascript/reducers/usersReducer.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import {
|
||||
UsersRequestActionTypes,
|
||||
USERS_REQUEST_START,
|
||||
USERS_REQUEST_SUCCESS,
|
||||
USERS_REQUEST_FAILURE,
|
||||
} from '../actions/User/requestUsers';
|
||||
|
||||
import {
|
||||
UserUpdateActionTypes,
|
||||
USER_UPDATE_SUCCESS,
|
||||
} from '../actions/User/updateUser';
|
||||
|
||||
import IUser from "../interfaces/IUser";
|
||||
|
||||
export interface UsersState {
|
||||
items: Array<IUser>;
|
||||
areLoading: boolean;
|
||||
error: string;
|
||||
}
|
||||
|
||||
const initialState: UsersState = {
|
||||
items: [],
|
||||
areLoading: false,
|
||||
error: '',
|
||||
};
|
||||
|
||||
const usersReducer = (
|
||||
state = initialState,
|
||||
action: UsersRequestActionTypes | UserUpdateActionTypes,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case USERS_REQUEST_START:
|
||||
return {
|
||||
...state,
|
||||
areLoading: true,
|
||||
};
|
||||
|
||||
case USERS_REQUEST_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
areLoading: false,
|
||||
error: '',
|
||||
items: action.users.map(userJson => ({
|
||||
id: userJson.id,
|
||||
email: userJson.email,
|
||||
fullName: userJson.full_name,
|
||||
role: userJson.role,
|
||||
status: userJson.status,
|
||||
})),
|
||||
};
|
||||
|
||||
case USERS_REQUEST_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
areLoading: false,
|
||||
error: action.error,
|
||||
};
|
||||
|
||||
case USER_UPDATE_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
items: state.items.map(user => {
|
||||
return (user.id === action.user.id) ?
|
||||
{...user, role: action.user.role, status: action.user.status}
|
||||
:
|
||||
user;
|
||||
}),
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default usersReducer;
|
||||
@@ -172,4 +172,9 @@
|
||||
background-color: $primary-color !important;
|
||||
border-color: $primary-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
.selectPicker {
|
||||
@extend
|
||||
.custom-select;
|
||||
}
|
||||
@@ -119,12 +119,6 @@
|
||||
@extend
|
||||
.d-flex,
|
||||
.justify-content-between;
|
||||
|
||||
.selectPicker {
|
||||
@extend
|
||||
.custom-select,
|
||||
.mx-2;
|
||||
}
|
||||
}
|
||||
|
||||
.postDescription {
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
ul.usersList {
|
||||
@extend
|
||||
.pl-1;
|
||||
|
||||
list-style: none;
|
||||
|
||||
li.userEditable {
|
||||
@extend
|
||||
.d-flex,
|
||||
.justify-content-between,
|
||||
.my-2,
|
||||
.p-3;
|
||||
|
||||
.userGravatar {
|
||||
@extend .mr-3, .align-self-center;
|
||||
}
|
||||
|
||||
.userFullName {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.userInfo {
|
||||
@extend .d-flex;
|
||||
|
||||
.userFullNameRoleStatus {
|
||||
@extend
|
||||
.d-flex,
|
||||
.flex-column;
|
||||
}
|
||||
}
|
||||
|
||||
.userEditableActions {
|
||||
@extend .align-self-center;
|
||||
}
|
||||
|
||||
.userForm {
|
||||
@extend .d-flex;
|
||||
|
||||
.userFullNameRoleForm {
|
||||
@extend .d-flex, .flex-column;
|
||||
}
|
||||
}
|
||||
|
||||
.updateUserButton { @extend .align-self-center; }
|
||||
|
||||
.userEditCancelButton { @extend .align-self-center; }
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover { text-decoration: underline; }
|
||||
}
|
||||
|
||||
a.actionDisabled {
|
||||
@extend .mutedText;
|
||||
|
||||
text-decoration: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.updateUserButton {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.userStatusblocked { color: orange; }
|
||||
.userStatusdeleted { color: red; }
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
@import 'components/SiteSettings/Boards';
|
||||
@import 'components/SiteSettings/PostStatuses';
|
||||
@import 'components/SiteSettings/Roadmap';
|
||||
@import 'components/SiteSettings/Users';
|
||||
|
||||
/* Icons */
|
||||
@import 'icons/drag_icon';
|
||||
Reference in New Issue
Block a user