15088: Adds initial link account UI
authorEric Biagiotti <ebiagiotti@veritasgenetics.com>
Thu, 25 Apr 2019 14:32:31 +0000 (10:32 -0400)
committerEric Biagiotti <ebiagiotti@veritasgenetics.com>
Thu, 25 Apr 2019 14:37:38 +0000 (10:37 -0400)
Also adds the createdAt attribute to the user state

Arvados-DCO-1.1-Signed-off-by: Eric Biagiotti <ebiagiotti@veritasgenetics.com>

src/models/user.ts
src/services/auth-service/auth-service.ts
src/store/auth/auth-action-session.ts
src/store/auth/auth-reducer.test.ts
src/store/link-account/link-account-panel-actions.ts
src/views/link-account-panel/link-account-panel-root.tsx [new file with mode: 0644]
src/views/link-account-panel/link-account-panel.tsx [new file with mode: 0644]
src/views/workbench/workbench.tsx

index 2497864507787cef09d6d3914780b79136090d77..afc4fc72265ea62c03e9b55e436fcc074ef475bf 100644 (file)
@@ -24,6 +24,7 @@ export interface User {
     prefs: UserPrefs;
     isAdmin: boolean;
     isActive: boolean;
+    createdAt: string;
 }
 
 export const getUserFullname = (user?: User) => {
index eae219dd0ad2a547883b94047a961d10048f5019..e7641fdca9cd5bb1ad4efb9ea200bdd65d452356 100644 (file)
@@ -20,6 +20,7 @@ export const USER_IS_ADMIN = 'isAdmin';
 export const USER_IS_ACTIVE = 'isActive';
 export const USER_USERNAME = 'username';
 export const USER_PREFS = 'prefs';
+export const USER_CREATED_AT = 'createdAt';
 
 export interface UserDetailsResponse {
     email: string;
@@ -30,6 +31,7 @@ export interface UserDetailsResponse {
     is_admin: boolean;
     is_active: boolean;
     username: string;
+    created_at: string;
     prefs: UserPrefs;
 }
 
@@ -77,10 +79,11 @@ export class AuthService {
         const isAdmin = this.getIsAdmin();
         const isActive = this.getIsActive();
         const username = localStorage.getItem(USER_USERNAME);
+        const createdAt = localStorage.getItem(USER_CREATED_AT);
         const prefs = JSON.parse(localStorage.getItem(USER_PREFS) || '{"profile": {}}');
 
-        return email && firstName && lastName && uuid && ownerUuid && username && prefs
-            ? { email, firstName, lastName, uuid, ownerUuid, isAdmin, isActive, username, prefs }
+        return email && firstName && lastName && uuid && ownerUuid && username && createdAt && prefs
+            ? { email, firstName, lastName, uuid, ownerUuid, isAdmin, isActive, username, createdAt, prefs }
             : undefined;
     }
 
@@ -93,6 +96,7 @@ export class AuthService {
         localStorage.setItem(USER_IS_ADMIN, JSON.stringify(user.isAdmin));
         localStorage.setItem(USER_IS_ACTIVE, JSON.stringify(user.isActive));
         localStorage.setItem(USER_USERNAME, user.username);
+        localStorage.setItem(USER_CREATED_AT, user.createdAt);
         localStorage.setItem(USER_PREFS, JSON.stringify(user.prefs));
     }
 
@@ -105,6 +109,7 @@ export class AuthService {
         localStorage.removeItem(USER_IS_ADMIN);
         localStorage.removeItem(USER_IS_ACTIVE);
         localStorage.removeItem(USER_USERNAME);
+        localStorage.removeItem(USER_CREATED_AT);
         localStorage.removeItem(USER_PREFS);
     }
 
@@ -135,6 +140,7 @@ export class AuthService {
                     isAdmin: resp.data.is_admin,
                     isActive: resp.data.is_active,
                     username: resp.data.username,
+                    createdAt: resp.data.created_at,
                     prefs
                 };
             })
index 5bb192b8816e6c4fc7bff110424ff7ad83617d02..0a41fe58f66ffb0b57008d56ccb8d45ac8ec20b4 100644 (file)
@@ -94,6 +94,7 @@ const clusterLogin = async (clusterId: string, baseUrl: string, activeSession: S
             isAdmin: user.is_admin,
             isActive: user.is_active,
             username: user.username,
+            createdAt: user.created_at,
             prefs: user.prefs
         },
         token: saltedToken
