diff --git a/src/components/spaces/ViewSpace.tsx b/src/components/spaces/ViewSpace.tsx
index dbb486f01..6d2336bc5 100644
--- a/src/components/spaces/ViewSpace.tsx
+++ b/src/components/spaces/ViewSpace.tsx
@@ -1,16 +1,22 @@
import { EditOutlined } from '@ant-design/icons'
import { isEmptyStr, newLogger, nonEmptyStr } from '@subsocial/utils'
+import { Button } from 'antd'
import clsx from 'clsx'
import dynamic from 'next/dynamic'
-import React, { MouseEvent, useCallback, useState } from 'react'
+import React, { MouseEvent, useCallback, useEffect, useState } from 'react'
import { ButtonLink } from 'src/components/utils/CustomLinks'
import { Segment } from 'src/components/utils/Segment'
import { LARGE_AVATAR_SIZE } from 'src/config/Size.config'
-import { SpaceContent, SpaceId, SpaceStruct, SpaceWithSomeDetails } from 'src/types'
-import config from '../../config'
+import { useSetChatEntityConfig, useSetChatOpen } from 'src/rtk/app/hooks'
+import { useIsCreatorSpace } from 'src/rtk/features/creators/creatorsListHooks'
+import { useFetchStakeData } from 'src/rtk/features/creators/stakesHooks'
+import { SpaceContent, SpaceData, SpaceId, SpaceStruct, SpaceWithSomeDetails } from 'src/types'
import { useSelectProfileSpace } from '../../rtk/features/profiles/profilesHooks'
import { useSelectSpace } from '../../rtk/features/spaces/spacesHooks'
import { useMyAddress } from '../auth/MyAccountsContext'
+import MyStakeCard from '../creators/cards/MyStakeCard'
+import StakeSubCard from '../creators/cards/StakeSubCard'
+import MobileIncreaseSubRewards from '../creators/MobileIncreaseSubRewards'
import MakeAsProfileModal from '../profiles/address-views/utils/MakeAsProfileModal'
import { useIsMobileWidthOrDevice } from '../responsive'
import { editSpaceUrl, spaceUrl } from '../urls'
@@ -60,7 +66,8 @@ export const SpaceNameAsLink = React.memo(({ space, ...props }: SpaceNameAsLinkP
})
export const StakeButton = ({ spaceStruct }: { spaceStruct: SpaceStruct }) => {
- return config.creatorIds?.includes(spaceStruct.id) ? (
+ const { isCreatorSpace } = useIsCreatorSpace(spaceStruct.id)
+ return isCreatorSpace ? (
{
withFollowButton = true,
withStats = true,
withTags = true,
- withStakeButton = true,
showFullAbout = false,
dropdownPreview = false,
@@ -115,6 +121,19 @@ export const InnerViewSpace = (props: Props) => {
)
}, [spaceData, imageSize])
+ const setChatConfig = useSetChatEntityConfig()
+ const setChatOpen = useSetChatOpen()
+ useEffect(() => {
+ if (!spaceData) return
+ setChatConfig({ entity: { data: spaceData, type: 'space' }, withFloatingButton: false })
+
+ return () => {
+ setChatConfig(null)
+ }
+ }, [spaceData])
+
+ const { isCreatorSpace } = useIsCreatorSpace(spaceData?.id)
+
// We do not return 404 page here, because this component could be used to render a space in list.
if (!spaceData) return null
@@ -178,6 +197,10 @@ export const InnerViewSpace = (props: Props) => {
e.stopPropagation()
setCollapseAbout(prev => !prev)
}
+ const toggleCreatorChat = () => {
+ setChatOpen(true)
+ }
+
const renderPreview = () => (
@@ -185,26 +208,25 @@ export const InnerViewSpace = (props: Props) => {
{title}
-
-
- {!isMobile &&
- (isMy ? (
-
- Edit
-
- ) : (
- withStakeButton &&
- ))}
-
- {withFollowButton && }
+
+
+ {!isMobile && isMy && (
+
+ Edit
+
+ )}
+
+ {!isMobile && isCreatorSpace && (
+
+ Creator Chat
+
+ )}
+
+ {withFollowButton && }
@@ -276,11 +298,17 @@ export const InnerViewSpace = (props: Props) => {
)
}
+ const showCreatorCards = isCreatorSpace && isMobile
+
return (
+ {showCreatorCards && (
+
+ )}
+ {showCreatorCards && }
@@ -289,6 +317,17 @@ export const InnerViewSpace = (props: Props) => {
)
}
+function MobileCreatorCard({ spaceData }: { spaceData: SpaceData }) {
+ const myAddress = useMyAddress()
+ const { data } = useFetchStakeData(myAddress ?? '', spaceData.id)
+
+ return (
+
+ {data?.hasStaked ? : }
+
+ )
+}
+
export const ViewSpace = (props: Props) => {
const { spaceData: initialSpaceData } = props
diff --git a/src/components/spaces/ViewSpacePage.tsx b/src/components/spaces/ViewSpacePage.tsx
index 3cc2fb5e7..aa1d2de54 100644
--- a/src/components/spaces/ViewSpacePage.tsx
+++ b/src/components/spaces/ViewSpacePage.tsx
@@ -48,8 +48,9 @@ const InnerViewSpacePage: FC = props => {
image,
canonical: spaceUrl(spaceData.struct),
}}
- withOnBoarding
+ withSidebar
withVoteBanner
+ creatorDashboardSidebarType={{ name: 'space-page', space: spaceData }}
>
{showBanner && (
- {children || (
-
- {getTitle(asProfile)}
-
- )}
+ {children || {getTitle(asProfile)} }
)
}
diff --git a/src/components/spaces/helpers/PostPreviewsOnSpace.tsx b/src/components/spaces/helpers/PostPreviewsOnSpace.tsx
index 9fe82e3c5..a7f35e9cd 100644
--- a/src/components/spaces/helpers/PostPreviewsOnSpace.tsx
+++ b/src/components/spaces/helpers/PostPreviewsOnSpace.tsx
@@ -1,11 +1,10 @@
-import React, { useCallback } from 'react'
+import { useCallback } from 'react'
import { useDispatch } from 'react-redux'
import { InnerLoadMoreFn } from 'src/components/lists'
import { InfinitePageList } from 'src/components/lists/InfiniteList'
import { PublicPostPreviewById } from 'src/components/posts/PublicPostPreview'
import { useSubsocialApi } from 'src/components/substrate/SubstrateContext'
import { getPageOfIds } from 'src/components/utils/getIds'
-import { Pluralize } from 'src/components/utils/Plularize'
import { fetchPosts } from 'src/rtk/features/posts/postsSlice'
import { DataSourceTypes, PostId, PostWithSomeDetails, SpaceData } from 'src/types'
import { FollowerCanPostAlert } from '../permissions/FollowerCanPostAlert'
@@ -17,21 +16,6 @@ type Props = {
posts: PostWithSomeDetails[]
}
-const PostsSectionTitle = React.memo((props: Props) => {
- const { spaceData } = props
- const { struct: space } = spaceData
- const { postsCount } = space
-
- return (
-
-
-
-
- {!!postsCount &&
}
-
- )
-})
-
const InfiniteListOfPublicPosts = (props: Props) => {
const { spaceData, posts, postIds } = props
const { struct: space } = spaceData
@@ -54,7 +38,6 @@ const InfiniteListOfPublicPosts = (props: Props) => {
() => (
}
dataSource={initialPostIds}
loadMore={loadMore}
totalCount={postsCount || 0}
diff --git a/src/components/spaces/helpers/SpaceDropdownMenu.tsx b/src/components/spaces/helpers/SpaceDropdownMenu.tsx
index 37af6b1b3..72b7f2901 100644
--- a/src/components/spaces/helpers/SpaceDropdownMenu.tsx
+++ b/src/components/spaces/helpers/SpaceDropdownMenu.tsx
@@ -7,6 +7,9 @@ import { BasicDropDownMenuProps, DropdownMenu } from 'src/components/utils/DropD
import { showSuccessMessage } from 'src/components/utils/Message'
import { useHasUserASpacePermission } from 'src/permissions/checkPermission'
import { useSendEvent } from 'src/providers/AnalyticContext'
+import { useSetChatOpen } from 'src/rtk/app/hooks'
+import { useAppSelector } from 'src/rtk/app/store'
+import { useIsCreatorSpace } from 'src/rtk/features/creators/creatorsListHooks'
import { SpaceData } from 'src/types'
import { useSelectProfile } from '../../../rtk/features/profiles/profilesHooks'
import { useIsUsingEmail, useMyAddress } from '../../auth/MyAccountsContext'
@@ -40,12 +43,20 @@ export const SpaceDropdownMenu = (props: SpaceDropDownProps) => {
const showMakeAsProfileButton = isMySpace && (!profileSpaceId || profileSpaceId !== id)
const sendEvent = useSendEvent()
+ const { isCreatorSpace } = useIsCreatorSpace(struct.id)
+ const hasChatSetup = useAppSelector(state => !!state.chat.entity)
+ const setChatOpen = useSetChatOpen()
const buildMenuItems = () => {
sendEvent('open_space_dropdown_menu')
return (
<>
+ {isCreatorSpace && hasChatSetup && (
+ setChatOpen(true)}>
+ Creator chat
+
+ )}
{isMySpace && (
diff --git a/src/components/spaces/helpers/loadSpaceOnNextReq.ts b/src/components/spaces/helpers/loadSpaceOnNextReq.ts
index 951d4c664..43ca016b9 100644
--- a/src/components/spaces/helpers/loadSpaceOnNextReq.ts
+++ b/src/components/spaces/helpers/loadSpaceOnNextReq.ts
@@ -29,7 +29,7 @@ export async function loadSpaceOnNextReq(
id: idStr,
reload: true,
eagerLoadHandles: true,
- dataSource: DataSourceTypes.SQUID,
+ dataSource: DataSourceTypes.CHAIN,
}),
)
const spaceData = selectSpace(reduxStore.getState(), { id: idStr })
diff --git a/src/components/utils/CollapsibleParagraph/CollapsibleParagraph.module.sass b/src/components/utils/CollapsibleParagraph/CollapsibleParagraph.module.sass
new file mode 100644
index 000000000..12a476466
--- /dev/null
+++ b/src/components/utils/CollapsibleParagraph/CollapsibleParagraph.module.sass
@@ -0,0 +1,3 @@
+.CollapsibleParagraph
+ p
+ margin-bottom: 0
\ No newline at end of file
diff --git a/src/components/utils/CollapsibleParagraph/CollapsibleParagraph.tsx b/src/components/utils/CollapsibleParagraph/CollapsibleParagraph.tsx
new file mode 100644
index 000000000..1ac94008e
--- /dev/null
+++ b/src/components/utils/CollapsibleParagraph/CollapsibleParagraph.tsx
@@ -0,0 +1,44 @@
+import { summarizeMd } from '@subsocial/utils'
+import clsx from 'clsx'
+import { ComponentProps, useMemo, useState } from 'react'
+import { DfMd } from '../DfMd'
+import { SummarizeMd } from '../md'
+import styles from './CollapsibleParagraph.module.sass'
+
+export type CollapsibleParagraphProps = ComponentProps<'span'> & {
+ text: string
+}
+
+export default function CollapsibleParagraph({ text, ...props }: CollapsibleParagraphProps) {
+ const [collapseAbout, setCollapseAbout] = useState(true)
+ const summarized = useMemo(() => summarizeMd(text), [text])
+
+ const onToggleShow = (e: any) => {
+ e.preventDefault()
+ e.stopPropagation()
+ setCollapseAbout(prev => !prev)
+ }
+
+ return (
+
+ {!collapseAbout ? (
+ <>
+
+
+ Show Less
+
+ >
+ ) : (
+
+ Show More
+
+ }
+ />
+ )}
+
+ )
+}
diff --git a/src/components/utils/DfMd.tsx b/src/components/utils/DfMd.tsx
index d38091dd2..2df5ba5e5 100644
--- a/src/components/utils/DfMd.tsx
+++ b/src/components/utils/DfMd.tsx
@@ -1,15 +1,17 @@
+import clsx from 'clsx'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
interface Props {
source?: string
className?: string
+ omitDefaultClassName?: boolean
}
-export const DfMd = ({ source, className = '' }: Props) => (
+export const DfMd = ({ source, omitDefaultClassName, className = '' }: Props) => (
{source?.replaceAll('(https://app.subsocial.network/ipfs', '(https://ipfs.subsocial.network') ??
diff --git a/src/components/utils/OffchainUtils.ts b/src/components/utils/OffchainUtils.ts
index c991332fe..cc3354be4 100644
--- a/src/components/utils/OffchainUtils.ts
+++ b/src/components/utils/OffchainUtils.ts
@@ -59,6 +59,36 @@ export const getChainsInfo = async () => {
return res?.data
}
+export const getCreatorList = async () => {
+ const res = await axiosRequest(`${subIdApiUrl}/staking/creator/list`)
+ const creators = (res?.data as { spaceId: string; status: 'Active' | '' }[]) || []
+ return creators.filter(({ status }) => status === 'Active').map(({ spaceId }) => ({ spaceId }))
+}
+
+export const getTotalStake = async ({ address }: { address: string }) => {
+ const res = await axiosRequest(`${subIdApiUrl}/staking/creator/backer/ledger?account=${address}`)
+ const totalStake = (res?.data?.totalLocked as string) || ''
+ const stakeAmount = BigInt(totalStake)
+
+ return { amount: stakeAmount.toString(), hasStaked: stakeAmount > 0 }
+}
+
+export const getStakeAmount = async ({
+ address,
+ spaceId,
+}: {
+ spaceId: string
+ address: string
+}) => {
+ const res = await axiosRequest(
+ `${subIdApiUrl}/staking/creator/backer/info?account=${address}&ids=${spaceId}`,
+ )
+ const newestStakeInfo = (res?.data?.[spaceId]?.[0] as { staked: string; era: number }) || {}
+ const stakeAmount = BigInt(newestStakeInfo.staked)
+
+ return { stakeAmount: stakeAmount.toString(), hasStaked: stakeAmount > 0 }
+}
+
type BalanceByNetworkProps = {
account: AccountId
network: string
diff --git a/src/components/utils/md/SummarizeMd.tsx b/src/components/utils/md/SummarizeMd.tsx
index ffe5578a9..965250aa1 100644
--- a/src/components/utils/md/SummarizeMd.tsx
+++ b/src/components/utils/md/SummarizeMd.tsx
@@ -12,10 +12,18 @@ type Props = {
content?: SummarizedContent
limit?: number
more?: JSX.Element
+ omitDefaultClassName?: boolean
} & Omit, 'content'>
export const SummarizeMd = React.memo((props: Props) => {
- const { content, limit: initialLimit, more, className, ...otherProps } = props
+ const {
+ content,
+ limit: initialLimit,
+ more,
+ className,
+ omitDefaultClassName,
+ ...otherProps
+ } = props
const { summary: initialSummary = '', isShowMore: initialIsShowMore = false } = content || {}
const isMobile = useIsMobileWidthOrDevice()
@@ -29,7 +37,7 @@ export const SummarizeMd = React.memo((props: Props) => {
if (isEmptyStr(summary)) return null
return (
-
+
{summary}
{isShowMore && (
e.stopPropagation()}>
diff --git a/src/components/voting/VoterButtons.tsx b/src/components/voting/VoterButtons.tsx
index bdf6adfb2..05cefdd1e 100644
--- a/src/components/voting/VoterButtons.tsx
+++ b/src/components/voting/VoterButtons.tsx
@@ -1,7 +1,9 @@
-import { DislikeOutlined, DislikeTwoTone, LikeOutlined, LikeTwoTone } from '@ant-design/icons'
+import { DislikeOutlined, DislikeTwoTone } from '@ant-design/icons'
import { ButtonProps } from 'antd/lib/button'
+import clsx from 'clsx'
import dynamic from 'next/dynamic'
import React, { CSSProperties } from 'react'
+import { AiFillHeart, AiOutlineHeart } from 'react-icons/ai'
import { useCreateReloadPost, useCreateUpsertPost } from 'src/rtk/app/hooks'
import { useAppSelector } from 'src/rtk/app/store'
import { useCreateUpsertMyReaction } from 'src/rtk/features/reactions/myPostReactionsHooks'
@@ -15,7 +17,6 @@ import {
ReactionType,
} from 'src/types'
import { useMyAddress } from '../auth/MyAccountsContext'
-import { useResponsiveSize } from '../responsive'
import { getNewIdsFromEvent } from '../substrate'
import { IconWithLabel } from '../utils'
import { BareProps } from '../utils/types'
@@ -25,7 +26,6 @@ const TxButton = dynamic(() => import('../utils/TxButton'), { ssr: false })
type VoterProps = BareProps & {
post: PostStruct
- preview?: boolean
}
type VoterButtonProps = VoterProps &
@@ -33,7 +33,6 @@ type VoterButtonProps = VoterProps &
reactionEnum: ReactionEnum
reaction?: ReactionStruct
onSuccess?: () => void
- preview?: boolean
}
const VoterButton = React.memo(
@@ -44,11 +43,9 @@ const VoterButton = React.memo(
className,
style,
onSuccess,
- preview,
disabled,
}: VoterButtonProps) => {
const { id: postId, upvotesCount, downvotesCount } = post
- const { isMobile } = useResponsiveSize()
const upsertMyReaction = useCreateUpsertMyReaction()
const upsertPost = useCreateUpsertPost()
@@ -76,8 +73,7 @@ const VoterButton = React.memo(
}
const isActive = oldKind === newKind
-
- const color = isUpvote ? '#00a500' : '#ff0000'
+ const color = isUpvote ? '#eb2f96' : '#ff0000'
const changeReactionTx = isActive
? 'reactions.deletePostReaction'
@@ -99,27 +95,27 @@ const VoterButton = React.memo(
}
let icon: JSX.Element
- const label = preview || isMobile ? undefined : newKind
+ const labelText = isUpvote ? 'Like' : 'Dislike'
if (isUpvote) {
// offsets is based on icon, use em to recalculate based on icon's font-size.
const upvoteButtonStyle: CSSProperties = { position: 'relative', top: '0.07em' }
icon = isActive ? (
-
+
) : (
-
+
)
} else {
const downvoteButtonStyle: CSSProperties = { position: 'relative', top: '0.21em' }
icon = isActive ? (
-
+
) : (
-
+
)
}
return (
-
+
)
},
@@ -159,7 +155,7 @@ const InnerVoterButtons = (props: InnerVoterButtonsProps) => {
return (
<>
-
+ {/* */}
>
)
}
diff --git a/src/config/app/polkaverse/index.ts b/src/config/app/polkaverse/index.ts
index fc71713d7..e1d222bbb 100644
--- a/src/config/app/polkaverse/index.ts
+++ b/src/config/app/polkaverse/index.ts
@@ -23,23 +23,6 @@ const index: AppConfig = {
claimedSpaceIds: ['1', '2', '3', '4', '5'],
recommendedSpaceIds: polkaverseSpaces,
suggestedTlds: ['sub', 'polka'],
- creatorIds: [
- '11414',
- '4809',
- '4777',
- '6953',
- '10132',
- '6283',
- '11581',
- '7366',
- '11157',
- '11566',
- '10124',
- '11581',
- '1573',
- '1238',
- '11844',
- ],
}
export default index
diff --git a/src/config/types.ts b/src/config/types.ts
index 4d628480f..5718932a1 100644
--- a/src/config/types.ts
+++ b/src/config/types.ts
@@ -100,7 +100,6 @@ export type AppConfig = {
claimedSpaceIds: SpaceId[]
recommendedSpaceIds: SpaceId[]
suggestedTlds?: string[]
- creatorIds?: string[]
}
export type CommonSubsocialFeatures = {
diff --git a/src/layout/Navigation.tsx b/src/layout/Navigation.tsx
index ec9d27a93..b269ed19e 100644
--- a/src/layout/Navigation.tsx
+++ b/src/layout/Navigation.tsx
@@ -6,8 +6,6 @@ import styles from './Sider.module.sass'
import clsx from 'clsx'
import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
-import ChatSidePanel from 'src/components/chat/ChatSidePanel'
-import { useResponsiveSize } from 'src/components/responsive'
const TopMenu = dynamic(() => import('./TopMenu'), { ssr: false })
const Menu = dynamic(() => import('./SideMenu'), { ssr: false })
@@ -70,16 +68,12 @@ export const Navigation = (props: Props): JSX.Element => {
const {
state: { asDrawer },
} = useSidebarCollapsed()
- const { isLargeDesktop } = useResponsiveSize()
- const { pathname } = useRouter()
const content = useMemo(
() => {children} ,
[children],
)
- const isPostPage = pathname === '/[spaceId]/[slug]'
-
return (
@@ -88,7 +82,6 @@ export const Navigation = (props: Props): JSX.Element => {
{asDrawer ? : }
{content}
- {isLargeDesktop && isPostPage && }
)
diff --git a/src/rtk/app/hooksCommon.ts b/src/rtk/app/hooksCommon.ts
index eeb3aad10..246ba1f46 100644
--- a/src/rtk/app/hooksCommon.ts
+++ b/src/rtk/app/hooksCommon.ts
@@ -1,6 +1,6 @@
import { AsyncThunkAction } from '@reduxjs/toolkit'
import { isEmptyArray, newLogger } from '@subsocial/utils'
-import { useState } from 'react'
+import { useEffect, useMemo, useState } from 'react'
import { shallowEqual } from 'react-redux'
import useSubsocialEffect from 'src/components/api/useSubsocialEffect'
import {
@@ -41,6 +41,52 @@ export type FetchOneFn = FetchFn, Struct>
const log = newLogger('useFetchEntities')
+export function useFetchWithoutApi(
+ fetch: FetchFn,
+ args: Args,
+ config?: { enabled?: boolean },
+): CommonResult {
+ const { enabled } = config || {}
+
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState()
+ const dispatch = useAppDispatch()
+
+ const jsonArgs = useMemo(() => {
+ return JSON.stringify(args)
+ }, [args])
+
+ const isEnabled = !!enabled
+ useEffect(() => {
+ if (!isEnabled) return
+
+ let isMounted = true
+ setError(undefined)
+
+ dispatch(fetch(args))
+ .catch(err => {
+ if (isMounted) {
+ setError(err)
+ log.error(error)
+ }
+ })
+ .finally(() => {
+ if (isMounted) {
+ setLoading(false)
+ }
+ })
+
+ return () => {
+ isMounted = false
+ }
+ }, [dispatch, jsonArgs, isEnabled])
+
+ return {
+ loading,
+ error,
+ }
+}
+
export function useFetch(
fetch: FetchFn,
args: Omit | Partial,
diff --git a/src/rtk/app/rootReducer.ts b/src/rtk/app/rootReducer.ts
index aedf9fcfc..a9d1d2ab3 100644
--- a/src/rtk/app/rootReducer.ts
+++ b/src/rtk/app/rootReducer.ts
@@ -5,6 +5,9 @@ import chainsInfo from '../features/chainsInfo/chainsInfoSlice'
import chat from '../features/chat/chatSlice'
import enableConfirmation from '../features/confirmationPopup/enableConfirmationSlice'
import contents from '../features/contents/contentsSlice'
+import creatorsList from '../features/creators/creatorsListSlice'
+import stakes from '../features/creators/stakesSlice'
+import totalStake from '../features/creators/totalStakeSlice'
import ordersById from '../features/domainPendingOrders/pendingOrdersSlice'
import domainByOwner from '../features/domains/domainsByOwnerSlice'
import domains from '../features/domains/domainsSlice'
@@ -49,6 +52,9 @@ const rootReducer = combineReducers({
sellerConfig,
enableConfirmation,
chat,
+ stakes,
+ totalStake,
+ creatorsList,
})
export type RootState = ReturnType
diff --git a/src/rtk/app/wrappers.ts b/src/rtk/app/wrappers.ts
index 397ba9f46..6fcb76ec8 100644
--- a/src/rtk/app/wrappers.ts
+++ b/src/rtk/app/wrappers.ts
@@ -1,6 +1,7 @@
import { ApolloClient, NormalizedCacheObject } from '@apollo/client'
-import { AsyncThunkPayloadCreator, Dictionary, EntityId } from '@reduxjs/toolkit'
+import { AsyncThunkPayloadCreator, createAsyncThunk, Dictionary, EntityId } from '@reduxjs/toolkit'
import { isDef } from '@subsocial/utils'
+import sortKeysRecursive from 'sort-keys-recursive'
import { getApolloClient } from 'src/graphql/client'
import { DataSourceTypes } from 'src/types'
import {
@@ -186,3 +187,41 @@ export function generatePrefetchDataFn({
+ sliceName,
+ getCachedData,
+ fetchData,
+ saveToCacheAction,
+ shouldFetchCondition,
+}: {
+ sliceName: string
+ getCachedData: (state: RootState, args: Args) => ReturnValue | undefined
+ saveToCacheAction: (data: ReturnValue) => any
+ fetchData: (args: Args) => Promise
+ shouldFetchCondition?: (cachedData: ReturnValue | undefined) => boolean
+}) {
+ const currentlyFetchingMap = new Map>()
+ return createAsyncThunk(
+ `${sliceName}/fetchOne`,
+ async (allArgs, { getState, dispatch }): Promise => {
+ const { reload, ...args } = allArgs
+ const id = JSON.stringify(sortKeysRecursive(args))
+ if (!reload) {
+ const fetchedData = getCachedData(getState(), allArgs)
+ if (fetchedData && !shouldFetchCondition?.(fetchedData)) return fetchedData
+ }
+ const alreadyFetchedPromise = currentlyFetchingMap.get(id)
+ if (alreadyFetchedPromise) return alreadyFetchedPromise
+
+ const promise = fetchData(allArgs)
+ currentlyFetchingMap.set(id, promise)
+ const res = await promise
+
+ currentlyFetchingMap.delete(id)
+ await dispatch(saveToCacheAction(res))
+
+ return promise
+ },
+ )
+}
diff --git a/src/rtk/features/chat/chatSlice.ts b/src/rtk/features/chat/chatSlice.ts
index a635777c9..b593c6c0b 100644
--- a/src/rtk/features/chat/chatSlice.ts
+++ b/src/rtk/features/chat/chatSlice.ts
@@ -1,22 +1,30 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
-import { PostData } from '@subsocial/api/types'
+import { PostData, SpaceData } from '@subsocial/api/types'
const sliceName = 'chats'
-type Entity = {
- type: 'post'
- data: PostData
-} | null
+type Entity =
+ | {
+ type: 'post'
+ data: PostData
+ }
+ | {
+ type: 'space'
+ data: SpaceData
+ }
+ | null
export interface ChatEntity {
isOpen: boolean
entity: Entity
totalMessageCount: number
+ withFloatingButton?: boolean
}
const initialState: ChatEntity = {
isOpen: false,
entity: null,
totalMessageCount: 0,
+ withFloatingButton: false,
}
const slice = createSlice({
@@ -26,8 +34,12 @@ const slice = createSlice({
setChatOpen: (state, action: PayloadAction) => {
state.isOpen = action.payload
},
- setChatConfig: (state, action: PayloadAction) => {
- state.entity = action.payload
+ setChatConfig: (
+ state,
+ action: PayloadAction<{ entity: Entity; withFloatingButton: boolean } | null>,
+ ) => {
+ state.entity = action?.payload?.entity ?? null
+ state.withFloatingButton = action?.payload?.withFloatingButton
state.totalMessageCount = 0
},
setTotalMessageCount: (state, action: PayloadAction) => {
diff --git a/src/rtk/features/creators/creatorsListHooks.ts b/src/rtk/features/creators/creatorsListHooks.ts
new file mode 100644
index 000000000..ce7279549
--- /dev/null
+++ b/src/rtk/features/creators/creatorsListHooks.ts
@@ -0,0 +1,21 @@
+import { useFetchWithoutApi } from 'src/rtk/app/hooksCommon'
+import { useAppSelector } from 'src/rtk/app/store'
+import { fetchCreators, selectCreators } from './creatorsListSlice'
+
+const emptyArgs = {}
+export function useFetchCreators(config?: { enabled?: boolean }) {
+ const { enabled } = config || {}
+ const data = useAppSelector(state => selectCreators(state))
+
+ const props = useFetchWithoutApi(fetchCreators, emptyArgs, { enabled })
+
+ return {
+ ...props,
+ data,
+ }
+}
+
+export function useIsCreatorSpace(spaceId?: string) {
+ const { data, loading } = useFetchCreators({ enabled: !!spaceId })
+ return { isCreatorSpace: data.map(({ spaceId }) => spaceId).includes(spaceId ?? ''), loading }
+}
diff --git a/src/rtk/features/creators/creatorsListSlice.ts b/src/rtk/features/creators/creatorsListSlice.ts
new file mode 100644
index 000000000..60c24b8be
--- /dev/null
+++ b/src/rtk/features/creators/creatorsListSlice.ts
@@ -0,0 +1,35 @@
+import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'
+import { getCreatorList } from 'src/components/utils/OffchainUtils'
+import { RootState } from 'src/rtk/app/rootReducer'
+import { createSimpleFetchWrapper } from 'src/rtk/app/wrappers'
+
+export type Creator = { spaceId: string }
+const sliceName = 'creatorsList'
+
+const adapter = createEntityAdapter({
+ selectId: data => data.spaceId,
+})
+const selectors = adapter.getSelectors(state => state.creatorsList)
+
+export const selectCreators = selectors.selectAll
+
+export const fetchCreators = createSimpleFetchWrapper<{}, Creator[]>({
+ sliceName,
+ fetchData: async function () {
+ const data = await getCreatorList()
+ return data
+ },
+ saveToCacheAction: data => slice.actions.setCreators(data),
+ getCachedData: state => selectCreators(state),
+ shouldFetchCondition: cachedData => !cachedData?.length,
+})
+
+const slice = createSlice({
+ name: sliceName,
+ initialState: adapter.getInitialState(),
+ reducers: {
+ setCreators: adapter.setAll,
+ },
+})
+
+export default slice.reducer
diff --git a/src/rtk/features/creators/stakesHooks.ts b/src/rtk/features/creators/stakesHooks.ts
new file mode 100644
index 000000000..d013d2bdf
--- /dev/null
+++ b/src/rtk/features/creators/stakesHooks.ts
@@ -0,0 +1,25 @@
+import { useMemo } from 'react'
+import { useFetchWithoutApi } from 'src/rtk/app/hooksCommon'
+import { useAppSelector } from 'src/rtk/app/store'
+import { fetchStakeData, getStakeId, selectStakeForCreator } from './stakesSlice'
+
+export function useStakeData(address: string, creatorSpaceId: string) {
+ return useAppSelector(state =>
+ selectStakeForCreator(state, getStakeId({ address, creatorSpaceId })),
+ )
+}
+
+export function useFetchStakeData(address: string, creatorSpaceId: string) {
+ const data = useStakeData(address, creatorSpaceId)
+
+ const args = useMemo(() => {
+ return { address, creatorSpaceId }
+ }, [address, creatorSpaceId])
+
+ const props = useFetchWithoutApi(fetchStakeData, args, { enabled: !!address && !!creatorSpaceId })
+
+ return {
+ ...props,
+ data,
+ }
+}
diff --git a/src/rtk/features/creators/stakesSlice.ts b/src/rtk/features/creators/stakesSlice.ts
new file mode 100644
index 000000000..352a55bf0
--- /dev/null
+++ b/src/rtk/features/creators/stakesSlice.ts
@@ -0,0 +1,54 @@
+import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'
+import { getStakeAmount } from 'src/components/utils/OffchainUtils'
+import { RootState } from 'src/rtk/app/rootReducer'
+import { createSimpleFetchWrapper } from 'src/rtk/app/wrappers'
+
+export type StakeData = {
+ address: string
+ creatorSpaceId: string
+ stakeAmount: string
+ hasStaked: boolean
+}
+
+const sliceName = 'stakes'
+
+export function getStakeId({
+ address,
+ creatorSpaceId,
+}: Pick) {
+ return `${address}-${creatorSpaceId}`
+}
+
+const adapter = createEntityAdapter({
+ selectId: data => getStakeId(data),
+})
+const selectors = adapter.getSelectors(state => state.stakes)
+
+export const selectStakeForCreator = selectors.selectById
+
+export const fetchStakeData = createSimpleFetchWrapper<
+ { address: string; creatorSpaceId: string },
+ StakeData
+>({
+ fetchData: async function ({ address, creatorSpaceId }) {
+ const data = await getStakeAmount({ address, spaceId: creatorSpaceId })
+ const stakeAmount = data || { stakeAmount: '0', hasStaked: false }
+ const finalData = { address, creatorSpaceId, ...stakeAmount }
+
+ return finalData
+ },
+ saveToCacheAction: data => slice.actions.setStakeData(data),
+ getCachedData: (state, { address, creatorSpaceId }) =>
+ selectStakeForCreator(state, getStakeId({ address, creatorSpaceId })),
+ sliceName,
+})
+
+const slice = createSlice({
+ name: sliceName,
+ initialState: adapter.getInitialState(),
+ reducers: {
+ setStakeData: adapter.upsertOne,
+ },
+})
+
+export default slice.reducer
diff --git a/src/rtk/features/creators/totalStakeHooks.ts b/src/rtk/features/creators/totalStakeHooks.ts
new file mode 100644
index 000000000..68d92cd5a
--- /dev/null
+++ b/src/rtk/features/creators/totalStakeHooks.ts
@@ -0,0 +1,25 @@
+import { useMemo } from 'react'
+import { useFetchWithoutApi } from 'src/rtk/app/hooksCommon'
+import { useAppSelector } from 'src/rtk/app/store'
+import { fetchTotalStake, selectTotalStake } from './totalStakeSlice'
+
+export function useTotalStake(address: string) {
+ return useAppSelector(state => selectTotalStake(state, address))
+}
+
+export function useFetchTotalStake(address: string) {
+ const data = useTotalStake(address)
+
+ const args = useMemo(() => {
+ return { address }
+ }, [address])
+
+ const props = useFetchWithoutApi(fetchTotalStake, args, {
+ enabled: !!address,
+ })
+
+ return {
+ ...props,
+ data,
+ }
+}
diff --git a/src/rtk/features/creators/totalStakeSlice.ts b/src/rtk/features/creators/totalStakeSlice.ts
new file mode 100644
index 000000000..07b36bd47
--- /dev/null
+++ b/src/rtk/features/creators/totalStakeSlice.ts
@@ -0,0 +1,43 @@
+import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'
+import { getTotalStake } from 'src/components/utils/OffchainUtils'
+import { RootState } from 'src/rtk/app/rootReducer'
+import { createSimpleFetchWrapper } from 'src/rtk/app/wrappers'
+
+export type TotalStake = {
+ address: string
+ amount: string
+ hasStaked?: boolean
+}
+
+const sliceName = 'totalStakes'
+
+const adapter = createEntityAdapter({
+ selectId: data => data.address,
+})
+const selectors = adapter.getSelectors(state => state.totalStake)
+
+export const selectTotalStake = selectors.selectById
+
+export const fetchTotalStake = createSimpleFetchWrapper<{ address: string }, TotalStake>({
+ sliceName,
+ fetchData: async function ({ address }: { address: string }) {
+ const data = await getTotalStake({ address })
+ let stakeAmount = { amount: '0', hasStaked: false }
+ if (data) stakeAmount = data
+ const finalData = { address, ...stakeAmount }
+
+ return finalData
+ },
+ getCachedData: (state, { address }) => selectTotalStake(state, address),
+ saveToCacheAction: data => slice.actions.setTotalStake(data),
+})
+
+const slice = createSlice({
+ name: sliceName,
+ initialState: adapter.getInitialState(),
+ reducers: {
+ setTotalStake: adapter.upsertOne,
+ },
+})
+
+export default slice.reducer
diff --git a/src/rtk/features/spaceIds/spaceIdsHooks.ts b/src/rtk/features/spaceIds/spaceIdsHooks.ts
index 616be5efb..7572d29e1 100644
--- a/src/rtk/features/spaceIds/spaceIdsHooks.ts
+++ b/src/rtk/features/spaceIds/spaceIdsHooks.ts
@@ -73,3 +73,17 @@ export const useSelectSpaceIdsWhereAccountCanPost = (address?: AccountId) =>
return [...new Set([...ownSpaceIds, ...spaceIdsWithRolesByAccount])]
}, shallowEqual)
+
+export const useSelectSpaceIdsWhereAccountCanPostWithLoadingStatus = (address?: AccountId) =>
+ useAppSelector(state => {
+ if (!address) return { isLoading: false, spaceIds: [] }
+
+ const ownSpaceIds = selectEntityOfSpaceIdsByOwner(state, { id: address })
+ const isLoading = !ownSpaceIds
+ const spaceIdsWithRolesByAccount = selectSpaceIdsWithRolesByAccount(state, address) || []
+
+ return {
+ isLoading,
+ spaceIds: [...new Set([...(ownSpaceIds?.ownSpaceIds ?? []), ...spaceIdsWithRolesByAccount])],
+ }
+ }, shallowEqual)
diff --git a/src/styles/antd.css b/src/styles/antd.css
index f4597c892..164af572b 100644
--- a/src/styles/antd.css
+++ b/src/styles/antd.css
@@ -22563,7 +22563,7 @@ textarea.ant-input-number {
}
.ant-layout.ant-layout-has-sider > .ant-layout,
.ant-layout.ant-layout-has-sider > .ant-layout-content {
- overflow-x: hidden;
+ overflow-x: clip;
}
.ant-layout-header,
.ant-layout-footer {
diff --git a/src/styles/subsocial-vars.scss b/src/styles/subsocial-vars.scss
index a5cbf234f..cf7c7a570 100644
--- a/src/styles/subsocial-vars.scss
+++ b/src/styles/subsocial-vars.scss
@@ -3,6 +3,7 @@
$font_tiny: 0.75rem;
$font_small: 0.875rem;
$font_normal: 1rem;
+$font_semilarge: 1.125rem;
$font_large: 1.25rem;
$font_big: 1.5rem;
$font_huge: 2rem;
diff --git a/src/styles/subsocial.scss b/src/styles/subsocial.scss
index 0dd9a16f3..c119087eb 100644
--- a/src/styles/subsocial.scss
+++ b/src/styles/subsocial.scss
@@ -120,6 +120,10 @@ a {
font-size: $font_normal !important;
}
+.FontSemilarge {
+ font-size: $font_semilarge;
+}
+
.FontLarge {
font-size: $font_large;
}
@@ -128,6 +132,16 @@ a {
font-size: $font_big;
}
+.ColorPrimary {
+ color: $color_primary;
+}
+.ColorMuted {
+ color: $color_muted;
+}
+.ColorWhite {
+ color: white;
+}
+
.GapMini {
gap: $space_mini;
}
@@ -199,6 +213,16 @@ a {
border-radius: $border_radius_huge;
}
+.FontWeightMedium {
+ font-weight: $font_weight_medium;
+}
+.FontWeightSemibold {
+ font-weight: $font_weight_semibold;
+}
+.FontWeightBold {
+ font-weight: $font_weight_bold;
+}
+
.DfImagePreview {
width: 100%;
}
@@ -638,6 +662,15 @@ a {
}
}
+.HideScrollbar {
+ /* Hide scrollbar for Chrome, Safari and Opera */
+ &::-webkit-scrollbar {
+ display: none;
+ }
+ -ms-overflow-style: none; /* IE and Edge */
+ scrollbar-width: none; /* Firefox */
+}
+
.ant-dropdown-trigger {
height: min-content;
}
@@ -880,17 +913,15 @@ hr {
width: 100%;
display: flex;
align-items: center;
- justify-content: space-between;
+ gap: $space_big;
- &.DfActionBorder {
- border-top: 1px solid rgba(34, 36, 38, 0.15);
+ > * {
+ padding: 0;
+ height: auto;
}
- .DfReactionsAction {
- button:first-of-type {
- // margin-left: -$space_normal;
- margin-right: $space_normal;
- }
+ &.DfActionBorder {
+ border-top: 1px solid rgba(34, 36, 38, 0.15);
}
}
@@ -919,7 +950,7 @@ hr {
}
.DfSegment.DfPostPreview {
- padding-bottom: 0;
+ padding-bottom: $space_normal;
.DfInfo {
width: -webkit-fill-available;
@@ -939,11 +970,6 @@ hr {
border: 1px solid $color_light_border;
}
}
-
- .DfActionsPanel {
- padding: 0.25rem 0;
- justify-content: space-evenly;
- }
}
.DfPostImagePreviewWrapper {
@@ -1091,7 +1117,7 @@ hr {
display: flex;
justify-content: center;
align-items: flex-start;
- gap: $space_big;
+ gap: $space_normal;
}
@media (min-width: 767px) {
@@ -1100,6 +1126,8 @@ hr {
max-width: $max_width_content;
width: $max_width_content;
padding-bottom: 0;
+ position: relative;
+ z-index: 0;
.DfSectionOuter {
width: 100%;
@@ -1236,6 +1264,10 @@ hr {
padding: 0 $space_huge;
}
+.ant-btn {
+ font-weight: $font_weight_medium;
+}
+
.DfSearch {
width: 450px;
display: flex;
diff --git a/src/utils/links.ts b/src/utils/links.ts
index 0687cd896..10ff1a57b 100644
--- a/src/utils/links.ts
+++ b/src/utils/links.ts
@@ -1 +1,19 @@
+import { SpaceData } from '@subsocial/api/types'
+import { getCurrentWallet } from 'src/components/auth/utils'
+import { getSpaceHandleOrId } from './spaces'
+
export const getSubsocialDiscordLink = () => 'https://discord.com/invite/w2Rqy2M'
+
+export const getSubIdCreatorsLink = (space?: SpaceData) =>
+ `https://sub.id/creators/${space ? getSpaceHandleOrId(space.struct) : ''}`
+
+export const activeStakingLinks = {
+ learnMore:
+ 'https://polkaverse.com/@subsocial/boost-staking-rewards-by-up-to-3x-in-the-active-staking-40404',
+ discuss: () => {
+ const currentWallet = getCurrentWallet()
+ const link = 'https://grill.chat/creators/stakers-20132'
+ if (!currentWallet) return link
+ return `${link}?wallet=${currentWallet}`
+ },
+}
diff --git a/src/utils/spaces.ts b/src/utils/spaces.ts
new file mode 100644
index 000000000..e1eaac4ff
--- /dev/null
+++ b/src/utils/spaces.ts
@@ -0,0 +1,8 @@
+import { SpaceStruct } from '@subsocial/api/types'
+
+export function getSpaceHandleOrId(spaceStruct?: SpaceStruct) {
+ let handleOrId = spaceStruct?.handle
+ if (handleOrId) handleOrId = `@${handleOrId}`
+
+ return handleOrId || spaceStruct?.id
+}
diff --git a/yarn.lock b/yarn.lock
index 00718aade..c403f9993 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2395,15 +2395,15 @@
"@elastic/elasticsearch" "7.4.0"
"@subsocial/utils" latest
-"@subsocial/grill-widget@^0.0.12":
- version "0.0.12"
- resolved "https://registry.yarnpkg.com/@subsocial/grill-widget/-/grill-widget-0.0.12.tgz#57384850a3adb5342a5505a3f0d67c258d7e3b0e"
- integrity sha512-Xed+wN/NEZ+mQlKA1olVVcBviVIQOTyqaCYcl9pGsg8yDdIBsKVjqJUKty1vvBL7gY0A+1osdwBgX2xUsbHOBA==
+"@subsocial/grill-widget@^0.0.13":
+ version "0.0.13"
+ resolved "https://registry.yarnpkg.com/@subsocial/grill-widget/-/grill-widget-0.0.13.tgz#b3001beb2c5eef140f36befff00a581892f7547f"
+ integrity sha512-16t1zgWC0nFP/iqhlYEVkRL+kzSqBfjTS2xhCPMNTzb51MjpVpGgZLTd77Jq3NU/iqnplyH690iKGzQIixHFkw==
-"@subsocial/resource-discussions@^0.0.3":
- version "0.0.3"
- resolved "https://registry.yarnpkg.com/@subsocial/resource-discussions/-/resource-discussions-0.0.3.tgz#18a13c8ad27b039328e8fc8ac330b7f923a271cc"
- integrity sha512-XCrDFbypAyTibLgvEPYJkqI1uDoX+wmnyqis8Hrher6Y9QFGiG7gPnNk/Bmv5zmiEiz9HJ5k+C6IwbiS5B+KyQ==
+"@subsocial/resource-discussions@^0.0.4":
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/@subsocial/resource-discussions/-/resource-discussions-0.0.4.tgz#4afeb53dde4accc0c70e80fc245b1416c74afdf2"
+ integrity sha512-GAuC6SFfyrSpCdIuwLlA/hOouNgcj+6j+kZiDPz/cxLcNPdB4gqGVNnqKaL/rNE3MLNetpgI+gmatTomGRKeiA==
dependencies:
graphology "^0.25.1"
@@ -2862,6 +2862,13 @@
dependencies:
"@types/lodash" "*"
+"@types/lodash.shuffle@^4.2.9":
+ version "4.2.9"
+ resolved "https://registry.yarnpkg.com/@types/lodash.shuffle/-/lodash.shuffle-4.2.9.tgz#4af1b1c98dd8be8c0c6387e6a62caa476d0594a9"
+ integrity sha512-4siLZ4/vQH4T7Bm4254sG4n6hh9k7vd/bqfDVoeIwSha4Itu3MuoTxPX2I2Tue2JN94y7Y2I27QzwHZLdMlrBg==
+ dependencies:
+ "@types/lodash" "*"
+
"@types/lodash.truncate@^4.4.6":
version "4.4.7"
resolved "https://registry.yarnpkg.com/@types/lodash.truncate/-/lodash.truncate-4.4.7.tgz#662a66e990b2de7002400d960c377cae95d731a3"
@@ -8923,7 +8930,7 @@ kind-of@^5.0.0:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==
-kind-of@^6.0.0, kind-of@^6.0.2:
+kind-of@^6.0.0, kind-of@^6.0.2, kind-of@~6.0.2:
version "6.0.3"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
@@ -9199,6 +9206,11 @@ lodash.pickby@4.6.0:
resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff"
integrity sha512-AZV+GsS/6ckvPOVQPXSiFFacKvKB4kOQu6ynt9wz0F3LO4R9Ij4K1ddYsIytDpSgLz88JHd9P+oaLeej5/Sl7Q==
+lodash.shuffle@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.shuffle/-/lodash.shuffle-4.2.0.tgz#145b5053cf875f6f5c2a33f48b6e9948c6ec7b4b"
+ integrity sha512-V/rTAABKLFjoecTZjKSv+A1ZomG8hZg8hlgeG6wwQVD9AGv+10zqqSf6mFq2tVA703Zd5R0YhSuSlXA+E/Ei+Q==
+
lodash.sortby@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
@@ -12640,6 +12652,21 @@ snapdragon@^0.8.1:
source-map-resolve "^0.5.0"
use "^3.1.0"
+sort-keys-recursive@^2.1.10:
+ version "2.1.10"
+ resolved "https://registry.yarnpkg.com/sort-keys-recursive/-/sort-keys-recursive-2.1.10.tgz#df5e22d3f3ff0427fdc4a088f16c37c1839456b8"
+ integrity sha512-yRLJbEER/PjU7hSRwXvP+NyXiORufu8rbSbp+3wFRuJZXoi/AhuKczbjuipqn7Le0SsTXK4VUeri2+Ni6WS8Hg==
+ dependencies:
+ kind-of "~6.0.2"
+ sort-keys "~4.2.0"
+
+sort-keys@~4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-4.2.0.tgz#6b7638cee42c506fff8c1cecde7376d21315be18"
+ integrity sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg==
+ dependencies:
+ is-plain-obj "^2.0.0"
+
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"