@openneuro/app 4.35.0 → 4.36.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.
Files changed (51) hide show
  1. package/package.json +3 -3
  2. package/src/client.jsx +1 -0
  3. package/src/scripts/components/button/button.scss +14 -0
  4. package/src/scripts/components/page/page.scss +2 -70
  5. package/src/scripts/components/search-page/SearchResultItem.tsx +3 -1
  6. package/src/scripts/components/search-page/search-page.scss +1 -13
  7. package/src/scripts/config.ts +6 -0
  8. package/src/scripts/dataset/files/__tests__/__snapshots__/file.spec.jsx.snap +2 -2
  9. package/src/scripts/dataset/files/file.tsx +2 -2
  10. package/src/scripts/dataset/mutations/__tests__/update-file.spec.tsx +126 -0
  11. package/src/scripts/dataset/mutations/update-file.jsx +20 -5
  12. package/src/scripts/dataset/routes/snapshot.tsx +1 -1
  13. package/src/scripts/errors/errorRoute.tsx +2 -0
  14. package/src/scripts/pages/admin/__tests__/users.spec.tsx +51 -18
  15. package/src/scripts/pages/admin/admin.jsx +2 -2
  16. package/src/scripts/pages/admin/user-fragment.ts +10 -3
  17. package/src/scripts/pages/admin/user-summary.tsx +100 -0
  18. package/src/scripts/pages/admin/user-tools.tsx +81 -58
  19. package/src/scripts/pages/admin/users.module.scss +277 -0
  20. package/src/scripts/pages/admin/users.tsx +351 -152
  21. package/src/scripts/queries/user.ts +120 -3
  22. package/src/scripts/queries/users.ts +247 -0
  23. package/src/scripts/routes.tsx +7 -15
  24. package/src/scripts/types/user-types.ts +12 -13
  25. package/src/scripts/uploader/file-select.tsx +42 -57
  26. package/src/scripts/uploader/upload-select.jsx +1 -1
  27. package/src/scripts/users/__tests__/dataset-card.spec.tsx +127 -0
  28. package/src/scripts/users/__tests__/user-account-view.spec.tsx +150 -67
  29. package/src/scripts/users/__tests__/user-card.spec.tsx +6 -17
  30. package/src/scripts/users/__tests__/user-query.spec.tsx +133 -38
  31. package/src/scripts/users/__tests__/user-routes.spec.tsx +156 -27
  32. package/src/scripts/users/__tests__/user-tabs.spec.tsx +7 -7
  33. package/src/scripts/users/components/edit-list.tsx +26 -5
  34. package/src/scripts/users/components/edit-string.tsx +40 -13
  35. package/src/scripts/users/components/editable-content.tsx +10 -3
  36. package/src/scripts/users/components/user-dataset-filters.tsx +205 -121
  37. package/src/scripts/users/dataset-card.tsx +3 -2
  38. package/src/scripts/users/github-auth-button.tsx +98 -0
  39. package/src/scripts/users/scss/datasetcard.module.scss +65 -12
  40. package/src/scripts/users/scss/useraccountview.module.scss +1 -1
  41. package/src/scripts/users/user-account-view.tsx +43 -34
  42. package/src/scripts/users/user-card.tsx +23 -22
  43. package/src/scripts/users/user-container.tsx +9 -5
  44. package/src/scripts/users/user-datasets-view.tsx +350 -40
  45. package/src/scripts/users/user-menu.tsx +4 -9
  46. package/src/scripts/users/user-notifications-view.tsx +9 -7
  47. package/src/scripts/users/user-query.tsx +3 -6
  48. package/src/scripts/users/user-routes.tsx +11 -5
  49. package/src/scripts/users/user-tabs.tsx +4 -2
  50. package/src/scripts/users/__tests__/datasest-card.spec.tsx +0 -201
  51. package/src/scripts/users/fragments/query.js +0 -42
@@ -1,79 +1,102 @@
1
1
  import React from "react"
2
- import type { FC, ReactElement } from "react"
3
- import { gql } from "@apollo/client"
4
- import { Mutation } from "@apollo/client/react/components"
2
+ import type { FC } from "react"
3
+ import { useMutation } from "@apollo/client"
5
4
  import { WarnButton } from "../../components/warn-button/WarnButton"
