@sanity/dashboard 2.30.2-shopify.0 → 3.0.0-studio-v3.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 (48) hide show
  1. package/README.md +123 -44
  2. package/lib/cjs/index.js +1002 -0
  3. package/lib/cjs/index.js.map +1 -0
  4. package/lib/esm/index.js +977 -0
  5. package/lib/esm/index.js.map +1 -0
  6. package/lib/types/index.d.ts +42 -0
  7. package/lib/types/index.d.ts.map +1 -0
  8. package/package.json +53 -14
  9. package/src/components/DashboardLayout.tsx +10 -0
  10. package/src/components/DashboardWidgetContainer.tsx +69 -0
  11. package/src/components/NotFoundWidget.tsx +30 -0
  12. package/src/components/WidgetGroup.tsx +108 -0
  13. package/src/containers/Dashboard.tsx +19 -0
  14. package/src/containers/DashboardContext.tsx +8 -0
  15. package/src/containers/WidgetContainer.tsx +21 -0
  16. package/src/index.tsx +62 -0
  17. package/src/types.ts +21 -0
  18. package/src/versionedClient.ts +7 -0
  19. package/src/widgets/projectInfo/ProjectInfo.tsx +233 -0
  20. package/src/widgets/projectInfo/index.ts +10 -0
  21. package/src/widgets/projectUsers/ProjectUsers.tsx +171 -0
  22. package/src/widgets/projectUsers/index.ts +10 -0
  23. package/src/widgets/sanityTutorials/SanityTutorials.tsx +77 -0
  24. package/src/widgets/sanityTutorials/Tutorial.tsx +111 -0
  25. package/src/widgets/sanityTutorials/dataAdapter.ts +48 -0
  26. package/src/widgets/sanityTutorials/index.ts +10 -0
  27. package/.babelrc +0 -4
  28. package/lib/DashboardTool.js +0 -59
  29. package/lib/components/DashboardLayout.js +0 -35
  30. package/lib/components/NotFoundWidget.js +0 -51
  31. package/lib/components/WidgetGroup.js +0 -67
  32. package/lib/components/dashboardWidget.js +0 -51
  33. package/lib/containers/Dashboard.js +0 -32
  34. package/lib/containers/WidgetContainer.js +0 -56
  35. package/lib/dashboardConfig.js +0 -16
  36. package/lib/legacyParts.js +0 -55
  37. package/lib/versionedClient.js +0 -20
  38. package/lib/widget.css +0 -62
  39. package/lib/widgets/projectInfo/ProjectInfo.js +0 -265
  40. package/lib/widgets/projectInfo/index.js +0 -19
  41. package/lib/widgets/projectUsers/ProjectUsers.js +0 -188
  42. package/lib/widgets/projectUsers/index.js +0 -16
  43. package/lib/widgets/sanityTutorials/SanityTutorials.js +0 -115
  44. package/lib/widgets/sanityTutorials/Tutorial.js +0 -111
  45. package/lib/widgets/sanityTutorials/dataAdapter.js +0 -28
  46. package/lib/widgets/sanityTutorials/index.js +0 -19
  47. package/sanity.json +0 -59
  48. package/tsconfig.json +0 -17
