@sanity/dashboard 5.0.1 → 6.0.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.
Files changed (38) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +5 -51
  3. package/dist/index.d.ts +54 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +605 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +37 -79
  8. package/lib/index.d.mts +0 -65
  9. package/lib/index.d.ts +0 -65
  10. package/lib/index.esm.js +0 -568
  11. package/lib/index.esm.js.map +0 -1
  12. package/lib/index.js +0 -564
  13. package/lib/index.js.map +0 -1
  14. package/lib/index.mjs +0 -568
  15. package/lib/index.mjs.map +0 -1
  16. package/sanity.json +0 -8
  17. package/src/components/DashboardLayout.tsx +0 -10
  18. package/src/components/DashboardWidgetContainer.tsx +0 -69
  19. package/src/components/NotFoundWidget.tsx +0 -30
  20. package/src/components/WidgetGroup.tsx +0 -106
  21. package/src/containers/Dashboard.tsx +0 -19
  22. package/src/containers/DashboardContext.tsx +0 -8
  23. package/src/containers/WidgetContainer.tsx +0 -21
  24. package/src/index.ts +0 -7
  25. package/src/plugin.tsx +0 -72
  26. package/src/types.ts +0 -21
  27. package/src/versionedClient.ts +0 -5
  28. package/src/widgets/projectInfo/ProjectInfo.tsx +0 -221
  29. package/src/widgets/projectInfo/index.ts +0 -10
  30. package/src/widgets/projectInfo/types.ts +0 -28
  31. package/src/widgets/projectUsers/ProjectUser.tsx +0 -45
  32. package/src/widgets/projectUsers/ProjectUsers.tsx +0 -148
  33. package/src/widgets/projectUsers/index.ts +0 -10
  34. package/src/widgets/sanityTutorials/SanityTutorials.tsx +0 -77
  35. package/src/widgets/sanityTutorials/Tutorial.tsx +0 -111
  36. package/src/widgets/sanityTutorials/dataAdapter.ts +0 -49
  37. package/src/widgets/sanityTutorials/index.ts +0 -10
  38. package/v2-incompatible.js +0 -11
