From a01e95ba87f30b2d745a14ced972a62a8ed6c22c Mon Sep 17 00:00:00 2001 From: Viet Nguyen <3805254+vnugent@users.noreply.github.com> Date: Sun, 2 Jul 2023 23:53:09 +0200 Subject: [PATCH] refactor: restore tagging feature (#869) * refactor: restore tagging feature * wire api with infinity scroll * refactor: consolidate media pagination/tagging in one hook * refactor: get rid of zustood store for tags in favor of Apollo client cache * refactor: maintain pagination data in gallery component * feat: make the list of profile pre-builds customizable --- .env | 3 + __mocks__/next-auth/react.js | 6 + package.json | 4 +- src/components/UploadPhotoTrigger.tsx | 15 +- src/components/media/AddTag.tsx | 29 ++-- src/components/media/MobileMediaCard.tsx | 69 +++----- src/components/media/Tag.tsx | 11 +- src/components/media/TagList.tsx | 75 +++++++-- src/components/media/UserGallery.tsx | 93 +++++++---- .../media/__tests__/MobileMediaCard.tsx | 7 +- .../media/__tests__/MobilePopupTagMenu.tsx | 9 +- .../media/__tests__/UserGallery.tsx | 20 ++- src/components/media/__tests__/data.ts | 17 +- src/js/graphql/Client.ts | 35 ++++ src/js/graphql/gql/tags.ts | 122 +++++++++----- src/js/graphql/gql/users.ts | 17 +- src/js/hooks/auth/usePermissions.ts | 1 - src/js/hooks/useDeleteTagBackend.tsx | 54 ------ src/js/hooks/useMediaCmd.tsx | 158 ++++++++++++++++++ src/js/hooks/useMediaDS.ts | 41 ----- src/js/hooks/usePhotoTagCmd.tsx | 36 ---- src/js/stores/media.ts | 58 +------ src/js/types.ts | 17 ++ src/js/types/User.ts | 4 +- src/js/utils.ts | 12 +- src/pages/_app.tsx | 1 - src/pages/api/withAuth.ts | 2 - src/pages/u/[...slug].tsx | 92 +++++----- yarn.lock | 75 +++++---- 29 files changed, 637 insertions(+), 446 deletions(-) create mode 100644 __mocks__/next-auth/react.js delete mode 100644 src/js/hooks/useDeleteTagBackend.tsx create mode 100644 src/js/hooks/useMediaCmd.tsx delete mode 100644 src/js/hooks/useMediaDS.ts delete mode 100644 src/js/hooks/usePhotoTagCmd.tsx diff --git a/.env b/.env index 5c0c7b7f6..f1d98ca90 100644 --- a/.env +++ b/.env @@ -34,3 +34,6 @@ NEXT_PUBLIC_MAPBOX_API_KEY=pk.eyJ1IjoibWFwcGFuZGFzIiwiYSI6ImNsZG1wcnBhZTA5eXozb3 # Open Collective API URL OPEN_COLLECTIVE_API_URI=https://api.opencollective.com/graphql/v2 + +# A comma-separate-list of profiles to pre-build +PREBUILD_PROFILES= \ No newline at end of file diff --git a/__mocks__/next-auth/react.js b/__mocks__/next-auth/react.js new file mode 100644 index 000000000..3887904ab --- /dev/null +++ b/__mocks__/next-auth/react.js @@ -0,0 +1,6 @@ +export function useSession () { + return { + status: 'authenticated', + data: {} + } +} diff --git a/package.json b/package.json index 2806e05d4..940efda08 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dependencies": { "@algolia/autocomplete-js": "1.7.1", "@algolia/autocomplete-theme-classic": "1.7.1", - "@apollo/client": "^3.6.9", + "@apollo/client": "^3.7.16", "@dnd-kit/core": "^6.0.8", "@dnd-kit/sortable": "^7.0.2", "@dnd-kit/utilities": "^3.2.1", @@ -27,7 +27,7 @@ "@turf/bbox": "^6.5.0", "@types/underscore": "^1.11.4", "@types/uuid": "^8.3.4", - "@udecode/zustood": "^0.4.4", + "@udecode/zustood": "^1.1.3", "auth0": "^2.42.0", "awesome-debounce-promise": "^2.1.0", "aws-sdk": "^2.1265.0", diff --git a/src/components/UploadPhotoTrigger.tsx b/src/components/UploadPhotoTrigger.tsx index 4c8235dab..c5e76ebaa 100644 --- a/src/components/UploadPhotoTrigger.tsx +++ b/src/components/UploadPhotoTrigger.tsx @@ -7,8 +7,6 @@ import clx from 'classnames' import usePhotoUploader from '../js/hooks/usePhotoUploader' import { userMediaStore, revalidateUserHomePage } from '../js/stores/media' import useReturnToProfile from '../js/hooks/useReturnToProfile' -import usePhotoTag from '../js/hooks/usePhotoTagCmd' -import { mediaUrlHash } from '../js/sirv/SirvClient' import { BlockingAlert } from './ui/micro/AlertDialogue' interface UploadPhotoTriggerProps { @@ -35,7 +33,6 @@ export default function UploadPhotoTrigger ({ className = '', onUploaded, childr const sessionRef = useRef() sessionRef.current = data?.user - const { tagPhotoCmd } = usePhotoTag() const { toMyProfile } = useReturnToProfile() const onUploadedHannder = async (url: string): Promise => { @@ -53,12 +50,12 @@ export default function UploadPhotoTrigger ({ className = '', onUploaded, childr // let's see if we're viewing the climb or area page if (id != null && isValidUuid(id) && (destType === 0 || destType === 1)) { // yes! let's tag it - await tagPhotoCmd({ - mediaUrl: url, - mediaUuid: mediaUrlHash(url), - destinationId: id, - destType - }) + // await tagPhotoCmd({ + // mediaUrl: url, + // mediaUuid: mediaUrlHash(url), + // destinationId: id, + // destType + // }) if (onUploaded != null) onUploaded() diff --git a/src/components/media/AddTag.tsx b/src/components/media/AddTag.tsx index 9397d37e9..1d7caa1fc 100644 --- a/src/components/media/AddTag.tsx +++ b/src/components/media/AddTag.tsx @@ -2,13 +2,14 @@ import { PlusIcon } from '@heroicons/react/24/outline' import ClimbSearchForTagging from '../search/ClimbSearchForTagging' import { EntityType, MediaWithTags, TagTargetType, TypesenseAreaType, TypesenseDocumentType } from '../../js/types' -import usePhotoTagCmd from '../../js/hooks/usePhotoTagCmd' +import { AddEntityTagProps } from '../../js/graphql/gql/tags' interface ImageTaggerProps { mediaWithTags: MediaWithTags label?: JSX.Element openSearch?: boolean onCancel?: () => void + onAdd: (props: AddEntityTagProps) => Promise } /** @@ -16,29 +17,21 @@ interface ImageTaggerProps { * @param label A button that opens the climb search * @param imageInfo image info object */ -export default function AddTag ({ mediaWithTags, onCancel, label, openSearch = false }: ImageTaggerProps): JSX.Element | null { - const { tagPhotoCmd } = usePhotoTagCmd() +export default function AddTag ({ mediaWithTags, onCancel, onAdd, label, openSearch = false }: ImageTaggerProps): JSX.Element | null { return ( { - try { - const linkedEntityId = props.type === EntityType.climb - ? (props as TypesenseDocumentType).climbUUID - : (props as TypesenseAreaType).id - - await tagPhotoCmd({ - mediaUuid: mediaWithTags.mediaUrl, - mediaUrl: mediaWithTags.mediaUrl, - destinationId: linkedEntityId, - destType: props.type === EntityType.climb ? TagTargetType.climb : TagTargetType.area - }) - } catch (e) { - // TODO: Add friendly error message - console.log('tagging API error', e) - } + const linkedEntityId = props.type === EntityType.climb + ? (props as TypesenseDocumentType).climbUUID + : (props as TypesenseAreaType).id + void onAdd({ + mediaId: mediaWithTags.id, + entityId: linkedEntityId, + entityType: props.type === EntityType.climb ? TagTargetType.climb : TagTargetType.area + }) }} /> ) diff --git a/src/components/media/MobileMediaCard.tsx b/src/components/media/MobileMediaCard.tsx index 005e7b9ae..448165d0c 100644 --- a/src/components/media/MobileMediaCard.tsx +++ b/src/components/media/MobileMediaCard.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react' import Card from '../ui/Card/Card' import TagList, { MobilePopupTagList } from './TagList' @@ -15,8 +16,19 @@ export interface MobileMediaCardProps { isAuthenticated?: boolean } -export default function MobileMediaCard ({ header, showTagActions = false, isAuthorized = false, isAuthenticated = false, mediaWithTags }: MobileMediaCardProps): JSX.Element { - const { mediaUrl, entityTags, uploadTime } = mediaWithTags +/** + * Media card for mobile view + */ +export default function MobileMediaCard ({ header, isAuthorized = false, isAuthenticated = false, mediaWithTags }: MobileMediaCardProps): JSX.Element { + /** + * Why maintaining media object in a local state? + * Normally, this component receives tag data via props. However, when the media owner + * adds/removes tags, after the backend is updated, we also update the media object + * in Apollo cache and keep the updated state here. This way we only need to deal + * with a single media instead a large list. + */ + const [localMediaWithTags, setMedia] = useState(mediaWithTags) + const { mediaUrl, entityTags, uploadTime } = localMediaWithTags const tagCount = entityTags.length return (
 
- + } body={ @@ -41,8 +57,10 @@ export default function MobileMediaCard ({ header, showTagActions = false, isAut {tagCount > 0 && ( @@ -56,44 +74,3 @@ export default function MobileMediaCard ({ header, showTagActions = false, isAut /> ) } - -// interface RecentImageCardProps { -// header?: JSX.Element -// imageInfo: MediaType -// tagList: HybridMediaTag[] -// } - -// export const RecentImageCard = ({ header, imageInfo, tagList }: RecentImageCardProps): JSX.Element => { -// return ( -// } -// image={ -// -// } -// body={ -// <> -//
-// -//
-// {getUploadDateSummary(imageInfo.ctime)} -//
- -//
-// -// } -// /> -// ) -// } diff --git a/src/components/media/Tag.tsx b/src/components/media/Tag.tsx index 8af50888e..84887bc73 100644 --- a/src/components/media/Tag.tsx +++ b/src/components/media/Tag.tsx @@ -4,16 +4,18 @@ import NetworkSquareIcon from '../../assets/icons/network-square-icon.svg' import clx from 'classnames' import { EntityTag, TagTargetType } from '../../js/types' +import { OnDeleteCallback } from './TagList' interface PhotoTagProps { + mediaId: string tag: EntityTag - onDelete: (tagId: string) => void + onDelete: OnDeleteCallback isAuthorized?: boolean showDelete?: boolean size?: 'md' | 'lg' } -export default function Tag ({ tag, onDelete, size = 'md', showDelete = false, isAuthorized = false }: PhotoTagProps): JSX.Element | null { +export default function Tag ({ mediaId, tag, onDelete, size = 'md', showDelete = false, isAuthorized = false }: PhotoTagProps): JSX.Element | null { const [url, name] = resolver(tag) if (url == null || name == null) return null const isArea = tag.type === TagTargetType.area @@ -34,10 +36,9 @@ export default function Tag ({ tag, onDelete, size = 'md', showDelete = false, i
{name}
{isAuthorized && showDelete && the browser) + } + } + ) + + /** + * Remove an entity tag from a media + */ + const removeEntityTagCmd: RemoveEntityTagCmd = async ({ mediaId, tagId }) => { + try { + const res = await removeEntityTagGQL({ + variables: { + mediaId, + tagId + }, + context: { + headers: { + authorization: `Bearer ${session.data?.accessToken ?? ''}` + } + } + }) + + // refetch the media object to update local cache + const mediaRes = await getMediaById(mediaId) + return [res.data?.removeEntityTag ?? false, mediaRes] + } catch { + return [false, null] + } + } + + return { fetchMoreMediaForward, getMediaById, addEntityTagCmd, removeEntityTagCmd } +} diff --git a/src/js/hooks/useMediaDS.ts b/src/js/hooks/useMediaDS.ts deleted file mode 100644 index 0e31fe6d5..000000000 --- a/src/js/hooks/useMediaDS.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { useEffect } from 'react' -import { MediaType } from '../types' - -import { userMediaStore } from '../stores/media' - -interface MediaStoreHookProp { - isAuthorized: boolean - uid: string - serverMediaList: MediaType[] -} - -interface MediaStoreHookResult { - mediaList: MediaType[] - // tagMap: Dictionary - // singleTagList: HybridMediaTag[] // For single view page -} -/** - * A React hook for initializing user media datastore. - * By mirroring image and tag state on the client, we can provide a more - * interactive UI (using local state), and at the same time be able to - * take advantage of NextJS server-side page generation (using DB data). - */ -export default function useMediaDS ({ isAuthorized, uid, serverMediaList }: MediaStoreHookProp): MediaStoreHookResult { - useEffect(() => { - if (isAuthorized && !userMediaStore.get.initialized()) { - // Load server side image data into local state for client-side add/remove - userMediaStore.set.initialized(true) - userMediaStore.set.imageList(serverMediaList) - userMediaStore.set.uid(uid) - } - }, [isAuthorized]) - - const clientSideList = userMediaStore.use.imageList() - - const mediaList = isAuthorized ? clientSideList : serverMediaList - - return { - mediaList - // singleTagList: (mediaList?.length ?? 0) === 1 ? tagMap[mediaList[0]?.mediaId] : [] - } -} diff --git a/src/js/hooks/usePhotoTagCmd.tsx b/src/js/hooks/usePhotoTagCmd.tsx deleted file mode 100644 index 2fe946f6a..000000000 --- a/src/js/hooks/usePhotoTagCmd.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useMutation } from '@apollo/client' - -import { graphqlClient } from '../graphql/Client' -import { MUTATION_ADD_CLIMB_TAG_TO_MEDIA, SetTagType } from '../graphql/gql/tags' -import { actions } from '../stores' - -export interface UsePhotTagReturn { - tagPhotoCmd: (props: SetTagType) => Promise -} - -/** - * A React hook for handling photo tagging. - * Todo: Move `useDeleteTagBanckend()` here. - */ -export default function usePhotoTagCmd (): UsePhotTagReturn { - const addTagToLocalStore = async (data: any): Promise => await actions.media.addTag(data) - - // eslint-disable-next-line - const [tagPhoto] = useMutation( - MUTATION_ADD_CLIMB_TAG_TO_MEDIA, { - client: graphqlClient, - errorPolicy: 'none', - onError: error => console.log('Error adding tag: ', error.message), // Todo: send a Toast message - onCompleted: addTagToLocalStore // Todo: send a Toast message - } - ) - - const tagPhotoCmd = async (props: SetTagType): Promise => { - console.log('Tagging temporarily disabled') - // await tagPhoto({ - // variables: props - // }) - } - - return { tagPhotoCmd } -} diff --git a/src/js/stores/media.ts b/src/js/stores/media.ts index bb9e50374..ede0d1d4d 100644 --- a/src/js/stores/media.ts +++ b/src/js/stores/media.ts @@ -1,22 +1,12 @@ import { createStore } from '@udecode/zustood' import { v5 as uuidv5 } from 'uuid' -import { Dictionary } from 'underscore' import produce from 'immer' -import type { MediaType, HybridMediaTag, MediaWithTags } from '../../js/types' -import { DeleteTagResult } from '../hooks/useDeleteTagBackend' +import type { MediaType, MediaWithTags } from '../../js/types' interface UserMediaStateProps { uid: string | null imageList: MediaType[] - /** - * A map of \: \ - * - * Why use array of tags where JS `Set` would have been a better choice - * for handling dups? - * Because we use `underscore.groupBy()` to process server-side tags. - */ - tagMap: Dictionary initialized: boolean photoUploadErrorMessage: string | null photoList: MediaWithTags[] @@ -25,7 +15,6 @@ interface UserMediaStateProps { const INITIAL_STATE: UserMediaStateProps = { uid: null, imageList: [], - tagMap: {}, initialized: false, photoUploadErrorMessage: null, photoList: [] @@ -93,51 +82,6 @@ export const userMediaStore = createStore('userMedia')(INITIAL_STATE, STORE_OPTS set.imageList(updatedList) await revalidateUserHomePage(get.uid()) } - })).extendActions((set, get, api) => ({ - /** - * Add a new tag to local store - */ - addTag: async (data: any) => { - const { setTag } = data - if (setTag == null) return - const { mediaUuid } = setTag - - const newState = produce>(get.tagMap(), draft => { - const currentTagList = draft?.[mediaUuid] ?? [] - if (currentTagList.length === 0) { - draft[mediaUuid] = [setTag] - } else { - draft[mediaUuid].push(setTag) - } - return draft - }) - - set.tagMap(newState) - await revalidateUserHomePage(get.uid()) - }, - /** - * Remove a tag from local store - */ - removeTag: async ({ id, mediaUuid }: DeleteTagResult) => { - // Let's see if the media exists in local store? - if ((get.tagMap()?.[mediaUuid] ?? null) == null) { - // Media doesn't exist. Do nothing. - return - } - - // find the tag by id and remove it - const newState = produce>(get.tagMap(), draft => { - const idx = draft[mediaUuid].findIndex((tag: HybridMediaTag) => tag.id === id) - if (idx > -1) { - draft[mediaUuid].splice(idx, 1) - } - return draft - }) - - set.tagMap(newState) - // rebuild user home page - await revalidateUserHomePage(get.uid()) - } })).extendActions((set, get, api) => ({ /** * diff --git a/src/js/types.ts b/src/js/types.ts index 056bfbe08..3de256d6e 100644 --- a/src/js/types.ts +++ b/src/js/types.ts @@ -434,3 +434,20 @@ export interface Username { username: string lastUpdated: Date } + +export interface UserMedia { + userUuid: string + mediaConnection: MediaConnection +} + +export interface MediaConnection { + edges: MediaEdge[] + pageInfo: { + hasNextPage: boolean + endCursor: string + } +} +export interface MediaEdge { + node: MediaWithTags + cursor: string +} diff --git a/src/js/types/User.ts b/src/js/types/User.ts index 5be593730..58ac6545c 100644 --- a/src/js/types/User.ts +++ b/src/js/types/User.ts @@ -1,4 +1,4 @@ -import { MediaWithTags } from '../types' +import { UserMedia } from '../types' export enum UserRole { // These need to match https://manage.auth0.com/dashboard/us/dev-fmjy7n5n/roles. EDITOR = 'editor', @@ -78,5 +78,5 @@ export interface UserPublicProfile { } export interface UserPublicPage { profile: UserPublicProfile - mediaList: MediaWithTags[] + media: UserMedia } diff --git a/src/js/utils.ts b/src/js/utils.ts index 783cd943d..492726ca0 100644 --- a/src/js/utils.ts +++ b/src/js/utils.ts @@ -1,7 +1,7 @@ import { ClimbTypeToColor } from './constants' import { formatDistanceToNowStrict, differenceInYears, format } from 'date-fns' -import { AreaType, ClimbType, ClimbDisciplineRecord, ClimbDiscipline } from './types' +import { AreaType, ClimbType, ClimbDisciplineRecord, ClimbDiscipline, MediaWithTags, MediaConnection } from './types' /** * Given a path or parent id and the type of the page generate the GitHub URL @@ -264,3 +264,13 @@ export const removeTypenameFromDisciplines = (discplines: ClimbDisciplineRecord) const omitTypename = (key: string, value: boolean): boolean | undefined => (key === '__typename' || !value ? undefined : value) return JSON.parse(JSON.stringify(discplines), omitTypename) } + +/** + * Convert Relay edges to media array + * @param edges + * @returns Media with atgs array + */ +export const relayMediaConnectionToMediaArray = (mediaConnection: MediaConnection): MediaWithTags[] => { + if (mediaConnection == null) return [] + return mediaConnection.edges.map(entry => entry.node) +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index f6791d7ff..6dd093675 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -63,7 +63,6 @@ export default function MyApp ({ Component, pageProps: { session, ...pageProps } function Auth ({ children }): ReactElement { const { status } = useSession() - console.log('##auth page', status) useEffect(() => { if (status === 'unauthenticated') { void signIn('auth0') diff --git a/src/pages/api/withAuth.ts b/src/pages/api/withAuth.ts index b65750fca..42aa78e81 100644 --- a/src/pages/api/withAuth.ts +++ b/src/pages/api/withAuth.ts @@ -9,8 +9,6 @@ import { authOptions } from './auth/[...nextauth]' const withAuth = (handler: NextApiHandler): NextApiHandler => { return async (req, res) => { const session = await getServerSession(req, res, authOptions) - - console.log('#withAuth', session) if (session != null) { await handler(req, res) } else { diff --git a/src/pages/u/[...slug].tsx b/src/pages/u/[...slug].tsx index 62e0640c3..183b95de4 100644 --- a/src/pages/u/[...slug].tsx +++ b/src/pages/u/[...slug].tsx @@ -1,4 +1,4 @@ -import { NextPage, GetStaticProps } from 'next' +import { NextPage, GetStaticProps, GetStaticPaths } from 'next' import { useRouter } from 'next/router' import dynamic from 'next/dynamic' @@ -10,6 +10,7 @@ import type { UserGalleryProps } from '../../components/media/UserGallery' import useUserProfileCmd from '../../js/hooks/useUserProfileCmd' import { UserPublicPage } from '../../js/types/User' import usePermissions from '../../js/hooks/auth/usePermissions' +import { relayMediaConnectionToMediaArray } from '../../js/utils' interface UserHomeProps { uid: string @@ -22,56 +23,62 @@ const UserHomePage: NextPage = ({ uid, postId = null, userPublicP const { isAuthorized } = usePermissions({ currentUserUuid: userPublicPage?.profile?.userUuid }) + const mediaList = relayMediaConnectionToMediaArray(userPublicPage?.media?.mediaConnection) + const { author, pageTitle, pageImages } = useUserProfileSeo({ username: uid, fullName: userPublicPage?.profile?.displayName, - imageList: userPublicPage?.mediaList ?? [] + imageList: mediaList }) const { isFallback } = router return ( <> - + {!isFallback && + } -
- - - - {isAuthorized && ( -
-
-
    -
  • Please upload 3 photos to complete your profile {userPublicPage.mediaList?.length >= 3 && }
  • -
  • Upload only your own photos
  • -
  • Keep it Safe For Work and climbing-related
  • -
-
+ {isFallback + ? (
Loading ...
) + : ( +
+ + + + {isAuthorized && ( +
+
+
    +
  • Please upload 3 photos to complete your profile {mediaList?.length >= 3 && }
  • +
  • Upload only your own photos
  • +
  • Keep it Safe For Work and climbing-related
  • +
+
+
)} + +
+ + + + {!isAuthorized && ( +
+ All photos are copyrighted by their respective owners. All Rights Reserved. +
+ )}
)} - -
- - - - {!isAuthorized && !isFallback && ( -
- All photos are copyrighted by their respective owners. All Rights Reserved. -
- )} -
@@ -79,9 +86,18 @@ const UserHomePage: NextPage = ({ uid, postId = null, userPublicP } export default UserHomePage -export async function getStaticPaths (): Promise { +export const getStaticPaths: GetStaticPaths = () => { + let profiles: any + const csvStr = process.env.PREBUILD_PROFILES + if (csvStr != null && csvStr.trim().length > 2) { + const userList = csvStr.split(',') + profiles = userList.map(username => ({ + params: { slug: [username.trim()] } + })) + } + return { - paths: [], + paths: profiles ?? [], fallback: true } } diff --git a/yarn.lock b/yarn.lock index 50b99e7df..a5baf5561 100644 --- a/yarn.lock +++ b/yarn.lock @@ -50,18 +50,18 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" -"@apollo/client@^3.6.9": - version "3.7.1" - resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.7.1.tgz#86ce47c18a0714e229231148b0306562550c2248" - integrity sha512-xu5M/l7p9gT9Fx7nF3AQivp0XukjB7TM7tOd5wifIpI8RskYveL4I+rpTijzWrnqCPZabkbzJKH7WEAKdctt9w== +"@apollo/client@^3.7.16": + version "3.7.16" + resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.7.16.tgz#418cb23566a6d52e9e22d34484167149269efd40" + integrity sha512-rdhoc7baSD7ZzcjavEpYN8gZJle1KhjEKj4SJeMgBpcnO4as7oXUVU4LtFpotzZdFlo57qaLrNzfvppSTsKvZQ== dependencies: "@graphql-typed-document-node/core" "^3.1.1" "@wry/context" "^0.7.0" "@wry/equality" "^0.5.0" - "@wry/trie" "^0.3.0" + "@wry/trie" "^0.4.0" graphql-tag "^2.12.6" hoist-non-react-statics "^3.3.2" - optimism "^0.16.1" + optimism "^0.16.2" prop-types "^15.7.2" response-iterator "^0.2.6" symbol-observable "^4.0.0" @@ -2961,19 +2961,13 @@ "@typescript-eslint/types" "5.42.0" eslint-visitor-keys "^3.3.0" -"@udecode/zustood@^0.4.4": - version "0.4.4" - resolved "https://registry.yarnpkg.com/@udecode/zustood/-/zustood-0.4.4.tgz#0f3467f1ec3656623c16bb803d64e6dc55ea7b42" - integrity sha512-OtkRGu8TKBkidOOf28TY4tqYJRhMAiVt+BF6lHbF/FnXRmOa8eLal8Q/k9sqQcGzQAHBaZYcx0pR1+wlQzaurg== - dependencies: - immer "9.0.6" - -"@wry/context@^0.6.0": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.6.1.tgz#c3c29c0ad622adb00f6a53303c4f965ee06ebeb2" - integrity sha512-LOmVnY1iTU2D8tv4Xf6MVMZZ+juIJ87Kt/plMijjN20NMAXGmH4u8bS1t0uT74cZ5gwpocYueV58YwyI8y+GKw== +"@udecode/zustood@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@udecode/zustood/-/zustood-1.1.3.tgz#0ccff0beeaf7a79510117e8946f589bafdcb8df4" + integrity sha512-f3mxHDaOF+q2XvDh/mMvLhCNs0LfCLhIBl8jGmvZT/i3WWq7YujzGXgnbK8mxIkun9irfe6wlPhg9sTIB9Gnug== dependencies: - tslib "^2.3.0" + immer "^9.0.6" + react-tracked "^1.7.9" "@wry/context@^0.7.0": version "0.7.0" @@ -2996,6 +2990,13 @@ dependencies: tslib "^2.3.0" +"@wry/trie@^0.4.0": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@wry/trie/-/trie-0.4.3.tgz#077d52c22365871bf3ffcbab8e95cb8bc5689af4" + integrity sha512-I6bHwH0fSf6RqQcnnXLJKhkSXG45MFral3GxPaY4uAl0LYDZM+YDVDAiU9bYwjTuysy1S0IeecWtmq1SZA3M1w== + dependencies: + tslib "^2.3.0" + "@xobotyi/scrollbar-width@^1.9.5": version "1.9.5" resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d" @@ -5201,16 +5202,16 @@ ignore@^5.1.1, ignore@^5.1.8, ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== -immer@9.0.6: - version "9.0.6" - resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.6.tgz#7a96bf2674d06c8143e327cbf73539388ddf1a73" - integrity sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ== - immer@^9.0.12: version "9.0.16" resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.16.tgz#8e7caab80118c2b54b37ad43e05758cdefad0198" integrity sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ== +immer@^9.0.6: + version "9.0.21" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" + integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== + import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -6708,12 +6709,12 @@ openid-client@^5.4.0: object-hash "^2.2.0" oidc-token-hash "^5.0.3" -optimism@^0.16.1: - version "0.16.1" - resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.16.1.tgz#7c8efc1f3179f18307b887e18c15c5b7133f6e7d" - integrity sha512-64i+Uw3otrndfq5kaoGNoY7pvOhSsjFEN4bdEFh80MWVk/dbgJfMv7VFDeCT8LxNAlEVhQmdVEbfE7X2nWNIIg== +optimism@^0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.16.2.tgz#519b0c78b3b30954baed0defe5143de7776bf081" + integrity sha512-zWNbgWj+3vLEjZNIh/okkY2EUfX+vB9TJopzIZwT1xxaMqC5hRLLraePod4c5n4He08xuXNH+zhKFFCu390wiQ== dependencies: - "@wry/context" "^0.6.0" + "@wry/context" "^0.7.0" "@wry/trie" "^0.3.0" optionator@^0.8.1: @@ -7059,6 +7060,11 @@ protocol-buffers-schema@^3.3.1: resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz#77bc75a48b2ff142c1ad5b5b90c94cd0fa2efd03" integrity sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw== +proxy-compare@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/proxy-compare/-/proxy-compare-2.4.0.tgz#90f6abffe734ef86d8e37428c5026268606a9c1b" + integrity sha512-FD8KmQUQD6Mfpd0hywCOzcon/dbkFP8XBd9F1ycbKtvVsfv6TsFUKJ2eC0Iz2y+KzlkdT1Z8SY6ZSgm07zOyqg== + proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" @@ -7314,6 +7320,14 @@ react-toastify@^9.1.1: dependencies: clsx "^1.1.1" +react-tracked@^1.7.9: + version "1.7.11" + resolved "https://registry.yarnpkg.com/react-tracked/-/react-tracked-1.7.11.tgz#b2adccf1d8ee3941e8dc68f754513bdb7b235db4" + integrity sha512-+XXv4dJH7NnLtSD/cPVL9omra4A3KRK91L33owevXZ81r7qF/a9DdCsVZa90jMGht/V1Ym9sasbmidsJykhULQ== + dependencies: + proxy-compare "2.4.0" + use-context-selector "1.4.1" + react-transition-group@2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" @@ -8441,6 +8455,11 @@ use-callback-ref@^1.3.0: dependencies: tslib "^2.0.0" +use-context-selector@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/use-context-selector/-/use-context-selector-1.4.1.tgz#eb96279965846b72915d7f899b8e6ef1d768b0ae" + integrity sha512-Io2ArvcRO+6MWIhkdfMFt+WKQX+Vb++W8DS2l03z/Vw/rz3BclKpM0ynr4LYGyU85Eke+Yx5oIhTY++QR0ZDoA== + use-isomorphic-layout-effect@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb"