@sanity/dashboard 3.1.6 → 4.1.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/LICENSE +1 -1
- package/README.md +42 -55
- package/lib/index.d.mts +65 -0
- package/lib/index.esm.js +460 -645
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +449 -654
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +568 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +43 -39
- package/src/components/DashboardWidgetContainer.tsx +2 -2
- package/src/components/NotFoundWidget.tsx +1 -1
- package/src/components/WidgetGroup.tsx +11 -13
- package/src/containers/WidgetContainer.tsx +1 -1
- package/src/versionedClient.ts +1 -1
- package/src/widgets/projectInfo/ProjectInfo.tsx +33 -45
- package/src/widgets/projectInfo/index.ts +1 -1
- package/src/widgets/projectInfo/types.ts +28 -0
- package/src/widgets/projectUsers/ProjectUser.tsx +45 -0
- package/src/widgets/projectUsers/ProjectUsers.tsx +28 -49
- package/src/widgets/sanityTutorials/Tutorial.tsx +1 -1
- package/src/widgets/sanityTutorials/dataAdapter.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/dashboard",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Tool for rendering dashboard widgets",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -22,19 +22,18 @@
|
|
|
22
22
|
},
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"author": "Sanity.io <hello@sanity.io>",
|
|
25
|
+
"sideEffects": false,
|
|
25
26
|
"exports": {
|
|
26
27
|
".": {
|
|
27
|
-
"types": "./lib/index.d.ts",
|
|
28
28
|
"source": "./src/index.ts",
|
|
29
|
-
"import": "./lib/index.
|
|
29
|
+
"import": "./lib/index.mjs",
|
|
30
30
|
"require": "./lib/index.js",
|
|
31
|
-
"default": "./lib/index.
|
|
31
|
+
"default": "./lib/index.mjs"
|
|
32
32
|
},
|
|
33
33
|
"./package.json": "./package.json"
|
|
34
34
|
},
|
|
35
35
|
"main": "./lib/index.js",
|
|
36
36
|
"module": "./lib/index.esm.js",
|
|
37
|
-
"source": "./src/index.ts",
|
|
38
37
|
"types": "./lib/index.d.ts",
|
|
39
38
|
"files": [
|
|
40
39
|
"lib",
|
|
@@ -44,60 +43,65 @@
|
|
|
44
43
|
],
|
|
45
44
|
"scripts": {
|
|
46
45
|
"prebuild": "npm run clean && plugin-kit verify-package --silent && pkg-utils",
|
|
47
|
-
"build": "
|
|
46
|
+
"build": "plugin-kit verify-package --silent && pkg-utils build --strict --check --clean",
|
|
48
47
|
"clean": "rimraf lib",
|
|
49
48
|
"compile": "tsc --noEmit",
|
|
49
|
+
"dev": "sanity dev",
|
|
50
50
|
"format": "prettier --write --cache --ignore-unknown .",
|
|
51
51
|
"link-watch": "plugin-kit link-watch",
|
|
52
52
|
"lint": "eslint .",
|
|
53
53
|
"prepare": "husky install",
|
|
54
|
-
"prepublishOnly": "run
|
|
54
|
+
"prepublishOnly": "npm run build",
|
|
55
55
|
"watch": "pkg-utils watch --strict"
|
|
56
56
|
},
|
|
57
|
+
"browserslist": "extends @sanity/browserslist-config",
|
|
57
58
|
"dependencies": {
|
|
58
|
-
"@sanity/icons": "^
|
|
59
|
-
"@sanity/image-url": "^1.0.
|
|
59
|
+
"@sanity/icons": "^3.3.0",
|
|
60
|
+
"@sanity/image-url": "^1.0.2",
|
|
60
61
|
"@sanity/incompatible-plugin": "^1.0.4",
|
|
61
|
-
"@sanity/ui": "^
|
|
62
|
+
"@sanity/ui": "^2.6.8",
|
|
62
63
|
"lodash": "^4.17.21",
|
|
63
|
-
"rxjs": "^7.
|
|
64
|
+
"rxjs": "^7.8.1"
|
|
64
65
|
},
|
|
65
66
|
"devDependencies": {
|
|
66
|
-
"@commitlint/cli": "^
|
|
67
|
-
"@commitlint/config-conventional": "^
|
|
68
|
-
"@sanity/pkg-utils": "^
|
|
69
|
-
"@sanity/plugin-kit": "^
|
|
70
|
-
"@sanity/semantic-release-preset": "^
|
|
71
|
-
"@types/react": "^18.
|
|
72
|
-
"@
|
|
73
|
-
"@typescript-eslint/
|
|
74
|
-
"
|
|
75
|
-
"eslint": "^
|
|
76
|
-
"eslint-config-prettier": "^8.10.0",
|
|
67
|
+
"@commitlint/cli": "^19.5.0",
|
|
68
|
+
"@commitlint/config-conventional": "^19.5.0",
|
|
69
|
+
"@sanity/pkg-utils": "^6.11.2",
|
|
70
|
+
"@sanity/plugin-kit": "^4.0.18",
|
|
71
|
+
"@sanity/semantic-release-preset": "^5.0.0",
|
|
72
|
+
"@types/react": "^18.3.10",
|
|
73
|
+
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
74
|
+
"@typescript-eslint/parser": "^7.18.0",
|
|
75
|
+
"eslint": "^8.57.1",
|
|
76
|
+
"eslint-config-prettier": "^9.1.0",
|
|
77
77
|
"eslint-config-sanity": "^6.0.0",
|
|
78
|
-
"eslint-plugin-prettier": "^
|
|
79
|
-
"eslint-plugin-react": "^7.
|
|
80
|
-
"eslint-plugin-react-hooks": "^4.6.
|
|
81
|
-
"husky": "^8.0.
|
|
82
|
-
"lint-staged": "^13.0
|
|
83
|
-
"npm-run-
|
|
84
|
-
"prettier": "^
|
|
85
|
-
"prettier-plugin-packagejson": "^2.
|
|
86
|
-
"
|
|
87
|
-
"react
|
|
88
|
-
"react-
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
78
|
+
"eslint-plugin-prettier": "^5.2.1",
|
|
79
|
+
"eslint-plugin-react": "^7.37.1",
|
|
80
|
+
"eslint-plugin-react-hooks": "^4.6.2",
|
|
81
|
+
"husky": "^8.0.3",
|
|
82
|
+
"lint-staged": "^13.3.0",
|
|
83
|
+
"npm-run-all2": "^5.0.0",
|
|
84
|
+
"prettier": "^3.3.3",
|
|
85
|
+
"prettier-plugin-packagejson": "^2.5.2",
|
|
86
|
+
"semantic-release": "^24.1.2",
|
|
87
|
+
"react": "^18.3.1",
|
|
88
|
+
"react-dom": "^18.3.1",
|
|
89
|
+
"react-is": "^18.3.1",
|
|
90
|
+
"rimraf": "^6.0.0",
|
|
91
|
+
"sanity": "^3.59.0",
|
|
92
|
+
"styled-components": "^6.1.13",
|
|
93
|
+
"typescript": "^5.6.2"
|
|
93
94
|
},
|
|
94
95
|
"peerDependencies": {
|
|
95
96
|
"react": "^18",
|
|
96
97
|
"sanity": "^3",
|
|
97
|
-
"styled-components": "^
|
|
98
|
+
"styled-components": "^6.1"
|
|
98
99
|
},
|
|
99
100
|
"engines": {
|
|
100
|
-
"node": ">=
|
|
101
|
+
"node": ">=18"
|
|
102
|
+
},
|
|
103
|
+
"overrides": {
|
|
104
|
+
"conventional-changelog-conventionalcommits": ">= 8.0.0"
|
|
101
105
|
},
|
|
102
106
|
"sanityExchangeUrl": "https://www.sanity.io/exchange/dashboard"
|
|
103
107
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, {forwardRef} from 'react'
|
|
2
2
|
import {Card, Box, Heading} from '@sanity/ui'
|
|
3
|
-
import styled from 'styled-components'
|
|
3
|
+
import {styled} from 'styled-components'
|
|
4
4
|
|
|
5
5
|
const Root = styled(Card)`
|
|
6
6
|
display: flex;
|
|
@@ -49,7 +49,7 @@ interface DashboardWidgetProps {
|
|
|
49
49
|
|
|
50
50
|
export const DashboardWidgetContainer = forwardRef(function DashboardWidgetContainer(
|
|
51
51
|
props: DashboardWidgetProps,
|
|
52
|
-
ref: React.Ref<HTMLDivElement
|
|
52
|
+
ref: React.Ref<HTMLDivElement>,
|
|
53
53
|
) {
|
|
54
54
|
const {header, children, footer} = props
|
|
55
55
|
|
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import styled,
|
|
2
|
+
import {styled, css} from 'styled-components'
|
|
3
3
|
import {Box, Card, Grid, Text} from '@sanity/ui'
|
|
4
4
|
import {WidgetContainer} from '../containers/WidgetContainer'
|
|
5
5
|
import {DashboardConfig, LayoutConfig, DashboardWidget} from '../types'
|
|
6
6
|
|
|
7
7
|
const media = {
|
|
8
|
-
small: (...args: Parameters<typeof css>) =>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
`,
|
|
8
|
+
small: (...args: Parameters<typeof css>) => css`
|
|
9
|
+
@media (min-width: ${({theme}) => theme.sanity.media[0]}px) {
|
|
10
|
+
${css(...args)}
|
|
11
|
+
}
|
|
12
|
+
`,
|
|
13
|
+
medium: (...args: Parameters<typeof css>) => css`
|
|
14
|
+
@media (min-width: ${({theme}) => theme.sanity.media[2]}px) {
|
|
15
|
+
${css(...args)}
|
|
16
|
+
}
|
|
17
|
+
`,
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
const Root = styled(Grid)`
|
package/src/versionedClient.ts
CHANGED
|
@@ -4,29 +4,14 @@ import {useVersionedClient} from '../../versionedClient'
|
|
|
4
4
|
import {Subscription} from 'rxjs'
|
|
5
5
|
import {WidgetContainer} from '../../containers/WidgetContainer'
|
|
6
6
|
import {DashboardWidgetContainer} from '../../components/DashboardWidgetContainer'
|
|
7
|
-
import {DashboardWidget} from '../../types'
|
|
8
|
-
|
|
9
|
-
export interface ProjectInfoProps {
|
|
10
|
-
__experimental_before?: DashboardWidget[]
|
|
11
|
-
data: ProjectData[]
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface App {
|
|
15
|
-
title: string
|
|
16
|
-
rows?: App[]
|
|
17
|
-
value?: string | {error: string}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface ProjectData {
|
|
21
|
-
title: string
|
|
22
|
-
category?: string
|
|
23
|
-
}
|
|
7
|
+
import {type DashboardWidget} from '../../types'
|
|
8
|
+
import {type App, type ProjectInfoProps, type ProjectData, UserApplication} from './types'
|
|
24
9
|
|
|
25
10
|
function isUrl(url?: string) {
|
|
26
11
|
return url && /^https?:\/\//.test(`${url}`)
|
|
27
12
|
}
|
|
28
13
|
|
|
29
|
-
function
|
|
14
|
+
function getGraphQLUrl(projectId: string, dataset: string) {
|
|
30
15
|
return `https://${projectId}.api.sanity.io/v1/graphql/${dataset}/default`
|
|
31
16
|
}
|
|
32
17
|
|
|
@@ -43,8 +28,8 @@ const NO_DATA: ProjectData[] = []
|
|
|
43
28
|
|
|
44
29
|
export function ProjectInfo(props: ProjectInfoProps) {
|
|
45
30
|
const {__experimental_before = NO_EXPERIMENTAL, data = NO_DATA} = props
|
|
46
|
-
const [
|
|
47
|
-
const [
|
|
31
|
+
const [studioApps, setStudioApps] = useState<UserApplication[] | {error: string} | undefined>()
|
|
32
|
+
const [graphQLApi, setGraphQLApi] = useState<string | {error: string} | undefined>()
|
|
48
33
|
const versionedClient = useVersionedClient()
|
|
49
34
|
const {projectId = 'unknown', dataset = 'unknown'} = versionedClient.config()
|
|
50
35
|
|
|
@@ -53,19 +38,16 @@ export function ProjectInfo(props: ProjectInfoProps) {
|
|
|
53
38
|
|
|
54
39
|
subscriptions.push(
|
|
55
40
|
versionedClient.observable
|
|
56
|
-
.request<
|
|
41
|
+
.request<UserApplication[]>({uri: '/user-applications', tag: 'dashboard.project-info'})
|
|
57
42
|
.subscribe({
|
|
58
|
-
next: (result) =>
|
|
59
|
-
const {studioHost: host} = result
|
|
60
|
-
setStudioHost(host ? `https://${host}.sanity.studio` : undefined)
|
|
61
|
-
},
|
|
43
|
+
next: (result) => setStudioApps(result.filter((app) => app.type === 'studio')),
|
|
62
44
|
error: (error) => {
|
|
63
|
-
console.error('Error while
|
|
64
|
-
|
|
65
|
-
error: 'Something went wrong while
|
|
45
|
+
console.error('Error while resolving user applications', error)
|
|
46
|
+
setStudioApps({
|
|
47
|
+
error: 'Something went wrong while resolving user applications. See console.',
|
|
66
48
|
})
|
|
67
49
|
},
|
|
68
|
-
})
|
|
50
|
+
}),
|
|
69
51
|
)
|
|
70
52
|
|
|
71
53
|
// ping assumed graphql endpoint
|
|
@@ -74,26 +56,27 @@ export function ProjectInfo(props: ProjectInfoProps) {
|
|
|
74
56
|
.request({
|
|
75
57
|
method: 'HEAD',
|
|
76
58
|
uri: `/graphql/${dataset}/default`,
|
|
59
|
+
tag: 'dashboard.project-info.graphql-api',
|
|
77
60
|
})
|
|
78
61
|
.subscribe({
|
|
79
|
-
next: () =>
|
|
62
|
+
next: () => setGraphQLApi(getGraphQLUrl(projectId, dataset)),
|
|
80
63
|
error: (error) => {
|
|
81
64
|
if (error.statusCode === 404) {
|
|
82
|
-
|
|
65
|
+
setGraphQLApi(undefined)
|
|
83
66
|
} else {
|
|
84
|
-
console.error('Error while looking for
|
|
85
|
-
|
|
86
|
-
error: 'Something went wrong while looking up
|
|
67
|
+
console.error('Error while looking for graphQLApi', error)
|
|
68
|
+
setGraphQLApi({
|
|
69
|
+
error: 'Something went wrong while looking up graphQLApi. See console.',
|
|
87
70
|
})
|
|
88
71
|
}
|
|
89
72
|
},
|
|
90
|
-
})
|
|
73
|
+
}),
|
|
91
74
|
)
|
|
92
75
|
|
|
93
76
|
return () => {
|
|
94
77
|
subscriptions.forEach((s) => s.unsubscribe())
|
|
95
78
|
}
|
|
96
|
-
}, [dataset, projectId, versionedClient,
|
|
79
|
+
}, [dataset, projectId, versionedClient, setGraphQLApi])
|
|
97
80
|
|
|
98
81
|
const assembleTableRows = useMemo(() => {
|
|
99
82
|
let result: App[] = [
|
|
@@ -106,11 +89,16 @@ export function ProjectInfo(props: ProjectInfoProps) {
|
|
|
106
89
|
},
|
|
107
90
|
]
|
|
108
91
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
92
|
+
const apps: App[] = data.filter((item) => item.category === 'apps')
|
|
93
|
+
|
|
94
|
+
// Handle studios
|
|
95
|
+
;(Array.isArray(studioApps) ? studioApps : []).forEach((app) => {
|
|
96
|
+
apps.push({
|
|
97
|
+
title: app.title || 'Studio',
|
|
98
|
+
value: app.urlType === 'internal' ? `https://${app.appHost}.sanity.studio` : app.appHost,
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
114
102
|
if (apps.length > 0) {
|
|
115
103
|
result = result.concat([{title: 'Apps', rows: apps}])
|
|
116
104
|
}
|
|
@@ -124,12 +112,12 @@ export function ProjectInfo(props: ProjectInfoProps) {
|
|
|
124
112
|
{title: 'GROQ', value: getGroqUrl(projectId, dataset)},
|
|
125
113
|
{
|
|
126
114
|
title: 'GraphQL',
|
|
127
|
-
value: (typeof
|
|
115
|
+
value: (typeof graphQLApi === 'object' ? 'Error' : graphQLApi) ?? 'Not deployed',
|
|
128
116
|
},
|
|
129
117
|
],
|
|
130
118
|
},
|
|
131
119
|
],
|
|
132
|
-
data.filter((item) => item.category === 'apis')
|
|
120
|
+
data.filter((item) => item.category === 'apis'),
|
|
133
121
|
)
|
|
134
122
|
|
|
135
123
|
// Handle whatever else there might be
|
|
@@ -147,7 +135,7 @@ export function ProjectInfo(props: ProjectInfoProps) {
|
|
|
147
135
|
})
|
|
148
136
|
|
|
149
137
|
return result
|
|
150
|
-
}, [
|
|
138
|
+
}, [graphQLApi, studioApps, projectId, dataset, data])
|
|
151
139
|
|
|
152
140
|
return (
|
|
153
141
|
<>
|
|
@@ -197,7 +185,7 @@ export function ProjectInfo(props: ProjectInfoProps) {
|
|
|
197
185
|
<Stack space={4} paddingX={3} role="rowgroup">
|
|
198
186
|
{item.rows.map((row) => {
|
|
199
187
|
return (
|
|
200
|
-
<Grid key={row.title} columns={2} role="row">
|
|
188
|
+
<Grid key={`${row.value}-${row.title}`} columns={2} role="row">
|
|
201
189
|
<Text weight="medium" role="rowheader">
|
|
202
190
|
{row.title}
|
|
203
191
|
</Text>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {ProjectInfo} from './ProjectInfo'
|
|
2
|
-
import {LayoutConfig, DashboardWidget} from '../../types'
|
|
2
|
+
import {type LayoutConfig, type DashboardWidget} from '../../types'
|
|
3
3
|
|
|
4
4
|
export function projectInfoWidget(config?: {layout?: LayoutConfig}): DashboardWidget {
|
|
5
5
|
return {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {type DashboardWidget} from '../../types'
|
|
2
|
+
|
|
3
|
+
export interface ProjectInfoProps {
|
|
4
|
+
__experimental_before?: DashboardWidget[]
|
|
5
|
+
data: ProjectData[]
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface App {
|
|
9
|
+
title: string
|
|
10
|
+
rows?: App[]
|
|
11
|
+
value?: string | {error: string}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ProjectData {
|
|
15
|
+
title: string
|
|
16
|
+
category?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UserApplication {
|
|
20
|
+
id: string
|
|
21
|
+
projectId: string
|
|
22
|
+
title: string | null
|
|
23
|
+
type: string
|
|
24
|
+
urlType: 'internal' | 'external'
|
|
25
|
+
appHost: string
|
|
26
|
+
|
|
27
|
+
// … there are other props here, but we don't really care about them for our use case
|
|
28
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {Box, Flex, rem, Stack, Text} from '@sanity/ui'
|
|
3
|
+
import {styled} from 'styled-components'
|
|
4
|
+
import {useListFormat, type User, UserAvatar} from 'sanity'
|
|
5
|
+
import {RobotIcon} from '@sanity/icons'
|
|
6
|
+
|
|
7
|
+
const Root = styled(Flex)`
|
|
8
|
+
height: ${rem(33)}; // 33 = PREVIEW_SIZES.default.media.height
|
|
9
|
+
box-sizing: content-box;
|
|
10
|
+
`
|
|
11
|
+
|
|
12
|
+
export interface ProjectUserProps {
|
|
13
|
+
user: User
|
|
14
|
+
isRobot: boolean
|
|
15
|
+
roles: string[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function ProjectUser({user, isRobot, roles}: ProjectUserProps) {
|
|
19
|
+
const listFormat = useListFormat({style: 'narrow'})
|
|
20
|
+
return (
|
|
21
|
+
<Root align="center">
|
|
22
|
+
<Flex align="center" flex={1} gap={2}>
|
|
23
|
+
<Box flex="none">
|
|
24
|
+
{isRobot ? (
|
|
25
|
+
<Text size={2}>
|
|
26
|
+
<RobotIcon />
|
|
27
|
+
</Text>
|
|
28
|
+
) : (
|
|
29
|
+
<UserAvatar user={user} />
|
|
30
|
+
)}
|
|
31
|
+
</Box>
|
|
32
|
+
|
|
33
|
+
<Stack flex={1} space={2}>
|
|
34
|
+
<Text size={1} style={{color: 'inherit'}} textOverflow="ellipsis" weight="medium">
|
|
35
|
+
{user.displayName}
|
|
36
|
+
</Text>
|
|
37
|
+
|
|
38
|
+
<Text muted size={1} textOverflow="ellipsis">
|
|
39
|
+
{listFormat.format(roles)}
|
|
40
|
+
</Text>
|
|
41
|
+
</Stack>
|
|
42
|
+
</Flex>
|
|
43
|
+
</Root>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -1,36 +1,23 @@
|
|
|
1
1
|
import React, {useCallback, useEffect, useState} from 'react'
|
|
2
2
|
import {from} from 'rxjs'
|
|
3
3
|
import {map, switchMap} from 'rxjs/operators'
|
|
4
|
-
import {Stack, Spinner,
|
|
5
|
-
import {
|
|
6
|
-
import styled from 'styled-components'
|
|
7
|
-
import {DefaultPreview, useUserStore} from 'sanity'
|
|
4
|
+
import {Stack, Spinner, Box, Text, Button} from '@sanity/ui'
|
|
5
|
+
import {Role, useUserStore} from 'sanity'
|
|
8
6
|
import {useVersionedClient} from '../../versionedClient'
|
|
9
7
|
import {User} from 'sanity'
|
|
10
8
|
import {DashboardWidgetContainer} from '../../components/DashboardWidgetContainer'
|
|
11
|
-
|
|
12
|
-
const AvatarWrapper = styled(Card)`
|
|
13
|
-
box-sizing: border-box;
|
|
14
|
-
border-radius: 50%;
|
|
15
|
-
border-color: transparent;
|
|
16
|
-
overflow: hidden;
|
|
17
|
-
width: 100%;
|
|
18
|
-
height: 100%;
|
|
19
|
-
|
|
20
|
-
& > img {
|
|
21
|
-
width: 100%;
|
|
22
|
-
height: auto;
|
|
23
|
-
}
|
|
24
|
-
`
|
|
9
|
+
import {ProjectUser} from './ProjectUser'
|
|
25
10
|
|
|
26
11
|
function getInviteUrl(projectId: string) {
|
|
27
|
-
return `https://manage.sanity.io/projects/${projectId}/
|
|
12
|
+
return `https://manage.sanity.io/projects/${projectId}/members`
|
|
28
13
|
}
|
|
29
14
|
|
|
30
15
|
interface Member {
|
|
31
16
|
id: string
|
|
32
|
-
|
|
17
|
+
roles: Role[]
|
|
33
18
|
isRobot: boolean
|
|
19
|
+
isCurrentUser: boolean
|
|
20
|
+
createdAt: string
|
|
34
21
|
}
|
|
35
22
|
|
|
36
23
|
interface Project {
|
|
@@ -51,21 +38,22 @@ export function ProjectUsers() {
|
|
|
51
38
|
const subscription = versionedClient.observable
|
|
52
39
|
.request<Project>({
|
|
53
40
|
uri: `/projects/${projectId}`,
|
|
41
|
+
tag: 'dashboard.project-users',
|
|
54
42
|
})
|
|
55
43
|
.pipe(
|
|
56
44
|
switchMap((_project) =>
|
|
57
45
|
from(userStore.getUsers(_project.members.map((mem) => mem.id))).pipe(
|
|
58
|
-
map((_users) => ({project: _project, users: _users}))
|
|
59
|
-
)
|
|
60
|
-
)
|
|
46
|
+
map((_users) => ({project: _project, users: _users})),
|
|
47
|
+
),
|
|
48
|
+
),
|
|
61
49
|
)
|
|
62
50
|
.subscribe({
|
|
63
51
|
next: ({users: _users, project: _project}) => {
|
|
64
52
|
setProject(_project)
|
|
65
53
|
setUsers(
|
|
66
54
|
(Array.isArray(_users) ? _users : [_users]).sort((userA, userB) =>
|
|
67
|
-
sortUsersByRobotStatus(userA, userB, _project)
|
|
68
|
-
)
|
|
55
|
+
sortUsersByRobotStatus(userA, userB, _project),
|
|
56
|
+
),
|
|
69
57
|
)
|
|
70
58
|
},
|
|
71
59
|
error: (e: Error) => setError(e),
|
|
@@ -106,7 +94,7 @@ export function ProjectUsers() {
|
|
|
106
94
|
paddingY={4}
|
|
107
95
|
mode="bleed"
|
|
108
96
|
tone="primary"
|
|
109
|
-
text="
|
|
97
|
+
text="Manage members"
|
|
110
98
|
as="a"
|
|
111
99
|
loading={isLoading}
|
|
112
100
|
href={isLoading ? undefined : getInviteUrl(project.id)}
|
|
@@ -120,7 +108,7 @@ export function ProjectUsers() {
|
|
|
120
108
|
<Spinner />
|
|
121
109
|
</Text>
|
|
122
110
|
<Text align="center" size={1} muted>
|
|
123
|
-
Loading items
|
|
111
|
+
Loading items…
|
|
124
112
|
</Text>
|
|
125
113
|
</Stack>
|
|
126
114
|
</Box>
|
|
@@ -130,23 +118,13 @@ export function ProjectUsers() {
|
|
|
130
118
|
<Stack space={3} padding={3}>
|
|
131
119
|
{users?.map((user) => {
|
|
132
120
|
const membership = project.members.find((member) => member.id === user.id)
|
|
133
|
-
const media = membership?.isRobot ? (
|
|
134
|
-
<Text size={3}>
|
|
135
|
-
<RobotIcon />
|
|
136
|
-
</Text>
|
|
137
|
-
) : (
|
|
138
|
-
<AvatarWrapper tone="transparent">
|
|
139
|
-
{user?.imageUrl && <img src={user.imageUrl} alt={user?.displayName} />}
|
|
140
|
-
</AvatarWrapper>
|
|
141
|
-
)
|
|
142
121
|
return (
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
</Box>
|
|
122
|
+
<ProjectUser
|
|
123
|
+
key={user.id}
|
|
124
|
+
user={user}
|
|
125
|
+
isRobot={membership?.isRobot ?? false}
|
|
126
|
+
roles={membership?.roles.map((role) => role.title) || []}
|
|
127
|
+
/>
|
|
150
128
|
)
|
|
151
129
|
})}
|
|
152
130
|
</Stack>
|
|
@@ -159,11 +137,12 @@ function sortUsersByRobotStatus(userA: User, userB: User, project: Project) {
|
|
|
159
137
|
const {members} = project
|
|
160
138
|
const membershipA = members.find((member) => member.id === userA?.id)
|
|
161
139
|
const membershipB = members.find((member) => member.id === userB?.id)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return -1
|
|
140
|
+
|
|
141
|
+
// On ties, sort by when the user was added
|
|
142
|
+
if (membershipA?.isRobot === membershipB?.isRobot) {
|
|
143
|
+
return (membershipA?.createdAt || '') > (membershipB?.createdAt || '') ? 1 : -1
|
|
167
144
|
}
|
|
168
|
-
|
|
145
|
+
|
|
146
|
+
// Robots go to the bottom
|
|
147
|
+
return membershipA?.isRobot ? 1 : -1
|
|
169
148
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import {Card, Box, Heading, Flex, Text, Stack} from '@sanity/ui'
|
|
3
3
|
import {PlayIcon} from '@sanity/icons'
|
|
4
|
-
import styled from 'styled-components'
|
|
4
|
+
import {styled} from 'styled-components'
|
|
5
5
|
|
|
6
6
|
const PlayIconBox = styled(Box)`
|
|
7
7
|
position: absolute;
|
|
@@ -38,11 +38,12 @@ export function useDataAdapter() {
|
|
|
38
38
|
: '/addons/dashboard'
|
|
39
39
|
return versionedClient.observable.request<{items: FeedItem[]}>({
|
|
40
40
|
uri,
|
|
41
|
+
tag: 'dashboard.sanity-tutorials',
|
|
41
42
|
withCredentials: false,
|
|
42
43
|
})
|
|
43
44
|
},
|
|
44
45
|
urlBuilder: imageUrlBuilder(tutorialsProjectConfig),
|
|
45
46
|
}),
|
|
46
|
-
[versionedClient]
|
|
47
|
+
[versionedClient],
|
|
47
48
|
)
|
|
48
49
|
}
|