"react-router-redux": "5.0.0-alpha.9",
"react-scripts-ts": "2.16.0",
"redux": "4.0.0",
- "redux-devtools": "3.4.1",
- "typesafe-actions": "2.0.4"
+ "redux-thunk": "2.3.0",
+ "unionize": "2.1.2"
},
"scripts": {
"start": "react-scripts-ts start",
"@types/react-router-dom": "4.2.7",
"@types/react-router-redux": "5.0.15",
"@types/redux-devtools": "3.0.44",
- "typescript": "2.9.1"
+ "typescript": "2.9.1",
+ "redux-devtools": "3.4.1"
},
"moduleNameMapper": {
"^~/(.*)$": "<rootDir>/src/$1"
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
import Axios, { AxiosInstance } from "axios";
-export const API_HOST = 'https://qr1hi.arvadosapi.com/arvados/v1';
+export const API_HOST = 'https://qr1hi.arvadosapi.com';
export const serverApi: AxiosInstance = Axios.create({
- baseURL: API_HOST
+ baseURL: API_HOST + '/arvados/v1'
});
export function setServerApiAuthorizationHeader(token: string) {
serverApi.defaults.headers.common = {
'Authorization': `OAuth2 ${token}`
- };
-}
+ };}
export function removeServerApiAuthorizationHeader() {
delete serverApi.defaults.headers.common.Authorization;
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
import { Redirect, RouteProps } from "react-router";
import * as React from "react";
-import { connect } from "react-redux";
-import authActions from "../../store/auth-action";
+import { connect, DispatchProp } from "react-redux";
+import authActions, { getUserDetails } from "../../store/auth-action";
interface ApiTokenProps {
- saveApiToken: (token: string) => void;
}
-class ApiToken extends React.Component<ApiTokenProps & RouteProps, {}> {
+class ApiToken extends React.Component<ApiTokenProps & RouteProps & DispatchProp<any>, {}> {
static getUrlParameter(search: string, name: string) {
const safeName = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
const regex = new RegExp('[\\?&]' + safeName + '=([^&#]*)');
componentDidMount() {
const search = this.props.location ? this.props.location.search : "";
const apiToken = ApiToken.getUrlParameter(search, 'api_token');
- this.props.saveApiToken(apiToken);
+ this.props.dispatch(authActions.SAVE_API_TOKEN(apiToken));
+ this.props.dispatch(getUserDetails());
}
render() {
return <Redirect to="/"/>
}
}
-export default connect<ApiTokenProps>(null, {
- saveApiToken: authActions.saveApiToken
-})(ApiToken);
+export default connect()(ApiToken);
class Tree<T> extends React.Component<TreeProps<T>, {}> {
render() {
return <List>
- {this.props.items.map((it: T, idx: number) =>
+ {this.props.items && this.props.items.map((it: T, idx: number) =>
<ListItem key={`item/${idx}`} button>
{this.props.render(it)}
</ListItem>
location: null
},
auth: {
+ user: undefined
}
}, history);
export interface User {
email: string;
- apiToken: string;
- apiHost: string;
+ firstName: string;
+ lastName: string;
}
// SPDX-License-Identifier: AGPL-3.0
import Axios from "axios";
-import { API_HOST, serverApi } from "../../common/server-api";
+import { API_HOST } from "../../common/server-api";
const API_TOKEN_KEY = 'api_token';
//
// SPDX-License-Identifier: AGPL-3.0
-import { ActionType, createStandardAction } from "typesafe-actions";
+import { serverApi } from "../common/server-api";
+import { ofType, default as unionize, UnionOf } from "unionize";
+import { Dispatch } from "redux";
-const actions = {
- saveApiToken: createStandardAction('@@auth/saveApiToken')<string>(),
- getUserTokenDetails: createStandardAction('@@auth/userTokenDetails')(),
- login: createStandardAction('@@auth/login')(),
- logout: createStandardAction('@@auth/logout')()
-};
+export interface UserDetailsResponse {
+ email: string;
+ first_name: string;
+ last_name: string;
+ is_admin: boolean;
+}
+
+const actions = unionize({
+ SAVE_API_TOKEN: ofType<string>(),
+ LOGIN: {},
+ LOGOUT: {},
+ USER_DETAILS_REQUEST: {},
+ USER_DETAILS_SUCCESS: ofType<UserDetailsResponse>()
+}, {
+ tag: 'type',
+ value: 'payload'
+});
-export type AuthAction = ActionType<typeof actions>;
+export type AuthAction = UnionOf<typeof actions>;
export default actions;
+
+export const getUserDetails = () => (dispatch: Dispatch) => {
+ dispatch(actions.USER_DETAILS_REQUEST());
+ serverApi
+ .get<UserDetailsResponse>('/users/current')
+ .then(resp => {
+ dispatch(actions.USER_DETAILS_SUCCESS(resp.data));
+ })
+ // .catch(err => {
+ // });
+};
+
+
//
// SPDX-License-Identifier: AGPL-3.0
-import { getType } from "typesafe-actions";
-import actions, { AuthAction } from "./auth-action";
+import actions, { AuthAction, UserDetailsResponse } from "./auth-action";
import { User } from "../models/user";
import { authService } from "../services/services";
-import { removeServerApiAuthorizationHeader, serverApi, setServerApiAuthorizationHeader } from "../common/server-api";
+import { removeServerApiAuthorizationHeader, setServerApiAuthorizationHeader } from "../common/server-api";
-type AuthState = User | {};
+export interface AuthState {
+ user?: User;
+ apiToken?: string;
+};
const authReducer = (state: AuthState = {}, action: AuthAction) => {
- switch (action.type) {
- case getType(actions.saveApiToken): {
- authService.saveApiToken(action.payload);
- setServerApiAuthorizationHeader(action.payload);
- serverApi.get('/users/current');
- return {...state, apiToken: action.payload};
- }
- case getType(actions.login): {
+ return actions.match(action, {
+ SAVE_API_TOKEN: (token: string) => {
+ authService.saveApiToken(token);
+ setServerApiAuthorizationHeader(token);
+ return {...state, apiToken: token};
+ },
+ LOGIN: () => {
authService.login();
return state;
- }
- case getType(actions.logout): {
+ },
+ LOGOUT: () => {
authService.removeApiToken();
removeServerApiAuthorizationHeader();
authService.logout();
- return {...state, apiToken: null };
- }
- default:
- return state;
- }
+ return {...state, apiToken: undefined};
+ },
+ USER_DETAILS_SUCCESS: (ud: UserDetailsResponse) => {
+ return {...state, user: {
+ email: ud.email,
+ firstName: ud.first_name,
+ lastName: ud.last_name
+ }}
+ },
+ default: () => state
+ });
};
export default authReducer;
//
// SPDX-License-Identifier: AGPL-3.0
-import { ActionType, createStandardAction } from "typesafe-actions";
import { Project } from "../models/project";
+import { default as unionize, ofType, UnionOf } from "unionize";
-const actions = {
- createProject: createStandardAction('@@project/create')<Project>(),
- removeProject: createStandardAction('@@project/remove')<string>()
-};
+const actions = unionize({
+ CREATE_PROJECT: ofType<Project>(),
+ REMOVE_PROJECT: ofType<string>()
+}, {
+ tag: 'type',
+ value: 'payload'
+});
-export type ProjectAction = ActionType<typeof actions>;
+export type ProjectAction = UnionOf<typeof actions>;
export default actions;
//
// SPDX-License-Identifier: AGPL-3.0
-import { getType } from "typesafe-actions";
import { Project } from "../models/project";
import actions, { ProjectAction } from "./project-action";
-type ProjectState = Project[];
+export type ProjectState = Project[];
const projectsReducer = (state: ProjectState = [], action: ProjectAction) => {
- switch (action.type) {
- case getType(actions.createProject): {
- return [...state, action.payload];
- }
- default:
- return state;
- }
+ return actions.match(action, {
+ CREATE_PROJECT: (project) => [...state, project],
+ REMOVE_PROJECT: () => state,
+ default: () => state
+ });
};
export default projectsReducer;
// SPDX-License-Identifier: AGPL-3.0
import { combineReducers } from "redux";
-import { StateType } from "typesafe-actions";
-import { routerReducer } from "react-router-redux";
-import authReducer from "./auth-reducer";
-import projectsReducer from "./project-reducer";
+import { routerReducer, RouterState } from "react-router-redux";
+import authReducer, { AuthState } from "./auth-reducer";
+import projectsReducer, { ProjectState } from "./project-reducer";
+
+export interface RootState {
+ auth: AuthState,
+ projects: ProjectState,
+ router: RouterState
+}
const rootReducer = combineReducers({
auth: authReducer,
router: routerReducer
});
-export type RootState = StateType<typeof rootReducer>;
-
export default rootReducer;
import { createStore, applyMiddleware, compose, Middleware } from 'redux';
import { default as rootReducer, RootState } from "./root-reducer";
import { routerMiddleware } from "react-router-redux";
+import thunkMiddleware from 'redux-thunk';
import { History } from "history";
const composeEnhancers =
export default function configureStore(initialState: RootState, history: History) {
const middlewares: Middleware[] = [
- routerMiddleware(history)
+ routerMiddleware(history),
+ thunkMiddleware
];
const enhancer = composeEnhancers(applyMiddleware(...middlewares));
return createStore(rootReducer, initialState!, enhancer);
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
-import { connect } from "react-redux";
+import { connect, DispatchProp } from "react-redux";
import Tree from "../../components/tree/tree";
import { Project } from "../../models/project";
import { RootState } from "../../store/root-reducer";
import Menu from "@material-ui/core/Menu/Menu";
import MenuItem from "@material-ui/core/MenuItem/MenuItem";
import { AccountCircle } from "@material-ui/icons";
+import { AnyAction } from "redux";
const drawerWidth = 240;
}
interface WorkbenchActionProps {
- login?: () => void;
- logout?: () => void;
}
-type WorkbenchProps = WorkbenchDataProps & WorkbenchActionProps & WithStyles<CssRules>;
+type WorkbenchProps = WorkbenchDataProps & WorkbenchActionProps & DispatchProp & WithStyles<CssRules>;
interface WorkbenchState {
anchorEl: any;
}
login = () => {
- this.props.login!();
+ this.props.dispatch(authActions.LOGIN() as AnyAction);
};
logout = () => {
this.handleClose();
- this.props.logout!();
+ this.props.dispatch(authActions.LOGOUT() as AnyAction);
};
handleOpenMenu = (event: React.MouseEvent<any>) => {
export default connect<WorkbenchDataProps>(
(state: RootState) => ({
projects: state.projects
- }), {
- login: authActions.login,
- logout: authActions.logout
- }
+ })
)(
withStyles(styles)(Workbench)
);
prop-types "^15.5.7"
redux-devtools-instrument "^1.0.1"
+redux-thunk@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
+
redux@4.0.0, "redux@>= 3.7.2", redux@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.0.tgz#aa698a92b729315d22b34a0553d7e6533555cc03"
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
-typesafe-actions@2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/typesafe-actions/-/typesafe-actions-2.0.4.tgz#31c8f8df3566d549eb52edb64a75997e970c17d0"
-
typescript@2.9.1:
version "2.9.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.1.tgz#fdb19d2c67a15d11995fd15640e373e09ab09961"
is-extendable "^0.1.1"
set-value "^0.4.3"
+unionize@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/unionize/-/unionize-2.1.2.tgz#2513b148de515bec93f045d1685bd88eab62b608"
+
uniq@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"