diff --git a/.github/workflows/build-deploy.yml b/.github/workflows/build-deploy.yml index b2c220755..aad678397 100644 --- a/.github/workflows/build-deploy.yml +++ b/.github/workflows/build-deploy.yml @@ -96,7 +96,7 @@ jobs: file: docker/Dockerfile push: true build-args: | - GH_NEXT_PUBLIC_IS_MAINTENANCE=true + GH_NEXT_PUBLIC_IS_MAINTENANCE=false GH_NEXT_PUBLIC_AMP_ID=2ecd0305eaa641e48ef9a0fd6ab8a923 GH_NEXT_PUBLIC_GA_ID=fake GH_IPFS_WRITE_URL=https://gw-seattle.crustcloud.io @@ -129,8 +129,8 @@ jobs: # with base path GH_NEXTAUTH_URL=https://${{ env.name }}.subsocial.network/c/api/auth GH_NEXT_PUBLIC_BASE_PATH=/c - GH_NEXT_PUBLIC_TELEGRAM_BOT_ID=6342977780 - GH_NEXT_PUBLIC_TELEGRAM_BOT_ID=subsocial_staging_bot + GH_NEXT_PUBLIC_TELEGRAM_BOT_ID=1960764016 + GH_NEXT_PUBLIC_TELEGRAM_BOT_USERNAME=subsocial_staging_bot GH_NEXT_PUBLIC_NEYNAR_CLIENT_ID=e8dc01b0-046c-4698-98ac-05432050961d GH_FRAMES_SECRET=JG/W96svVHQ3bdaEKWm1h8xJXqxDtqZ6t7tKCZNWhh0= target: runner diff --git a/package.json b/package.json index d065d0828..e470f3374 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@dicebear/core": "^5.3.4", "@ethersproject/hash": "^5.7.0", "@floating-ui/react": "^0.26.0", - "@headlessui/react": "^1.7.11", + "@headlessui/react": "2.1", "@hookform/resolvers": "^3.1.1", "@khmyznikov/pwa-install": "^0.2.5", "@kiltprotocol/augment-api": "^0.34.0", diff --git a/src/components/BottomDrawer.tsx b/src/components/BottomDrawer.tsx index 4a00953ac..1583a0cea 100644 --- a/src/components/BottomDrawer.tsx +++ b/src/components/BottomDrawer.tsx @@ -26,6 +26,7 @@ export default function BottomDrawer({ & { trigger?: (onClick: () => void) => JSX.Element + enableMaxHeight?: boolean + withBluredImage?: boolean } export default function ClickableMedia({ @@ -29,8 +31,9 @@ export default function ClickableMedia({ setIsOpenModal(false)} - panelClassName='bg-transparent shadow-none' - contentClassName='!p-0' + panelClassName='bg-transparent shadow-none h-full w-fit' + contentClassName='!p-0 h-full' + containerClassName='h-full' size='screen-md' > e.stopPropagation()} onContextMenu={(e) => e.stopPropagation()} src={props.src ?? ''} - className='w-full max-w-screen-md' + containerClassName='h-full' + className='h-full w-full max-w-screen-md' alt={props.alt ?? ''} - width={1024} - height={1024} /> diff --git a/src/components/MediaLoader.tsx b/src/components/MediaLoader.tsx index ebdf52be4..c76f14f4e 100644 --- a/src/components/MediaLoader.tsx +++ b/src/components/MediaLoader.tsx @@ -12,6 +12,8 @@ export type MediaLoaderProps = Omit & { loadingClassName?: string placeholderClassName?: string withSpinner?: boolean + enableMaxHeight?: boolean + withBluredImage?: boolean } export default function MediaLoader({ @@ -21,6 +23,8 @@ export default function MediaLoader({ loadingClassName, placeholderClassName, withSpinner, + enableMaxHeight = true, + withBluredImage = true, ...props }: MediaLoaderProps) { const [isLoaded, setIsLoaded] = useState(false) @@ -86,25 +90,29 @@ export default function MediaLoader({ isLoaded && 'hidden' )} /> - {props.alt + {withBluredImage && ( + {props.alt + )} diff --git a/src/components/chats/ChatItem/ChatItemMenus.tsx b/src/components/chats/ChatItem/ChatItemMenus.tsx index d54b23bc5..291383e1a 100644 --- a/src/components/chats/ChatItem/ChatItemMenus.tsx +++ b/src/components/chats/ChatItem/ChatItemMenus.tsx @@ -408,6 +408,7 @@ function MintingMessageNotice({ message }: { message: PostData }) { , 'children'> & { hubId: string showApproveButton?: boolean showBlockButton?: boolean + withWrapper?: boolean disableSuperLike?: boolean menuIdPrefix?: string dummySuperLike?: SuperLikeButtonProps @@ -51,6 +52,7 @@ export default function MemeChatItem({ enableChatMenu = true, chatId, hubId, + withWrapper, disableSuperLike, showApproveButton, showBlockButton, @@ -101,7 +103,9 @@ export default function MemeChatItem({
@@ -138,7 +142,13 @@ export default function MemeChatItem({ />
(null) - const scrollContainerRef = _scrollContainerRef || innerScrollContainerRef - - const innerRef = useRef(null) + const isDesktop = !isTouchDevice() && !app?.result const { isAuthorized } = useAuthorizedForModeration(chatId) @@ -81,15 +90,14 @@ function ProfilePostsListContent({ const { data: chat } = getPostQuery.useQuery(chatId) const isMyChat = chat?.struct.ownerId === myAddress - const Component = asContainer ? Container<'div'> : 'div' - const renderedMessageQueries = getPostQuery.useQueries(messageIds) return (
@@ -104,66 +112,215 @@ function ProfilePostsListContent({ className='absolute left-1/2 top-1/2 z-10 -translate-x-1/2 -translate-y-1/2' /> )} - + ) : ( + + )} +
+ ) +} + +type MobileProfilePostsListProps = { + scrollContainerRef?: React.RefObject + scrollableContainerClassName?: string + asContainer?: boolean + renderedMessageQueries: ReturnType[] + currentPage: number + loadMore: () => void + hasMore: boolean + hubId: string + chatId: string +} + +const MobileProfilePostsList = ({ + scrollContainerRef: _scrollContainerRef, + scrollableContainerClassName, + asContainer, + renderedMessageQueries, + currentPage, + loadMore, + hasMore, + hubId, + chatId, +}: MobileProfilePostsListProps) => { + const sendEvent = useSendEvent() + const { enableBackButton } = useConfigContext() + const scrollableContainerId = useId() + + const innerScrollContainerRef = useRef(null) + const scrollContainerRef = _scrollContainerRef || innerScrollContainerRef + const { isAuthorized } = useAuthorizedForModeration(chatId) + + const innerRef = useRef(null) + + const Component = asContainer ? Container<'div'> : 'div' + return ( + + - -
- { - loadMore() - sendEvent('load_more_messages', { currentPage }) - }} - className={cx( - 'relative flex w-full flex-col !overflow-hidden pb-2', - // need to have enough room to open message menu - 'min-h-[400px]' - )} - hasMore={hasMore} - scrollableTarget={scrollableContainerId} - loader={} - endMessage={ - renderedMessageQueries.length === 0 ? null : ( - + { + loadMore() + sendEvent('load_more_messages', { currentPage }) + }} + className={cx( + 'relative flex w-full flex-col !overflow-hidden pb-2', + // need to have enough room to open message menu + 'min-h-[400px]' + )} + hasMore={hasMore} + scrollableTarget={scrollableContainerId} + loader={} + endMessage={ + renderedMessageQueries.length === 0 ? null : ( + + ) + } + scrollThreshold={`${SCROLL_THRESHOLD}px`} + > + {renderedMessageQueries.map(({ data: message }, index) => { + // bottom message is the first element, because the flex direction is reversed + if (!message) return null + + return ( + + - ) - } - scrollThreshold={`${SCROLL_THRESHOLD}px`} - > - {renderedMessageQueries.map(({ data: message }, index) => { - // bottom message is the first element, because the flex direction is reversed - if (!message) return null - - return ( - - - - ) - })} - -
-
-
+ + ) + })} + +
+ + + ) +} + +const PAGE_SIZE = 8 + +type DesktopProfilePostsListProps = { + messageIds: string[] + totalDataCount: number + renderedMessageQueries: ReturnType[] + loadMore: () => void + chatId: string + hubId: string +} + +const DesktopProfilePostsList = ({ + messageIds, + totalDataCount, + renderedMessageQueries, + loadMore, + chatId, + hubId, +}: DesktopProfilePostsListProps) => { + const [page, setPage] = useState(1) + + const offset = (page - 1) * PAGE_SIZE + + const totalByPage = offset + PAGE_SIZE + const { isAuthorized } = useAuthorizedForModeration(chatId) + + const postsIdsByPage = useMemo(() => { + return renderedMessageQueries.slice(offset, offset + PAGE_SIZE) + }, [renderedMessageQueries, offset]) + + return ( +
+
+ + {messageIds.length ? offset + 1 : 0}- + {totalByPage > totalDataCount ? totalDataCount : totalByPage} from{' '} + {totalDataCount} + +
+ + +
+
+
+ {postsIdsByPage.map(({ data: message }, index) => { + if (!message) return null + + return ( +
+ +
+ ) + })} +
) } diff --git a/src/components/chats/ChatItem/profilePosts/ProfileProstsListModal.tsx b/src/components/chats/ChatItem/profilePosts/ProfileProstsListModal.tsx index 95f78d77d..3b54bcb0a 100644 --- a/src/components/chats/ChatItem/profilePosts/ProfileProstsListModal.tsx +++ b/src/components/chats/ChatItem/profilePosts/ProfileProstsListModal.tsx @@ -11,7 +11,9 @@ import { getPaginatedPostIdsByPostIdAndAccount } from '@/services/datahub/posts/ import { useSendEvent } from '@/stores/analytics' import { useProfilePostsModal } from '@/stores/profile-posts-modal' import { cx } from '@/utils/class-names' +import { isTouchDevice } from '@/utils/device' import { Transition } from '@headlessui/react' +import { useMiniAppRaw } from '@tma.js/sdk-react' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import { createPortal } from 'react-dom' @@ -61,6 +63,9 @@ const ProfilePostsListModal = ({ tabsConfig }: ProfilePostsListModalProps) => { const sendEvent = useSendEvent() const isAdmin = useIsModerationAdmin() const { isAuthorized } = useAuthorizedForModeration(chatId) + const app = useMiniAppRaw(true) + + const isDesktop = !isTouchDevice() && !app?.result const { data, isLoading } = getPaginatedPostIdsByPostIdAndAccount.useInfiniteQuery(chatId, address) @@ -93,6 +98,7 @@ const ProfilePostsListModal = ({ tabsConfig }: ProfilePostsListModalProps) => { <> { /> { closeModal={() => setIsOpenDetail(false)} address={address} /> -
+
+ ) +} + +const ApproveMessagesButton = ({ + selectedMessageIds, + onSuccess, +}: Omit) => { + const { mutateAsync, isLoading, isSuccess } = useApproveMessage() + const myAddress = useMyMainAddress() + + const approveMessages = async () => { + const approvePromise = selectedMessageIds.map(async (messageId) => { + await mutateAsync({ + approvedInRootPost: true, + postId: messageId, + }) + }) + + await Promise.all(approvePromise) + + setTimeout(() => onSuccess(), 1000) + } + return ( + + ) +} + +export default BlockAndApproveButtons diff --git a/src/modules/moderator/Checkbox.tsx b/src/modules/moderator/Checkbox.tsx new file mode 100644 index 000000000..e87292e68 --- /dev/null +++ b/src/modules/moderator/Checkbox.tsx @@ -0,0 +1,31 @@ +import { cx } from '@/utils/class-names' +import { Checkbox } from '@headlessui/react' +import { FaCheck } from 'react-icons/fa6' + +type ModerationCheckboxProps = { + checked: boolean + onChange: (checked: boolean) => void + className?: string +} + +const ModerationCheckbox = ({ + checked, + onChange, + className, +}: ModerationCheckboxProps) => { + return ( + + + + ) +} + +export default ModerationCheckbox diff --git a/src/modules/moderator/ModerationActionSection.tsx b/src/modules/moderator/ModerationActionSection.tsx new file mode 100644 index 000000000..ec75324af --- /dev/null +++ b/src/modules/moderator/ModerationActionSection.tsx @@ -0,0 +1,130 @@ +import Button from '@/components/Button' +import { getPostQuery } from '@/services/api/query' +import { useQueryClient } from '@tanstack/react-query' +import { useEffect, useState } from 'react' +import { AiOutlineReload } from 'react-icons/ai' +import { + MdOutlineKeyboardArrowLeft, + MdOutlineKeyboardArrowRight, +} from 'react-icons/md' +import BlockAndApproveButtons from './BlockAndApproveButtons' +import ModerationCheckbox from './Checkbox' +import { useModerationContext } from './ModerationContext' + +type ModerationActionSectionProps = { + chatId: string + offset: number + messageIds: string[] + totalDataCount: number + refetch?: () => void + loadMore: () => void + isFetching?: boolean +} + +const ModerationActionSection = ({ + totalDataCount, + messageIds, + offset, + chatId, + isFetching, + refetch, + loadMore, +}: ModerationActionSectionProps) => { + const { selectedPostIds, setSelectedPostIds, page, setPage, pageSize } = + useModerationContext() + const [enabled, setEnabled] = useState(false) + const queryClient = useQueryClient() + + const totalByPage = offset + pageSize + const messagesByPage = messageIds.slice(offset, offset + pageSize) + + useEffect(() => { + if (selectedPostIds.length !== messagesByPage.length) { + setEnabled(false) + } + }, [messagesByPage.length, selectedPostIds]) + + const onSuccess = () => { + selectedPostIds.forEach((postId) => { + getPostQuery.invalidate(queryClient, postId) + }) + setSelectedPostIds([]) + + refetch?.() + } + + const selectAll = () => { + setSelectedPostIds(messageIds.slice(offset, offset + pageSize)) + } + + return ( +
+
+ { + setEnabled(checked) + checked ? selectAll() : setSelectedPostIds([]) + }} + className='h-8 w-8' + /> + + {!!selectedPostIds.length && ( + <> + + Selected: {selectedPostIds.length}{' '} + + + + + )} +
+
+ + {messageIds.length ? offset + 1 : 0}- + {totalByPage > totalDataCount ? totalDataCount : totalByPage} from{' '} + {totalDataCount} + +
+ + +
+
+
+ ) +} + +export default ModerationActionSection diff --git a/src/modules/moderator/ModerationContext.tsx b/src/modules/moderator/ModerationContext.tsx new file mode 100644 index 000000000..c9bcace8d --- /dev/null +++ b/src/modules/moderator/ModerationContext.tsx @@ -0,0 +1,40 @@ +import { createContext, useContext, useState } from 'react' + +const PAGE_SIZE = 8 + +export type ModerationContext = { + selectedPostIds: string[] + setSelectedPostIds: (ids: string[]) => void + page: number + setPage: (page: number) => void + pageSize: number +} + +const ModerationContext = createContext({} as any) + +type ContextWrapperProps = { + children: React.ReactNode +} + +export const ModerationContextWrapper: React.FC = ({ + children, +}) => { + const [selectedPostIds, setSelectedPostIds] = useState([]) + const [page, setPage] = useState(1) + + const value = { + selectedPostIds, + setSelectedPostIds, + page, + setPage, + pageSize: PAGE_SIZE, + } + + return ( + + {children} + + ) +} + +export const useModerationContext = () => useContext(ModerationContext) diff --git a/src/modules/moderator/ModerationMemeItem.tsx b/src/modules/moderator/ModerationMemeItem.tsx new file mode 100644 index 000000000..a5c5d87d0 --- /dev/null +++ b/src/modules/moderator/ModerationMemeItem.tsx @@ -0,0 +1,137 @@ +import AddressAvatar from '@/components/AddressAvatar' +import Button from '@/components/Button' +import ClickableMedia from '@/components/ClickableMedia' +import MediaLoader from '@/components/MediaLoader' +import { ProfilePreviewModalName } from '@/components/ProfilePreviewModalWrapper' +import ChatRelativeTime from '@/components/chats/ChatItem/ChatRelativeTime' +import UnapprovedMemeCount from '@/components/chats/UnapprovedMemeCount' +import { getPostExtensionProperties } from '@/components/extensions/utils' +import { cx } from '@/utils/class-names' +import { PostData } from '@subsocial/api/types' +import { ComponentProps } from 'react' +import { FiZoomIn } from 'react-icons/fi' +import ModerationCheckbox from './Checkbox' +import { useModerationContext } from './ModerationContext' + +export type MemeChatItemProps = Omit, 'children'> & { + message: PostData + messageBubbleId?: string + chatId: string + hubId: string +} + +export default function ModerationMemeItem({ + message, + messageBubbleId, + chatId, + hubId, + ...props +}: MemeChatItemProps) { + const { selectedPostIds, setSelectedPostIds } = useModerationContext() + const { ownerId } = message.struct + const { body, extensions } = message.content || {} + + const displayedTime = message.struct.createdAtTime + + if (!body && (!extensions || extensions.length === 0)) return null + if (message.struct.approvedInRootPost) return null + + const imageExt = getPostExtensionProperties( + extensions?.[0], + 'subsocial-image' + ) + + const isSelected = selectedPostIds.includes(message.struct.id) + + return ( +
{ + setSelectedPostIds( + selectedPostIds.includes(message.struct.id) + ? selectedPostIds.filter((id) => id !== message.struct.id) + : [...selectedPostIds, message.struct.id] + ) + }} + > +
+
+ +
+ +
+
+
+ ( + + )} + /> + { + setSelectedPostIds( + selectedPostIds.includes(message.struct.id) + ? selectedPostIds.filter((id) => id !== message.struct.id) + : [...selectedPostIds, message.struct.id] + ) + }} + /> +
+
+ + + {body && ( +

+ {body} +

+ )} +
+ + +
+
+ ) +} diff --git a/src/modules/moderator/PendingPostsList.tsx b/src/modules/moderator/PendingPostsList.tsx new file mode 100644 index 000000000..67ef4d04a --- /dev/null +++ b/src/modules/moderator/PendingPostsList.tsx @@ -0,0 +1,78 @@ +import usePaginatedMessageIds from '@/components/chats/hooks/usePaginatedMessageIds' +import { getPostQuery } from '@/services/api/query' +import { useEffect, useMemo } from 'react' +import ModerationActionSection from './ModerationActionSection' +import { useModerationContext } from './ModerationContext' +import ModerationMemeItem from './ModerationMemeItem' + +type PendingPostsListProps = { + hubId: string + chatId: string +} + +const PendingPostsList = ({ hubId, chatId }: PendingPostsListProps) => { + const { setSelectedPostIds, page, pageSize } = useModerationContext() + + const { messageIds, hasMore, loadMore, totalDataCount, refetch, isFetching } = + usePaginatedMessageIds({ + hubId, + chatId, + onlyDisplayUnapprovedMessages: true, + pageSize, + }) + + const renderedMessageQueries = getPostQuery.useQueries(messageIds) + + const offset = (page - 1) * pageSize + + const postsIdsByPage = useMemo(() => { + return renderedMessageQueries.slice(offset, offset + pageSize) + }, [renderedMessageQueries, offset, pageSize]) + + useEffect(() => { + loadMore() + }, [loadMore]) + + useEffect(() => { + setSelectedPostIds([]) + }, [page, setSelectedPostIds]) + + return ( +
+ { + hasMore && loadMore() + }} + /> + + {postsIdsByPage.length === 0 && ( +
+ No pending posts +
+ )} +
+ {postsIdsByPage.length !== 0 && + postsIdsByPage.map(({ data: message }, index) => { + if (!message) return null + + return ( + + ) + })} +
+
+ ) +} + +export default PendingPostsList diff --git a/src/modules/moderator/index.tsx b/src/modules/moderator/index.tsx new file mode 100644 index 000000000..6a764df03 --- /dev/null +++ b/src/modules/moderator/index.tsx @@ -0,0 +1,78 @@ +import AddressAvatar from '@/components/AddressAvatar' +import Button from '@/components/Button' +import Logo from '@/components/Logo' +import { env } from '@/env.mjs' +import { useTelegramLogin } from '@/providers/config/TelegramLoginProvider' +import { useMyAccount, useMyMainAddress } from '@/stores/my-account' +import { ModerationContextWrapper } from './ModerationContext' +import PendingPostsList from './PendingPostsList' + +const ModeratorPage = () => { + return ( + +
+
+ +
+
+ ) +} + +const Header = () => { + const { loginTelegram, isLoadingOrSubmitted } = useTelegramLogin() + + const myAddress = useMyMainAddress() + + const logout = useMyAccount((state) => state.logout) + + return ( +
+
+
+ + + Moderator + +
+ {myAddress ? ( +
+ + +
+ ) : ( + + )} +
+
+ ) +} + +const Content = () => { + return ( +
+
+ +
+
+ ) +} + +export default ModeratorPage diff --git a/src/modules/points/PointsWidget.tsx b/src/modules/points/PointsWidget.tsx index bc4eaf725..e4acc7f0c 100644 --- a/src/modules/points/PointsWidget.tsx +++ b/src/modules/points/PointsWidget.tsx @@ -164,6 +164,7 @@ function PointsDrawerContent({ { { ) { return ( - - - - - - -
- - - - - - - - - -
+ + + + + + + +
+ + + + + + + + + +
+
) diff --git a/src/pages/moderator.tsx b/src/pages/moderator.tsx new file mode 100644 index 000000000..05dfe7e64 --- /dev/null +++ b/src/pages/moderator.tsx @@ -0,0 +1,20 @@ +import ModeratorPage from '@/modules/moderator' +import { getCommonStaticProps } from '@/utils/page' +import { AppCommonProps } from './_app' + +export const getStaticProps = getCommonStaticProps( + () => ({ + head: { + title: 'EPIC - A Meme-to-Earn Platform', + description: 'Earn meme coins 💰 by posting and liking memes 🤣', + disableZoom: true, + }, + }), + async () => { + return { + props: {}, + } + } +) + +export default ModeratorPage diff --git a/src/services/datahub/posts/query.ts b/src/services/datahub/posts/query.ts index 64fbc15b3..ddc5ba776 100644 --- a/src/services/datahub/posts/query.ts +++ b/src/services/datahub/posts/query.ts @@ -58,6 +58,7 @@ type Data = { postId: string onlyDisplayUnapprovedMessages: boolean myAddress: string + pageSize?: number } export const blockedCountOffset = { blocked: 0, @@ -68,9 +69,11 @@ async function getPaginatedPostIdsByRootPostId({ client, onlyDisplayUnapprovedMessages, myAddress, + pageSize, }: { page: number client?: QueryClient | null + pageSize?: number } & Data): Promise { if (!postId || !client) return { data: [], page, hasMore: false, totalData: 0 } @@ -84,15 +87,17 @@ async function getPaginatedPostIdsByRootPostId({ const chatPerPage = onlyDisplayUnapprovedMessages ? 50 : CHAT_PER_PAGE const firstPageDataLength = oldIds?.length || chatPerPage + const postsPerPage = pageSize || chatPerPage + // only first page that has dynamic content, where its length can increase from: // - subscription // - invalidation // so the offset has to accommodate the length of the current first page - let offset = Math.max((page - 2) * chatPerPage + firstPageDataLength, 0) + let offset = Math.max((page - 2) * postsPerPage + firstPageDataLength, 0) if (page === 1) offset = 0 if (onlyDisplayUnapprovedMessages) { // no need to check for first page for pending tabs, to avoid skipped data - offset = Math.max((page - 1) * chatPerPage - blockedCountOffset.blocked, 0) + offset = Math.max((page - 1) * postsPerPage - blockedCountOffset.blocked, 0) } const res = await datahubQueryRequest< @@ -127,7 +132,7 @@ async function getPaginatedPostIdsByRootPostId({ orderDirection: onlyDisplayUnapprovedMessages ? QueryOrder.Asc : QueryOrder.Desc, - pageSize: chatPerPage, + pageSize: postsPerPage, offset, }, }, @@ -215,12 +220,14 @@ export const getPaginatedPostIdsByPostId = { fetchFirstPageQuery: async ( client: QueryClient | null, data: Data, - page = 1 + page = 1, + pageSize?: number ) => { const res = await getPaginatedPostIdsByRootPostId({ ...data, page, client, + pageSize, }) if (!client) return res diff --git a/yarn.lock b/yarn.lock index 1a1a47896..fad39ba1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3073,6 +3073,21 @@ dependencies: "@floating-ui/utils" "^0.1.1" +"@floating-ui/core@^1.6.0": + version "1.6.5" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.5.tgz#102335cac0d22035b04d70ca5ff092d2d1a26f2b" + integrity sha512-8GrTWmoFhm5BsMZOTHeGD2/0FLKLQQHvO/ZmQga4tKempYRLz8aqJGqXVuQgisnMObq2YZ2SgkwctN1LOOxcqA== + dependencies: + "@floating-ui/utils" "^0.2.5" + +"@floating-ui/dom@^1.0.0": + version "1.6.8" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.8.tgz#45e20532b6d8a061b356a4fb336022cf2609754d" + integrity sha512-kx62rP19VZ767Q653wsP1XZCGIirkE09E0QUGNYTM/ttbbQHqcGPdSfWFxUyyNLc/W6aoJRBajOSXhP6GXjC0Q== + dependencies: + "@floating-ui/core" "^1.6.0" + "@floating-ui/utils" "^0.2.5" + "@floating-ui/dom@^1.3.0": version "1.5.1" resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.5.1.tgz#88b70defd002fe851f17b4a25efb2d3c04d7a8d7" @@ -3088,6 +3103,13 @@ dependencies: "@floating-ui/dom" "^1.3.0" +"@floating-ui/react-dom@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.1.tgz#cca58b6b04fc92b4c39288252e285e0422291fb0" + integrity sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg== + dependencies: + "@floating-ui/dom" "^1.0.0" + "@floating-ui/react@^0.26.0": version "0.26.12" resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.12.tgz#6908f774d8e3167d89b37fd83be975c7e5d8be99" @@ -3097,6 +3119,15 @@ "@floating-ui/utils" "^0.2.0" tabbable "^6.0.0" +"@floating-ui/react@^0.26.16": + version "0.26.20" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.20.tgz#49ae23347666626db8671c2aa2df469bbec7db71" + integrity sha512-RixKJJG92fcIsVoqrFr4Onpzh7hlOx4U7NV4aLhMLmtvjZ5oTB/WzXaANYUZATKqXvvW7t9sCxtzejip26N5Ag== + dependencies: + "@floating-ui/react-dom" "^2.1.1" + "@floating-ui/utils" "^0.2.5" + tabbable "^6.0.0" + "@floating-ui/utils@^0.1.1": version "0.1.1" resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.1.tgz#1a5b1959a528e374e8037c4396c3e825d6cf4a83" @@ -3107,6 +3138,11 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== +"@floating-ui/utils@^0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.5.tgz#105c37d9d9620ce69b7f692a20c821bf1ad2cbf9" + integrity sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ== + "@graphql-codegen/add@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@graphql-codegen/add/-/add-4.0.1.tgz#c187af820fdd2fc7a9c1c2453bc389cd4e16699e" @@ -3668,12 +3704,15 @@ protobufjs "^7.2.4" yargs "^17.7.2" -"@headlessui/react@^1.7.11": - version "1.7.14" - resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.14.tgz#75f19552c535113640fe8a3a40e71474f49e89c9" - integrity sha512-znzdq9PG8rkwcu9oQ2FwIy0ZFtP9Z7ycS+BAqJ3R5EIqC/0bJGvhT7193rFf+45i9nnPsYvCQVW4V/bB9Xc+gA== +"@headlessui/react@2.1": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-2.1.2.tgz#3ca9378d7d0db6aefdb135f957815790786214ef" + integrity sha512-Kb3hgk9gRNRcTZktBrKdHhF3xFhYkca1Rk6e1/im2ENf83dgN54orMW0uSKTXFnUpZOUFZ+wcY05LlipwgZIFQ== dependencies: - client-only "^0.0.1" + "@floating-ui/react" "^0.26.16" + "@react-aria/focus" "^3.17.1" + "@react-aria/interactions" "^3.21.3" + "@tanstack/react-virtual" "^3.8.1" "@hono/node-server@^1.11.2": version "1.12.0" @@ -5404,6 +5443,45 @@ react-remove-scroll "2.5.7" ua-parser-js "^1.0.37" +"@react-aria/focus@^3.17.1": + version "3.18.1" + resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.18.1.tgz#b54b88e78662549ddae917e3143723c8dd7a4e90" + integrity sha512-N0Cy61WCIv+57mbqC7hiZAsB+3rF5n4JKabxUmg/2RTJL6lq7hJ5N4gx75ymKxkN8GnVDwt4pKZah48Wopa5jw== + dependencies: + "@react-aria/interactions" "^3.22.1" + "@react-aria/utils" "^3.25.1" + "@react-types/shared" "^3.24.1" + "@swc/helpers" "^0.5.0" + clsx "^2.0.0" + +"@react-aria/interactions@^3.21.3", "@react-aria/interactions@^3.22.1": + version "3.22.1" + resolved "https://registry.yarnpkg.com/@react-aria/interactions/-/interactions-3.22.1.tgz#f2219a100c886cee383da7be9ae05e9dd940d39a" + integrity sha512-5TLzQaDAQQ5C70yG8GInbO4wIylKY67RfTIIwQPGR/4n5OIjbUD8BOj3NuSsuZ/frUPaBXo1VEBBmSO23fxkjw== + dependencies: + "@react-aria/ssr" "^3.9.5" + "@react-aria/utils" "^3.25.1" + "@react-types/shared" "^3.24.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/ssr@^3.9.5": + version "3.9.5" + resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.9.5.tgz#775d84f51f90934ff51ae74eeba3728daac1a381" + integrity sha512-xEwGKoysu+oXulibNUSkXf8itW0npHHTa6c4AyYeZIJyRoegeteYuFpZUBPtIDE8RfHdNsSmE1ssOkxRnwbkuQ== + dependencies: + "@swc/helpers" "^0.5.0" + +"@react-aria/utils@^3.25.1": + version "3.25.1" + resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.25.1.tgz#f6530ce47aa28617924cc6868b4cf1c113a909c5" + integrity sha512-5Uj864e7T5+yj78ZfLnfHqmypLiqW2mN+nsdslog2z5ssunTqjolVeM15ootXskjISlZ7MojLpq97kIC4nlnAw== + dependencies: + "@react-aria/ssr" "^3.9.5" + "@react-stately/utils" "^3.10.2" + "@react-types/shared" "^3.24.1" + "@swc/helpers" "^0.5.0" + clsx "^2.0.0" + "@react-native-async-storage/async-storage@^1.18.1": version "1.19.1" resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.19.1.tgz#09d35caaa31823b40fdfeebf95decf8f992a6274" @@ -5411,6 +5489,18 @@ dependencies: merge-options "^3.0.4" +"@react-stately/utils@^3.10.2": + version "3.10.2" + resolved "https://registry.yarnpkg.com/@react-stately/utils/-/utils-3.10.2.tgz#09377f771592ff537c901aa64178cb3a004a916f" + integrity sha512-fh6OTQtbeQC0ywp6LJuuKs6tKIgFvt/DlIZEcIpGho6/oZG229UnIk6TUekwxnDbumuYyan6D9EgUtEMmT8UIg== + dependencies: + "@swc/helpers" "^0.5.0" + +"@react-types/shared@^3.24.1": + version "3.24.1" + resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.24.1.tgz#fa06cb681d144fce9c515d8bd296d81440a45d25" + integrity sha512-AUQeGYEm/zDTN6zLzdXolDxz3Jk5dDL7f506F07U8tBwxNNI3WRdhU84G0/AaFikOZzDXhOZDr3MhQMzyE7Ydw== + "@repeaterjs/repeater@3.0.4", "@repeaterjs/repeater@^3.0.4": version "3.0.4" resolved "https://registry.yarnpkg.com/@repeaterjs/repeater/-/repeater-3.0.4.tgz#a04d63f4d1bf5540a41b01a921c9a7fddc3bd1ca" @@ -6203,6 +6293,13 @@ dependencies: tslib "^2.4.0" +"@swc/helpers@^0.5.0": + version "0.5.12" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.12.tgz#37aaca95284019eb5d2207101249435659709f4b" + integrity sha512-KMZNXiGibsW9kvZAO1Pam2JPTDBm+KSHMMHWdsyI/1DbIZjT2A6Gy3hblVXUMEDvUAKq+e0vL0X0o54owWji7g== + dependencies: + tslib "^2.4.0" + "@swc/helpers@^0.5.1": version "0.5.1" resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.1.tgz#e9031491aa3f26bfcc974a67f48bd456c8a5357a" @@ -6291,6 +6388,18 @@ "@tanstack/query-core" "4.36.1" use-sync-external-store "^1.2.0" +"@tanstack/react-virtual@^3.8.1": + version "3.8.4" + resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.8.4.tgz#7f5d8a71f525976c98be46fecd3dbee2d90b5dac" + integrity sha512-Dq0VQr3QlTS2qL35g360QaJWBt7tCn/0xw4uZ0dHXPLO1Ak4Z4nVX4vuj1Npg1b/jqNMDToRtR5OIxM2NXRBWg== + dependencies: + "@tanstack/virtual-core" "3.8.4" + +"@tanstack/virtual-core@3.8.4": + version "3.8.4" + resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.8.4.tgz#0ff84b6a0e4b394335cf7ccf759c36b58cbd02eb" + integrity sha512-iO5Ujgw3O1yIxWDe9FgUPNkGjyT657b1WNX52u+Wv1DyBFEpdCdGkuVaky0M3hHFqNWjAmHWTn4wgj9rTr7ZQg== + "@tma.js/sdk-react@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@tma.js/sdk-react/-/sdk-react-2.2.0.tgz#69c489f60dc6e3c42aa0df4df29315ed24c0c728" @@ -8660,6 +8769,11 @@ clsx@^1.2.1: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== +clsx@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + cluster-key-slot@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac"