diff --git a/package.json b/package.json index 5d1582fb95..1c96fec89c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "showtime", - "version": "149.0.7", + "version": "149.0.12", "private": true, "license": "MIT", "devDependencies": { diff --git a/packages/app/components/creator-channels/channels.web.tsx b/packages/app/components/creator-channels/channels.web.tsx index e7a760601b..eab49125c9 100644 --- a/packages/app/components/creator-channels/channels.web.tsx +++ b/packages/app/components/creator-channels/channels.web.tsx @@ -25,7 +25,6 @@ import { breakpoints } from "design-system/theme"; import { useJoinedChannelsList, useOwnedChannelsList, - useSuggestedChannelsList, } from "./hooks/use-channels-list"; import { useJoinChannel } from "./hooks/use-join-channel"; import { CreatorChannelsList as CreatorChannelsListMobile } from "./list"; @@ -46,12 +45,6 @@ const channelsSection = { "Get exclusive updates, presale access and unreleased content from your favorite creators.", }; -const suggestedChannelsSection = { - type: "section", - title: "Popular creators", - tw: "text-xl", -}; - const CreatorChannelsHeader = memo( ({ title, @@ -330,10 +323,6 @@ export const CreatorChannels = memo(() => { isLoading: isLoadingJoinedChannels, } = useJoinedChannelsList(); - // suggested channels - const { data: suggestedChannelsData, isLoading: isLoadingSuggestedChannels } = - useSuggestedChannelsList(); - // since we're quering two different endpoints, and based on the amount of data from the first endpoint // we have to transform our data a bit and decide if we build a section list or a single FlashList // we're going to useMemo for that and return the data in the format we need @@ -353,16 +342,6 @@ export const CreatorChannels = memo(() => { ...joinedChannelsData, ] : []), - // check if we have any suggested channels, if we do, we're going to add a section for them (+ the suggested channels) - ...(suggestedChannelsData.length > 0 - ? [ - suggestedChannelsSection, - ...suggestedChannelsData.map((suggestedChannel) => ({ - ...suggestedChannel, - itemType: "creator", - })), - ] - : []), ]; } else { return [ @@ -378,11 +357,7 @@ export const CreatorChannels = memo(() => { ...joinedChannelsData, ]; } - }, [ - joinedChannelsData, - ownedChannelsData, - suggestedChannelsData, - ]) as CreatorChannelsListItemProps[]; + }, [joinedChannelsData, ownedChannelsData]) as CreatorChannelsListItemProps[]; const renderItem = useCallback(({ item }: CreatorChannelsListProps) => { if (item.type === "section") { @@ -403,11 +378,7 @@ export const CreatorChannels = memo(() => { }, []); const ListFooterComponent = useCallback(() => { - if ( - isLoadingJoinedChannels || - isLoadingOwnChannels || - isLoadingSuggestedChannels - ) { + if (isLoadingJoinedChannels || isLoadingOwnChannels) { return ; } @@ -427,7 +398,6 @@ export const CreatorChannels = memo(() => { }, [ isLoadingJoinedChannels, isLoadingOwnChannels, - isLoadingSuggestedChannels, isLoadingMoreJoinedChannels, ]); if (!isLgWidth) { @@ -463,9 +433,7 @@ export const CreatorChannels = memo(() => { { const router = useRouter(); const isDark = useIsDarkMode(); - const viewMembersList = useCallback(() => { - const as = `/channels/${props.channelId}/members`; - - router.push( - Platform.select({ - native: as, - web: { - pathname: router.pathname, - query: { - ...router.query, - channelsMembersModal: true, - }, - } as any, - }), - Platform.select({ - native: as, - web: router.asPath, - }), - { shallow: true } - ); - }, [props.channelId, router]); - const inviteAllowlist = useCallback(() => { const as = "/creator-token/import-allowlist"; router.push( @@ -112,8 +90,7 @@ export const MessagesHeader = (props: HeaderProps) => { /> ) : ( - // TODO: Creator Tokens P1 (hide hidden class) - + { data: newData, }; }; - -export const useSuggestedChannelsList = (params?: { pageSize?: number }) => { - const pageSize = params?.pageSize || PAGE_SIZE; - const channelsFetcher = useCallback( - (index: number, previousPageData: []) => { - if (previousPageData && !previousPageData.length) return null; - return `/v1/channels/suggested?page=${index + 1}&limit=${pageSize}`; - }, - [pageSize] - ); - - const queryState = useInfiniteListQuerySWR( - channelsFetcher, - { - pageSize, - } - ); - const newData = useMemo(() => { - let newData: CreatorChannel[] = []; - if (queryState.data) { - queryState.data.forEach((p) => { - if (p) { - newData = newData.concat(p); - } - }); - } - return newData; - }, [queryState.data]); - - return { - ...queryState, - data: newData, - }; -}; diff --git a/packages/app/components/creator-channels/hooks/use-join-channel.ts b/packages/app/components/creator-channels/hooks/use-join-channel.ts index 9f1b86d7f2..332a4fc15c 100644 --- a/packages/app/components/creator-channels/hooks/use-join-channel.ts +++ b/packages/app/components/creator-channels/hooks/use-join-channel.ts @@ -7,10 +7,7 @@ import { Logger } from "app/lib/logger"; import { useLogInPromise } from "app/lib/login-promise"; import { captureException } from "app/lib/sentry"; -import { - useJoinedChannelsList, - useSuggestedChannelsList, -} from "./use-channels-list"; +import { useJoinedChannelsList } from "./use-channels-list"; async function joinChannel( url: string, @@ -34,7 +31,6 @@ export const useJoinChannel = () => { joinChannel ); const joinedChannels = useJoinedChannelsList(); - const suggestedChannels = useSuggestedChannelsList(); const { onboardingPromise } = useOnboardingPromise(); const handleSubmit = useStableCallback( @@ -47,7 +43,6 @@ export const useJoinChannel = () => { captureException(e); Logger.error(e); } finally { - suggestedChannels.mutate(); joinedChannels.mutate(); } } diff --git a/packages/app/components/creator-channels/hooks/use-leave-channel.ts b/packages/app/components/creator-channels/hooks/use-leave-channel.ts index 2e089c7911..df433a5135 100644 --- a/packages/app/components/creator-channels/hooks/use-leave-channel.ts +++ b/packages/app/components/creator-channels/hooks/use-leave-channel.ts @@ -5,10 +5,7 @@ import { axios } from "app/lib/axios"; import { Logger } from "app/lib/logger"; import { captureException } from "app/lib/sentry"; -import { - useJoinedChannelsList, - useSuggestedChannelsList, -} from "./use-channels-list"; +import { useJoinedChannelsList } from "./use-channels-list"; async function leaveChannel( url: string, @@ -26,7 +23,6 @@ export const useLeaveChannel = () => { leaveChannel ); const joinedChannels = useJoinedChannelsList(); - const suggestedChannels = useSuggestedChannelsList(); const handleSubmit = useStableCallback( async ({ channelId }: { channelId: string }) => { @@ -37,7 +33,6 @@ export const useLeaveChannel = () => { Logger.error(e); } finally { joinedChannels.mutate(); - suggestedChannels.mutate(); } } ); diff --git a/packages/app/components/creator-channels/list.tsx b/packages/app/components/creator-channels/list.tsx index 15469f975d..bfd466a06a 100644 --- a/packages/app/components/creator-channels/list.tsx +++ b/packages/app/components/creator-channels/list.tsx @@ -38,7 +38,6 @@ import { LeanText } from "./components/lean-text"; import { useJoinedChannelsList, useOwnedChannelsList, - useSuggestedChannelsList, } from "./hooks/use-channels-list"; import { useJoinChannel } from "./hooks/use-join-channel"; import { @@ -340,12 +339,6 @@ const channelsSection = { "Get exclusive updates, presale access and unreleased content from your favorite creators.", }; -const suggestedChannelsSection = { - type: "section", - title: "Popular creators", - tw: "text-xl", -}; - export const CreatorChannelsList = memo( ({ useWindowScroll = true }: { useWindowScroll?: boolean }) => { const listRef = useRef>(null); @@ -376,14 +369,6 @@ export const CreatorChannelsList = memo( isLoading: isLoadingJoinedChannels, } = useJoinedChannelsList(); - // suggested channels - const { - data: suggestedChannelsData, - refresh: refreshSuggestedChannels, - isLoading: isLoadingSuggestedChannels, - isRefreshing: isRefreshingSuggestedChannels, - } = useSuggestedChannelsList(); - // since we're quering two different endpoints, and based on the amount of data from the first endpoint // we have to transform our data a bit and decide if we build a section list or a single FlashList // we're going to useMemo for that and return the data in the format we need @@ -403,16 +388,6 @@ export const CreatorChannelsList = memo( ...joinedChannelsData, ] : []), - // check if we have any suggested channels, if we do, we're going to add a section for them (+ the suggested channels) - ...(suggestedChannelsData.length > 0 - ? [ - suggestedChannelsSection, - ...suggestedChannelsData.map((suggestedChannel) => ({ - ...suggestedChannel, - itemType: "creator", - })), - ] - : []), ]; } else { return [ @@ -431,7 +406,6 @@ export const CreatorChannelsList = memo( }, [ joinedChannelsData, ownedChannelsData, - suggestedChannelsData, ]) as CreatorChannelsListItemProps[]; const renderItem = useCallback(({ item }: CreatorChannelsListProps) => { @@ -479,17 +453,9 @@ export const CreatorChannelsList = memo( ]); const refreshPage = useCallback(async () => { - await Promise.all([ - refresh(), - refreshOwnedChannels(), - refreshSuggestedChannels(), - ]); - }, [refresh, refreshOwnedChannels, refreshSuggestedChannels]); - if ( - isLoadingOwnChannels || - isLoadingJoinedChannels || - isLoadingSuggestedChannels - ) { + await Promise.all([refresh(), refreshOwnedChannels()]); + }, [refresh, refreshOwnedChannels]); + if (isLoadingOwnChannels || isLoadingJoinedChannels) { return ; } return ( @@ -521,11 +487,7 @@ export const CreatorChannelsList = memo( // Todo: unity refresh control same as tab view refreshControl={ (); // Disable ETH payment on dev for now because it doesn't support the dev environment yet. -const PAYMENT_METHODS = __DEV__ - ? [ - { - title: "USDC", - value: "USDC", - }, - ] - : [ - { - title: "ETH", - value: "ETH", - }, - { - title: "USDC", - value: "USDC", - }, - ]; +const BUY_PAYMENTS = [ + { + title: "ETH", + value: "ETH", + }, + { + title: "USDC", + value: "USDC", + }, +]; +const SELL_PAYMENTS = [ + { + title: "USDC", + value: "USDC", + }, +]; + const SELECT_LIST = [ { title: "Buy", @@ -168,6 +168,7 @@ export const BuyCreatorToken = () => { "https://app.uniswap.org/swap?outputCurrency=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913&chain=base" ) } + size="regular" > Buy USDC on Uniswap @@ -180,6 +181,7 @@ export const BuyCreatorToken = () => { return ( @@ -262,15 +264,21 @@ export const BuyCreatorToken = () => { - m.value === "USDC") - } - value={paymentMethod} - onChange={(value: any) => setPaymentMethod(value)} - /> + {selectedAction === "buy" ? ( + setPaymentMethod(value)} + key="BUY_PAYMENTS" + /> + ) : ( + setPaymentMethod(value)} + key="SELL_PAYMENTS" + /> + )} { setShowExplanation(true); diff --git a/packages/app/components/creator-token/self-serve-explainer.tsx b/packages/app/components/creator-token/self-serve-explainer.tsx index 82a8636113..e34763d2e9 100644 --- a/packages/app/components/creator-token/self-serve-explainer.tsx +++ b/packages/app/components/creator-token/self-serve-explainer.tsx @@ -36,7 +36,9 @@ export const SelfServeExplainer = () => { const isDark = useIsDarkMode(); const { user } = useUser(); const { top } = useSafeAreaInsets(); - const userProfilePic = user?.data.profile.img_url; + const userProfilePic = + user?.data.profile.img_url || + "https://media.showtime.xyz/assets/default-creator-token-pic.png"; const [profilePic, setProfilePic] = useState( userProfilePic diff --git a/packages/app/components/footer/index.tsx b/packages/app/components/footer/index.tsx index 0ded471b5c..767ee1de12 100644 --- a/packages/app/components/footer/index.tsx +++ b/packages/app/components/footer/index.tsx @@ -1,3 +1,4 @@ +import { useContext } from "react"; import { useWindowDimensions } from "react-native"; import { useIsDarkMode } from "@showtime-xyz/universal.hooks"; @@ -5,6 +6,7 @@ import { useRouter } from "@showtime-xyz/universal.router"; import { View } from "@showtime-xyz/universal.view"; import { MOBILE_WEB_TABS_HEIGHT } from "app/constants/layout"; +import { UserContext } from "app/context/user-context"; import { HIDE_MOBILE_WEB_FOOTER_SCREENS, SWIPE_LIST_SCREENS, @@ -19,6 +21,7 @@ import { import { useNavigationElements } from "app/navigation/use-navigation-elements"; const Footer = () => { + const user = useContext(UserContext); const router = useRouter(); const isDark = useIsDarkMode(); const isDarkThemePage = SWIPE_LIST_SCREENS.includes(router.pathname); @@ -33,6 +36,11 @@ const Footer = () => { const { width } = useWindowDimensions(); const { isTabBarHidden } = useNavigationElements(); + const canCreateMusicDrop = + !!user?.user?.data.profile.bypass_track_ownership_validation || + !!user?.user?.data.profile.spotify_artist_id || + !!user?.user?.data.profile.apple_music_artist_id; + if (width >= 768) { return null; } @@ -65,11 +73,13 @@ const Footer = () => { color={color} focused={router.pathname === "/channels"} /> - + {canCreateMusicDrop && ( + + )} - {/* TODO: Creator Tokens P1 {isAuthenticated && ( { @@ -218,7 +217,7 @@ function HeaderDropdown({ )} - */} + { )} - {/* TODO: Creator Tokens P1 {isAuthenticated && ( { @@ -484,7 +483,6 @@ export const HeaderMd = withColorScheme(() => { )} - */} { return ( <> - ); } diff --git a/packages/app/components/home/popular-creators.tsx b/packages/app/components/home/popular-creators.tsx deleted file mode 100644 index e2d4786696..0000000000 --- a/packages/app/components/home/popular-creators.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import { memo, useCallback } from "react"; -import { - useWindowDimensions, - Dimensions, - Platform, - ListRenderItemInfo, -} from "react-native"; - -import { BorderlessButton } from "react-native-gesture-handler"; - -import { Avatar } from "@showtime-xyz/universal.avatar"; -import { useIsDarkMode } from "@showtime-xyz/universal.hooks"; -import { CreatorChannel as CreatorChannelIcon } from "@showtime-xyz/universal.icon"; -import { Pressable } from "@showtime-xyz/universal.pressable"; -import { useRouter } from "@showtime-xyz/universal.router"; -import { Skeleton } from "@showtime-xyz/universal.skeleton"; -import { colors } from "@showtime-xyz/universal.tailwind"; -import { Text } from "@showtime-xyz/universal.text"; -import { View, ViewProps } from "@showtime-xyz/universal.view"; - -import { - useSuggestedChannelsList, - CreatorChannel, -} from "app/components/creator-channels/hooks/use-channels-list"; -import { DESKTOP_CONTENT_WIDTH } from "app/constants/layout"; - -import { breakpoints } from "design-system/theme"; - -import { useJoinChannel } from "../creator-channels/hooks/use-join-channel"; -import { HomeSlider } from "./home-slider"; - -const PlatformPressable = Platform.OS === "web" ? Pressable : BorderlessButton; - -const INFO_HEIGTH = 230; -const windowWidth = Dimensions.get("window").width; -const PopularCreatorItem = memo(function PopularCreatorItem({ - item, - width, - style, - tw = "", - ...rest -}: { - item: CreatorChannel; - width: number; - index: number; -} & ViewProps) { - const isDark = useIsDarkMode(); - const router = useRouter(); - const joinChannel = useJoinChannel(); - const onJoinChannel = useCallback(async () => { - await joinChannel.trigger({ channelId: item.id }); - router.push(`/channels/${item.id}?fresh=channel`); - }, [item.id, joinChannel, router]); - - return ( - router.push(`/@${item.owner.username}`)} - {...rest} - > - - - - - - - - - {item?.owner?.name || item?.owner?.username} - - - - {`${item?.member_count} members`} - - - - {item?.owner?.bio} - - - - Join - - - ); -}); -const PopularCreatorSkeletonItem = ({ - width = 174, - style, - ...rest -}: { width?: number } & ViewProps) => { - return ( - - - - - - - - - - - - - - - - ); -}; -export const PopularCreators = memo(function PopularCreators() { - const { width } = useWindowDimensions(); - const isMdWidth = width >= breakpoints["md"]; - const { data, isLoading } = useSuggestedChannelsList({ pageSize: 10 }); - const isShowSeeAll = data.length > (isMdWidth ? 3 : 2); - const router = useRouter(); - const pagerWidth = isMdWidth ? DESKTOP_CONTENT_WIDTH : windowWidth - 32; - const itemWidth = Platform.select({ - web: undefined, - default: pagerWidth / 2, - }); - const renderItem = useCallback( - ({ item, index }: ListRenderItemInfo) => ( - - ), - [itemWidth] - ); - if (data.length === 0) return null; - return ( - - - - Popular artists - - {isShowSeeAll && ( - { - router.push("/channels"); - }} - shouldActivateOnStart - hitSlop={10} - > - - see all - - - )} - - - {isLoading ? ( - - - - - - - ) : ( - - )} - - - ); -}); diff --git a/packages/app/components/profile/creator-tokens-panel.tsx b/packages/app/components/profile/creator-tokens-panel.tsx index e85fec60c0..70254be421 100644 --- a/packages/app/components/profile/creator-tokens-panel.tsx +++ b/packages/app/components/profile/creator-tokens-panel.tsx @@ -18,6 +18,7 @@ import { useWalletUSDCBalance } from "app/hooks/creator-token/use-wallet-usdc-ba import { useWallet } from "app/hooks/use-wallet"; import { getCurrencyPrice } from "app/utilities"; +import { TextTooltip } from "../tooltips/text-tooltip"; import { PlatformBuyButton, PlatformSellButton } from "./buy-and-sell-buttons"; type CreatorTokensPanelProps = { isSelf?: boolean; username?: string }; @@ -157,8 +158,6 @@ export const CreatorTokensPanel = ({ username, }: CreatorTokensPanelProps) => { const isDark = useIsDarkMode(); - const router = useRouter(); - const { data: userProfileData } = useUserProfile({ address: username }); const usdcBalance = useWalletUSDCBalance(); @@ -171,84 +170,48 @@ export const CreatorTokensPanel = ({ - Wallet balance + USDC wallet balance - { - router.push( - Platform.select({ - native: "/creator-token/explanation", - web: { - pathname: router.pathname, - query: { - ...router.query, - creatorTokensExplanationModal: true, - }, - } as any, - }), - Platform.select({ - native: "/creator-token/explanation", - web: - router.asPath === "/" - ? "/creator-token/explanation" - : router.asPath, - }), - { shallow: true } - ); - }} - hitSlop={{ top: 12, left: 12, right: 12, bottom: 12 }} - > - - + + + } + text={ + "Your estimated USDC wallet\nbalance on the Base network." + } + /> {getCurrencyPrice("USD", usdcBalance.data?.displayBalance)} - {/* - // TODO: Creator Tokens P1 + {/* TODO: creator tokens p2 Token earnings - { - router.push( - Platform.select({ - native: "/creator-token/explanation", - web: { - pathname: router.pathname, - query: { - ...router.query, - creatorTokensExplanationModal: true, - }, - } as any, - }), - Platform.select({ - native: "/creator-token/explanation", - web: - router.asPath === "/" - ? "/creator-token/explanation" - : router.asPath, - }), - { shallow: true } - ); - }} - hitSlop={{ top: 12, left: 12, right: 12, bottom: 12 }} - > - - + + } + text={ + "Every time someone trades\nyour token you earn a 7%\nfee." + } + /> $21.67 diff --git a/packages/app/components/profile/import-allowlist.tsx b/packages/app/components/profile/import-allowlist.tsx index 118082ac8e..6326fe5d8d 100644 --- a/packages/app/components/profile/import-allowlist.tsx +++ b/packages/app/components/profile/import-allowlist.tsx @@ -1,4 +1,4 @@ -import { useCallback } from "react"; +import { useCallback, useState } from "react"; import { Platform, Linking } from "react-native"; import axios from "axios"; @@ -22,6 +22,7 @@ import { toast } from "design-system/toast"; export const ImportAllowlist = () => { const router = useRouter(); + const [isLoading, setIsLoading] = useState(false); const pickCSV = useCallback(async () => { try { const file = await DocumentPicker.getDocumentAsync({ @@ -38,30 +39,38 @@ export const ImportAllowlist = () => { "Content-Type": `multipart/form-data`, }; const attachment = file.assets[0].uri; - if (Platform.OS === "web") { - const attachmentFormData = await getFileFormData(attachment); - const formData = new FormData(); - formData.append( - "file", - attachmentFormData!, - generateRandomFilename(extractMimeType(attachment)) - ); - await axios({ - url: uploadUrl, - method: "POST", - headers: headers, - data: formData, - }); - } else { - await FileSystem.uploadAsync(uploadUrl, attachment, { - uploadType: FileSystem.FileSystemUploadType.MULTIPART, - sessionType: FileSystem.FileSystemSessionType.BACKGROUND, - fieldName: "file", - httpMethod: "POST", - headers, - }); + + try { + setIsLoading(true); + if (Platform.OS === "web") { + const attachmentFormData = await getFileFormData(attachment); + const formData = new FormData(); + formData.append( + "file", + attachmentFormData!, + generateRandomFilename(extractMimeType(attachment)) + ); + await axios({ + url: uploadUrl, + method: "POST", + headers: headers, + data: formData, + }); + } else { + await FileSystem.uploadAsync(uploadUrl, attachment, { + uploadType: FileSystem.FileSystemUploadType.MULTIPART, + sessionType: FileSystem.FileSystemSessionType.BACKGROUND, + fieldName: "file", + httpMethod: "POST", + headers, + }); + } + toast.success("Allowlist imported successfully"); + } catch { + toast.error("An error occurred while uploading the file"); + } finally { + setIsLoading(false); } - toast.success("Allowlist imported successfully"); const nativeUrl = "/creator-token/import-allowlist-success"; const url = Platform.select({ @@ -107,14 +116,22 @@ export const ImportAllowlist = () => { Creator Tokens to unlock your channel. - diff --git a/packages/app/components/profile/profile.tsx b/packages/app/components/profile/profile.tsx index f5a3c95660..e0d1b0ec26 100644 --- a/packages/app/components/profile/profile.tsx +++ b/packages/app/components/profile/profile.tsx @@ -126,6 +126,10 @@ const Profile = ({ username }: ProfileScreenProps) => { return profileData?.data?.profile?.channels?.[0]?.message_count || 0; }, [profileData?.data?.profile.channels]); + const channelPermissions = useMemo(() => { + return profileData?.data?.profile?.channels?.[0]?.permissions; + }, [profileData?.data?.profile.channels]); + const renderScene = useCallback( ({ route: { index: routeIndex, key }, @@ -143,6 +147,7 @@ const Profile = ({ username }: ProfileScreenProps) => { channelId={channelId} isSelf={isSelf} messageCount={messageCount} + channelPermissions={channelPermissions} /> ); } @@ -176,6 +181,7 @@ const Profile = ({ username }: ProfileScreenProps) => { channelId, isSelf, messageCount, + channelPermissions, tabRefs, ] ); diff --git a/packages/app/components/profile/profile.web.tsx b/packages/app/components/profile/profile.web.tsx index 00c11ccc9f..e63f6a586b 100644 --- a/packages/app/components/profile/profile.web.tsx +++ b/packages/app/components/profile/profile.web.tsx @@ -112,6 +112,10 @@ const Profile = ({ username }: ProfileScreenProps) => { return profileData?.data?.profile?.channels?.[0]?.message_count || 0; }, [profileData?.data?.profile.channels]); + const channelPermissions = useMemo(() => { + return profileData?.data?.profile?.channels?.[0]?.permissions; + }, [profileData?.data?.profile.channels]); + const routes = useMemo(() => formatProfileRoutes(data?.tabs), [data?.tabs]); const [queryTab] = useParam("type", { @@ -306,6 +310,7 @@ const Profile = ({ username }: ProfileScreenProps) => { channelId={channelId} isSelf={isSelf} messageCount={messageCount} + channelPermissions={channelPermissions} /> {isProfileMdScreen ? null : ( diff --git a/packages/app/components/profile/tokens-tab.tsx b/packages/app/components/profile/tokens-tab.tsx index 964c92cb82..1cfaa5fe26 100644 --- a/packages/app/components/profile/tokens-tab.tsx +++ b/packages/app/components/profile/tokens-tab.tsx @@ -4,6 +4,7 @@ import React, { forwardRef, useImperativeHandle, useRef, + useMemo, } from "react"; import { Platform } from "react-native"; @@ -12,7 +13,13 @@ import type { ListRenderItemInfo } from "@shopify/flash-list"; import { Avatar } from "@showtime-xyz/universal.avatar"; import { Button } from "@showtime-xyz/universal.button"; import { useIsDarkMode } from "@showtime-xyz/universal.hooks"; -import { ChevronRight, Showtime } from "@showtime-xyz/universal.icon"; +import { + ChevronRight, + Lock, + LockBadge, + LockRounded, + UnLocked, +} from "@showtime-xyz/universal.icon"; import { Pressable } from "@showtime-xyz/universal.pressable"; import { useRouter } from "@showtime-xyz/universal.router"; import { @@ -32,18 +39,20 @@ import { } from "app/hooks/creator-token/use-creator-tokens"; import { useContentWidth } from "app/hooks/use-content-width"; import { usePlatformBottomHeight } from "app/hooks/use-platform-bottom-height"; -import { getNFTSlug } from "app/hooks/use-share-nft"; import { useUser } from "app/hooks/use-user"; import { useScrollToTop } from "app/lib/react-navigation/native"; import { MutateProvider } from "app/providers/mutate-provider"; import { NFT, Profile } from "app/types"; import { formatNumber } from "app/utilities"; +import SvgUnlocked from "design-system/icon/Unlocked"; + +import { ChannelPermissions } from "../creator-channels/types"; import { TopCreatorTokensItem } from "../creator-token/creator-token-users"; import { EmptyPlaceholder } from "../empty-placeholder"; import { FilterContext } from "./fillter-context"; import { MyCollection } from "./my-collection"; -import { ProfileFooter, ProfileSpinnerFooter } from "./profile-footer"; +import { ProfileSpinnerFooter } from "./profile-footer"; type TabListProps = { profile?: Profile; @@ -59,14 +68,21 @@ export const TokensTabHeader = ({ channelId, isSelf, messageCount, + channelPermissions, }: { channelId: number | null | undefined; messageCount?: number | null; isSelf: boolean; + channelPermissions?: ChannelPermissions | null; }) => { const isDark = useIsDarkMode(); const router = useRouter(); + const channelMessageCountFormatted = useMemo( + () => formatNumber(messageCount || 0), + [messageCount] + ); + return ( {channelId && isSelf ? ( @@ -121,7 +137,7 @@ export const TokensTabHeader = ({ */} {isSelf && } - {channelId && messageCount && messageCount > 0 ? ( + {channelId && (messageCount || messageCount == 0) && messageCount >= 0 ? ( { router.push(`/channels/${channelId}`); @@ -129,9 +145,38 @@ export const TokensTabHeader = ({ tw="mt-6 rounded-xl border border-gray-200 bg-white px-4 dark:border-gray-700 dark:bg-gray-900" > - - {formatNumber(messageCount || 0)} Channel messages - + + + {(channelPermissions && + !channelPermissions?.can_view_creator_messages) || + !channelPermissions ? ( + + ) : ( + + )} + + + {messageCount === 0 ? ( + + View Channel + + ) : ( + + {channelPermissions && + !channelPermissions?.can_view_creator_messages + ? `You've unlocked ${channelMessageCountFormatted} messages` + : `Channel locked (${channelMessageCountFormatted} messages)`} + + )} + {/* TODO: Creator tokens P1 @@ -305,6 +350,7 @@ export const TokensTab = forwardRef< TabListProps & { channelId: number | null | undefined; messageCount?: number | null; + channelPermissions?: ChannelPermissions | null; isSelf: boolean; } >(function ProfileTabList( @@ -316,6 +362,10 @@ export const TokensTab = forwardRef< const username = profile?.username; const { user } = useUser(); const listRef = useRef(null); + const channelPermissions = useMemo(() => { + return profile?.channels?.[0]?.permissions; + }, [profile?.channels]); + const bottomHeight = usePlatformBottomHeight(); useScrollToTop(listRef); useImperativeHandle(ref, () => ({ @@ -405,6 +455,7 @@ export const TokensTab = forwardRef< channelId={channelId} isSelf={isSelf} messageCount={messageCount} + channelPermissions={channelPermissions} /> { const wallet = useWallet(); const res = useSWR("ethBalance" + wallet.address, async () => { + if (__DEV__) return { balance: 0, displayBalance: "0" }; if (wallet.address) { const res = (await publicClient.getBalance({ address: wallet.address, diff --git a/packages/app/types.ts b/packages/app/types.ts index cf7338a89b..9f59cddbf6 100644 --- a/packages/app/types.ts +++ b/packages/app/types.ts @@ -1,3 +1,4 @@ +import { ChannelPermissions } from "./components/creator-channels/types"; import { DropPlan } from "./hooks/use-paid-drop-plans"; export type BunnyVideoUrls = { @@ -154,6 +155,7 @@ export interface Profile { name: string; self_is_member: boolean; message_count: number; + permissions: ChannelPermissions | null; }>; website_url: string; username: string; diff --git a/packages/design-system/toggle/index.tsx b/packages/design-system/toggle/index.tsx index 582a5e9ca0..9d892d2b28 100644 --- a/packages/design-system/toggle/index.tsx +++ b/packages/design-system/toggle/index.tsx @@ -4,6 +4,8 @@ import Animated, { useAnimatedStyle, withTiming, useSharedValue, + FadeIn, + Layout, } from "react-native-reanimated"; import { useIsDarkMode } from "@showtime-xyz/universal.hooks"; @@ -71,6 +73,7 @@ export const Toggle = ({ backgroundColor: isDark ? colors.white : colors.gray[900], }, ]} + entering={FadeIn} /> ) : null} {options.map((item, index) => (