@openneuro/app 4.34.2 → 4.35.0-alpha.0
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 +3 -3
- 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/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/snapshot-container.tsx +13 -11
- package/src/scripts/errors/freshdesk-widget.jsx +5 -1
- 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 +0 -9
- package/src/scripts/types/user-types.ts +2 -0
- 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
|
@@ -85,6 +85,16 @@ $on-light-orange: rgb(255, 110, 43);
|
|
|
85
85
|
$primary: $on-dark-aqua;
|
|
86
86
|
|
|
87
87
|
$chiclet-color: #a2059e;
|
|
88
|
+
//Base unit used for spacing gutters
|
|
89
|
+
$base-unit: 20px;
|
|
90
|
+
|
|
91
|
+
//Screen Sizes
|
|
92
|
+
$screen-sm: 480px;
|
|
93
|
+
$screen-md: 767px;
|
|
94
|
+
$screen-lg: 989px;
|
|
95
|
+
|
|
96
|
+
$border-radius-default: 4px;
|
|
97
|
+
|
|
88
98
|
|
|
89
99
|
/* CSS HEX */
|
|
90
100
|
:root {
|
|
@@ -105,6 +115,9 @@ $chiclet-color: #a2059e;
|
|
|
105
115
|
--current-theme-primary-light: #{$on-dark-aqua-light};
|
|
106
116
|
--current-theme-header: #333;
|
|
107
117
|
--current-theme-header-dark: #333;
|
|
118
|
+
--font-sans: #{$font-sans};
|
|
119
|
+
--border-radius-default: #{$border-radius-default};
|
|
120
|
+
--newspaper: #{$newspaper};
|
|
108
121
|
}
|
|
109
122
|
|
|
110
123
|
.mri-theme {
|
|
@@ -139,15 +152,6 @@ $chiclet-color: #a2059e;
|
|
|
139
152
|
background: #f3f3f3;
|
|
140
153
|
}
|
|
141
154
|
|
|
142
|
-
//Base unit used for spacing gutters
|
|
143
|
-
$base-unit: 20px;
|
|
144
|
-
|
|
145
|
-
//Screen Sizes
|
|
146
|
-
$screen-sm: 480px;
|
|
147
|
-
$screen-md: 767px;
|
|
148
|
-
$screen-lg: 989px;
|
|
149
|
-
|
|
150
|
-
$border-radius-default: 4px;
|
|
151
155
|
|
|
152
156
|
.search-page-mri,
|
|
153
157
|
.dataset-page-mri {
|
|
@@ -4,10 +4,6 @@ import { useLocation } from "react-router-dom"
|
|
|
4
4
|
import { SearchPage } from "../components/search-page/SearchPage"
|
|
5
5
|
import { SearchResultsList } from "../components/search-page/SearchResultsList"
|
|
6
6
|
import { NeurobagelSearch } from "../components/search-page/NeurobagelSearch"
|
|
7
|
-
import {
|
|
8
|
-
getUnexpiredProfile,
|
|
9
|
-
hasEditPermissions,
|
|
10
|
-
} from "../authentication/profile"
|
|
11
7
|
import { Button } from "../components/button/Button"
|
|
12
8
|
import { Loading } from "../components/loading/Loading"
|
|
13
9
|
import {
|
|
@@ -36,7 +32,6 @@ import {
|
|
|
36
32
|
} from "./inputs"
|
|
37
33
|
import FiltersBlockContainer from "./filters-block-container"
|
|
38
34
|
import AggregateCountsContainer from "../pages/front-page/aggregate-queries/aggregate-counts-container"
|
|
39
|
-
import { useCookies } from "react-cookie"
|
|
40
35
|
import { useSearchResults } from "./use-search-results"
|
|
41
36
|
import { SearchParamsCtx } from "./search-params-ctx"
|
|
42
37
|
import type { SearchParams } from "./initial-search-params"
|
|
@@ -113,8 +108,6 @@ export const setDefaultSearch = (
|
|
|
113
108
|
}
|
|
114
109
|
|
|
115
110
|
const SearchContainer: FC<SearchContainerProps> = ({ portalContent }) => {
|
|
116
|
-
const [cookies] = useCookies()
|
|
117
|
-
const profile = getUnexpiredProfile(cookies)
|
|
118
111
|
const location = useLocation()
|
|
119
112
|
|
|
120
113
|
const { searchParams, setSearchParams } = useContext(SearchParamsCtx)
|
|
@@ -238,9 +231,7 @@ const SearchContainer: FC<SearchContainerProps> = ({ portalContent }) => {
|
|
|
238
231
|
: (
|
|
239
232
|
<>
|
|
240
233
|
<SearchResultsList
|
|
241
|
-
hasEditPermissions={hasEditPermissions}
|
|
242
234
|
items={resultsList}
|
|
243
|
-
profile={profile}
|
|
244
235
|
datasetTypeSelected={searchParams.datasetType_selected}
|
|
245
236
|
/>
|
|
246
237
|
{/* TODO: make div below into display component. */}
|
|
@@ -13,6 +13,7 @@ export interface User {
|
|
|
13
13
|
export interface UserRoutesProps {
|
|
14
14
|
user: User
|
|
15
15
|
hasEdit: boolean
|
|
16
|
+
isUser: boolean
|
|
16
17
|
}
|
|
17
18
|
export interface UserCardProps {
|
|
18
19
|
user: User
|
|
@@ -69,4 +70,5 @@ export interface UserDatasetsViewProps {
|
|
|
69
70
|
export interface AccountContainerProps {
|
|
70
71
|
user: User
|
|
71
72
|
hasEdit: boolean
|
|
73
|
+
isUser: boolean
|
|
72
74
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { vi } from "vitest"
|
|
1
2
|
import React from "react"
|
|
2
3
|
import { MockedProvider } from "@apollo/client/testing"
|
|
3
4
|
import {
|
|
@@ -8,24 +9,38 @@ import {
|
|
|
8
9
|
within,
|
|
9
10
|
} from "@testing-library/react"
|
|
10
11
|
import { UserAccountView } from "../user-account-view"
|
|
11
|
-
import {
|
|
12
|
+
import type { User } from "../../types/user-types"
|
|
13
|
+
import * as userQueries from "../../queries/user"
|
|
12
14
|
|
|
13
|
-
const baseUser = {
|
|
15
|
+
const baseUser: User = {
|
|
14
16
|
id: "1",
|
|
15
17
|
name: "John Doe",
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
location: "Unknown",
|
|
19
|
+
github: "",
|
|
20
|
+
institution: "Unknown Institution",
|
|
21
|
+
email: "john.doe@example.com",
|
|
22
|
+
avatar: "https://dummyimage.com/200x200/000/fff",
|
|
23
|
+
orcid: "0000-0000-0000-0000",
|
|
24
|
+
links: [],
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
vi.mock("../../queries/user", async () => {
|
|
28
|
+
const actual = await vi.importActual("../../queries/user")
|
|
29
|
+
return {
|
|
30
|
+
...actual,
|
|
31
|
+
useUser: () => ({
|
|
32
|
+
user: baseUser,
|
|
33
|
+
loading: false,
|
|
34
|
+
error: undefined,
|
|
35
|
+
}),
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
24
39
|
const mocks = [
|
|
25
40
|
{
|
|
26
41
|
request: {
|
|
27
|
-
query:
|
|
28
|
-
variables: {
|
|
42
|
+
query: userQueries.GET_USER,
|
|
43
|
+
variables: { id: baseUser.orcid },
|
|
29
44
|
},
|
|
30
45
|
result: {
|
|
31
46
|
data: {
|
|
@@ -35,9 +50,9 @@ const mocks = [
|
|
|
35
50
|
},
|
|
36
51
|
{
|
|
37
52
|
request: {
|
|
38
|
-
query: UPDATE_USER,
|
|
53
|
+
query: userQueries.UPDATE_USER,
|
|
39
54
|
variables: {
|
|
40
|
-
id: baseUser.
|
|
55
|
+
id: baseUser.orcid,
|
|
41
56
|
location: "Marin, CA",
|
|
42
57
|
links: ["https://newlink.com"],
|
|
43
58
|
institution: "New University",
|
|
@@ -46,7 +61,7 @@ const mocks = [
|
|
|
46
61
|
result: {
|
|
47
62
|
data: {
|
|
48
63
|
updateUser: {
|
|
49
|
-
id: baseUser.
|
|
64
|
+
id: baseUser.orcid,
|
|
50
65
|
location: "Marin, CA",
|
|
51
66
|
links: ["https://newlink.com"],
|
|
52
67
|
institution: "New University",
|
|
@@ -60,23 +75,22 @@ describe("<UserAccountView />", () => {
|
|
|
60
75
|
it("should render the user details correctly", () => {
|
|
61
76
|
render(
|
|
62
77
|
<MockedProvider mocks={mocks} addTypename={false}>
|
|
63
|
-
<UserAccountView
|
|
78
|
+
<UserAccountView />
|
|
64
79
|
</MockedProvider>,
|
|
65
80
|
)
|
|
66
81
|
expect(screen.getByText("Name:")).toBeInTheDocument()
|
|
67
82
|
expect(screen.getByText("John Doe")).toBeInTheDocument()
|
|
68
83
|
expect(screen.getByText("Email:")).toBeInTheDocument()
|
|
69
|
-
expect(screen.getByText("
|
|
84
|
+
expect(screen.getByText("john.doe@example.com")).toBeInTheDocument()
|
|
70
85
|
expect(screen.getByText("ORCID:")).toBeInTheDocument()
|
|
71
|
-
expect(screen.getByText("0000-
|
|
72
|
-
expect(screen.getByText("GitHub
|
|
73
|
-
expect(screen.getByText("johndoe")).toBeInTheDocument()
|
|
86
|
+
expect(screen.getByText("0000-0000-0000-0000")).toBeInTheDocument()
|
|
87
|
+
expect(screen.getByText("Connect your GitHub")).toBeInTheDocument()
|
|
74
88
|
})
|
|
75
89
|
|
|
76
90
|
it("should render location with EditableContent", async () => {
|
|
77
91
|
render(
|
|
78
92
|
<MockedProvider mocks={mocks} addTypename={false}>
|
|
79
|
-
<UserAccountView
|
|
93
|
+
<UserAccountView />
|
|
80
94
|
</MockedProvider>,
|
|
81
95
|
)
|
|
82
96
|
const locationSection = within(screen.getByTestId("location-section"))
|
|
@@ -95,7 +109,7 @@ describe("<UserAccountView />", () => {
|
|
|
95
109
|
it("should render institution with EditableContent", async () => {
|
|
96
110
|
render(
|
|
97
111
|
<MockedProvider mocks={mocks} addTypename={false}>
|
|
98
|
-
<UserAccountView
|
|
112
|
+
<UserAccountView />
|
|
99
113
|
</MockedProvider>,
|
|
100
114
|
)
|
|
101
115
|
const institutionSection = within(screen.getByTestId("institution-section"))
|
|
@@ -114,7 +128,7 @@ describe("<UserAccountView />", () => {
|
|
|
114
128
|
it("should render links with EditableContent and validation", async () => {
|
|
115
129
|
render(
|
|
116
130
|
<MockedProvider mocks={mocks} addTypename={false}>
|
|
117
|
-
<UserAccountView
|
|
131
|
+
<UserAccountView />
|
|
118
132
|
</MockedProvider>,
|
|
119
133
|
)
|
|
120
134
|
const linksSection = within(screen.getByTestId("links-section"))
|
|
@@ -133,7 +147,7 @@ describe("<UserAccountView />", () => {
|
|
|
133
147
|
it("should show an error message when invalid URL is entered in links section", async () => {
|
|
134
148
|
render(
|
|
135
149
|
<MockedProvider mocks={mocks} addTypename={false}>
|
|
136
|
-
<UserAccountView
|
|
150
|
+
<UserAccountView />
|
|
137
151
|
</MockedProvider>,
|
|
138
152
|
)
|
|
139
153
|
const linksSection = within(screen.getByTestId("links-section"))
|
|
@@ -3,7 +3,7 @@ import { render, screen, waitFor } from "@testing-library/react"
|
|
|
3
3
|
import { MockedProvider } from "@apollo/client/testing"
|
|
4
4
|
import { MemoryRouter, Route, Routes } from "react-router-dom"
|
|
5
5
|
import { UserQuery } from "../user-query"
|
|
6
|
-
import {
|
|
6
|
+
import { GET_USER } from "../../queries/user"
|
|
7
7
|
import * as ProfileUtils from "../../authentication/profile"
|
|
8
8
|
|
|
9
9
|
Object.defineProperty(ProfileUtils, "getProfile", {
|
|
@@ -15,8 +15,8 @@ const validOrcid = "0009-0001-9689-7232"
|
|
|
15
15
|
|
|
16
16
|
const userMock = {
|
|
17
17
|
request: {
|
|
18
|
-
query:
|
|
19
|
-
variables: { userId:
|
|
18
|
+
query: GET_USER,
|
|
19
|
+
variables: { userId: "1" },
|
|
20
20
|
},
|
|
21
21
|
result: {
|
|
22
22
|
data: {
|
|
@@ -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 />
|