@@ -1,45 +0,0 @@
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,148 +0,0 @@
1
- import React, {useCallback, useEffect, useState} from 'react'
2
- import {from} from 'rxjs'
3
- import {map, switchMap} from 'rxjs/operators'
4
- import {Stack, Spinner, Box, Text, Button} from '@sanity/ui'
5
- import {Role, useUserStore} from 'sanity'
6
- import {useVersionedClient} from '../../versionedClient'
7
- import {User} from 'sanity'
8
- import {DashboardWidgetContainer} from '../../components/DashboardWidgetContainer'
9
- import {ProjectUser} from './ProjectUser'
10
-
11
- function getInviteUrl(projectId: string) {
12
- return `https://manage.sanity.io/projects/${projectId}/members`
13
- }
14
-
15
- interface Member {
16
- id: string
17
- roles: Role[]
18
- isRobot: boolean
19
- isCurrentUser: boolean
20
- createdAt: string
21
- }
22
-
23
- interface Project {
24
- id: string
25
- members: Member[]
26
- }
27
-
28
- export function ProjectUsers() {
29
- const [project, setProject] = useState<Project | undefined>()
30
- const [users, setUsers] = useState<User[] | undefined>()
31
- const [error, setError] = useState<Error | undefined>()
32
-
33
- const userStore = useUserStore()
34
- const versionedClient = useVersionedClient()
35
-
36
- const fetchData = useCallback(() => {
37
- const {projectId} = versionedClient.config()
38
- const subscription = versionedClient.observable
39
- .request<Project>({
40
- uri: `/projects/${projectId}`,
41
- tag: 'dashboard.project-users',
42
- })
43
- .pipe(
44
- switchMap((_project) =>
45
- from(userStore.getUsers(_project.members.map((mem) => mem.id))).pipe(
46
- map((_users) => ({project: _project, users: _users})),
47
- ),
48
- ),
49
- )
50
- .subscribe({
51
- next: ({users: _users, project: _project}) => {
52
- setProject(_project)
53
- setUsers(
54
- (Array.isArray(_users) ? _users : [_users]).sort((userA, userB) =>
55
- sortUsersByRobotStatus(userA, userB, _project),
56
- ),
57
- )
58
- },
59
- error: (e: Error) => setError(e),
60
- })
61
-
62
- return () => subscription.unsubscribe()
63
- }, [userStore, versionedClient])
64
-
65
- useEffect(() => fetchData(), [fetchData])
66
-
67
- const handleRetryFetch = useCallback(() => fetchData(), [fetchData])
68
-
69
- const isLoading = !users || !project
70
-
71
- if (error) {
72
- return (
73
- <DashboardWidgetContainer header="Project users">
74
- <Box padding={4}>
75
- <Text>
76
- Something went wrong while fetching data. You could{' '}
77
- <a onClick={handleRetryFetch} title="Retry users fetch" style={{cursor: 'pointer'}}>
78
- retry
79
- </a>
80
- ..?
81
- </Text>
82
- </Box>
83
- </DashboardWidgetContainer>
84
- )
85
- }
86
-
87
- return (
88
- <DashboardWidgetContainer
89
- header="Project users"
90
- footer={
91
- <Button
92
- style={{width: '100%'}}
93
- paddingX={2}
94
- paddingY={4}
95
- mode="bleed"
96
- tone="primary"
97
- text="Manage members"
98
- as="a"
99
- loading={isLoading}
100
- href={isLoading ? undefined : getInviteUrl(project.id)}
101
- />
102
- }
103
- >
104
- {isLoading && (
105
- <Box paddingY={5} paddingX={2}>
106
- <Stack space={4}>
107
- <Text align="center" muted size={1}>
108
- <Spinner />
109
- </Text>
110
- <Text align="center" size={1} muted>
111
- Loading items…
112
- </Text>
113
- </Stack>
114
- </Box>
115
- )}
116
-
117
- {!isLoading && (
118
- <Stack space={3} padding={3}>
119
- {users?.map((user) => {
120
- const membership = project.members.find((member) => member.id === user.id)
121
- return (
122
- <ProjectUser
123
- key={user.id}
124
- user={user}
125
- isRobot={membership?.isRobot ?? false}
126
- roles={membership?.roles.map((role) => role.title) || []}
127
- />
128
- )
129
- })}
130
- </Stack>
131
- )}
132
- </DashboardWidgetContainer>
133
- )
134
- }
135
-
136
- function sortUsersByRobotStatus(userA: User, userB: User, project: Project) {
137
- const {members} = project
138
- const membershipA = members.find((member) => member.id === userA?.id)
139
- const membershipB = members.find((member) => member.id === userB?.id)
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
144
- }
145
-
146
- // Robots go to the bottom
147
- return membershipA?.isRobot ? 1 : -1
148
- }
@@ -1,10 +0,0 @@
1
- import {ProjectUsers} from './ProjectUsers'
2
- import {LayoutConfig, DashboardWidget} from '../../types'
3
-
4
- export function projectUsersWidget(config?: {layout?: LayoutConfig}): DashboardWidget {
5
- return {
6
- name: 'project-info',
7
- component: ProjectUsers,
8
- layout: config?.layout,
9
- }
10
- }
@@ -1,77 +0,0 @@
1
- import React, {useEffect, useState} from 'react'
2
- import {Flex} from '@sanity/ui'
3
- import {Tutorial} from './Tutorial'
4
- import {FeedItem, Guide, useDataAdapter} from './dataAdapter'
5
- import {DashboardWidgetContainer} from '../../components/DashboardWidgetContainer'
6
-
7
- function createUrl(slug: {current: string}, type?: string) {
8
- if (type === 'tutorial') {
9
- return `https://www.sanity.io/docs/tutorials/${slug.current}`
10
- } else if (type === 'guide') {
11
- return `https://www.sanity.io/docs/guides/${slug.current}`
12
- }
13
- return false
14
- }
15
-
16
- export interface SanityTutorialsProps {
17
- templateRepoId: string
18
- }
19
-
20
- export function SanityTutorials(props: SanityTutorialsProps) {
21
- const {templateRepoId} = props
22
- const [feedItems, setFeedItems] = useState<FeedItem[]>([])
23
-
24
- const {getFeed, urlBuilder} = useDataAdapter()
25
-
26
- useEffect(() => {
27
- const subscription = getFeed(templateRepoId).subscribe((response) => {
28
- setFeedItems(response.items)
29
- })
30
- return () => {
31
- subscription.unsubscribe()
32
- }
33
- }, [setFeedItems, getFeed, templateRepoId])
34
-
35
- const title = 'Learn about Sanity'
36
-
37
- return (
38
- <DashboardWidgetContainer header={title}>
39
- <Flex as="ul" overflow="auto" align="stretch" paddingY={2}>
40
- {feedItems?.map((feedItem, index) => {
41
- if (!feedItem.title || (!feedItem.guideOrTutorial && !feedItem.externalLink)) {
42
- return null
43
- }
44
- const presenter = feedItem.presenter || feedItem.guideOrTutorial?.presenter || {}
45
- const subtitle = feedItem.category
46
- const {guideOrTutorial = {} as Guide} = feedItem
47
- const href =
48
- (guideOrTutorial.slug
49
- ? createUrl(guideOrTutorial.slug, guideOrTutorial._type)
50
- : feedItem.externalLink) || feedItem.externalLink
51
-
52
- return (
53
- <Flex
54
- as="li"
55
- key={feedItem._id}
56
- paddingRight={index < feedItems?.length - 1 ? 1 : 3}
57
- paddingLeft={index === 0 ? 3 : 0}
58
- align="stretch"
59
- style={{minWidth: 272, width: '30%'}}
60
- >
61
- <Tutorial
62
- title={feedItem.title}
63
- href={href ?? ''}
64
- presenterName={presenter.name}
65
- presenterSubtitle={subtitle}
66
- showPlayIcon={feedItem.hasVideo}
67
- posterURL={
68
- feedItem.poster ? urlBuilder.image(feedItem.poster).height(360).url() : undefined
69
- }
70
- />
71
- </Flex>
72
- )
73
- })}
74
- </Flex>
75
- </DashboardWidgetContainer>
76
- )
77
- }
@@ -1,111 +0,0 @@
1
- import React from 'react'
2
- import {Card, Box, Heading, Flex, Text, Stack} from '@sanity/ui'
3
- import {PlayIcon} from '@sanity/icons'
4
- import {styled} from 'styled-components'
5
-
6
- const PlayIconBox = styled(Box)`
7
- position: absolute;
8
- top: 50%;
9
- left: 50%;
10
- transform: translate(-50%, -50%);
11
-
12
- &:before {
13
- content: '';
14
- position: absolute;
15
- top: 50%;
16
- left: 50%;
17
- transform: translate(-50%, -50%);
18
- width: 2.75em;
19
- height: 2.75em;
20
- border-radius: 50%;
21
- background: ${({theme}) => theme.sanity.color.card.enabled.bg};
22
- opacity: 0.75;
23
- }
24
- `
25
-
26
- const Root = styled(Flex)`
27
- &:hover {
28
- ${PlayIconBox} {
29
- &:before {
30
- opacity: 1;
31
- }
32
- }
33
- }
34
- `
35
-
36
- const PosterCard = styled(Card)`
37
- width: 100%;
38
- padding-bottom: calc(9 / 16 * 100%);
39
- position: relative;
40
- `
41
-
42
- const Poster = styled.img`
43
- position: absolute;
44
- top: 0;
45
- left: 0;
46
- height: 100%;
47
- width: 100%;
48
- object-fit: cover;
49
- display: block;
50
-
51
- &:not([src]) {
52
- display: none;
53
- }
54
- `
55
-
56
- export interface TutorialProps {
57
- title: string
58
- posterURL?: string
59
- href: string
60
- showPlayIcon?: boolean
61
- presenterName?: string
62
- presenterSubtitle?: string
63
- }
64
-
65
- export function Tutorial(props: TutorialProps) {
66
- const {title, posterURL, showPlayIcon, href, presenterName, presenterSubtitle} = props
67
-
68
- return (
69
- <Root flex={1}>
70
- <Card
71
- sizing="border"
72
- flex={1}
73
- padding={2}
74
- radius={2}
75
- as="a"
76
- href={href}
77
- target="_blank"
78
- rel="noopener noreferrer"
79
- style={{position: 'relative'}}
80
- >
81
- <Flex direction="column" style={{height: '100%'}}>
82
- {posterURL && (
83
- <PosterCard marginBottom={1}>
84
- <Poster src={posterURL} />
85
- {showPlayIcon && (
86
- <PlayIconBox display="flex">
87
- <Text align="center">
88
- <PlayIcon />
89
- </Text>
90
- </PlayIconBox>
91
- )}
92
- </PosterCard>
93
- )}
94
- <Flex direction="column" justify="space-between" paddingY={2} flex={1}>
95
- <Heading as="h3" size={1}>
96
- {title}
97
- </Heading>
98
- <Box marginTop={4}>
99
- <Stack space={2} flex={1}>
100
- <Text size={1}>{presenterName}</Text>
101
- <Text size={0} style={{opacity: 0.7}}>
102
- {presenterSubtitle}
103
- </Text>
104
- </Stack>
105
- </Box>
106
- </Flex>
107
- </Flex>
108
- </Card>
109
- </Root>
110
- )
111
- }
@@ -1,49 +0,0 @@
1
- import {useMemo} from 'react'
2
- import {useVersionedClient} from '../../versionedClient'
3
- import imageUrlBuilder from '@sanity/image-url'
4
-
5
- const tutorialsProjectConfig = {
6
- projectId: '3do82whm',
7
- dataset: 'next',
8
- }
9
-
10
- export interface Guide {
11
- _type?: string
12
- slug?: {current: string}
13
- presenter?: {
14
- name?: string
15
- }
16
- }
17
-
18
- export interface FeedItem {
19
- _id: string
20
- title?: string
21
- poster?: string
22
- category?: string
23
- guideOrTutorial?: Guide
24
- externalLink?: string
25
- presenter?: {
26
- name?: string
27
- }
28
- hasVideo?: boolean
29
- }
30
-
31
- export function useDataAdapter() {
32
- const versionedClient = useVersionedClient()
33
- return useMemo(
34
- () => ({
35
- getFeed: (templateRepoId: string) => {
36
- const uri = templateRepoId
37
- ? `/addons/dashboard?templateRepoId=${templateRepoId}`
38
- : '/addons/dashboard'
39
- return versionedClient.observable.request<{items: FeedItem[]}>({
40
- uri,
41
- tag: 'dashboard.sanity-tutorials',
42
- withCredentials: false,
43
- })
44
- },
45
- urlBuilder: imageUrlBuilder(tutorialsProjectConfig),
46
- }),
47
- [versionedClient],
48
- )
49
- }
@@ -1,10 +0,0 @@
1
- import {SanityTutorials} from './SanityTutorials'
2
- import {LayoutConfig, DashboardWidget} from '../../types'
3
-
4
- export function sanityTutorialsWidget(config?: {layout?: LayoutConfig}): DashboardWidget {
5
- return {
6
- name: 'sanity-tutorials',
7
- component: SanityTutorials,
8
- layout: config?.layout ?? {width: 'full'},
9
- }
10
- }
@@ -1,11 +0,0 @@
1
- const {showIncompatiblePluginDialog} = require('@sanity/incompatible-plugin')
2
- const {name, version, sanityExchangeUrl} = require('./package.json')
3
-
4
- export default showIncompatiblePluginDialog({
5
- name: name,
6
- versions: {
7
- v3: version,
8
- v2: '^2.30.0',
9
- },
10
- sanityExchangeUrl,
11
- })