index 38cf1581d3796dc5b89a4d7cfc897adc425271ce..e008818234e23a5fc5069a857711ecdfad813271 100644 (file)
@@ -33,7 +33,8 @@ describe('auth-reducer', () => {
             username: "username",
             prefs: {},
             isAdmin: false,
-            isActive: true
+            isActive: true,
+            createdAt: "createdAt"
         };
         const state = reducer(initialState, authActions.INIT({ user, token: "token" }));
         expect(state).toEqual({
@@ -74,7 +75,8 @@ describe('auth-reducer', () => {
             username: "username",
             prefs: {},
             isAdmin: false,
-            isActive: true
+            isActive: true,
+            createdAt: "createdAt"
         };
 
         const state = reducer(initialState, authActions.USER_DETAILS_SUCCESS(user));
@@ -94,7 +96,8 @@ describe('auth-reducer', () => {
                 username: "username",
                 prefs: {},
                 isAdmin: false,
-                isActive: true
+                isActive: true,
+                createdAt: "createdAt"
             }
         });
     });
index 51397524c1f97662a4a23eb37a189af3f9c84308..9d0b6a0552853e90a7a35791f45cbcb3387cea87 100644 (file)
@@ -10,8 +10,6 @@ import { setBreadcrumbs } from "~/store/breadcrumbs/breadcrumbs-actions";
 import { authActions } from "~/store/auth/auth-action";
 import { snackbarActions, SnackbarKind } from "~/store/snackbar/snackbar-actions";
 
-export const LINK_ACCOUNT_FORM = 'linkAccountForm';
-
 export const loadLinkAccountPanel = () =>
     (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
        dispatch(setBreadcrumbs([{ label: 'Link account'}]));
diff --git a/src/views/link-account-panel/link-account-panel-root.tsx b/src/views/link-account-panel/link-account-panel-root.tsx
new file mode 100644 (file)
index 0000000..ceb3ffa
--- /dev/null
@@ -0,0 +1,61 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import {
+    StyleRulesCallback,
+    WithStyles,
+    withStyles,
+    Card,
+    CardContent,
+    Button,
+    Typography,
+    Grid,
+} from '@material-ui/core';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { User } from "~/models/user";
+import { formatDate }from "~/common/formatters";
+
+type CssRules = 'root';// | 'gridItem' | 'label' | 'title' | 'actions';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    root: {
+        width: '100%',
+        overflow: 'auto'
+    }
+});
+
+export interface LinkAccountPanelRootDataProps {
+    user?: User;
+}
+
+export interface LinkAccountPanelRootActionProps { }
+
+type LinkAccountPanelRootProps = LinkAccountPanelRootDataProps & LinkAccountPanelRootActionProps & WithStyles<CssRules>;
+
+export const LinkAccountPanelRoot = withStyles(styles) (
+    ({classes, user}: LinkAccountPanelRootProps) => {
+        return <Card className={classes.root}>
+            <CardContent>
+            <Grid container spacing={24}>
+            { user && <Grid container item direction="column" spacing={24}>
+                <Grid item>
+                    You are currently logged in as <b>{user.email}</b> ({user.username}, {user.uuid}) created on <b>{formatDate(user.createdAt)}</b>
+                </Grid>
+                <Grid item>
+                    You can link Arvados accounts. After linking, either login will take you to the same account.
+                </Grid>
+            </Grid> }
+            <Grid container item direction="row" spacing={24}>
+                <Grid item>
+                    <Button color="primary" variant="contained">Add another login to this account</Button>
+                </Grid>
+                <Grid item>
+                    <Button color="primary" variant="contained">Use this login to access another account</Button>
+                </Grid>
+            </Grid>
+            </Grid>
+            </CardContent>
+        </Card>;
+});
\ No newline at end of file
diff --git a/src/views/link-account-panel/link-account-panel.tsx b/src/views/link-account-panel/link-account-panel.tsx
new file mode 100644 (file)
index 0000000..c49b511
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { RootState } from '~/store/store';
+import { Dispatch } from 'redux';
+import { compose } from 'redux';
+import { reduxForm } from 'redux-form';
+import { connect } from 'react-redux';
+import { getResource, ResourcesState } from '~/store/resources/resources';
+import { Resource } from '~/models/resource';
+import { User, UserResource } from '~/models/user';
+import {
+    LinkAccountPanelRoot,
+    LinkAccountPanelRootDataProps,
+    LinkAccountPanelRootActionProps
+} from '~/views/link-account-panel/link-account-panel-root';
+
+const mapStateToProps = (state: RootState): LinkAccountPanelRootDataProps => {
+    return {
+        user: state.auth.user
+    };
+};
+
+const mapDispatchToProps = (dispatch: Dispatch): LinkAccountPanelRootActionProps => ({});
+
+export const LinkAccountPanel = connect(mapStateToProps, mapDispatchToProps)(LinkAccountPanelRoot);
index a31c7d25e00939729d2f9d9bf06b7ce04f7caca5..07b056554e65a7fca999d590ae61b6eb204bd619 100644 (file)
@@ -92,6 +92,7 @@ import { GroupMemberAttributesDialog } from '~/views-components/groups-dialog/me
 import { AddGroupMembersDialog } from '~/views-components/dialog-forms/add-group-member-dialog';
 import { PartialCopyToCollectionDialog } from '~/views-components/dialog-forms/partial-copy-to-collection-dialog';
 import { PublicFavoritePanel } from '~/views/public-favorites-panel/public-favorites-panel';
+import { LinkAccountPanel } from '~/views/link-account-panel/link-account-panel';
 
 type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
 
@@ -176,6 +177,7 @@ export const WorkbenchPanel =
                                 <Route path={Routes.GROUP_DETAILS} component={GroupDetailsPanel} />
                                 <Route path={Routes.LINKS} component={LinkPanel} />
                                 <Route path={Routes.PUBLIC_FAVORITES} component={PublicFavoritePanel} />
+                                <Route path={Routes.LINK_ACCOUNT} component={LinkAccountPanel} />
                             </Switch>
                         </Grid>
                     </Grid>