@@ -0,0 +1,233 @@
1
+ import React, {useEffect, useMemo, useState} from 'react'
2
+ import {Box, Card, Stack, Heading, Grid, Label, Text, Code, Button} from '@sanity/ui'
3
+ import {useVersionedClient} from '../../versionedClient'
4
+ import {Subscription} from 'rxjs'
5
+ import {WidgetContainer} from '../../containers/WidgetContainer'
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
+ }
24
+
25
+ function isUrl(url?: string) {
26
+ return url && /^https?:\/\//.test(`${url}`)
27
+ }
28
+
29
+ function getGraphQlUrl(projectId: string, dataset: string) {
30
+ return `https://${projectId}.api.sanity.io/v1/graphql/${dataset}/default`
31
+ }
32
+
33
+ function getGroqUrl(projectId: string, dataset: string) {
34
+ return `https://${projectId}.api.sanity.io/v1/groq/${dataset}`
35
+ }
36
+
37
+ function getManageUrl(projectId: string) {
38
+ return `https://manage.sanity.io/projects/${projectId}`
39
+ }
40
+
41
+ const NO_EXPERIMENTAL: DashboardWidget[] = []
42
+ const NO_DATA: ProjectData[] = []
43
+
44
+ export function ProjectInfo(props: ProjectInfoProps) {
45
+ const {__experimental_before = NO_EXPERIMENTAL, data = NO_DATA} = props
46
+ const [studioHost, setStudioHost] = useState<string | {error: string} | undefined>()
47
+ const [graphqlApi, setGraphQlApi] = useState<string | {error: string} | undefined>()
48
+ const versionedClient = useVersionedClient()
49
+ const {projectId = 'unknown', dataset = 'unknown'} = versionedClient.config()
50
+
51
+ useEffect(() => {
52
+ const subscriptions: Subscription[] = []
53
+
54
+ subscriptions.push(
55
+ versionedClient.observable
56
+ .request<{studioHost: string}>({uri: `/projects/${projectId}`})
57
+ .subscribe({
58
+ next: (result) => {
59
+ const {studioHost: host} = result
60
+ setStudioHost(host ? `https://${host}.sanity.studio` : undefined)
61
+ },
62
+ error: (error) => {
63
+ console.error('Error while looking for studioHost', error)
64
+ setStudioHost({
65
+ error: 'Something went wrong while looking up studioHost. See console.',
66
+ })
67
+ },
68
+ })
69
+ )
70
+
71
+ // ping assumed graphql endpoint
72
+ subscriptions.push(
73
+ versionedClient.observable
74
+ .request({
75
+ method: 'HEAD',
76
+ uri: `/graphql/${dataset}/default`,
77
+ })
78
+ .subscribe({
79
+ next: () => setGraphQlApi(getGraphQlUrl(projectId, dataset)),
80
+ error: (error) => {
81
+ if (error.statusCode === 404) {
82
+ setGraphQlApi(undefined)
83
+ } else {
84
+ console.error('Error while looking for graphqlApi', error)
85
+ setGraphQlApi({
86
+ error: 'Something went wrong while looking up graphqlApi. See console.',
87
+ })
88
+ }
89
+ },
90
+ })
91
+ )
92
+
93
+ return () => {
94
+ subscriptions.forEach((s) => s.unsubscribe())
95
+ }
96
+ }, [dataset, projectId, versionedClient, setGraphQlApi, setStudioHost])
97
+
98
+ const assembleTableRows = useMemo(() => {
99
+ let result: App[] = [
100
+ {
101
+ title: 'Sanity project',
102
+ rows: [
103
+ {title: 'Project ID', value: projectId},
104
+ {title: 'Dataset', value: dataset},
105
+ ],
106
+ },
107
+ ]
108
+
109
+ // Handle any apps
110
+ const apps: App[] = [
111
+ studioHost ? {title: 'Studio', value: studioHost} : null,
112
+ ...data.filter((item) => item.category === 'apps'),
113
+ ].filter((a): a is App => !!a)
114
+ if (apps.length > 0) {
115
+ result = result.concat([{title: 'Apps', rows: apps}])
116
+ }
117
+
118
+ // Handle APIs
119
+ result = result.concat(
120
+ [
121
+ {
122
+ title: 'APIs',
123
+ rows: [
124
+ {title: 'GROQ', value: getGroqUrl(projectId, dataset)},
125
+ {
126
+ title: 'GraphQL',
127
+ value: (typeof graphqlApi === 'object' ? 'Error' : graphqlApi) ?? 'Not deployed',
128
+ },
129
+ ],
130
+ },
131
+ ],
132
+ data.filter((item) => item.category === 'apis')
133
+ )
134
+
135
+ // Handle whatever else there might be
136
+ const otherStuff: Record<string, ProjectData[]> = {}
137
+ data.forEach((item) => {
138
+ if (item.category && item.category !== 'apps' && item.category !== 'apis') {
139
+ if (!otherStuff[item.category]) {
140
+ otherStuff[item.category] = []
141
+ }
142
+ otherStuff[item.category].push(item)
143
+ }
144
+ })
145
+ Object.keys(otherStuff).forEach((category) => {
146
+ result.push({title: category, rows: otherStuff[category]})
147
+ })
148
+
149
+ return result
150
+ }, [graphqlApi, studioHost, projectId, dataset, data])
151
+
152
+ return (
153
+ <>
154
+ {__experimental_before.map((widgetConfig, idx) => (
155
+ <WidgetContainer key={idx} {...widgetConfig} />
156
+ ))}
157
+ <Box height="fill" marginTop={__experimental_before?.length > 0 ? 4 : 0}>
158
+ <DashboardWidgetContainer
159
+ footer={
160
+ <Button
161
+ style={{width: '100%'}}
162
+ paddingX={2}
163
+ paddingY={4}
164
+ mode="bleed"
165
+ tone="primary"
166
+ text="Manage project"
167
+ as="a"
168
+ href={getManageUrl(projectId)}
169
+ />
170
+ }
171
+ >
172
+ <Card
173
+ paddingY={4}
174
+ radius={2}
175
+ role="table"
176
+ aria-label="Project info"
177
+ aria-describedby="project_info_table"
178
+ >
179
+ <Stack space={4}>
180
+ <Box paddingX={3} as="header">
181
+ <Heading size={1} as="h2" id="project_info_table">
182
+ Project info
183
+ </Heading>
184
+ </Box>
185
+ {assembleTableRows.map((item) => {
186
+ if (!item || !item.rows) {
187
+ return null
188
+ }
189
+
190
+ return (
191
+ <Stack key={item.title} space={3}>
192
+ <Card borderBottom padding={3}>
193
+ <Label size={0} muted role="columnheader">
194
+ {item.title}
195
+ </Label>
196
+ </Card>
197
+ <Stack space={4} paddingX={3} role="rowgroup">
198
+ {item.rows.map((row) => {
199
+ return (
200
+ <Grid key={row.title} columns={2} role="row">
201
+ <Text weight="medium" role="rowheader">
202
+ {row.title}
203
+ </Text>
204
+ {typeof row.value === 'object' && (
205
+ <Text size={1}>{row.value?.error}</Text>
206
+ )}
207
+ {typeof row.value === 'string' && (
208
+ <>
209
+ {isUrl(row.value) ? (
210
+ <Text size={1} role="cell" style={{wordBreak: 'break-word'}}>
211
+ <a href={row.value}>{row.value}</a>
212
+ </Text>
213
+ ) : (
214
+ <Code size={1} role="cell" style={{wordBreak: 'break-word'}}>
215
+ {row.value}
216
+ </Code>
217
+ )}
218
+ </>
219
+ )}
220
+ </Grid>
221
+ )
222
+ })}
223
+ </Stack>
224
+ </Stack>
225
+ )
226
+ })}
227
+ </Stack>
228
+ </Card>
229
+ </DashboardWidgetContainer>
230
+ </Box>
231
+ </>
232
+ )
233
+ }
@@ -0,0 +1,10 @@
1
+ import {ProjectInfo} from './ProjectInfo'
2
+ import {LayoutConfig, DashboardWidget} from '../../types'
3
+
4
+ export function projectInfoWidget(config?: {layout?: LayoutConfig}): DashboardWidget {
5
+ return {
6
+ name: 'project-info',
7
+ component: ProjectInfo,
8
+ layout: config?.layout ?? {width: 'medium'},
9
+ }
10
+ }
@@ -0,0 +1,171 @@
1
+ import React, {useCallback, useEffect, useState} from 'react'
2
+ import {map, switchMap} from 'rxjs/operators'
3
+ import {Stack, Spinner, Card, Box, Text, Button} from '@sanity/ui'
4
+ import {RobotIcon} from '@sanity/icons'
5
+ import styled from 'styled-components'
6
+ import {useSource} from 'sanity'
7
+ import {DefaultPreview} from 'sanity/_unstable'
8
+ import {useVersionedClient} from '../../versionedClient'
9
+ import {User} from '@sanity/types'
10
+ 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
+ `
25
+
26
+ function getInviteUrl(projectId: string) {
27
+ return `https://manage.sanity.io/projects/${projectId}/team/invite`
28
+ }
29
+
30
+ interface Member {
31
+ id: string
32
+ role: string
33
+ isRobot: boolean
34
+ }
35
+
36
+ interface Project {
37
+ id: string
38
+ members: Member[]
39
+ }
40
+
41
+ export function ProjectUsers() {
42
+ const [project, setProject] = useState<Project | undefined>()
43
+ const [users, setUsers] = useState<User[] | undefined>()
44
+ const [error, setError] = useState<Error | undefined>()
45
+
46
+ const versionedClient = useVersionedClient()
47
+ const {
48
+ __internal: {userStore},
49
+ } = useSource()
50
+
51
+ const fetchData = useCallback(() => {
52
+ const {projectId} = versionedClient.config()
53
+ const subscription = versionedClient.observable
54
+ .request<Project>({
55
+ uri: `/projects/${projectId}`,
56
+ })
57
+ .pipe(
58
+ switchMap((_project) =>
59
+ userStore.observable
60
+ .getUsers(_project.members.map((mem) => mem.id))
61
+ .pipe(map((_users) => ({project: _project, users: _users})))
62
+ )
63
+ )
64
+ .subscribe({
65
+ next: ({users: _users, project: _project}) => {
66
+ setProject(_project)
67
+ setUsers(
68
+ (Array.isArray(_users) ? _users : [_users]).sort((userA, userB) =>
69
+ sortUsersByRobotStatus(userA, userB, _project)
70
+ )
71
+ )
72
+ },
73
+ error: (e: Error) => setError(e),
74
+ })
75
+
76
+ return () => subscription.unsubscribe()
77
+ }, [userStore, versionedClient])
78
+
79
+ useEffect(() => fetchData(), [fetchData])
80
+
81
+ const handleRetryFetch = useCallback(() => fetchData(), [fetchData])
82
+
83
+ const isLoading = !users || !project
84
+
85
+ if (error) {
86
+ return (
87
+ <DashboardWidgetContainer header="Project users">
88
+ <Box padding={4}>
89
+ <Text>
90
+ Something went wrong while fetching data. You could{' '}
91
+ <a onClick={handleRetryFetch} title="Retry users fetch" style={{cursor: 'pointer'}}>
92
+ retry
93
+ </a>
94
+ ..?
95
+ </Text>
96
+ </Box>
97
+ </DashboardWidgetContainer>
98
+ )
99
+ }
100
+
101
+ return (
102
+ <DashboardWidgetContainer
103
+ header="Project users"
104
+ footer={
105
+ <Button
106
+ style={{width: '100%'}}
107
+ paddingX={2}
108
+ paddingY={4}
109
+ mode="bleed"
110
+ tone="primary"
111
+ text="Invite members"
112
+ as="a"
113
+ loading={isLoading}
114
+ href={isLoading ? undefined : getInviteUrl(project.id)}
115
+ />
116
+ }
117
+ >
118
+ {isLoading && (
119
+ <Box paddingY={5} paddingX={2}>
120
+ <Stack space={4}>
121
+ <Text align="center" muted size={1}>
122
+ <Spinner />
123
+ </Text>
124
+ <Text align="center" size={1} muted>
125
+ Loading items...
126
+ </Text>
127
+ </Stack>
128
+ </Box>
129
+ )}
130
+
131
+ {!isLoading && (
132
+ <Stack space={3} padding={3}>
133
+ {users?.map((user) => {
134
+ const membership = project.members.find((member) => member.id === user.id)
135
+ const media = membership?.isRobot ? (
136
+ <Text size={3}>
137
+ <RobotIcon />
138
+ </Text>
139
+ ) : (
140
+ <AvatarWrapper tone="transparent">
141
+ {user?.imageUrl && <img src={user.imageUrl} alt={user?.displayName} />}
142
+ </AvatarWrapper>
143
+ )
144
+ return (
145
+ <Box key={user.id}>
146
+ <DefaultPreview
147
+ title={user.displayName}
148
+ subtitle={membership?.role}
149
+ media={media}
150
+ />
151
+ </Box>
152
+ )
153
+ })}
154
+ </Stack>
155
+ )}
156
+ </DashboardWidgetContainer>
157
+ )
158
+ }
159
+
160
+ function sortUsersByRobotStatus(userA: User, userB: User, project: Project) {
161
+ const {members} = project
162
+ const membershipA = members.find((member) => member.id === userA?.id)
163
+ const membershipB = members.find((member) => member.id === userB?.id)
164
+ if (membershipA?.isRobot) {
165
+ return 1
166
+ }
167
+ if (membershipB?.isRobot) {
168
+ return -1
169
+ }
170
+ return 0
171
+ }
@@ -0,0 +1,10 @@
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
+ }
@@ -0,0 +1,77 @@
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
+ }
@@ -0,0 +1,111 @@
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
+ }
@@ -0,0 +1,48 @@
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
+ withCredentials: false,
42
+ })
43
+ },
44
+ urlBuilder: imageUrlBuilder(tutorialsProjectConfig),
45
+ }),
46
+ [versionedClient]
47
+ )
48
+ }
@@ -0,0 +1,10 @@
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
+ }
package/.babelrc DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "extends": "../../../.babelrc",
3
- "presets": ["@babel/react", "@babel/typescript"]
4
- }