6
5
  import { getProfile } from "../../authentication/profile"
7
6
  import { useCookies } from "react-cookie"
8
- import { USER_FRAGMENT } from "./user-fragment"
7
+ import type { User } from "../../types/user-types"
8
+ import styles from "./users.module.scss"
9
+ import * as Sentry from "@sentry/react"
10
+
11
+ import { SET_ADMIN_MUTATION, SET_BLOCKED_MUTATION } from "../../queries/users"
9
12
 
10
13
  interface UserToolsProps {
11
- user: Record<string, unknown>
12
- refetch: () => Record<string, unknown>
14
+ user: User
15
+ refetchCurrentPage: () => void
13
16
  }
14
17
 
15
- export const SET_ADMIN = gql`
16
- mutation ($id: ID!, $admin: Boolean!) {
17
- setAdmin(id: $id, admin: $admin) {
18
- ...userFields
19
- }
20
- }
21
- ${USER_FRAGMENT}
22
- `
23
-
24
- export const SET_BLOCKED = gql`
25
- mutation ($id: ID!, $blocked: Boolean!) {
26
- setBlocked(id: $id, blocked: $blocked) {
27
- ...userFields
28
- }
29
- }
30
- ${USER_FRAGMENT}
31
- `
32
-
33
- export const UserTools: FC<UserToolsProps> = ({ user, refetch }) => {
18
+ export const UserTools: FC<UserToolsProps> = ({ user, refetchCurrentPage }) => {
34
19
  const [cookies] = useCookies()
35
20
  const adminIcon = user.admin ? "fa-check-square-o" : "fa-square-o"
36
21
  const blacklistIcon = user.blocked ? "fa-check-square-o" : "fa-square-o"
37
22
 
23
+ // --- useMutation for SET_ADMIN ---
24
+ const [setAdmin] = useMutation(SET_ADMIN_MUTATION, {
25
+ update(cache, { data: { setAdmin: updatedUser } }) {
26
+ cache.modify({
27
+ id: cache.identify(updatedUser),
28
+ fields: {
29
+ admin() {
30
+ return updatedUser.admin
31
+ },
32
+ blocked() {
33
+ return updatedUser.blocked
34
+ },
35
+ modified() {
36
+ return updatedUser.modified
37
+ },
38
+ },
39
+ })
40
+ },
41
+ onCompleted: () => {
42
+ refetchCurrentPage()
43
+ },
44
+ onError: (error) => {
45
+ Sentry.captureException(error)
46
+ },
47
+ })
48
+
49
+ // --- useMutation for SET_BLOCKED ---
50
+ const [setBlocked] = useMutation(SET_BLOCKED_MUTATION, {
51
+ update(cache, { data: { setBlocked: updatedUser } }) {
52
+ cache.modify({
53
+ id: cache.identify(updatedUser),
54
+ fields: {
55
+ blocked() {
56
+ return updatedUser.blocked
57
+ },
58
+ admin() {
59
+ return updatedUser.admin
60
+ },
61
+ modified() {
62
+ return updatedUser.modified
63
+ },
64
+ },
65
+ })
66
+ },
67
+ onCompleted: () => {
68
+ refetchCurrentPage()
69
+ },
70
+ onError: (error) => {
71
+ Sentry.captureException(error)
72
+ },
73
+ })
74
+
38
75
  if (user.id !== getProfile(cookies).sub) {
39
76
  return (
40
77
  <div className="dataset-tools-wrap-admin">
41
- <div className="tools clearfix">
78
+ <div className={styles.tools}>
42
79
  <div className="tool">
43
- <Mutation
44
- mutation={SET_ADMIN}
45
- variables={{ id: user.id, admin: !user.admin }}
46
- >
47
- {(setAdmin): ReactElement => (
48
- <WarnButton
49
- message="Admin"
50
- icon={adminIcon}
51
- onConfirmedClick={(): void => {
52
- setAdmin().then(() => {
53
- refetch()
54
- })
55
- }}
56
- />
57
- )}
58
- </Mutation>
80
+ <WarnButton
81
+ message="Admin"
82
+ icon={adminIcon}
83
+ onConfirmedClick={async (): Promise<void> => {
84
+ await setAdmin({
85
+ variables: { id: user.id, admin: !user.admin },
86
+ })
87
+ }}
88
+ />
59
89
  </div>
60
90
  <div className="tool">
61
- <Mutation
62
- mutation={SET_BLOCKED}
63
- variables={{ id: user.id, blocked: !user.blocked }}
64
- >
65
- {(setBlocked): ReactElement => (
66
- <WarnButton
67
- message="Block"
68
- icon={blacklistIcon}
69
- onConfirmedClick={(): void => {
70
- setBlocked().then(() => {
71
- refetch()
72
- })
73
- }}
74
- />
75
- )}
76
- </Mutation>
91
+ <WarnButton
92
+ message="Block"
93
+ icon={blacklistIcon}
94
+ onConfirmedClick={async (): Promise<void> => {
95
+ await setBlocked({
96
+ variables: { id: user.id, blocked: !user.blocked },
97
+ })
98
+ }}
99
+ />
77
100
  </div>
78
101
  </div>
79
102
  </div>
@@ -0,0 +1,277 @@
1
+ .searchInputWrapper {
2
+ display: flex;
3
+ align-items: center;
4
+ position: relative;
5
+ }
6
+
7
+ .clearSearchButton {
8
+ position: absolute;
9
+ right: 5px;
10
+ top: 50%;
11
+ transform: translateY(-50%);
12
+ background: none;
13
+ border: none;
14
+ cursor: pointer;
15
+ font-size: 0.8em;
16
+ color: #888;
17
+
18
+ &:hover {
19
+ color: #333;
20
+ }
21
+ }
22
+
23
+ .searchControl {
24
+ display: flex;
25
+ input[type='text'] {
26
+ padding-right: 25px;
27
+ }
28
+ .searchSubmitButton {
29
+ background-color: var(--current-theme-secondary);
30
+ color: #fff;
31
+ border-radius: var(--border-radius-default);
32
+ font-weight: bold;
33
+ margin: 0 0 0 5px;
34
+ font-size: 14px;
35
+ padding: 10px 15px;
36
+ &:hover {
37
+ background-color: var(--current-theme-primary);
38
+ }
39
+ }
40
+ }
41
+
42
+ .gridContainer {
43
+ width: 100%;
44
+ box-sizing: border-box;
45
+ display: flex;
46
+ flex-direction: column;
47
+ }
48
+
49
+ .gridHead,
50
+ .gridRow {
51
+ display: flex;
52
+ margin: 0;
53
+ width: 100%;
54
+ box-sizing: border-box;
55
+ }
56
+
57
+ .sortButton {
58
+ cursor: pointer;
59
+ }
60
+ .gridHead {
61
+ border-top: 1px solid #ddd;
62
+ border-left: 1px solid #ddd;
63
+ .sortButton,
64
+ .headingCol {
65
+ min-height: 50px;
66
+ padding: 10px;
67
+ background-color: #f0f0f0;
68
+ border: 0;
69
+ border-right: 1px solid #ddd;
70
+ border-bottom: 1px solid #ddd;
71
+ box-sizing: border-box;
72
+ display: flex;
73
+ align-items: center;
74
+ justify-content: flex-start;
75
+ text-align: left;
76
+ overflow: hidden;
77
+ text-overflow: ellipsis;
78
+ white-space: nowrap;
79
+
80
+ &.colSmall {
81
+ flex: 0 0 180px;
82
+ }
83
+ &.colLarge {
84
+ flex: 0 0 300px;
85
+ }
86
+ &.colXLarge {
87
+ flex: 0 0 360px;
88
+ }
89
+ &.colFlex {
90
+ flex: 1;
91
+ min-width: 100px;
92
+ }
93
+ }
94
+ .sortButton {
95
+ &:hover {
96
+ background-color: #cbe4e9;
97
+ }
98
+ &.active {
99
+ background-color: var(--current-theme-primary-light);
100
+ }
101
+ }
102
+ }
103
+
104
+ .gridRow {
105
+ border-left: 1px solid #ddd;
106
+ .gtCell {
107
+ min-height: 50px;
108
+ padding: 10px;
109
+ background-color: #fff;
110
+ border: 0;
111
+ border-bottom: 1px solid #ddd;
112
+ box-sizing: border-box;
113
+ display: flex;
114
+ flex-direction: column;
115
+ align-items: flex-start;
116
+ justify-content: center;
117
+ text-align: left;
118
+ overflow: hidden;
119
+ text-overflow: ellipsis;
120
+ white-space: nowrap;
121
+ &:last-child {
122
+ border-right: 1px solid #ddd;
123
+ }
124
+ &.colSmall {
125
+ flex: 0 0 180px;
126
+ }
127
+ &.colLarge {
128
+ flex: 0 0 300px;
129
+ }
130
+ &.colXLarge {
131
+ flex: 0 0 360px;
132
+ }
133
+ &.colFlex {
134
+ flex: 1;
135
+ min-width: 100px;
136
+ }
137
+ }
138
+ }
139
+
140
+ .gridRow:hover {
141
+ background-color: #f9f9f9;
142
+ }
143
+
144
+ .gridContainer .gridRow:last-child .gtCell {
145
+ border-bottom: 1px solid #ddd;
146
+ }
147
+
148
+ .user-panel-inner {
149
+ display: flex;
150
+ justify-content: space-between;
151
+ }
152
+
153
+ .usersWrap {
154
+ margin: 0;
155
+ padding: 0;
156
+ font-size: 14px;
157
+ .userPanel {
158
+ margin: 0;
159
+ border-bottom: 0;
160
+ list-style: none;
161
+ h3 {
162
+ margin: 0;
163
+ font-size: 14px;
164
+ display: flex;
165
+ align-items: center;
166
+ .badge {
167
+ font-size: 10px;
168
+ width: 25px;
169
+ height: 25px;
170
+ background: blue;
171
+ color: #fff;
172
+ border-radius: 50%;
173
+ line-height: 10px;
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: center;
177
+ margin-left: 5px;
178
+ &.admin {
179
+ background: #127d00;
180
+ }
181
+ &.blocked {
182
+ background: rgb(192, 3, 66);
183
+ }
184
+ }
185
+ [data-tooltip] {
186
+ display: inline;
187
+ font-size: 10px;
188
+ }
189
+ }
190
+
191
+ .tools {
192
+ display: flex;
193
+ flex-direction: column;
194
+ button {
195
+ background-color: transparent;
196
+ border: 0;
197
+ outline: 0;
198
+ font-size: 12px;
199
+ line-height: 14px;
200
+ width: auto;
201
+ height: auto;
202
+ i {
203
+ font-size: 14px;
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+
210
+ .summaryFooter {
211
+ display: flex;
212
+ justify-content: space-between;
213
+ }
214
+
215
+ .loadMoreButton {
216
+ cursor: pointer;
217
+ display: block;
218
+ margin: 20px 0;
219
+ padding: 10px;
220
+ border: 1px solid var(--current-theme-primary);
221
+ width: 100%;
222
+ background-color: #fff;
223
+ transition: background-color 0.3s;
224
+ &:hover {
225
+ text-decoration: none;
226
+ background-color: #eee;
227
+ }
228
+ }
229
+
230
+ .filterControls {
231
+ display: flex;
232
+ align-items: center;
233
+ flex-wrap: wrap;
234
+ margin-left: auto;
235
+ > div {
236
+ flex-basis: 100%;
237
+ font-size: 12px;
238
+ }
239
+ label {
240
+ display: flex;
241
+ align-items: center;
242
+ cursor: pointer;
243
+ font-size: 1rem;
244
+ position: relative;
245
+ font-weight: bold;
246
+ margin: 0 10px 0 0;
247
+
248
+ input[type='checkbox'] {
249
+ -webkit-appearance: none;
250
+ -moz-appearance: none;
251
+ appearance: none;
252
+ border: 0;
253
+ clip: rect(0 0 0 0);
254
+ height: 1px;
255
+ margin: -1px;
256
+ overflow: hidden;
257
+ padding: 0;
258
+ position: absolute;
259
+ white-space: nowrap;
260
+ width: 1px;
261
+ }
262
+
263
+
264
+ i {
265
+ margin-left: 8px;
266
+ font-size: 1.2em;
267
+ color: var(--current-theme-primary);
268
+ transition: color 0.2s ease-in-out;
269
+ }
270
+
271
+
272
+ input[type='checkbox']:focus + i {
273
+ box-shadow: 0 0 0 3px blue;
274
+ outline: none;
275
+ }
276
+ }
277
+ }