@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,124 +1,121 @@
1
- import React, { useState } from "react"
2
- import { Query } from "@apollo/client/react/components"
3
- import { gql } from "@apollo/client"
4
- import parseISO from "date-fns/parseISO"
5
- import formatDistanceToNow from "date-fns/formatDistanceToNow"
6
- import { Input } from "../../components/input/Input"
1
+ // packages/openneuro-app/src/scripts/pages/admin/users.tsx
2
+
3
+ import React, { useCallback, useEffect, useState } from "react"
7
4
  import { Loading } from "../../components/loading/Loading"
8
- import { formatDate } from "../../utils/date.js"
9
5
  import Helmet from "react-helmet"
10
6
  import { pageTitle } from "../../resources/strings.js"
11
- import { UserTools } from "./user-tools.js"
12
- import { USER_FRAGMENT } from "./user-fragment.js"
13
-
14
- export const GET_USERS = gql`
15
- query {
16
- users {
17
- ...userFields
18
- }
19
- }
20
- ${USER_FRAGMENT}
21
- `
22
-
23
- // TODO - Use the GraphQL type
24
- export interface User {
25
- id: string
26
- name: string
27
- admin: boolean
28
- blocked: boolean
29
- email?: string
30
- provider: string
31
- lastSeen?: string
32
- created: string
7
+ import type { User } from "../../types/user-types"
8
+ import styles from "./users.module.scss"
9
+ import { useUsers } from "../../queries/users"
10
+ import UserSummary from "./user-summary"
11
+ import * as Sentry from "@sentry/react"
12
+
13
+ const SORT_ASC_ICON = "fa fa-sort-asc"
14
+ const SORT_DESC_ICON = "fa fa-sort-desc"
15
+
16
+ const noResults = (loading: boolean) =>
17
+ loading ? <Loading /> : <h3>No Results Found</h3>
18
+
19
+ interface SortConfig {
20
+ field: string | null
21
+ order: "ascending" | "descending"
33
22
  }
34
23
 
35
- interface UsersQueryResultProps {
24
+ interface UsersProps {
25
+ users: User[]
26
+ refetchCurrentPage: () => void
36
27
  loading: boolean
37
- data: { users: User[] }
38
- refetch: () => void
28
+ onSortChange: (
29
+ field: string | null,
30
+ order: "ascending" | "descending",
31
+ ) => void
32
+ sortConfig: SortConfig
33
+ onFilterChange: (
34
+ filterType: "admin" | "blocked" | null,
35
+ value: boolean | null,
36
+ ) => void
37
+ filters: { admin: boolean | null; blocked: boolean | null }
38
+ onSearchChange: (searchValue: string | undefined) => void
39
+ currentSearchTerm: string | undefined
40
+ loadMore: () => void
41
+ hasMore: boolean
42
+ totalCount: number
39
43
  }
40
44
 
41
- export const UsersQueryResult = (
42
- { loading, data, refetch }: UsersQueryResultProps,
43
- ) => {
44
- if (loading) {
45
- return <Loading />
46
- } else {
47
- return (
48
- <Users loading={loading} users={data.users || []} refetch={refetch} />
49
- )
50
- }
51
- }
45
+ const Users = ({
46
+ users,
47
+ refetchCurrentPage,
48
+ loading,
49
+ onSortChange,
50
+ sortConfig,
51
+ onFilterChange,
52
+ filters,
53
+ onSearchChange,
54
+ currentSearchTerm,
55
+ loadMore,
56
+ hasMore,
57
+ totalCount,
58
+ }: UsersProps) => {
59
+ const hasUsers = users && users.length > 0
60
+ const [searchTerm, setSearchTerm] = useState<string | undefined>(
61
+ currentSearchTerm,
62
+ )
52
63
 
53
- export const UsersQuery = () => (
54
- <Query query={GET_USERS}>{UsersQueryResult}</Query>
55
- )
64
+ useEffect(() => {
65
+ setSearchTerm(currentSearchTerm)
66
+ }, [currentSearchTerm])
56
67
 
57
- const userSummary = (user) => {
58
- const lastLogin = user.lastlogin ? user.lastlogin : user.created
59
- const created = user.created
60
- return (
61
- <>
62
- <div className="summary-data">
63
- <b>Signed Up:</b>{" "}
64
- <div>
65
- {formatDate(created)} - {formatDistanceToNow(parseISO(created))} ago
66
- </div>
67
- </div>
68
- <div className="summary-data">
69
- <b>Last Signed In:</b>{" "}
70
- <div>
71
- {formatDate(lastLogin)} - {formatDistanceToNow(parseISO(lastLogin))}
72
- {" "}
73
- ago
74
- </div>
75
- </div>
76
- </>
68
+ const handleAdminFilterCheckboxChange = useCallback(
69
+ (e: React.ChangeEvent<HTMLInputElement>) => {
70
+ onFilterChange("admin", e.target.checked)
71
+ },
72
+ [onFilterChange],
77
73
  )
78
- }
79
74
 
80
- const noResults = (loading) => {
81
- return loading ? <Loading /> : <h4>No Results Found</h4>
82
- }
75
+ const handleBlockedFilterCheckboxChange = useCallback(
76
+ (e: React.ChangeEvent<HTMLInputElement>) => {
77
+ onFilterChange("blocked", e.target.checked)
78
+ },
79
+ [onFilterChange],
80
+ )
83
81
 
84
- const Users = ({ users, refetch, loading }) => {
85
- const [stringFilter, setStringFilter] = useState(null)
86
- const [adminFilter, setAdminFilter] = useState(false)
87
- const [blacklistFilter, setBlacklistFilter] = useState(false)
88
-
89
- const filteredUsers = users
90
- .filter((user) => !adminFilter || user.admin)
91
- .filter(
92
- (user) =>
93
- !stringFilter ||
94
- user.email?.toLowerCase().includes(stringFilter.toLowerCase()) ||
95
- user.name?.toLowerCase().includes(stringFilter.toLowerCase()),
96
- )
97
- .map((user, index) => {
98
- const adminBadge = user.admin ? "Admin" : null
99
- const userEmail = Object.hasOwn(user, "email") ? user.email : user.id
100
- return (
101
- <div className="fade-in user-panel panel panel-default" key={index}>
102
- <div className="user-col uc-name">
103
- <div>
104
- {user.name}{" "}
105
- {adminBadge && <span className="badge">{adminBadge}</span>}
106
- <UserTools user={user} refetch={refetch} />
107
- </div>
108
- </div>
109
- <div className="user-col user-panel-inner">
110
- <div className=" user-col uc-email">
111
- {userEmail}
112
- <div className=" uc-provider">
113
- <b>Provider:</b> {user.provider}
114
- </div>
115
- </div>
82
+ const handleInputChange = useCallback(
83
+ (e: React.ChangeEvent<HTMLInputElement>) => {
84
+ setSearchTerm(e.target.value || undefined)
85
+ },
86
+ [setSearchTerm],
87
+ )
116
88
 
117
- <div className=" user-col uc-summary">{userSummary(user)}</div>
118
- </div>
119
- </div>
120
- )
121
- })
89
+ const handleSearchSubmit = useCallback(() => {
90
+ onSearchChange(searchTerm)
91
+ }, [onSearchChange, searchTerm])
92
+
93
+ const handleClearSearch = useCallback(() => {
94
+ setSearchTerm(undefined)
95
+ onSearchChange(undefined)
96
+ }, [onSearchChange, setSearchTerm])
97
+
98
+ const handleSortButtonClick = useCallback(
99
+ (field: string) => {
100
+ let newOrder: "ascending" | "descending" = "ascending"
101
+
102
+ if (sortConfig.field === field) {
103
+ newOrder = sortConfig.order === "ascending" ? "descending" : "ascending"
104
+ } else {
105
+ if (field === "name" || field === "email" || field === "orcid") {
106
+ newOrder = "ascending"
107
+ } else if (
108
+ field === "created" ||
109
+ field === "lastSeen" ||
110
+ field === "modified"
111
+ ) {
112
+ newOrder = "descending"
113
+ }
114
+ }
115
+ onSortChange(field, newOrder)
116
+ },
117
+ [onSortChange, sortConfig],
118
+ )
122
119
 
123
120
  return (
124
121
  <>
@@ -128,58 +125,260 @@ const Users = ({ users, refetch, loading }) => {
128
125
  <div className="admin-users">
129
126
  <div className="header-wrap ">
130
127
  <h2>Current Users</h2>
131
-
132
- <Input
133
- name="Search Name Or Email"
134
- type="text"
135
- placeholder="Search Name or Email"
136
- onKeyDown={(e) => setStringFilter(e.target.value)}
137
- setValue={(_) => {}}
138
- />
139
- </div>
140
-
141
- <div className="filters-sort-wrap ">
142
- <span>
143
- <div className="filters">
144
- <label>Filter By:</label>
145
- <button
146
- className={adminFilter ? "active" : null}
147
- onClick={() => setAdminFilter(!adminFilter)}
148
- >
149
- <span className="filter-admin">
150
- <i
151
- className={adminFilter
152
- ? "fa fa-check-square-o"
153
- : "fa fa-square-o"}
154
- />{" "}
155
- Admin
156
- </span>
157
- </button>
158
- <button
159
- className={blacklistFilter ? "active" : null}
160
- onClick={() => setBlacklistFilter(!blacklistFilter)}
161
- >
162
- <span className="filter-admin">
163
- <i
164
- className={blacklistFilter
165
- ? "fa fa-check-square-o"
166
- : "fa fa-square-o"}
167
- />{" "}
168
- Blocked
169
- </span>
170
- </button>
128
+ <div className={styles.filterControls}>
129
+ <div>Filter:</div>
130
+ <label>
131
+ Admin:
132
+ <input
133
+ type="checkbox"
134
+ checked={filters.admin === true}
135
+ onChange={handleAdminFilterCheckboxChange}
136
+ />
137
+ {filters.admin === true
138
+ ? <i className="fa fa-check-square-o"></i>
139
+ : <i className="fa fa-square-o"></i>}
140
+ </label>
141
+ <label>
142
+ Blocked:
143
+ <input
144
+ type="checkbox"
145
+ checked={filters.blocked === true}
146
+ onChange={handleBlockedFilterCheckboxChange}
147
+ />
148
+ {filters.blocked === true
149
+ ? <i className="fa fa-check-square-o"></i>
150
+ : <i className="fa fa-square-o"></i>}
151
+ </label>
152
+ </div>
153
+ <div className={styles.searchControl}>
154
+ <div className={styles.searchInputWrapper}>
155
+ <input
156
+ type="text"
157
+ placeholder="Search name or email"
158
+ value={searchTerm || ""}
159
+ onChange={handleInputChange}
160
+ />
161
+ {searchTerm && (
162
+ <button
163
+ className={styles.clearSearchButton}
164
+ onClick={handleClearSearch}
165
+ >
166
+ &#x2715;
167
+ </button>
168
+ )}
171
169
  </div>
172
- </span>
170
+ <button
171
+ className={styles.searchSubmitButton}
172
+ onClick={handleSearchSubmit}
173
+ >
174
+ Search
175
+ </button>
176
+ </div>
173
177
  </div>
174
178
 
175
- <div>
176
- <div className="users-panel-wrap">
177
- {filteredUsers.length ? filteredUsers : noResults(loading)}
179
+ <div className={styles.gridContainer}>
180
+ <div className={styles.gridHead}>
181
+ <button
182
+ className={`${styles.sortButton} ${styles.colLarge} ${
183
+ sortConfig.field === "name" ? styles.active : ""
184
+ }`}
185
+ onClick={() => handleSortButtonClick("name")}
186
+ >
187
+ Name {sortConfig.field === "name" && (
188
+ <i
189
+ className={sortConfig.order === "ascending"
190
+ ? SORT_ASC_ICON
191
+ : SORT_DESC_ICON}
192
+ >
193
+ </i>
194
+ )}
195
+ </button>
196
+ <button
197
+ className={`${styles.sortButton} ${styles.colSmall} ${
198
+ sortConfig.field === "email" ? styles.active : ""
199
+ }`}
200
+ onClick={() => handleSortButtonClick("email")}
201
+ >
202
+ Email {sortConfig.field === "email" && (
203
+ <i
204
+ className={sortConfig.order === "ascending"
205
+ ? SORT_ASC_ICON
206
+ : SORT_DESC_ICON}
207
+ >
208
+ </i>
209
+ )}
210
+ </button>
211
+ <button
212
+ className={`${styles.sortButton} ${styles.colSmall} ${
213
+ sortConfig.field === "orcid" ? styles.active : ""
214
+ }`}
215
+ onClick={() => handleSortButtonClick("orcid")}
216
+ >
217
+ ORCID {sortConfig.field === "orcid" && (
218
+ <i
219
+ className={sortConfig.order === "ascending"
220
+ ? SORT_ASC_ICON
221
+ : SORT_DESC_ICON}
222
+ >
223
+ </i>
224
+ )}
225
+ </button>
226
+ <button
227
+ className={`${styles.sortButton} ${styles.colSmall} ${
228
+ sortConfig.field === "created" ? styles.active : ""
229
+ }`}
230
+ onClick={() => handleSortButtonClick("created")}
231
+ >
232
+ Created {sortConfig.field === "created" && (
233
+ <i
234
+ className={sortConfig.order === "ascending"
235
+ ? SORT_ASC_ICON
236
+ : SORT_DESC_ICON}
237
+ >
238
+ </i>
239
+ )}
240
+ </button>
241
+ <button
242
+ className={`${styles.sortButton} ${styles.colSmall} ${
243
+ sortConfig.field === "lastSeen" ? styles.active : ""
244
+ }`}
245
+ onClick={() => handleSortButtonClick("lastSeen")}
246
+ >
247
+ Login {sortConfig.field === "lastSeen" && (
248
+ <i
249
+ className={sortConfig.order === "ascending"
250
+ ? SORT_ASC_ICON
251
+ : SORT_DESC_ICON}
252
+ >
253
+ </i>
254
+ )}
255
+ </button>
256
+ <button
257
+ className={`${styles.sortButton} ${styles.colSmall} ${
258
+ sortConfig.field === "modified" ? styles.active : ""
259
+ }`}
260
+ onClick={() => handleSortButtonClick("modified")}
261
+ >
262
+ Modified {sortConfig.field === "modified" && (
263
+ <i
264
+ className={sortConfig.order === "ascending"
265
+ ? SORT_ASC_ICON
266
+ : SORT_DESC_ICON}
267
+ >
268
+ </i>
269
+ )}
270
+ </button>
271
+ <span className={`${styles.headingCol} ${styles.colFlex}`}>
272
+ Actions
273
+ </span>
178
274
  </div>
275
+ <ul className={styles.usersWrap}>
276
+ {hasUsers
277
+ ? users.map((user, index) => (
278
+ <li
279
+ className={styles.userPanel + " panel panel-default fade-in"}
280
+ key={user.id || index}
281
+ >
282
+ <UserSummary
283
+ user={user}
284
+ refetchCurrentPage={refetchCurrentPage}
285
+ />
286
+ </li>
287
+ ))
288
+ : noResults(loading)}
289
+ </ul>
290
+ </div>
291
+
292
+ <div className={styles.loadMoreContainer}>
293
+ {loading && users.length > 0 && <p>Loading more users...</p>}
294
+ {!loading && hasMore && (
295
+ <button onClick={loadMore} className={styles.loadMoreButton}>
296
+ Load More ({users.length} of {totalCount})
297
+ </button>
298
+ )}
299
+ {!hasMore && users.length > 0 && !loading && (
300
+ <p>All {totalCount} users loaded.</p>
301
+ )}
179
302
  </div>
180
303
  </div>
181
304
  </>
182
305
  )
183
306
  }
184
307
 
185
- export default UsersQuery
308
+ export const UsersPage = () => {
309
+ const [sortConfig, setSortConfig] = useState<{
310
+ field: string | null
311
+ order: "ascending" | "descending"
312
+ }>({ field: "name", order: "ascending" })
313
+ const [filters, setFilters] = useState<{
314
+ admin: boolean | null
315
+ blocked: boolean | null
316
+ }>({ admin: null, blocked: null })
317
+ const [search, setSearch] = useState<string | undefined>(undefined)
318
+
319
+ const {
320
+ users,
321
+ loading,
322
+ error,
323
+ refetchCurrentPage,
324
+ loadMore,
325
+ hasMore,
326
+ totalCount,
327
+ } = useUsers({
328
+ orderBy: sortConfig,
329
+ isAdmin: filters.admin,
330
+ isBlocked: filters.blocked,
331
+ search: search,
332
+ initialLimit: 100,
333
+ })
334
+
335
+ const handleSortChange = useCallback(
336
+ (field: string | null, order: "ascending" | "descending") => {
337
+ setSortConfig({ field, order })
338
+ },
339
+ [setSortConfig],
340
+ )
341
+
342
+ const handleFilterChange = useCallback(
343
+ (filterType: "admin" | "blocked" | null, value: boolean) => {
344
+ setFilters((prevFilters) => ({
345
+ ...prevFilters,
346
+ [filterType]: value ? true : null,
347
+ }))
348
+ },
349
+ [setFilters],
350
+ )
351
+
352
+ const handleSearchChange = useCallback(
353
+ (searchValue: string | undefined) => {
354
+ setSearch(searchValue)
355
+ },
356
+ [setSearch],
357
+ )
358
+
359
+ if (loading && users.length === 0) {
360
+ return <Loading />
361
+ }
362
+
363
+ if (error) {
364
+ Sentry.captureException(error)
365
+ return <p>Error loading users...</p>
366
+ }
367
+
368
+ return (
369
+ <Users
370
+ users={users || []}
371
+ refetchCurrentPage={refetchCurrentPage}
372
+ loading={loading}
373
+ onSortChange={handleSortChange}
374
+ sortConfig={sortConfig}
375
+ onFilterChange={handleFilterChange}
376
+ filters={filters}
377
+ onSearchChange={handleSearchChange}
378
+ currentSearchTerm={search}
379
+ loadMore={loadMore}
380
+ hasMore={hasMore}
381
+ totalCount={totalCount}
382
+ />
383
+ )
384
+ }
@@ -20,6 +20,8 @@ export const GET_USER = gql`
20
20
  created
21
21
  lastSeen
22
22
  blocked
23
+ githubSynced
24
+ github
23
25
  }
24
26
  }
25
27
  `
@@ -45,17 +47,132 @@ export const UPDATE_USER = gql`
45
47
  }
46
48
  `
47
49
 
50
+ export const ADVANCED_SEARCH_DATASETS_QUERY = gql`
51
+ query advancedSearchDatasets(
52
+ $query: JSON!
53
+ $cursor: String
54
+ $allDatasets: Boolean
55
+ $datasetStatus: String
56
+ $sortBy: JSON
57
+ $first: Int!
58
+ ) {
59
+ datasets: advancedSearch(
60
+ query: $query
61
+ allDatasets: $allDatasets
62
+ datasetStatus: $datasetStatus
63
+ sortBy: $sortBy
64
+ first: $first
65
+ after: $cursor
66
+ ) {
67
+ edges {
68
+ id
69
+ node {
70
+ id
71
+ created
72
+ name
73
+ uploader {
74
+ id
75
+ name
76
+ orcid
77
+ }
78
+ public
79
+ permissions {
80
+ id
81
+ userPermissions {
82
+ userId
83
+ level
84
+ access: level
85
+ user {
86
+ id
87
+ name
88
+ email
89
+ provider
90
+ }
91
+ }
92
+ }
93
+ metadata {
94
+ ages
95
+ }
96
+ latestSnapshot {
97
+ size
98
+ summary {
99
+ modalities
100
+ secondaryModalities
101
+ sessions
102
+ subjects
103
+ subjectMetadata {
104
+ participantId
105
+ age
106
+ sex
107
+ group
108
+ }
109
+ tasks
110
+ size
111
+ totalFiles
112
+ dataProcessed
113
+ pet {
114
+ BodyPart
115
+ ScannerManufacturer
116
+ ScannerManufacturersModelName
117
+ TracerName
118
+ TracerRadionuclide
119
+ }
120
+ }
121
+ issues {
122
+ severity
123
+ }
124
+ validation {
125
+ errors
126
+ warnings
127
+ }
128
+ description {
129
+ Name
130
+ Authors
131
+ }
132
+ }
133
+ analytics {
134
+ views
135
+ downloads
136
+ }
137
+ stars {
138
+ userId
139
+ datasetId
140
+ }
141
+ followers {
142
+ userId
143
+ datasetId
144
+ }
145
+ snapshots {
146
+ id
147
+ created
148
+ tag
149
+ }
150
+ }
151
+ }
152
+ pageInfo {
153
+ startCursor
154
+ endCursor
155
+ hasPreviousPage
156
+ hasNextPage
157
+ count
158
+ }
159
+ }
160
+ }
161
+ `
162
+
48
163
  // Reusable hook to fetch user data
49
- export const useUser = () => {
164
+ export const useUser = (userId?: string) => {
50
165
  const [cookies] = useCookies()
51
166
  const profile = getProfile(cookies)
52
167
  const profileSub = profile?.sub
53
168
 
169
+ const finalUserId = userId || profileSub
170
+
54
171
  const { data: userData, loading: userLoading, error: userError } = useQuery(
55
172
  GET_USER,
56
173
  {
57
- variables: { userId: profileSub },
58
- skip: !profileSub,
174
+ variables: { userId: finalUserId },
175
+ skip: !finalUserId,
59
176
  },
60
177
  )
61
178