From 021ff10202981fbb3037f5034a4934089acf0f09 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 18 Oct 2024 11:39:25 +0200 Subject: [PATCH] feat: add preview access sharing --- packages/presentation/package.json | 1 + .../presentation/src/PresentationTool.tsx | 7 +- .../src/PresentationToolGrantsCheck.tsx | 52 ++- .../presentation/src/preview/PreviewFrame.tsx | 7 + .../src/preview/SharePreviewMenu.tsx | 358 ++++++++++++------ packages/presentation/typings/sanity-ui.d.ts | 6 + packages/preview-url-secret/package.json | 9 + .../_exports/toggle-preview-access-sharing.ts | 1 + packages/preview-url-secret/src/constants.ts | 31 +- .../src/createPreviewSecret.ts | 14 +- .../debugUrlSecrets.tsx | 13 +- .../src/togglePreviewAccessSharing.ts | 41 ++ packages/preview-url-secret/src/types.ts | 9 + .../preview-url-secret/src/validateSecret.ts | 32 +- pnpm-lock.yaml | 55 +-- 15 files changed, 445 insertions(+), 191 deletions(-) create mode 100644 packages/presentation/typings/sanity-ui.d.ts create mode 100644 packages/preview-url-secret/src/_exports/toggle-preview-access-sharing.ts create mode 100644 packages/preview-url-secret/src/togglePreviewAccessSharing.ts diff --git a/packages/presentation/package.json b/packages/presentation/package.json index 7e844b977..9d470b5f3 100644 --- a/packages/presentation/package.json +++ b/packages/presentation/package.json @@ -49,6 +49,7 @@ "dependencies": { "@sanity/comlink": "workspace:*", "@sanity/icons": "^3.4.0", + "@sanity/logos": "^2.1.13", "@sanity/preview-url-secret": "workspace:*", "@sanity/ui": "^2.8.9", "@sanity/uuid": "3.0.2", diff --git a/packages/presentation/src/PresentationTool.tsx b/packages/presentation/src/PresentationTool.tsx index d0e07a9b1..163d0f227 100644 --- a/packages/presentation/src/PresentationTool.tsx +++ b/packages/presentation/src/PresentationTool.tsx @@ -83,8 +83,11 @@ const Container = styled(Flex)` export default function PresentationTool(props: { tool: Tool canCreateUrlPreviewSecrets: boolean + canToggleSharePreviewAccess: boolean + canUseSharedPreviewAccess: boolean }): ReactElement { - const {canCreateUrlPreviewSecrets, tool} = props + const {canCreateUrlPreviewSecrets, canToggleSharePreviewAccess, canUseSharedPreviewAccess, tool} = + props const components = tool.options?.components const _previewUrl = tool.options?.previewUrl const name = tool.name || DEFAULT_TOOL_NAME @@ -536,6 +539,8 @@ export default function PresentationTool(props: { (null) + const [previewAccessSharingCreatePermission, setCreateAccessSharingPermission] = + useState(null) + const [previewAccessSharingUpdatePermission, setUpdateAccessSharingPermission] = + useState(null) + const [previewAccessSharingReadPermission, setReadAccessSharingPermission] = + useState(null) const [previewUrlSecretPermission, setPreviewUrlSecretPermission] = useState(null) useEffect(() => { if (!willGeneratePreviewUrlSecret) return undefined - const draftPermissionSubscription = grantsStore - .checkDocumentPermission('create', {_id: schemaIdSingleton, _type: schemaType}) - .subscribe(setDraftPermission) + const previewCreateAccessSharingPermissionSubscription = grantsStore + .checkDocumentPermission('create', {_id: schemaIdSingleton, _type: schemaTypeSingleton}) + .subscribe(setCreateAccessSharingPermission) + const previewUpdateAccessSharingPermissionSubscription = grantsStore + .checkDocumentPermission('update', {_id: schemaIdSingleton, _type: schemaTypeSingleton}) + .subscribe(setUpdateAccessSharingPermission) + const previewReadAccessSharingPermissionSubscription = grantsStore + .checkDocumentPermission('read', {_id: schemaIdSingleton, _type: schemaTypeSingleton}) + .subscribe(setReadAccessSharingPermission) const previewUrlSecretPermissionSubscription = grantsStore .checkDocumentPermission('create', {_id: `${schemaIdPrefix}.${uuid()}`, _type: schemaType}) .subscribe(setPreviewUrlSecretPermission) return () => { - draftPermissionSubscription.unsubscribe() + previewCreateAccessSharingPermissionSubscription.unsubscribe() + previewUpdateAccessSharingPermissionSubscription.unsubscribe() + previewReadAccessSharingPermissionSubscription.unsubscribe() previewUrlSecretPermissionSubscription.unsubscribe() } }, [grantsStore, willGeneratePreviewUrlSecret]) - const canCreateUrlPreviewSecrets = draftPermission?.granted && previewUrlSecretPermission?.granted + const canCreateUrlPreviewSecrets = previewUrlSecretPermission?.granted useEffect(() => { if (!willGeneratePreviewUrlSecret || canCreateUrlPreviewSecrets !== false) return undefined @@ -54,13 +72,27 @@ export default function PresentationToolGrantsCheck(props: { if ( willGeneratePreviewUrlSecret && - (!draftPermission || - typeof draftPermission.granted === 'undefined' || + (!previewAccessSharingCreatePermission || + typeof previewAccessSharingCreatePermission.granted === 'undefined' || + !previewAccessSharingUpdatePermission || + typeof previewAccessSharingUpdatePermission.granted === 'undefined' || !previewUrlSecretPermission || + !previewAccessSharingReadPermission || + typeof previewAccessSharingReadPermission.granted === 'undefined' || typeof previewUrlSecretPermission.granted === 'undefined') ) { return } - return + return ( + + ) } diff --git a/packages/presentation/src/preview/PreviewFrame.tsx b/packages/presentation/src/preview/PreviewFrame.tsx index 15c163e51..02f429d48 100644 --- a/packages/presentation/src/preview/PreviewFrame.tsx +++ b/packages/presentation/src/preview/PreviewFrame.tsx @@ -79,7 +79,10 @@ const PERSPECTIVE_ICONS: Record = { } export interface PreviewFrameProps extends Pick { + // @TODO rename to share preview access is enabled in the config canSharePreviewAccess: boolean + canToggleSharePreviewAccess: boolean + canUseSharedPreviewAccess: boolean dispatch: DispatchPresentationAction initialUrl: URL loadersConnection: Status @@ -103,6 +106,8 @@ export const PreviewFrame = memo( function PreviewFrameComponent(props, forwardedRef) { const { canSharePreviewAccess, + canToggleSharePreviewAccess, + canUseSharedPreviewAccess, dispatch, iframe, initialUrl, @@ -562,6 +567,8 @@ export const PreviewFrame = memo( {canSharePreviewAccess && ( (null) - const busy = enabling || disabling + const [secret, setSecret] = useState(null) + const busy = enabling || disabling || loading + const url = useMemo( + () => + secret ? setSecretSearchParams(initialUrl, secret, previewLocationRoute, perspective) : null, + [initialUrl, perspective, previewLocationRoute, secret], + ) const [error, setError] = useState(null) if (error) { @@ -61,51 +93,47 @@ export const SharePreviewMenu = memo(function SharePreviewMenuComponent( // } // }, [busy, onClose]) + const handleUnableToToggle = useCallback(() => { + pushToast({ + closable: true, + status: 'warning', + title: `You don't have permissions to toggle sharing of this preview`, + }) + }, [pushToast]) + const handleDisableSharing = useCallback(async () => { try { setDisabling(true) - // await revokePreviewSecret(client, '@sanity/presentation', url.searchParams.get('secret')) - pushToast({ - closable: true, - status: 'warning', - title: `This isn't implemented yet. The link expires automatically after 1 hour.`, - }) - await new Promise((resolve) => setTimeout(resolve, 2_000)) - setUrl(null) + await disablePreviewAccessSharing( + client, + '@sanity/presentation', + typeof window === 'undefined' ? '' : location.href, + currentUser?.id, + ) + setSecret(null) } catch (error) { setError(error) } finally { setDisabling(false) } - }, [pushToast]) + }, [client, currentUser?.id]) const handleEnableSharing = useCallback(async () => { try { setEnabling(true) - // Preload the qr code logo - const qrCodeLogoImage = new Image() - qrCodeLogoImage.src = qrCodeLogo - - const previewUrlSecret = await createPreviewSecret( + const previewUrlSecret = await enablePreviewAccessSharing( client, '@sanity/presentation', typeof window === 'undefined' ? '' : location.href, currentUser?.id, ) - - const newUrl = setSecretSearchParams( - initialUrl, - previewUrlSecret.secret, - previewLocationRoute, - perspective, - ) - setUrl(newUrl) + setSecret(previewUrlSecret.secret) } catch (error) { setError(error) } finally { setEnabling(false) } - }, [client, currentUser?.id, initialUrl, perspective, previewLocationRoute]) + }, [client, currentUser?.id]) const handleCopyUrl = useCallback(() => { try { @@ -123,94 +151,188 @@ export const SharePreviewMenu = memo(function SharePreviewMenuComponent( } }, [pushToast, t, url]) - return ( - {t('preview-frame.share-button.tooltip')}} - fallbackPlacements={['bottom-start']} - padding={2} - placement="bottom" - portal - > - + useEffect(() => { + let controller = new AbortController() + let usedTags: SyncTag[] = [] + async function fetchShareSecret(lastLiveEventId: string | null, signal: AbortSignal) { + const {result, syncTags} = await client.fetch( + fetchSharedAccessQuery, + {}, + {filterResponse: false, lastLiveEventId, tag: 'presentation.fetch-shared-access-secret'}, + ) + if (Array.isArray(syncTags)) { + usedTags = syncTags + } + if (!signal.aborted) { + setSecret(result) + } + } + const subscription = client.live.events().subscribe({ + next: (event) => { + if (event.type === 'message') { + controller.abort() + controller = new AbortController() + if (event.tags.some((tag) => usedTags.includes(tag))) { + fetchShareSecret(event.id, controller.signal) + } } - id="share-menu" - menu={ - - - - - Share this preview - - - - with anyone who has the link - - - - - {url ? ( - - + }, + error: setError, + }) + + fetchShareSecret(null, controller.signal).finally(() => setLoading(false)) + + return () => { + subscription.unsubscribe() + controller.abort() + } + }, [client]) + + return ( + + } + id="share-menu" + menu={ + + {canUseSharedPreviewAccess ? ( + <> + + {t('preview-frame.overlay.toggle-button.tooltip', { + context: canToggleSharePreviewAccess ? 'disable' : 'enable', + })} + + } + fallbackPlacements={['bottom-start']} + padding={1} + placement="bottom" + portal + > + + + + + + {busy ? ( + + ) : url ? ( + <> + + + + ) : ( + + QR code will appear here + + )} - ) : null} - - Scan the QR Code to open the preview on your phone. - - - - - - - } - popover={{ - animate: true, - constrainSize: true, - placement: 'bottom', - portal: true, - }} - /> - + + Scan the QR Code to open the preview on your phone. + + + + + + + ) : ( + + You don't have permission to share previews. + + )} + + } + popover={{ + animate: true, + constrainSize: true, + placement: 'bottom', + portal: true, + }} + /> ) }) SharePreviewMenu.displayName = 'Memo(SharePreviewMenu)' diff --git a/packages/presentation/typings/sanity-ui.d.ts b/packages/presentation/typings/sanity-ui.d.ts new file mode 100644 index 000000000..46b60b46e --- /dev/null +++ b/packages/presentation/typings/sanity-ui.d.ts @@ -0,0 +1,6 @@ +import {type Theme} from '@sanity/ui' + +declare module 'styled-components' { + // eslint-disable-next-line + interface DefaultTheme extends Theme {} +} diff --git a/packages/preview-url-secret/package.json b/packages/preview-url-secret/package.json index 1e2f4fc96..3149c7716 100644 --- a/packages/preview-url-secret/package.json +++ b/packages/preview-url-secret/package.json @@ -51,6 +51,12 @@ "require": "./dist/sanity-plugin-debug-secrets.cjs", "default": "./dist/sanity-plugin-debug-secrets.js" }, + "./toggle-preview-access-sharing": { + "source": "./src/_exports/toggle-preview-access-sharing.ts", + "import": "./dist/toggle-preview-access-sharing.js", + "require": "./dist/toggle-preview-access-sharing.cjs", + "default": "./dist/toggle-preview-access-sharing.js" + }, "./without-secret-search-params": { "source": "./src/_exports/without-secret-search-params.ts", "import": "./dist/without-secret-search-params.js", @@ -79,6 +85,9 @@ "sanity-plugin-debug-secrets": [ "./dist/sanity-plugin-debug-secrets.d.ts" ], + "toggle-preview-access-sharing": [ + "./dist/toggle-preview-access-sharing.d.ts" + ], "without-secret-search-params": [ "./dist/without-secret-search-params.d.ts" ] diff --git a/packages/preview-url-secret/src/_exports/toggle-preview-access-sharing.ts b/packages/preview-url-secret/src/_exports/toggle-preview-access-sharing.ts new file mode 100644 index 000000000..51e25d947 --- /dev/null +++ b/packages/preview-url-secret/src/_exports/toggle-preview-access-sharing.ts @@ -0,0 +1 @@ +export * from '../togglePreviewAccessSharing' diff --git a/packages/preview-url-secret/src/constants.ts b/packages/preview-url-secret/src/constants.ts index 9025d246d..21eba4120 100644 --- a/packages/preview-url-secret/src/constants.ts +++ b/packages/preview-url-secret/src/constants.ts @@ -1,4 +1,8 @@ -import type {PreviewUrlSecretSchemaIdPrefix, PreviewUrlSecretSchemaType} from './types' +import type { + PreviewUrlSecretSchemaIdPrefix, + PreviewUrlSecretSchemaType, + PreviewUrlSecretSchemaTypeSingleton, +} from './types' /** @internal */ export const schemaType = 'sanity.previewUrlSecret' satisfies PreviewUrlSecretSchemaType @@ -7,7 +11,11 @@ export const schemaType = 'sanity.previewUrlSecret' satisfies PreviewUrlSecretSc export const schemaIdPrefix = 'sanity-preview-url-secret' satisfies PreviewUrlSecretSchemaIdPrefix /** @internal */ -export const schemaIdSingleton = `drafts.${schemaIdPrefix}` as const +export const schemaIdSingleton = `${schemaIdPrefix}.share-access` as const + +/** @internal */ +export const schemaTypeSingleton = + 'sanity.previewUrlShareAccess' satisfies PreviewUrlSecretSchemaTypeSingleton /** @internal */ export const apiVersion = '2023-11-09' @@ -32,16 +40,27 @@ export const SECRET_TTL = 60 * 60 /** @internal */ export const fetchSecretQuery = - /* groq */ `*[_type == "${schemaType}" && _id in path("${schemaIdPrefix}.**") && secret == $secret && dateTime(_updatedAt) > dateTime(now()) - ${SECRET_TTL}][0]{ - _id, - _updatedAt, + /* groq */ `*[_type == "${schemaType}" && secret == $secret && dateTime(_updatedAt) > dateTime(now()) - ${SECRET_TTL}][0]{ + _id, + _updatedAt, + secret, + studioUrl, + }` as const + +/** @internal */ +export const fetchSharedAccessQuery = + /* groq */ `*[_id == "${schemaIdSingleton}" && _type == "${schemaTypeSingleton}"][0].secret` as const + +/** @internal */ +export const fetchSharedAccessSecretQuery = + /* groq */ `*[_id == "${schemaIdSingleton}" && _type == "${schemaTypeSingleton}" && secret == $secret][0]{ secret, studioUrl, }` as const /** @internal */ export const deleteExpiredSecretsQuery = - /* groq */ `*[_type == "${schemaType}" && _id in path("${schemaIdPrefix}.**") && defined(secret) && dateTime(_updatedAt) <= dateTime(now()) - ${SECRET_TTL}]` as const + /* groq */ `*[_type == "${schemaType}" && dateTime(_updatedAt) <= dateTime(now()) - ${SECRET_TTL}]` as const /** * Used for tagging `client.fetch` queries diff --git a/packages/preview-url-secret/src/createPreviewSecret.ts b/packages/preview-url-secret/src/createPreviewSecret.ts index 6d5a9a357..2ce880a09 100644 --- a/packages/preview-url-secret/src/createPreviewSecret.ts +++ b/packages/preview-url-secret/src/createPreviewSecret.ts @@ -4,7 +4,6 @@ import { apiVersion, deleteExpiredSecretsQuery, schemaIdPrefix, - schemaIdSingleton, schemaType, SECRET_TTL, tag, @@ -27,18 +26,7 @@ export async function createPreviewSecret( const _id = `${schemaIdPrefix}.${id}` const newSecret = generateUrlSecret() const patch = client.patch(_id).set({secret: newSecret, source, studioUrl, userId}) - await client - .transaction() - .createIfNotExists({ - _id: schemaIdSingleton, - _type: schemaType, - source, - studioUrl, - userId, - }) - .createOrReplace({_id, _type: schemaType}) - .patch(patch) - .commit({tag}) + await client.transaction().createOrReplace({_id, _type: schemaType}).patch(patch).commit({tag}) return {secret: newSecret, expiresAt} } finally { diff --git a/packages/preview-url-secret/src/sanityPluginDebugSecrets/debugUrlSecrets.tsx b/packages/preview-url-secret/src/sanityPluginDebugSecrets/debugUrlSecrets.tsx index 9cd881cf2..f1b4b0910 100644 --- a/packages/preview-url-secret/src/sanityPluginDebugSecrets/debugUrlSecrets.tsx +++ b/packages/preview-url-secret/src/sanityPluginDebugSecrets/debugUrlSecrets.tsx @@ -1,6 +1,6 @@ -import {ApiIcon, CheckmarkCircleIcon, CloseCircleIcon, LockIcon} from '@sanity/icons' +import {CheckmarkCircleIcon, CloseCircleIcon, LockIcon} from '@sanity/icons' import {defineType} from 'sanity' -import {schemaIdSingleton, schemaType, SECRET_TTL} from '../constants' +import {schemaType, SECRET_TTL} from '../constants' export const debugUrlSecretsType = defineType({ type: 'document', @@ -13,7 +13,6 @@ export const debugUrlSecretsType = defineType({ type: 'string', name: 'secret', title: 'Secret', - hidden: ({document}) => document?._id === schemaIdSingleton, }, { type: 'string', @@ -33,19 +32,11 @@ export const debugUrlSecretsType = defineType({ ], preview: { select: { - _id: '_id', source: 'source', studioUrl: 'studioUrl', updatedAt: '_updatedAt', }, prepare(data) { - if (data?._id === schemaIdSingleton) { - return { - title: '@sanity/preview-url-secret is setup correctly', - subtitle: 'Never expires', - media: ApiIcon, - } - } const url = data.studioUrl ? new URL(data.studioUrl, location.origin) : undefined const updatedAt = new Date(data.updatedAt).getTime() const expiresAt = new Date(updatedAt + 1000 * SECRET_TTL) diff --git a/packages/preview-url-secret/src/togglePreviewAccessSharing.ts b/packages/preview-url-secret/src/togglePreviewAccessSharing.ts new file mode 100644 index 000000000..96c8a0985 --- /dev/null +++ b/packages/preview-url-secret/src/togglePreviewAccessSharing.ts @@ -0,0 +1,41 @@ +import type {SanityClient} from '@sanity/client' +import {schemaIdSingleton as _id, schemaTypeSingleton as _type, apiVersion, tag} from './constants' +import {generateUrlSecret} from './generateSecret' +import type {SanityClientLike} from './types' + +/** @internal */ +export async function enablePreviewAccessSharing( + _client: SanityClient, + source: string, + studioUrl: string, + userId?: string, +): Promise<{secret: string}> { + const client = _client.withConfig({apiVersion}) + const newSecret = generateUrlSecret() + const patch = client.patch(_id).set({secret: newSecret, studioUrl, userId}) + await client + .transaction() + .createIfNotExists({_id, _type, source, studioUrl, userId}) + .patch(patch) + .commit({tag}) + + return {secret: newSecret} +} + +/** @internal */ +export async function disablePreviewAccessSharing( + _client: SanityClient, + source: string, + studioUrl: string, + userId?: string, +): Promise { + const client = _client.withConfig({apiVersion}) + const patch = client.patch(_id).set({secret: null, studioUrl, userId}) + await client + .transaction() + .createIfNotExists({_id, _type, source, studioUrl, userId}) + .patch(patch) + .commit({tag}) +} + +export type {SanityClientLike} diff --git a/packages/preview-url-secret/src/types.ts b/packages/preview-url-secret/src/types.ts index f283e50f4..980e35461 100644 --- a/packages/preview-url-secret/src/types.ts +++ b/packages/preview-url-secret/src/types.ts @@ -11,6 +11,9 @@ export type PreviewUrlSecretSchemaIdType = /** @internal */ export type PreviewUrlSecretSchemaType = `sanity.previewUrlSecret` +/** @internal */ +export type PreviewUrlSecretSchemaTypeSingleton = `sanity.previewUrlShareAccess` + /** * A subset type that's compatible with most SanityClient typings, * this makes it easier to use this package in libraries that may use `import type { SanityClient } from 'sanity'` @@ -141,6 +144,12 @@ export type FetchSecretQueryResponse = { studioUrl: string | null } | null +/** @internal */ +export type FetchPublicSecretQueryResponse = { + secret: string | null + studioUrl: string | null +} | null + /** @internal */ export interface PreviewUrlResolverContext { client: SanityClientType diff --git a/packages/preview-url-secret/src/validateSecret.ts b/packages/preview-url-secret/src/validateSecret.ts index 6aafa072b..2ed4e5db0 100644 --- a/packages/preview-url-secret/src/validateSecret.ts +++ b/packages/preview-url-secret/src/validateSecret.ts @@ -1,5 +1,10 @@ -import {fetchSecretQuery, tag} from './constants' -import type {FetchSecretQueryParams, FetchSecretQueryResponse, SanityClientLike} from './types' +import {fetchSecretQuery, fetchSharedAccessSecretQuery, tag} from './constants' +import type { + FetchPublicSecretQueryResponse, + FetchSecretQueryParams, + FetchSecretQueryResponse, + SanityClientLike, +} from './types' export type {SanityClientLike} @@ -17,8 +22,17 @@ export async function validateSecret( if (!secret || !secret.trim()) { return {isValid: false, studioUrl: null} } - const result = await client.fetch( - fetchSecretQuery, + const {private: privateSecret, public: publicSecret} = await client.fetch< + { + private: FetchSecretQueryResponse + public: FetchPublicSecretQueryResponse + }, + FetchSecretQueryParams + >( + `{ + "private": ${fetchSecretQuery}, + "public": ${fetchSharedAccessSecretQuery} + }`, {secret: secret}, { tag, @@ -26,8 +40,14 @@ export async function validateSecret( ...(!disableCacheNoStore ? {cache: 'no-store'} : undefined), }, ) - if (!result?._id || !result?._updatedAt || !result?.secret) { + if (privateSecret) { + if (!privateSecret?._id || !privateSecret?._updatedAt || !privateSecret?.secret) { + return {isValid: false, studioUrl: null} + } + return {isValid: secret === privateSecret.secret, studioUrl: privateSecret.studioUrl} + } + if (!publicSecret?.secret) { return {isValid: false, studioUrl: null} } - return {isValid: secret === result.secret, studioUrl: result.studioUrl} + return {isValid: secret === publicSecret.secret, studioUrl: publicSecret.studioUrl} } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b13e2cb3..d04455c25 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,7 +72,7 @@ importers: version: 0.9.4(prettier-plugin-astro@0.14.1)(prettier@3.3.3)(typescript@5.6.3) '@astrojs/react': specifier: ^3.6.2 - version: 3.6.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@4.5.3(@types/node@22.5.5)(terser@5.33.0)) + version: 3.6.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@5.4.9(@types/node@22.5.5)(terser@5.33.0)) '@astrojs/tailwind': specifier: ^5.1.2 version: 5.1.2(astro@4.16.6(@types/node@22.5.5)(rollup@4.24.0)(terser@5.33.0)(typescript@5.6.3))(tailwindcss@3.4.14) @@ -269,7 +269,7 @@ importers: version: 0.1.2 '@vercel/toolbar': specifier: ^0.1.22 - version: 0.1.22(next@14.2.15(@babel/core@7.25.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(vite@5.4.9(@types/node@20.8.7)(terser@5.33.0)) + version: 0.1.22(next@14.2.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(vite@5.4.9(@types/node@20.8.7)(terser@5.33.0)) autoprefixer: specifier: 10.4.20 version: 10.4.20(postcss@8.4.47) @@ -1006,7 +1006,7 @@ importers: version: 8.57.1 eslint-import-resolver-typescript: specifier: ^3.6.3 - version: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + version: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-plugin-import: specifier: ^2.31.0 version: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) @@ -1179,7 +1179,7 @@ importers: version: 3.1.3 next: specifier: ^14.2.15 - version: 14.2.15(@babel/core@7.25.8)(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@18.3.1))(react@18.3.1) + version: 14.2.15(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@18.3.1))(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -1198,6 +1198,9 @@ importers: '@sanity/icons': specifier: ^3.4.0 version: 3.4.0(react@18.3.1) + '@sanity/logos': + specifier: ^2.1.13 + version: 2.1.13(@sanity/color@3.0.6)(react@18.3.1) '@sanity/preview-url-secret': specifier: workspace:* version: link:../preview-url-secret @@ -13753,11 +13756,11 @@ snapshots: dependencies: prismjs: 1.29.0 - '@astrojs/react@3.6.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@4.5.3(@types/node@22.5.5)(terser@5.33.0))': + '@astrojs/react@3.6.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@5.4.9(@types/node@22.5.5)(terser@5.33.0))': dependencies: '@types/react': 18.3.11 '@types/react-dom': 18.3.1 - '@vitejs/plugin-react': 4.3.1(vite@4.5.3(@types/node@22.5.5)(terser@5.33.0)) + '@vitejs/plugin-react': 4.3.1(vite@5.4.9(@types/node@22.5.5)(terser@5.33.0)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) ultrahtml: 1.5.3 @@ -16233,7 +16236,7 @@ snapshots: '@polka/url@1.0.0-next.25': {} - '@portabletext/editor@1.1.4(@sanity/block-tools@3.61.0(debug@4.3.7))(@sanity/schema@3.61.0(debug@4.3.7))(@sanity/types@3.61.0(debug@4.3.7))(@sanity/util@3.61.0(debug@4.3.7))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rxjs@7.8.1)(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': + '@portabletext/editor@1.1.4(@sanity/block-tools@3.61.0(debug@4.3.7))(@sanity/schema@3.61.0(debug@4.3.7))(@sanity/types@3.61.0)(@sanity/util@3.61.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rxjs@7.8.1)(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': dependencies: '@portabletext/patches': 1.1.0 '@sanity/block-tools': 3.61.0(debug@4.3.7) @@ -16409,7 +16412,7 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jest: 26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) eslint-plugin-jest-dom: 4.0.3(eslint@8.57.1) @@ -17013,7 +17016,7 @@ snapshots: react-copy-to-clipboard: 5.1.0(react@19.0.0-rc-fb9a90fa48-20240614) react-dom: 19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614) - '@sanity/insert-menu@1.0.9(@sanity/types@3.61.0(debug@4.3.7))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': + '@sanity/insert-menu@1.0.9(@sanity/types@3.61.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': dependencies: '@sanity/icons': 3.4.0(react@18.3.1) '@sanity/types': 3.61.0(debug@4.3.7) @@ -18752,7 +18755,7 @@ snapshots: '@vercel/stega@0.1.2': {} - '@vercel/toolbar@0.1.22(next@14.2.15(@babel/core@7.25.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(vite@5.4.9(@types/node@20.8.7)(terser@5.33.0))': + '@vercel/toolbar@0.1.22(next@14.2.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(vite@5.4.9(@types/node@20.8.7)(terser@5.33.0))': dependencies: '@tinyhttp/app': 1.3.0 chokidar: 3.6.0 @@ -21044,7 +21047,7 @@ snapshots: '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) eslint-plugin-react: 7.37.1(eslint@8.57.1) @@ -21064,7 +21067,7 @@ snapshots: '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) eslint-plugin-react: 7.37.1(eslint@8.57.1) @@ -21101,13 +21104,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.31.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -21120,7 +21123,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.7 @@ -21139,25 +21142,25 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -21168,7 +21171,7 @@ snapshots: '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -21189,7 +21192,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -24619,7 +24622,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@14.2.15(@babel/core@7.25.8)(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@18.3.1))(react@18.3.1): + next@14.2.15(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 14.2.15 '@swc/helpers': 0.5.5 @@ -26911,7 +26914,7 @@ snapshots: '@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) '@dnd-kit/utilities': 3.2.2(react@18.3.1) '@juggle/resize-observer': 3.4.0 - '@portabletext/editor': 1.1.4(@sanity/block-tools@3.61.0(debug@4.3.7))(@sanity/schema@3.61.0(debug@4.3.7))(@sanity/types@3.61.0(debug@4.3.7))(@sanity/util@3.61.0(debug@4.3.7))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rxjs@7.8.1)(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + '@portabletext/editor': 1.1.4(@sanity/block-tools@3.61.0(debug@4.3.7))(@sanity/schema@3.61.0(debug@4.3.7))(@sanity/types@3.61.0)(@sanity/util@3.61.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rxjs@7.8.1)(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) '@portabletext/react': 3.1.0(react@18.3.1) '@rexxars/react-json-inspector': 8.0.1(react@18.3.1) '@sanity/asset-utils': 2.0.6 @@ -26927,7 +26930,7 @@ snapshots: '@sanity/icons': 3.4.0(react@18.3.1) '@sanity/image-url': 1.0.2 '@sanity/import': 3.37.5 - '@sanity/insert-menu': 1.0.9(@sanity/types@3.61.0(debug@4.3.7))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + '@sanity/insert-menu': 1.0.9(@sanity/types@3.61.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) '@sanity/logos': 2.1.13(@sanity/color@3.0.6)(react@18.3.1) '@sanity/migrate': 3.61.0 '@sanity/mutator': 3.61.0 @@ -27047,7 +27050,7 @@ snapshots: '@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) '@dnd-kit/utilities': 3.2.2(react@18.3.1) '@juggle/resize-observer': 3.4.0 - '@portabletext/editor': 1.1.4(@sanity/block-tools@3.61.0(debug@4.3.7))(@sanity/schema@3.61.0(debug@4.3.7))(@sanity/types@3.61.0(debug@4.3.7))(@sanity/util@3.61.0(debug@4.3.7))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rxjs@7.8.1)(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + '@portabletext/editor': 1.1.4(@sanity/block-tools@3.61.0(debug@4.3.7))(@sanity/schema@3.61.0(debug@4.3.7))(@sanity/types@3.61.0)(@sanity/util@3.61.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rxjs@7.8.1)(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) '@portabletext/react': 3.1.0(react@18.3.1) '@rexxars/react-json-inspector': 8.0.1(react@18.3.1) '@sanity/asset-utils': 2.0.6 @@ -27063,7 +27066,7 @@ snapshots: '@sanity/icons': 3.4.0(react@18.3.1) '@sanity/image-url': 1.0.2 '@sanity/import': 3.37.5 - '@sanity/insert-menu': 1.0.9(@sanity/types@3.61.0(debug@4.3.7))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + '@sanity/insert-menu': 1.0.9(@sanity/types@3.61.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) '@sanity/logos': 2.1.13(@sanity/color@3.0.6)(react@18.3.1) '@sanity/migrate': 3.61.0 '@sanity/mutator': 3.61.0