From 0d80bacc7fab358f99f991d4945189245c48a3eb Mon Sep 17 00:00:00 2001 From: "Eng. Juan Combetto" Date: Tue, 28 Nov 2023 14:25:22 +0900 Subject: [PATCH] feat: add avatar and banner previews on TNS Form --- assets/default-images/tns-profile-cover.png | Bin 0 -> 33000 bytes assets/icons/upload-cloud.svg | 11 ++ package.json | 1 + packages/components/OptimizedImage.tsx | 17 +- .../components/inputs/TextInputCustom.tsx | 8 +- .../TNSNameFinderModal.tsx | 2 +- .../teritoriNameService/MediaPreview.tsx | 182 ++++++++++++++++++ .../teritoriNameService/NameData.tsx | 87 --------- .../teritoriNameService/NameDataForm.tsx | 103 +++++----- .../teritoriNameService/NameStatus.tsx | 1 - packages/hooks/useIpfs.ts | 24 +-- .../TeritoriNameService/TNSBurnNameScreen.tsx | 16 +- .../TNSConsultNameScreen.tsx | 57 ++---- .../TeritoriNameService/TNSExploreScreen.tsx | 19 +- .../TeritoriNameService/TNSRegisterScreen.tsx | 2 +- .../TNSUpdateNameScreen.tsx | 9 +- packages/utils/teritori.ts | 66 ------- yarn.lock | 10 + 18 files changed, 347 insertions(+), 268 deletions(-) create mode 100644 assets/default-images/tns-profile-cover.png create mode 100644 assets/icons/upload-cloud.svg create mode 100644 packages/components/teritoriNameService/MediaPreview.tsx delete mode 100644 packages/components/teritoriNameService/NameData.tsx diff --git a/assets/default-images/tns-profile-cover.png b/assets/default-images/tns-profile-cover.png new file mode 100644 index 0000000000000000000000000000000000000000..81cfe808d0301be6b6147324070c99b49101eeca GIT binary patch literal 33000 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 + + + + + + + + + + diff --git a/package.json b/package.json index 59359bce07..6c1bb445ba 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "expo": "^49.0.16", "expo-av": "~13.4.1", "expo-barcode-scanner": "~12.5.3", + "expo-document-picker": "~11.5.4", "expo-font": "~11.4.0", "expo-linear-gradient": "~12.3.0", "expo-optimize": "^0.2.20", diff --git a/packages/components/OptimizedImage.tsx b/packages/components/OptimizedImage.tsx index 7baade4989..87ef21296f 100644 --- a/packages/components/OptimizedImage.tsx +++ b/packages/components/OptimizedImage.tsx @@ -1,5 +1,5 @@ import { CID } from "multiformats"; -import React, { memo } from "react"; +import React, { memo, useEffect } from "react"; import { Image, ImageProps, View, StyleSheet, PixelRatio } from "react-native"; import { neutral33 } from "../utils/style/colors"; @@ -22,12 +22,27 @@ export const OptimizedImage: React.FC< const sourceWidth = PixelRatio.getPixelSizeForLayoutSize(width); const sourceHeight = PixelRatio.getPixelSizeForLayoutSize(height); + useEffect(() => { + setIsError(false); + }, [baseSourceURI]); + + useEffect(() => { + setIsFallbackError(false); + }, [fallbackURI]); + if ((shouldUseFallback && !fallbackURI) || isFallbackError) { return ( extends Omit { label: string; variant?: "regular" | "labelOutside" | "noStyle"; iconSVG?: React.FC; + onPressChildren?: () => void; placeHolder?: string; squaresBackgroundColor?: string; style?: StyleProp; @@ -139,6 +143,7 @@ export const TextInputCustom = ({ subtitle, labelStyle, iconSVG, + onPressChildren, hideLabel, valueModifier, errorStyle, @@ -292,9 +297,10 @@ export const TextInputCustom = ({ {...restProps} /> - {isLoading ? ( + ) : onPressChildren ? ( + {children} ) : ( <>{children} )} diff --git a/packages/components/modals/teritoriNameService/TNSNameFinderModal.tsx b/packages/components/modals/teritoriNameService/TNSNameFinderModal.tsx index d8ededb4d1..b4ee13a381 100644 --- a/packages/components/modals/teritoriNameService/TNSNameFinderModal.tsx +++ b/packages/components/modals/teritoriNameService/TNSNameFinderModal.tsx @@ -120,7 +120,7 @@ export const TNSNameFinderModal: React.FC<{ } width={372} > diff --git a/packages/components/teritoriNameService/MediaPreview.tsx b/packages/components/teritoriNameService/MediaPreview.tsx new file mode 100644 index 0000000000..07f85e7c4c --- /dev/null +++ b/packages/components/teritoriNameService/MediaPreview.tsx @@ -0,0 +1,182 @@ +import * as DocumentPicker from "expo-document-picker"; +import { DocumentPickerResult } from "expo-document-picker"; +import React, { Dispatch, SetStateAction, useState } from "react"; +import { ActivityIndicator, View, ViewStyle } from "react-native"; +import { useSelector } from "react-redux"; + +import tnsProfileAvatar from "../../../assets/default-images/default-name-nft.png"; +import tnsProfileCover from "../../../assets/default-images/tns-profile-cover.png"; +import uploadCloudIcon from "../../../assets/icons/upload-cloud.svg"; +import { useFeedbacks } from "../../context/FeedbacksProvider"; +import { Metadata } from "../../contracts-clients/teritori-name-service/TeritoriNameService.types"; +import { PinataFileProps, useIpfs } from "../../hooks/useIpfs"; +import { useSelectedNetworkInfo } from "../../hooks/useSelectedNetwork"; +import useSelectedWallet from "../../hooks/useSelectedWallet"; +import { selectNFTStorageAPI } from "../../store/slices/settings"; +import { generateIpfsKey } from "../../utils/ipfs"; +import { + neutral00, + neutral17, + neutral33, + secondaryColor, +} from "../../utils/style/colors"; +import { layout } from "../../utils/style/layout"; +import { OptimizedImage } from "../OptimizedImage"; +import { SVG } from "../SVG"; +import { TextInputCustom } from "../inputs/TextInputCustom"; + +export const MediaPreview: React.FC<{ + style: ViewStyle; + avatarImageUrl: string; + setAvatarImageUrl: Dispatch>; + bannerImageUrl: string; + setBannerImageUrl: Dispatch>; +}> = ({ + style, + avatarImageUrl, + setAvatarImageUrl, + bannerImageUrl, + setBannerImageUrl, +}) => { + const selectedNetwork = useSelectedNetworkInfo(); + const selectedWallet = useSelectedWallet(); + const { setToastError } = useFeedbacks(); + const { pinataPinFileToIPFS } = useIpfs(); + const userId = selectedWallet?.userId; + const [isAvatarImageUploadLoading, setAvatarImageUploadLoading] = + useState(false); + const [isBannerImageUploadLoading, setBannerImageUploadLoading] = + useState(false); + const userIPFSKey = useSelector(selectNFTStorageAPI); + + const upload = async ( + callback: Dispatch>, + documentPickerResult: DocumentPickerResult, + ) => { + const pinataJWTKey = + userIPFSKey || (await generateIpfsKey(selectedNetwork?.id || "", userId)); + if (!pinataJWTKey) { + console.error("upload file err : No Pinata JWT"); + setToastError({ + title: "File upload failed", + message: "No Pinata JWT", + }); + return; + } + if (documentPickerResult.output) { + const fileIpfsHash = await pinataPinFileToIPFS({ + pinataJWTKey, + file: { file: documentPickerResult.output[0] }, + } as PinataFileProps); + callback(`ipfs://${fileIpfsHash}`); + } + }; + + const onPressUploadAvatar = async () => { + const documentPickerResult = await DocumentPicker.getDocumentAsync({ + multiple: false, + }); + setAvatarImageUploadLoading(true); + await upload(setAvatarImageUrl, documentPickerResult); + setAvatarImageUploadLoading(false); + }; + + const onPressUploadBanner = async () => { + const documentPickerResult = await DocumentPicker.getDocumentAsync({ + multiple: false, + }); + setBannerImageUploadLoading(true); + await upload(setBannerImageUrl, documentPickerResult); + setBannerImageUploadLoading(false); + }; + + return ( + + + name="image" + style={style} + label="Avatar URL" + noBrokenCorners + variant="labelOutside" + placeHolder="https://website.com/avatar.jpg" + onPressChildren={onPressUploadAvatar} + value={avatarImageUrl} + onChangeText={setAvatarImageUrl} + squaresBackgroundColor={neutral17} + > + {isAvatarImageUploadLoading ? ( + + ) : ( + + )} + + + + name="public_profile_header" + style={style} + label="Cover Image URL" + noBrokenCorners + onPressChildren={onPressUploadBanner} + variant="labelOutside" + placeHolder="https://website.com/coverimage.jpg" + value={bannerImageUrl} + onChangeText={setBannerImageUrl} + squaresBackgroundColor={neutral17} + > + {isBannerImageUploadLoading ? ( + + ) : ( + + )} + + + + + + + + ); +}; diff --git a/packages/components/teritoriNameService/NameData.tsx b/packages/components/teritoriNameService/NameData.tsx deleted file mode 100644 index 35f2eca1ea..0000000000 --- a/packages/components/teritoriNameService/NameData.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React from "react"; -import { View, ViewStyle } from "react-native"; - -import { neutral17, neutral77 } from "../../utils/style/colors"; -import { imageDisplayLabel, prettyTokenData } from "../../utils/teritori"; -import { BrandText } from "../BrandText"; -import { ExternalLink } from "../ExternalLink"; -import { LegacyTertiaryBox } from "../boxes/LegacyTertiaryBox"; - -export const NameData: React.FC<{ - token: any; - name: string; - style?: ViewStyle; -}> = ({ token, name, style }) => { - const width = 396; - - return ( - - {token ? ( - <> - - - Name - - - {name} - - - - - {prettyTokenData(token) - // We display only the raw if there is a value - .filter((data) => data.value) - .map((data, i) => ( - - - {data.displayLabel} - - {/*---- We want some style depending on the data type*/} - {data.displayLabel === imageDisplayLabel ? ( - - {data.value} - - ) : ( - - {data.value} - - )} - - ))} - - - ) : ( - Loading - )} - - ); -}; diff --git a/packages/components/teritoriNameService/NameDataForm.tsx b/packages/components/teritoriNameService/NameDataForm.tsx index 4f72753a14..8d0b1b5040 100644 --- a/packages/components/teritoriNameService/NameDataForm.tsx +++ b/packages/components/teritoriNameService/NameDataForm.tsx @@ -1,15 +1,15 @@ import React, { useEffect, useState } from "react"; import { View, ViewStyle } from "react-native"; +import { MediaPreview } from "./MediaPreview"; import { Metadata } from "../../contracts-clients/teritori-name-service/TeritoriNameService.types"; import { neutral17, neutral77 } from "../../utils/style/colors"; +import { layout } from "../../utils/style/layout"; import { BrandText } from "../BrandText"; import { ExternalLink } from "../ExternalLink"; import { PrimaryButton } from "../buttons/PrimaryButton"; import { TextInputCustom } from "../inputs/TextInputCustom"; -// TODO: Later, create a reusable FormBase cpt to avoid writing too much code and call it in NameDataForm.tsx. Maybe use react-hook-form ? - export const NameDataForm: React.FC<{ isMintPath?: boolean; btnLabel: string; @@ -20,8 +20,8 @@ export const NameDataForm: React.FC<{ const [pathId, setPathId] = useState(""); const [publicName, setPublicName] = useState(""); const [public_bio, setBio] = useState(""); - const [image, setImageUrl] = useState(""); - const [bannerImage, setBannerImage] = useState(""); + const [avatarImageUrl, setAvatarImageUrl] = useState(""); + const [bannerImageUrl, setBannerImageUrl] = useState(""); const [email, setEmail] = useState(""); const [external_url, setWebsite] = useState(""); const [twitter_id, setTwitter] = useState(""); @@ -40,8 +40,8 @@ export const NameDataForm: React.FC<{ pathId, public_name: publicName, public_bio, - image, - public_profile_header: bannerImage, + image: avatarImageUrl, + public_profile_header: bannerImageUrl, email, external_url, twitter_id, @@ -54,8 +54,8 @@ export const NameDataForm: React.FC<{ // Setting initial inputs values (Pre-filled values if existing token) useEffect(() => { setBio(initialData.public_bio || ""); - setImageUrl(initialData.image || ""); - setBannerImage(initialData.public_profile_header || ""); + setAvatarImageUrl(initialData.image || ""); + setBannerImageUrl(initialData.public_profile_header || ""); setEmail(initialData.email || ""); setWebsite(initialData.external_url || ""); setTwitter(initialData.twitter_id || ""); @@ -70,6 +70,7 @@ export const NameDataForm: React.FC<{ {isMintPath ? ( @@ -97,9 +98,12 @@ export const NameDataForm: React.FC<{ . + + {/*// TODO: Refacto TextInputCustom and fix usages*/} + name="pathId" - style={inputStyle} + containerStyle={inputStyle} label="Path ID (must be unique)" placeHolder="Type path ID here" value={pathId} @@ -110,8 +114,11 @@ export const NameDataForm: React.FC<{ ) : null} name="name" - style={[inputStyle, !isMintPath && { marginTop: 4 }]} - label="NAME" + containerStyle={inputStyle} + label="Name" + rules={{ required: true }} + noBrokenCorners + variant="labelOutside" placeHolder="Type name here" value={publicName} onChangeText={setPublicName} @@ -120,35 +127,29 @@ export const NameDataForm: React.FC<{ /> name="public_bio" - style={inputStyle} - label="BIO" + containerStyle={inputStyle} + label="Bio" + noBrokenCorners + variant="labelOutside" placeHolder="Type bio here" value={public_bio} onChangeText={setBio} squaresBackgroundColor={neutral17} /> - - name="image" - style={inputStyle} - label="AVATAR IMAGE URL" - placeHolder="Insert image URL here" - value={image} - onChangeText={setImageUrl} - squaresBackgroundColor={neutral17} - /> - - name="public_profile_header" + + name="email" - style={inputStyle} - label="EMAIL" + containerStyle={inputStyle} + label="Email" + noBrokenCorners + variant="labelOutside" placeHolder="Type email here" value={email} onChangeText={setEmail} @@ -156,8 +157,10 @@ export const NameDataForm: React.FC<{ /> name="external_url" - style={inputStyle} - label="WEBSITE" + containerStyle={inputStyle} + label="Website" + noBrokenCorners + variant="labelOutside" placeHolder="Type/insert link here" value={external_url} onChangeText={setWebsite} @@ -165,8 +168,10 @@ export const NameDataForm: React.FC<{ /> name="twitter_id" - style={inputStyle} - label="TWITTER" + containerStyle={inputStyle} + label="Twitter (X)" + noBrokenCorners + variant="labelOutside" placeHolder="Link to Twitter account" value={twitter_id} onChangeText={setTwitter} @@ -174,8 +179,10 @@ export const NameDataForm: React.FC<{ /> name="discord_id" - style={inputStyle} - label="DISCORD" + containerStyle={inputStyle} + label="Discord" + noBrokenCorners + variant="labelOutside" placeHolder="Link to Discord" value={discord_id} onChangeText={setDiscord} @@ -183,8 +190,10 @@ export const NameDataForm: React.FC<{ /> name="telegram_id" - style={inputStyle} - label="TELEGRAM USERNAME" + containerStyle={inputStyle} + label="Telegram Username" + noBrokenCorners + variant="labelOutside" placeHolder="@nickname" value={telegram_id} onChangeText={setTelegrameUsername} @@ -192,8 +201,10 @@ export const NameDataForm: React.FC<{ /> name="keybase_id" - style={inputStyle} - label="KEYBASE.IO" + containerStyle={inputStyle} + label="Keybase.io" + noBrokenCorners + variant="labelOutside" placeHolder="Type/insert link here" value={keybase_id} onChangeText={setKeybaseIo} @@ -201,19 +212,21 @@ export const NameDataForm: React.FC<{ /> name="validator_operator_address" - style={inputStyle} - label="VALIDATOR OPERATOR ADDRESS" + containerStyle={inputStyle} + label="Validator Operator Address" + noBrokenCorners + variant="labelOutside" placeHolder="Type/insert link here" value={validator_operator_address} onChangeText={setValidatorOperatorAddress} squaresBackgroundColor={neutral17} /> diff --git a/packages/components/teritoriNameService/NameStatus.tsx b/packages/components/teritoriNameService/NameStatus.tsx index 3d554709de..5b37e0956b 100644 --- a/packages/components/teritoriNameService/NameStatus.tsx +++ b/packages/components/teritoriNameService/NameStatus.tsx @@ -7,7 +7,6 @@ import { BrandText } from "../BrandText"; import { SVG } from "../SVG"; import { LegacyTertiaryBox } from "../boxes/LegacyTertiaryBox"; -// A custom TextInput. You can add children (Ex: An icon or a small container) export const NameStatus: React.FC<{ available?: boolean; hasError?: boolean; diff --git a/packages/hooks/useIpfs.ts b/packages/hooks/useIpfs.ts index 30ec447eae..5a398073b5 100644 --- a/packages/hooks/useIpfs.ts +++ b/packages/hooks/useIpfs.ts @@ -5,11 +5,6 @@ import { useState } from "react"; import { LocalFileData, RemoteFileData } from "../utils/types/files"; -interface PinataFileProps { - file: LocalFileData; - pinataJWTKey: string; -} - interface UploadPostFilesToPinataParams { files: LocalFileData[]; pinataJWTKey: string; @@ -20,6 +15,11 @@ interface IPFSUploadProgress { progress: number; // 0 to 1 } +export interface PinataFileProps { + file: LocalFileData; + pinataJWTKey: string; +} + export const useIpfs = () => { const [ipfsUploadProgresses, setIpfsUploadProgresses] = useState< IPFSUploadProgress[] @@ -57,7 +57,7 @@ export const useIpfs = () => { "Content-Type": "multipart/form-data", }, }); - return responseFile.data.IpfsHash; + return CID.parse(responseFile.data.IpfsHash).toV1().toString(); } catch (err) { console.error("Error pinning " + file.fileName + " to IPFS", err); } @@ -74,9 +74,7 @@ export const useIpfs = () => { file, pinataJWTKey, }); - const url = !fileIpfsHash - ? "" - : "ipfs://" + CID.parse(fileIpfsHash).toV1().toString(); + const url = !fileIpfsHash ? "" : "ipfs://" + fileIpfsHash; if (file.thumbnailFileData) { const thumbnailFileIpfsHash = await pinataPinFileToIPFS({ @@ -85,7 +83,7 @@ export const useIpfs = () => { }); const thumbnailUrl = !thumbnailFileIpfsHash ? "" - : "ipfs://" + CID.parse(thumbnailFileIpfsHash).toV1().toString(); + : "ipfs://" + thumbnailFileIpfsHash; return { ...omit(file, "file"), @@ -128,5 +126,9 @@ export const useIpfs = () => { 0, ) / ipfsUploadProgresses.length; - return { uploadFilesToPinata, ipfsUploadProgress: finalProgress }; + return { + uploadFilesToPinata, + pinataPinFileToIPFS, + ipfsUploadProgress: finalProgress, + }; }; diff --git a/packages/screens/TeritoriNameService/TNSBurnNameScreen.tsx b/packages/screens/TeritoriNameService/TNSBurnNameScreen.tsx index 3cb2a34473..e91ec16604 100644 --- a/packages/screens/TeritoriNameService/TNSBurnNameScreen.tsx +++ b/packages/screens/TeritoriNameService/TNSBurnNameScreen.tsx @@ -16,10 +16,12 @@ import { nsNameInfoQueryKey } from "../../hooks/useNSNameInfo"; import { useNSTokensByOwner } from "../../hooks/useNSTokensByOwner"; import useSelectedWallet from "../../hooks/useSelectedWallet"; import { + getCosmosNetwork, getKeplrSigningCosmWasmClient, mustGetCosmosNetwork, } from "../../networks"; -import { neutral17 } from "../../utils/style/colors"; +import { neutral17, neutralA3 } from "../../utils/style/colors"; +import { layout } from "../../utils/style/layout"; interface TNSBurnNameScreenProps extends TNSModalCommonProps {} @@ -29,10 +31,12 @@ export const TNSBurnNameScreen: React.FC = ({ const { name } = useTNS(); const { setToastError, setToastSuccess } = useFeedbacks(); const selectedWallet = useSelectedWallet(); - const network = mustGetCosmosNetwork(selectedWallet?.networkId); + const network = getCosmosNetwork(selectedWallet?.networkId); const { tokens } = useNSTokensByOwner(selectedWallet?.userId); const walletAddress = selectedWallet?.address; - const normalizedTokenId = (name + network.nameServiceTLD || "").toLowerCase(); + const normalizedTokenId = ( + name + network?.nameServiceTLD || "" + ).toLowerCase(); const queryClient = useQueryClient(); @@ -53,6 +57,7 @@ export const TNSBurnNameScreen: React.FC = ({ } try { + const network = mustGetCosmosNetwork(selectedWallet?.networkId); if (!network.nameServiceContractAddress) { throw new Error("network not supported"); } @@ -93,6 +98,7 @@ export const TNSBurnNameScreen: React.FC = ({ onClose()} + label="Burn Name NFT" width={457} boxStyle={{ backgroundColor: neutral17, @@ -131,7 +137,7 @@ export const TNSBurnNameScreen: React.FC = ({ style={{ fontSize: 16, lineHeight: 20, - color: "#A3A3A3", + color: neutralA3, marginTop: 16, marginBottom: 20, }} @@ -145,7 +151,7 @@ export const TNSBurnNameScreen: React.FC = ({ size="XS" text="I understand, burn it" onPress={onSubmit} - style={{ marginBottom: 80 }} + style={{ marginBottom: layout.spacing_x4 }} loader /> diff --git a/packages/screens/TeritoriNameService/TNSConsultNameScreen.tsx b/packages/screens/TeritoriNameService/TNSConsultNameScreen.tsx index 551b02cea0..696a49222d 100644 --- a/packages/screens/TeritoriNameService/TNSConsultNameScreen.tsx +++ b/packages/screens/TeritoriNameService/TNSConsultNameScreen.tsx @@ -4,12 +4,10 @@ import { View } from "react-native"; import { TNSModalCommonProps } from "./TNSHomeScreen"; import { BrandText } from "../../components/BrandText"; -import { CopyToClipboard } from "../../components/CopyToClipboard"; import { PrimaryButton } from "../../components/buttons/PrimaryButton"; import { SecondaryButton } from "../../components/buttons/SecondaryButton"; import ModalBase from "../../components/modals/ModalBase"; import { TNSSendFundsModal } from "../../components/modals/teritoriNameService/TNSSendFundsModal"; -import { NameData } from "../../components/teritoriNameService/NameData"; import { NameNFT } from "../../components/teritoriNameService/NameNFT"; import { useFeedbacks } from "../../context/FeedbacksProvider"; import { useTNS } from "../../context/TNSProvider"; @@ -33,6 +31,7 @@ import { } from "../../networks"; import { useAppNavigation } from "../../utils/navigation"; import { neutral17, neutral33 } from "../../utils/style/colors"; +import { layout } from "../../utils/style/layout"; const NotOwnerActions: React.FC<{ tokenId: string; @@ -57,8 +56,8 @@ const NotOwnerActions: React.FC<{ {isPrimary && ( { onClose(); navigation.navigate("UserPublicProfile", { id: ownerId }); @@ -68,7 +67,7 @@ const NotOwnerActions: React.FC<{ setSendFundsModalVisible(true)} /> @@ -95,24 +94,25 @@ const OwnerActions: React.FC<{ style={{ flexDirection: "row", alignItems: "center", - marginBottom: 42, + justifyContent: "center", + marginBottom: layout.spacing_x3, alignSelf: "center", }} > {isPrimary && ( { + onClose(); navigation.navigate("UserPublicProfile", { id: ownerId }); }} /> )} { onClose("TNSUpdateName"); }} @@ -120,7 +120,7 @@ const OwnerActions: React.FC<{ { onClose("TNSBurnName"); }} @@ -130,7 +130,7 @@ const OwnerActions: React.FC<{ size="M" text="Set as Primary" loader - style={{ marginLeft: 24 }} + style={{ marginLeft: layout.spacing_x3 }} onPress={async () => { try { const network = mustGetCosmosNetwork(wallet?.networkId); @@ -178,11 +178,12 @@ export const TNSConsultNameScreen: React.FC = ({ const networkId = useSelectedNetworkId(); const network = getCosmosNetwork(networkId); const tokenId = (name + network?.nameServiceTLD || "").toLowerCase(); - const { nsInfo: token, notFound } = useNSNameInfo( + const { notFound } = useNSNameInfo( networkId, tokenId, !!network?.nameServiceTLD, ); + const { nameOwner } = useNSNameOwner(networkId, tokenId); const ownerId = getUserId(networkId, nameOwner); const { daos } = useDAOs({ networkId, memberAddress: wallet?.address }); @@ -213,14 +214,16 @@ export const TNSConsultNameScreen: React.FC = ({ {notFound ? ( Not found ) : ( <> - + {!notFound && (isOwnedByUser ? ( = ({ onClose={onClose} /> ))} - {!!token && !!name && ( - - {isOwnedByUser ? ( - - ) : ( - <> - {!!token.extension.contract_address && ( - - )} - - )} - - - )} )} diff --git a/packages/screens/TeritoriNameService/TNSExploreScreen.tsx b/packages/screens/TeritoriNameService/TNSExploreScreen.tsx index 924edbfa75..f98983d0e3 100644 --- a/packages/screens/TeritoriNameService/TNSExploreScreen.tsx +++ b/packages/screens/TeritoriNameService/TNSExploreScreen.tsx @@ -9,10 +9,12 @@ import { TNSSendFundsModal } from "../../components/modals/teritoriNameService/T import { FindAName } from "../../components/teritoriNameService/FindAName"; import { useTNS } from "../../context/TNSProvider"; import { useNSMintAvailability } from "../../hooks/useNSMintAvailability"; +import { useNSNameOwner } from "../../hooks/useNSNameOwner"; import { useNSTokensByOwner } from "../../hooks/useNSTokensByOwner"; import { useSelectedNetworkId } from "../../hooks/useSelectedNetwork"; import useSelectedWallet from "../../hooks/useSelectedWallet"; -import { getCosmosNetwork } from "../../networks"; +import { getCosmosNetwork, getUserId } from "../../networks"; +import { useAppNavigation } from "../../utils/navigation"; import { neutral17 } from "../../utils/style/colors"; interface TNSExploreScreenProps extends TNSModalCommonProps {} @@ -25,6 +27,11 @@ export const TNSExploreScreen: React.FC = ({ const selectedWallet = useSelectedWallet(); const networkId = useSelectedNetworkId(); const network = getCosmosNetwork(networkId); + const navigation = useAppNavigation(); + const { nameOwner } = useNSNameOwner( + networkId, + name + network?.nameServiceTLD || "", + ); const { tokens } = useNSTokensByOwner(selectedWallet?.userId); const tokenId = (name + network?.nameServiceTLD || "").toLowerCase(); const { nameAvailable, nameError, loading } = useNSMintAvailability( @@ -34,7 +41,7 @@ export const TNSExploreScreen: React.FC = ({ return ( onClose()} modalStatus={name && nameAvailable ? "success" : "danger"} @@ -66,16 +73,18 @@ export const TNSExploreScreen: React.FC = ({ { - onClose("TNSConsultName"); + navigation.navigate("UserPublicProfile", { + id: getUserId(networkId, nameOwner), + }); }} /> setSendFundsModalVisible(true)} squaresBackgroundColor={neutral17} /> diff --git a/packages/screens/TeritoriNameService/TNSRegisterScreen.tsx b/packages/screens/TeritoriNameService/TNSRegisterScreen.tsx index 8c26098d6a..e881646b16 100644 --- a/packages/screens/TeritoriNameService/TNSRegisterScreen.tsx +++ b/packages/screens/TeritoriNameService/TNSRegisterScreen.tsx @@ -29,7 +29,7 @@ export const TNSRegisterScreen: React.FC = ({ return ( onClose()} - label="Find a name" + label="Find a Name" width={457} modalStatus={name && nameAvailable ? "success" : "danger"} hideMainSeparator diff --git a/packages/screens/TeritoriNameService/TNSUpdateNameScreen.tsx b/packages/screens/TeritoriNameService/TNSUpdateNameScreen.tsx index dd8cb5f683..25d472fe8a 100644 --- a/packages/screens/TeritoriNameService/TNSUpdateNameScreen.tsx +++ b/packages/screens/TeritoriNameService/TNSUpdateNameScreen.tsx @@ -6,7 +6,6 @@ import { View } from "react-native"; import { TNSModalCommonProps } from "./TNSHomeScreen"; import ModalBase from "../../components/modals/ModalBase"; import { NameDataForm } from "../../components/teritoriNameService/NameDataForm"; -import { NameNFT } from "../../components/teritoriNameService/NameNFT"; import { useFeedbacks } from "../../context/FeedbacksProvider"; import { useTNS } from "../../context/TNSProvider"; import { TeritoriNameServiceQueryClient } from "../../contracts-clients/teritori-name-service/TeritoriNameService.client"; @@ -18,10 +17,10 @@ import { useNSNameOwner } from "../../hooks/useNSNameOwner"; import { useNSTokensByOwner } from "../../hooks/useNSTokensByOwner"; import useSelectedWallet from "../../hooks/useSelectedWallet"; import { + getCosmosNetwork, getKeplrSigningCosmWasmClient, - mustGetNonSigningCosmWasmClient, mustGetCosmosNetwork, - getCosmosNetwork, + mustGetNonSigningCosmWasmClient, } from "../../networks"; import { neutral17 } from "../../utils/style/colors"; import { defaultMetaData } from "../../utils/types/tns"; @@ -197,16 +196,16 @@ export const TNSUpdateNameScreen: React.FC = ({ onClose()} + label="Edit profile" scrollable width={457} boxStyle={{ backgroundColor: neutral17, }} > - { - const finalDatas: PrettyTokenData[] = []; - Object.entries(tokenData).map(([key, value], i) => { - switch (key) { - case "email": - finalDatas[0] = { displayLabel: "Email", value: value as string }; - break; - case "public_name": - finalDatas[1] = { - displayLabel: publicNameDisplayLabel, - value: value as string, - }; - break; - case "public_bio": - finalDatas[2] = { displayLabel: "Bio", value: value as string }; - break; - case "image": - finalDatas[3] = { - displayLabel: imageDisplayLabel, - value: value as string, - }; - break; - case "external_url": - finalDatas[4] = { - displayLabel: "External URL", - value: value as string, - }; - break; - case "discord_id": - finalDatas[5] = { - displayLabel: "Discord", - value: value as string, - }; - break; - case "twitter_id": - finalDatas[6] = { displayLabel: "Twitter", value: value as string }; - break; - case "telegram_id": - finalDatas[7] = { - displayLabel: "Telegram username", - value: value as string, - }; - break; - case "keybase_id": - finalDatas[8] = { displayLabel: "Keybase", value: value as string }; - break; - case "validator_operator_address": - finalDatas[9] = { displayLabel: "Validator", value: value as string }; - break; - } - }); - return finalDatas; -}; diff --git a/yarn.lock b/yarn.lock index 4d160c3a82..c9b183c9db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10682,6 +10682,15 @@ __metadata: languageName: node linkType: hard +"expo-document-picker@npm:~11.5.4": + version: 11.5.4 + resolution: "expo-document-picker@npm:11.5.4" + peerDependencies: + expo: "*" + checksum: a59571a356830b698ebeeb29cf1b33e42dcd05b348fe1125c5c52f0ef0bae410d9e6e57d62b7f9e9a699b38d824e90f787282d80b3feeddb35dcc319a389e546 + languageName: node + linkType: hard + "expo-file-system@npm:~15.4.0, expo-file-system@npm:~15.4.4": version: 15.4.4 resolution: "expo-file-system@npm:15.4.4" @@ -19548,6 +19557,7 @@ __metadata: expo-barcode-scanner: ~12.5.3 expo-dev-client: ~2.4.12 expo-doctor: ^1.1.3 + expo-document-picker: ~11.5.4 expo-font: ~11.4.0 expo-linear-gradient: ~12.3.0 expo-optimize: ^0.2.20