@openneuro/app 4.34.2 → 4.35.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/scripts/authentication/profile.ts +3 -5
- package/src/scripts/common/containers/header.tsx +1 -2
- package/src/scripts/common/partials/freshdesk-widget.jsx +5 -1
- package/src/scripts/components/header/Header.tsx +4 -10
- package/src/scripts/components/header/LandingExpandedHeader.tsx +11 -9
- package/src/scripts/components/header/header.scss +4 -2
- package/src/scripts/components/logo/Logo.tsx +1 -1
- package/src/scripts/components/modal/UserLoginModal.tsx +12 -11
- package/src/scripts/components/modal/__tests__/UserLoginModal.spec.tsx +1 -1
- package/src/scripts/components/page/Page.tsx +1 -2
- package/src/scripts/components/scss/upload-modal.scss +6 -0
- package/src/scripts/components/search-page/SearchResultItem.tsx +14 -13
- package/src/scripts/components/search-page/SearchResultsList.tsx +0 -19
- package/src/scripts/dataset/draft-container.tsx +0 -1
- package/src/scripts/dataset/fragments/__tests__/dataset-history.spec.tsx +155 -0
- package/src/scripts/dataset/fragments/dataset-history.jsx +6 -7
- package/src/scripts/dataset/snapshot-container.tsx +13 -11
- package/src/scripts/errors/errorRoute.tsx +2 -0
- package/src/scripts/errors/freshdesk-widget.jsx +5 -1
- package/src/scripts/errors/orcid/email-warning.tsx +21 -0
- package/src/scripts/index.tsx +15 -2
- package/src/scripts/pages/__tests__/orcid-link.spec.tsx +13 -0
- package/src/scripts/pages/orcid-link.tsx +60 -0
- package/src/scripts/queries/user.ts +71 -0
- package/src/scripts/routes.tsx +2 -0
- package/src/scripts/scss/variables.scss +13 -9
- package/src/scripts/search/search-container.tsx +2 -12
- package/src/scripts/search/search-params-ctx.tsx +6 -3
- package/src/scripts/search/use-search-results.tsx +1 -1
- package/src/scripts/types/user-types.ts +2 -0
- package/src/scripts/uploader/file-select.tsx +3 -1
- package/src/scripts/uploader/upload-select.jsx +40 -24
- package/src/scripts/users/__tests__/user-account-view.spec.tsx +36 -22
- package/src/scripts/users/__tests__/user-query.spec.tsx +3 -3
- package/src/scripts/users/__tests__/user-routes.spec.tsx +28 -11
- package/src/scripts/users/__tests__/user-tabs.spec.tsx +12 -9
- package/src/scripts/users/scss/user-menu.scss +133 -0
- package/src/scripts/users/scss/usernotifications.module.scss +1 -1
- package/src/scripts/users/user-account-view.tsx +35 -21
- package/src/scripts/users/user-container.tsx +2 -1
- package/src/scripts/users/user-menu.tsx +114 -0
- package/src/scripts/users/user-notification-accordion.tsx +2 -2
- package/src/scripts/users/user-query.tsx +6 -36
- package/src/scripts/users/user-routes.tsx +7 -5
- package/src/scripts/users/user-tabs.tsx +4 -3
- package/src/scripts/validation/__tests__/__snapshots__/validation-issues.spec.tsx.snap +10 -1
- package/src/scripts/validation/validation-issues.tsx +7 -3
- package/src/scripts/components/user/UserMenu.tsx +0 -72
- package/src/scripts/components/user/user-menu.scss +0 -88
|
@@ -5,6 +5,7 @@ import { MockedProvider } from "@apollo/client/testing"
|
|
|
5
5
|
import { UserRoutes } from "../user-routes"
|
|
6
6
|
import type { User } from "../../types/user-types"
|
|
7
7
|
import { DATASETS_QUERY } from "../user-datasets-view"
|
|
8
|
+
import { GET_USER } from "../../queries/user"
|
|
8
9
|
|
|
9
10
|
const defaultUser: User = {
|
|
10
11
|
id: "1",
|
|
@@ -68,13 +69,29 @@ const mocks = [
|
|
|
68
69
|
},
|
|
69
70
|
},
|
|
70
71
|
},
|
|
72
|
+
{
|
|
73
|
+
request: {
|
|
74
|
+
query: GET_USER,
|
|
75
|
+
variables: { userId: defaultUser.id },
|
|
76
|
+
},
|
|
77
|
+
result: {
|
|
78
|
+
data: {
|
|
79
|
+
user: defaultUser,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
71
83
|
]
|
|
72
84
|
|
|
73
|
-
const renderWithRouter = (
|
|
85
|
+
const renderWithRouter = (
|
|
86
|
+
user: User,
|
|
87
|
+
route: string,
|
|
88
|
+
hasEdit: boolean,
|
|
89
|
+
isUser: boolean,
|
|
90
|
+
) => {
|
|
74
91
|
return render(
|
|
75
92
|
<MockedProvider mocks={mocks} addTypename={false}>
|
|
76
93
|
<MemoryRouter initialEntries={[route]}>
|
|
77
|
-
<UserRoutes user={user} hasEdit={hasEdit} />
|
|
94
|
+
<UserRoutes user={user} hasEdit={hasEdit} isUser={isUser} />
|
|
78
95
|
</MemoryRouter>
|
|
79
96
|
</MockedProvider>,
|
|
80
97
|
)
|
|
@@ -84,27 +101,27 @@ describe("UserRoutes Component", () => {
|
|
|
84
101
|
const user: User = defaultUser
|
|
85
102
|
|
|
86
103
|
it("renders UserDatasetsView for the default route", async () => {
|
|
87
|
-
renderWithRouter(user, "/", true)
|
|
104
|
+
renderWithRouter(user, "/", true, true)
|
|
88
105
|
const datasetsView = await screen.findByTestId("user-datasets-view")
|
|
89
106
|
expect(datasetsView).toBeInTheDocument()
|
|
90
107
|
})
|
|
91
108
|
|
|
92
109
|
it("renders FourOFourPage for an invalid route", async () => {
|
|
93
|
-
renderWithRouter(user, "/nonexistent-route", true)
|
|
110
|
+
renderWithRouter(user, "/nonexistent-route", true, true)
|
|
94
111
|
const errorMessage = await screen.findByText(
|
|
95
112
|
/404: The page you are looking for does not exist./i,
|
|
96
113
|
)
|
|
97
114
|
expect(errorMessage).toBeInTheDocument()
|
|
98
115
|
})
|
|
99
116
|
|
|
100
|
-
it("renders UserAccountView when hasEdit is true", async () => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
})
|
|
117
|
+
// it("renders UserAccountView when hasEdit is true", async () => {
|
|
118
|
+
// renderWithRouter(user, "/account", true, true);
|
|
119
|
+
// const accountView = await screen.findByTestId("user-account-view");
|
|
120
|
+
// expect(accountView).toBeInTheDocument();
|
|
121
|
+
// });
|
|
105
122
|
|
|
106
123
|
it("renders UserNotificationsView when hasEdit is true", async () => {
|
|
107
|
-
renderWithRouter(user, "/notifications", true)
|
|
124
|
+
renderWithRouter(user, "/notifications", true, true)
|
|
108
125
|
const notificationsView = await screen.findByTestId(
|
|
109
126
|
"user-notifications-view",
|
|
110
127
|
)
|
|
@@ -116,7 +133,7 @@ describe("UserRoutes Component", () => {
|
|
|
116
133
|
|
|
117
134
|
for (const route of restrictedRoutes) {
|
|
118
135
|
cleanup()
|
|
119
|
-
renderWithRouter(user, route, false)
|
|
136
|
+
renderWithRouter(user, route, false, true)
|
|
120
137
|
const errorMessage = await screen.findByText(
|
|
121
138
|
/403: You do not have access to this page, you may need to sign in./i,
|
|
122
139
|
)
|
|
@@ -12,7 +12,10 @@ const UserAccountTabsWrapper: React.FC = () => {
|
|
|
12
12
|
<button onClick={() => setHasEdit(!hasEdit)}>Toggle hasEdit</button>
|
|
13
13
|
<MemoryRouter>
|
|
14
14
|
<Routes>
|
|
15
|
-
<Route
|
|
15
|
+
<Route
|
|
16
|
+
path="*"
|
|
17
|
+
element={<UserAccountTabs hasEdit={hasEdit} isUser={true} />}
|
|
18
|
+
/>
|
|
16
19
|
</Routes>
|
|
17
20
|
</MemoryRouter>
|
|
18
21
|
</>
|
|
@@ -23,23 +26,23 @@ describe("UserAccountTabs Component", () => {
|
|
|
23
26
|
it("should not render tabs when hasEdit is false", () => {
|
|
24
27
|
render(<UserAccountTabsWrapper />)
|
|
25
28
|
|
|
26
|
-
expect(screen.getByText("
|
|
29
|
+
expect(screen.getByText("My Datasets")).toBeInTheDocument()
|
|
27
30
|
|
|
28
31
|
fireEvent.click(screen.getByText("Toggle hasEdit"))
|
|
29
32
|
|
|
30
|
-
expect(screen.queryByText("
|
|
33
|
+
expect(screen.queryByText("My Datasets")).not.toBeInTheDocument()
|
|
31
34
|
})
|
|
32
35
|
|
|
33
36
|
it("should render tabs when hasEdit is toggled back to true", () => {
|
|
34
37
|
render(<UserAccountTabsWrapper />)
|
|
35
38
|
|
|
36
|
-
expect(screen.getByText("
|
|
39
|
+
expect(screen.getByText("My Datasets")).toBeInTheDocument()
|
|
37
40
|
|
|
38
41
|
fireEvent.click(screen.getByText("Toggle hasEdit"))
|
|
39
|
-
expect(screen.queryByText("
|
|
42
|
+
expect(screen.queryByText("My Datasets")).not.toBeInTheDocument()
|
|
40
43
|
|
|
41
44
|
fireEvent.click(screen.getByText("Toggle hasEdit"))
|
|
42
|
-
expect(screen.getByText("
|
|
45
|
+
expect(screen.getByText("My Datasets")).toBeInTheDocument()
|
|
43
46
|
})
|
|
44
47
|
|
|
45
48
|
it("should update active class on the correct NavLink based on route", () => {
|
|
@@ -48,10 +51,10 @@ describe("UserAccountTabs Component", () => {
|
|
|
48
51
|
// Utility function to check if an element has 'active' class - used because of CSS module discrepancies between classNames
|
|
49
52
|
const hasActiveClass = (element) => element.className.includes("active")
|
|
50
53
|
|
|
51
|
-
const datasetsTab = screen.getByText("
|
|
54
|
+
const datasetsTab = screen.getByText("My Datasets")
|
|
52
55
|
expect(hasActiveClass(datasetsTab)).toBe(true)
|
|
53
56
|
|
|
54
|
-
const notificationsTab = screen.getByText("
|
|
57
|
+
const notificationsTab = screen.getByText("Notifications")
|
|
55
58
|
|
|
56
59
|
fireEvent.click(notificationsTab)
|
|
57
60
|
|
|
@@ -70,7 +73,7 @@ describe("UserAccountTabs Component", () => {
|
|
|
70
73
|
it("should trigger animation state when a tab is clicked", async () => {
|
|
71
74
|
render(<UserAccountTabsWrapper />)
|
|
72
75
|
|
|
73
|
-
const notificationsTab = screen.getByText("
|
|
76
|
+
const notificationsTab = screen.getByText("Notifications")
|
|
74
77
|
// Utility function to check if an element has 'clicked' class - used because of CSS module discrepancies between classNames
|
|
75
78
|
const hasClickedClass = (element) => element.className.includes("clicked")
|
|
76
79
|
const tabsContainer = await screen.findByRole("list")
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
.user-menu-wrap{
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
.notifications-link a{
|
|
5
|
+
display: block;
|
|
6
|
+
color: #fff;
|
|
7
|
+
font-size: 20px;
|
|
8
|
+
margin-right: 15px;
|
|
9
|
+
padding: 5px 8px;
|
|
10
|
+
border: 1px solid rgba(255,255,255,.5);
|
|
11
|
+
border-radius: var(--border-radius-default);
|
|
12
|
+
transition: background-color .2s;
|
|
13
|
+
position: relative;
|
|
14
|
+
&:hover{
|
|
15
|
+
background-color: rgba(255,255,255,.5);
|
|
16
|
+
}
|
|
17
|
+
.count{
|
|
18
|
+
position: absolute;
|
|
19
|
+
background-color: #B20001;
|
|
20
|
+
font-size: 11px;
|
|
21
|
+
font-family: var(--font-sans);
|
|
22
|
+
line-height: 11px;
|
|
23
|
+
border-radius: 50%;
|
|
24
|
+
padding: 2px 3px;
|
|
25
|
+
min-width: 15px;
|
|
26
|
+
min-height: 15px;
|
|
27
|
+
top: -5px;
|
|
28
|
+
right: -5px;
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
text-align: center;
|
|
33
|
+
vertical-align: middle;
|
|
34
|
+
span > span{
|
|
35
|
+
font-size: 10px;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
.user-menu-dropdown {
|
|
41
|
+
position: relative;
|
|
42
|
+
.menu {
|
|
43
|
+
background-color: #fff;
|
|
44
|
+
border-radius: var(--border-radius-default);
|
|
45
|
+
width: 100%;
|
|
46
|
+
min-width: 250px;
|
|
47
|
+
right: -10px;
|
|
48
|
+
top: 50px;
|
|
49
|
+
box-shadow: 1px 2px 11px -5px rgba(0, 0, 0, .5)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.user-menu-dropdown-list {
|
|
53
|
+
ul {
|
|
54
|
+
list-style: none;
|
|
55
|
+
margin: 0;
|
|
56
|
+
padding: 20px;
|
|
57
|
+
li {
|
|
58
|
+
text-align: center;
|
|
59
|
+
border-bottom: 1px solid var(--newspaper);
|
|
60
|
+
&.dropdown-header {
|
|
61
|
+
padding: 10px 0;
|
|
62
|
+
font-size: 14px;
|
|
63
|
+
p:first-child {
|
|
64
|
+
margin-top: 0;
|
|
65
|
+
}
|
|
66
|
+
span {
|
|
67
|
+
color: #3d3d3d;
|
|
68
|
+
font-weight: bold;
|
|
69
|
+
font-size: 12px;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
a {
|
|
73
|
+
display: block;
|
|
74
|
+
text-decoration: none;
|
|
75
|
+
padding: 10px 0;
|
|
76
|
+
&:hover {
|
|
77
|
+
background-color: #eee;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
&:last-child {
|
|
81
|
+
border: 0;
|
|
82
|
+
margin: 0;
|
|
83
|
+
padding: 0;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.active {
|
|
90
|
+
img {
|
|
91
|
+
opacity: 0.5;
|
|
92
|
+
}
|
|
93
|
+
i {
|
|
94
|
+
color: var(--on-light-aqua);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.header-account-btn {
|
|
100
|
+
button.on-no-background.icon-text {
|
|
101
|
+
color: var(--on-dark-aqua);
|
|
102
|
+
margin: 5px 20px 0;
|
|
103
|
+
font-size: 16px;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
.header-account-btn .toggle {
|
|
107
|
+
.user-menu-label{
|
|
108
|
+
color: #204e5a;
|
|
109
|
+
font-weight: bold;
|
|
110
|
+
font-size: 12px;
|
|
111
|
+
padding: 11px 10px;
|
|
112
|
+
display: flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
background: #eee;
|
|
115
|
+
border-radius: var(--border-radius-default);
|
|
116
|
+
@media (max-width: 800px) {
|
|
117
|
+
padding: 10px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
&.avatar{
|
|
121
|
+
background-color: transparent;
|
|
122
|
+
border-radius: 50%;
|
|
123
|
+
padding: 0;
|
|
124
|
+
max-width: 40px;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
@media (max-width: 800px) {
|
|
129
|
+
.header-account-btn .dropdown-wrapper .menu {
|
|
130
|
+
right: -25vw;
|
|
131
|
+
left: -25vw;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import React, { useState } from "react"
|
|
2
|
+
import * as Sentry from "@sentry/react"
|
|
2
3
|
import { useMutation } from "@apollo/client"
|
|
3
4
|
import { EditableContent } from "./components/editable-content"
|
|
5
|
+
import { GET_USER, UPDATE_USER, useUser } from "../queries/user"
|
|
4
6
|
import styles from "./scss/useraccountview.module.scss"
|
|
5
|
-
import { GET_USER_BY_ORCID, UPDATE_USER } from "./user-query"
|
|
6
|
-
import type { UserAccountViewProps } from "../types/user-types"
|
|
7
7
|
|
|
8
|
-
export const UserAccountView: React.FC
|
|
9
|
-
const
|
|
10
|
-
|
|
8
|
+
export const UserAccountView: React.FC = () => {
|
|
9
|
+
const { user, loading, error } = useUser()
|
|
10
|
+
|
|
11
|
+
const [userLinks, setLinks] = useState<string[]>(user?.links || [])
|
|
12
|
+
const [userLocation, setLocation] = useState<string>(user?.location || "")
|
|
11
13
|
const [userInstitution, setInstitution] = useState<string>(
|
|
12
|
-
user
|
|
14
|
+
user?.institution || "",
|
|
13
15
|
)
|
|
14
16
|
const [updateUser] = useMutation(UPDATE_USER)
|
|
15
17
|
|
|
@@ -18,18 +20,18 @@ export const UserAccountView: React.FC<UserAccountViewProps> = ({ user }) => {
|
|
|
18
20
|
try {
|
|
19
21
|
await updateUser({
|
|
20
22
|
variables: {
|
|
21
|
-
id: user
|
|
23
|
+
id: user?.orcid,
|
|
22
24
|
links: newLinks,
|
|
23
25
|
},
|
|
24
26
|
refetchQueries: [
|
|
25
27
|
{
|
|
26
|
-
query:
|
|
27
|
-
variables: { id: user
|
|
28
|
+
query: GET_USER,
|
|
29
|
+
variables: { id: user?.orcid },
|
|
28
30
|
},
|
|
29
31
|
],
|
|
30
32
|
})
|
|
31
|
-
} catch {
|
|
32
|
-
|
|
33
|
+
} catch (error) {
|
|
34
|
+
Sentry.captureException(error)
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
|
|
@@ -39,18 +41,18 @@ export const UserAccountView: React.FC<UserAccountViewProps> = ({ user }) => {
|
|
|
39
41
|
try {
|
|
40
42
|
await updateUser({
|
|
41
43
|
variables: {
|
|
42
|
-
id: user
|
|
44
|
+
id: user?.orcid,
|
|
43
45
|
location: newLocation,
|
|
44
46
|
},
|
|
45
47
|
refetchQueries: [
|
|
46
48
|
{
|
|
47
|
-
query:
|
|
48
|
-
variables: { id: user
|
|
49
|
+
query: GET_USER,
|
|
50
|
+
variables: { id: user?.orcid },
|
|
49
51
|
},
|
|
50
52
|
],
|
|
51
53
|
})
|
|
52
|
-
} catch {
|
|
53
|
-
|
|
54
|
+
} catch (error) {
|
|
55
|
+
Sentry.captureException(error)
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
|
|
@@ -60,21 +62,33 @@ export const UserAccountView: React.FC<UserAccountViewProps> = ({ user }) => {
|
|
|
60
62
|
try {
|
|
61
63
|
await updateUser({
|
|
62
64
|
variables: {
|
|
63
|
-
id: user
|
|
65
|
+
id: user?.orcid,
|
|
64
66
|
institution: newInstitution,
|
|
65
67
|
},
|
|
66
68
|
refetchQueries: [
|
|
67
69
|
{
|
|
68
|
-
query:
|
|
69
|
-
variables: { id: user
|
|
70
|
+
query: GET_USER,
|
|
71
|
+
variables: { id: user?.orcid },
|
|
70
72
|
},
|
|
71
73
|
],
|
|
72
74
|
})
|
|
73
|
-
} catch {
|
|
74
|
-
|
|
75
|
+
} catch (error) {
|
|
76
|
+
Sentry.captureException(error)
|
|
75
77
|
}
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
if (loading) {
|
|
81
|
+
return <div>Loading Account Information...</div>
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (error) {
|
|
85
|
+
return <div>Error loading account information. Please try again.</div>
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!user) {
|
|
89
|
+
return <div>Could not load account information.</div>
|
|
90
|
+
}
|
|
91
|
+
|
|
78
92
|
return (
|
|
79
93
|
<div data-testid="user-account-view" className={styles.useraccountview}>
|
|
80
94
|
<h3>Account</h3>
|
|
@@ -8,6 +8,7 @@ import type { AccountContainerProps } from "../types/user-types"
|
|
|
8
8
|
export const UserAccountContainer: React.FC<AccountContainerProps> = ({
|
|
9
9
|
user,
|
|
10
10
|
hasEdit,
|
|
11
|
+
isUser,
|
|
11
12
|
}) => {
|
|
12
13
|
return (
|
|
13
14
|
<>
|
|
@@ -22,7 +23,7 @@ export const UserAccountContainer: React.FC<AccountContainerProps> = ({
|
|
|
22
23
|
<div className={styles.usercontainer + " container"}>
|
|
23
24
|
<section className={styles.userSidebar}>
|
|
24
25
|
<UserCard user={user} />
|
|
25
|
-
<UserAccountTabs hasEdit={hasEdit} />
|
|
26
|
+
<UserAccountTabs hasEdit={hasEdit} isUser={isUser} />
|
|
26
27
|
</section>
|
|
27
28
|
<section className={styles.userViews}>
|
|
28
29
|
<Outlet />
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { Link } from "react-router-dom"
|
|
3
|
+
import { Dropdown } from "../components/dropdown/Dropdown"
|
|
4
|
+
import { useUser } from "../queries/user"
|
|
5
|
+
import "./scss/user-menu.scss"
|
|
6
|
+
|
|
7
|
+
export interface UserMenuProps {
|
|
8
|
+
signOutAndRedirect: () => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const UserMenu = (
|
|
12
|
+
{ signOutAndRedirect }: UserMenuProps,
|
|
13
|
+
) => {
|
|
14
|
+
//const inboxCount = 99
|
|
15
|
+
|
|
16
|
+
const { user } = useUser()
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<span className="user-menu-wrap">
|
|
20
|
+
{
|
|
21
|
+
/* {user?.orcid && (
|
|
22
|
+
<span className="notifications-link">
|
|
23
|
+
<Link to={`/user/${user?.orcid}/notifications/unread`}>
|
|
24
|
+
<i className="fa fa-inbox">
|
|
25
|
+
{inboxCount > 0 && (
|
|
26
|
+
<span className="count">
|
|
27
|
+
{inboxCount > 99
|
|
28
|
+
? (
|
|
29
|
+
<span>
|
|
30
|
+
99<span>+</span>
|
|
31
|
+
</span>
|
|
32
|
+
)
|
|
33
|
+
: inboxCount}
|
|
34
|
+
</span>
|
|
35
|
+
)}
|
|
36
|
+
</i>
|
|
37
|
+
<span className="sr-only">Account Info</span>
|
|
38
|
+
</Link>
|
|
39
|
+
</span>
|
|
40
|
+
)} */
|
|
41
|
+
}
|
|
42
|
+
<Dropdown
|
|
43
|
+
className={"user-menu-dropdown"}
|
|
44
|
+
label={user?.avatar
|
|
45
|
+
? (
|
|
46
|
+
<img
|
|
47
|
+
className="user-menu-label avatar"
|
|
48
|
+
src={user?.avatar}
|
|
49
|
+
alt="User Avatar"
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
: <div className="user-menu-label">My Account</div>}
|
|
53
|
+
children={
|
|
54
|
+
<div className="user-menu-dropdown-list">
|
|
55
|
+
<ul>
|
|
56
|
+
<li className="dropdown-header">
|
|
57
|
+
<p>
|
|
58
|
+
<span>Hello</span> <br />
|
|
59
|
+
{user?.name} <br />
|
|
60
|
+
{user?.email}
|
|
61
|
+
</p>
|
|
62
|
+
<p>
|
|
63
|
+
<span>signed in via {user?.provider}</span>
|
|
64
|
+
</p>
|
|
65
|
+
</li>
|
|
66
|
+
<li>
|
|
67
|
+
{
|
|
68
|
+
/* {user?.orcid
|
|
69
|
+
? <Link to={`/user/${user?.orcid}`}>My Datasets</Link>
|
|
70
|
+
: <Link to="/search?mydatasets">My Datasets</Link>} */
|
|
71
|
+
}
|
|
72
|
+
<Link to="/search?mydatasets">My Datasets</Link>
|
|
73
|
+
</li>
|
|
74
|
+
|
|
75
|
+
{
|
|
76
|
+
/* {user?.orcid && (
|
|
77
|
+
<li>
|
|
78
|
+
<Link to={`/user/${user?.orcid}/account`}>
|
|
79
|
+
Account Info
|
|
80
|
+
</Link>
|
|
81
|
+
</li>
|
|
82
|
+
)} */
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
<li className="user-menu-link">
|
|
86
|
+
<Link to="/keygen">Obtain an API Key</Link>
|
|
87
|
+
</li>
|
|
88
|
+
{user?.provider !== "orcid" && (
|
|
89
|
+
<li className="user-menu-link">
|
|
90
|
+
<a href="/crn/auth/orcid?link=true">
|
|
91
|
+
Link ORCID to my account
|
|
92
|
+
</a>
|
|
93
|
+
</li>
|
|
94
|
+
)}
|
|
95
|
+
{user?.admin && (
|
|
96
|
+
<li className="user-menu-link">
|
|
97
|
+
<Link to="/admin">Admin</Link>
|
|
98
|
+
</li>
|
|
99
|
+
)}
|
|
100
|
+
<li className="user-menu-link">
|
|
101
|
+
<a
|
|
102
|
+
onClick={() => signOutAndRedirect()}
|
|
103
|
+
className="btn-submit-other"
|
|
104
|
+
>
|
|
105
|
+
Sign Out
|
|
106
|
+
</a>
|
|
107
|
+
</li>
|
|
108
|
+
</ul>
|
|
109
|
+
</div>
|
|
110
|
+
}
|
|
111
|
+
/>
|
|
112
|
+
</span>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
@@ -84,7 +84,7 @@ export const NotificationAccordion = ({ notification, onUpdate }) => {
|
|
|
84
84
|
>
|
|
85
85
|
<img
|
|
86
86
|
className={`${styles.accordionicon} ${styles.saveicon}`}
|
|
87
|
-
src={
|
|
87
|
+
src={iconUnread}
|
|
88
88
|
alt=""
|
|
89
89
|
/>
|
|
90
90
|
<span className="sr-only">Save</span>
|
|
@@ -114,7 +114,7 @@ export const NotificationAccordion = ({ notification, onUpdate }) => {
|
|
|
114
114
|
>
|
|
115
115
|
<img
|
|
116
116
|
className={`${styles.accordionicon} ${styles.unreadicon}`}
|
|
117
|
-
src={
|
|
117
|
+
src={iconSaved}
|
|
118
118
|
alt=""
|
|
119
119
|
/>
|
|
120
120
|
<span className="sr-only">Mark as Unread</span>
|
|
@@ -3,45 +3,15 @@ import { useParams } from "react-router-dom"
|
|
|
3
3
|
import { UserRoutes } from "./user-routes"
|
|
4
4
|
import FourOFourPage from "../errors/404page"
|
|
5
5
|
import { isValidOrcid } from "../utils/validationUtils"
|
|
6
|
-
import { gql, useQuery } from "@apollo/client"
|
|
7
6
|
import { isAdmin } from "../authentication/admin-user"
|
|
8
7
|
import { useCookies } from "react-cookie"
|
|
9
8
|
import { getProfile } from "../authentication/profile"
|
|
10
|
-
|
|
11
|
-
// GraphQL query to fetch user by ORCID
|
|
12
|
-
export const GET_USER_BY_ORCID = gql`
|
|
13
|
-
query User($userId: ID!) {
|
|
14
|
-
user(id: $userId) {
|
|
15
|
-
id
|
|
16
|
-
name
|
|
17
|
-
orcid
|
|
18
|
-
email
|
|
19
|
-
avatar
|
|
20
|
-
location
|
|
21
|
-
institution
|
|
22
|
-
links
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
`
|
|
26
|
-
|
|
27
|
-
export const UPDATE_USER = gql`
|
|
28
|
-
mutation updateUser($id: ID!, $location: String, $links: [String], $institution: String) {
|
|
29
|
-
updateUser(id: $id, location: $location, links: $links, institution: $institution) {
|
|
30
|
-
id
|
|
31
|
-
location
|
|
32
|
-
links
|
|
33
|
-
institution
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
`
|
|
9
|
+
import { useUser } from "../queries/user"
|
|
37
10
|
|
|
38
11
|
export const UserQuery: React.FC = () => {
|
|
39
12
|
const { orcid } = useParams()
|
|
40
13
|
const isOrcidValid = orcid && isValidOrcid(orcid)
|
|
41
|
-
const {
|
|
42
|
-
variables: { userId: orcid },
|
|
43
|
-
skip: !isOrcidValid,
|
|
44
|
-
})
|
|
14
|
+
const { user, loading, error } = useUser()
|
|
45
15
|
|
|
46
16
|
const [cookies] = useCookies()
|
|
47
17
|
const profile = getProfile(cookies)
|
|
@@ -53,16 +23,16 @@ export const UserQuery: React.FC = () => {
|
|
|
53
23
|
|
|
54
24
|
if (loading) return <div>Loading...</div>
|
|
55
25
|
|
|
56
|
-
if (error || !
|
|
26
|
+
if (error || !user || user?.orcid !== orcid) {
|
|
57
27
|
return <FourOFourPage />
|
|
58
28
|
}
|
|
59
29
|
|
|
60
30
|
if (!profile || !profile.sub) {
|
|
61
31
|
return <FourOFourPage />
|
|
62
32
|
}
|
|
63
|
-
|
|
64
33
|
// is admin or profile matches id from the user data being returned
|
|
65
|
-
const
|
|
34
|
+
const isUser = (user?.id === profile?.sub) ? true : false
|
|
35
|
+
const hasEdit = isAdminUser || (user?.id === profile?.sub) ? true : false
|
|
66
36
|
// Render user data with UserRoutes
|
|
67
|
-
return <UserRoutes user={
|
|
37
|
+
return <UserRoutes user={user} hasEdit={hasEdit} isUser={isUser} />
|
|
68
38
|
}
|
|
@@ -14,13 +14,17 @@ import {
|
|
|
14
14
|
|
|
15
15
|
import type { UserRoutesProps } from "../types/user-types"
|
|
16
16
|
|
|
17
|
-
export const UserRoutes: React.FC<UserRoutesProps> = (
|
|
17
|
+
export const UserRoutes: React.FC<UserRoutesProps> = (
|
|
18
|
+
{ user, hasEdit, isUser },
|
|
19
|
+
) => {
|
|
18
20
|
return (
|
|
19
21
|
<Routes>
|
|
20
22
|
<Route path="/*" element={<FourOFourPage />} />
|
|
21
23
|
<Route
|
|
22
24
|
path="*"
|
|
23
|
-
element={
|
|
25
|
+
element={
|
|
26
|
+
<UserAccountContainer user={user} hasEdit={hasEdit} isUser={isUser} />
|
|
27
|
+
}
|
|
24
28
|
>
|
|
25
29
|
<Route
|
|
26
30
|
path=""
|
|
@@ -28,9 +32,7 @@ export const UserRoutes: React.FC<UserRoutesProps> = ({ user, hasEdit }) => {
|
|
|
28
32
|
/>
|
|
29
33
|
<Route
|
|
30
34
|
path="account"
|
|
31
|
-
element={hasEdit
|
|
32
|
-
? <UserAccountView user={user} />
|
|
33
|
-
: <FourOThreePage />}
|
|
35
|
+
element={hasEdit ? <UserAccountView /> : <FourOThreePage />}
|
|
34
36
|
/>
|
|
35
37
|
<Route
|
|
36
38
|
path="notifications/*"
|
|
@@ -4,10 +4,11 @@ import styles from "./scss/usertabs.module.scss"
|
|
|
4
4
|
|
|
5
5
|
export interface UserAccountTabsProps {
|
|
6
6
|
hasEdit: boolean
|
|
7
|
+
isUser?: boolean
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export const UserAccountTabs: React.FC<UserAccountTabsProps> = (
|
|
10
|
-
{ hasEdit },
|
|
11
|
+
{ hasEdit, isUser },
|
|
11
12
|
) => {
|
|
12
13
|
const ulRef = useRef<HTMLUListElement>(null)
|
|
13
14
|
const [activePosition, setActivePosition] = useState<number>(0)
|
|
@@ -46,7 +47,7 @@ export const UserAccountTabs: React.FC<UserAccountTabsProps> = (
|
|
|
46
47
|
className={({ isActive }) => (isActive ? styles.active : "")}
|
|
47
48
|
onClick={handleClick}
|
|
48
49
|
>
|
|
49
|
-
User Datasets
|
|
50
|
+
{isUser ? "My" : "User"} Datasets
|
|
50
51
|
</NavLink>
|
|
51
52
|
</li>
|
|
52
53
|
<li>
|
|
@@ -56,7 +57,7 @@ export const UserAccountTabs: React.FC<UserAccountTabsProps> = (
|
|
|
56
57
|
className={({ isActive }) => (isActive ? styles.active : "")}
|
|
57
58
|
onClick={handleClick}
|
|
58
59
|
>
|
|
59
|
-
|
|
60
|
+
Notifications
|
|
60
61
|
</NavLink>
|
|
61
62
|
</li>
|
|
62
63
|
<li>
|