diff --git a/.eslintrc.js b/.eslintrc.js
index 24a2c01b4c..c46726dd12 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -6,6 +6,7 @@ module.exports = {
"@typescript-eslint/no-unused-vars": "error",
"react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
"react-hooks/exhaustive-deps": "error", // Checks effect dependencies
+ "prettier/prettier": "error",
},
overrides: [
{
diff --git a/assets/icons/lock.svg b/assets/icons/lock.svg
new file mode 100644
index 0000000000..bfce8aadd3
--- /dev/null
+++ b/assets/icons/lock.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/candymachine/pinata-upload.ts b/packages/candymachine/pinata-upload.ts
index 3b63118379..5c97ce635f 100644
--- a/packages/candymachine/pinata-upload.ts
+++ b/packages/candymachine/pinata-upload.ts
@@ -1,6 +1,6 @@
import axios from "axios";
-import { LocalFileData } from "../utils/types/feed";
+import { LocalFileData } from "../utils/types/files";
interface PinataFileProps {
file: LocalFileData;
diff --git a/packages/components/FilePreview/AudioView.tsx b/packages/components/FilePreview/AudioView.tsx
index b101c3b02c..60fd6e6472 100644
--- a/packages/components/FilePreview/AudioView.tsx
+++ b/packages/components/FilePreview/AudioView.tsx
@@ -4,11 +4,11 @@ import { View, Image, TouchableOpacity } from "react-native";
import { ActivityIndicator } from "react-native-paper";
import { AudioWaveform } from "./AudioWaveform";
-import { ipfsURLToHTTPURL } from "./ipfs";
import pauseSVG from "../../../assets/icons/pause.svg";
import playSVG from "../../../assets/icons/play.svg";
import { useMaxResolution } from "../../hooks/useMaxResolution";
import { getAudioDuration } from "../../utils/audio";
+import { ipfsURLToHTTPURL } from "../../utils/ipfs";
import {
errorColor,
neutral00,
@@ -17,7 +17,7 @@ import {
} from "../../utils/style/colors";
import { fontSemibold13, fontSemibold14 } from "../../utils/style/fonts";
import { layout, screenContentMaxWidth } from "../../utils/style/layout";
-import { RemoteFileData } from "../../utils/types/feed";
+import { RemoteFileData } from "../../utils/types/files";
import { BrandText } from "../BrandText";
import { SVG } from "../SVG";
import { THUMBNAIL_WIDTH } from "../socialFeed/SocialThread/SocialMessageContent";
diff --git a/packages/components/FilePreview/EditableAudioPreview.tsx b/packages/components/FilePreview/EditableAudioPreview.tsx
index 2ba285ae3c..20194d7461 100644
--- a/packages/components/FilePreview/EditableAudioPreview.tsx
+++ b/packages/components/FilePreview/EditableAudioPreview.tsx
@@ -15,7 +15,7 @@ import {
} from "../../utils/style/colors";
import { fontMedium32, fontSemibold12 } from "../../utils/style/fonts";
import { layout } from "../../utils/style/layout";
-import { LocalFileData } from "../../utils/types/feed";
+import { LocalFileData } from "../../utils/types/files";
import { BrandText } from "../BrandText";
import { SVG } from "../SVG";
import { FileUploader } from "../fileUploader";
diff --git a/packages/components/FilePreview/FilesPreviewsContainer.tsx b/packages/components/FilePreview/FilesPreviewsContainer.tsx
index 392af54e59..0aa172f034 100644
--- a/packages/components/FilePreview/FilesPreviewsContainer.tsx
+++ b/packages/components/FilePreview/FilesPreviewsContainer.tsx
@@ -8,7 +8,7 @@ import { VideoView } from "./VideoView";
import { GIF_MIME_TYPE } from "../../utils/mime";
import { convertGIFToLocalFileType } from "../../utils/social-feed";
import { layout } from "../../utils/style/layout";
-import { LocalFileData, RemoteFileData } from "../../utils/types/feed";
+import { LocalFileData, RemoteFileData } from "../../utils/types/files";
interface FilePreviewContainerProps {
files?: LocalFileData[];
diff --git a/packages/components/FilePreview/ImagesFullViewModal.tsx b/packages/components/FilePreview/ImagesFullViewModal.tsx
index 3314a058af..8e1efc94a4 100644
--- a/packages/components/FilePreview/ImagesFullViewModal.tsx
+++ b/packages/components/FilePreview/ImagesFullViewModal.tsx
@@ -7,9 +7,9 @@ import {
} from "react-native";
import { SvgProps } from "react-native-svg";
-import { ipfsURLToHTTPURL } from "./ipfs";
import chevronLeft from "../../../assets/icons/chevron-left.svg";
import chevronRight from "../../../assets/icons/chevron-right.svg";
+import { ipfsURLToHTTPURL } from "../../utils/ipfs";
import { neutral22, neutral33 } from "../../utils/style/colors";
import { SVG } from "../SVG";
import ModalBase from "../modals/ModalBase";
diff --git a/packages/components/FilePreview/ImagesViews.tsx b/packages/components/FilePreview/ImagesViews.tsx
index 34fa33f385..f28d648f17 100644
--- a/packages/components/FilePreview/ImagesViews.tsx
+++ b/packages/components/FilePreview/ImagesViews.tsx
@@ -3,11 +3,11 @@ import { Image, TouchableOpacity, View } from "react-native";
import { DeleteButton } from "./DeleteButton";
import { ImagesFullViewModal } from "./ImagesFullViewModal";
-import { ipfsURLToHTTPURL } from "./ipfs";
+import { ipfsURLToHTTPURL } from "../../utils/ipfs";
import { errorColor } from "../../utils/style/colors";
import { fontSemibold13 } from "../../utils/style/fonts";
import { layout } from "../../utils/style/layout";
-import { LocalFileData, RemoteFileData } from "../../utils/types/feed";
+import { LocalFileData, RemoteFileData } from "../../utils/types/files";
import { BrandText } from "../BrandText";
interface ImagePreviewProps {
diff --git a/packages/components/FilePreview/VideoView.tsx b/packages/components/FilePreview/VideoView.tsx
index b552186393..0b091b72de 100644
--- a/packages/components/FilePreview/VideoView.tsx
+++ b/packages/components/FilePreview/VideoView.tsx
@@ -3,11 +3,11 @@ import React from "react";
import { View } from "react-native";
import { DeleteButton } from "./DeleteButton";
-import { ipfsURLToHTTPURL } from "./ipfs";
+import { ipfsURLToHTTPURL } from "../../utils/ipfs";
import { errorColor } from "../../utils/style/colors";
import { fontSemibold13 } from "../../utils/style/fonts";
import { layout } from "../../utils/style/layout";
-import { LocalFileData, RemoteFileData } from "../../utils/types/feed";
+import { LocalFileData, RemoteFileData } from "../../utils/types/files";
import { BrandText } from "../BrandText";
interface VideoPreviewProps {
diff --git a/packages/components/FilePreview/ipfs.ts b/packages/components/FilePreview/ipfs.ts
deleted file mode 100644
index 8469975bd8..0000000000
--- a/packages/components/FilePreview/ipfs.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-// temporary hotfix
-// pinata-pinned files are weirdly handled by nft.storage gateway
-
-export const ipfsURLToHTTPURL = (ipfsURL: string | undefined) => {
- if (!ipfsURL) {
- return "";
- }
- if (ipfsURL.startsWith("https://") || ipfsURL.startsWith("blob:")) {
- return ipfsURL;
- }
- if (ipfsURL.startsWith("ipfs://")) {
- return ipfsURL.replace("ipfs://", "https://cloudflare-ipfs.com/ipfs/");
- }
- return "https://cloudflare-ipfs.com/ipfs/" + ipfsURL;
-};
diff --git a/packages/components/fileUploader/FileUploader.type.ts b/packages/components/fileUploader/FileUploader.type.ts
index d2a8e83dcd..6f028c0fef 100644
--- a/packages/components/fileUploader/FileUploader.type.ts
+++ b/packages/components/fileUploader/FileUploader.type.ts
@@ -1,7 +1,7 @@
import React from "react";
import { StyleProp, ViewStyle } from "react-native";
-import { LocalFileData } from "../../utils/types/feed";
+import { LocalFileData } from "../../utils/types/files";
export interface FileUploaderProps {
onUpload: (files: LocalFileData[]) => void;
diff --git a/packages/components/fileUploader/formatFile.ts b/packages/components/fileUploader/formatFile.ts
index cc555761f6..cdddc3dd07 100644
--- a/packages/components/fileUploader/formatFile.ts
+++ b/packages/components/fileUploader/formatFile.ts
@@ -3,7 +3,7 @@ import {
IMAGE_MIME_TYPES,
VIDEO_MIME_TYPES,
} from "./../../utils/mime";
-import { FileType, LocalFileData } from "../../utils/types/feed";
+import { FileType, LocalFileData } from "../../utils/types/files";
import { getAudioData } from "../../utils/waveform";
export const formatFile = async (file: File): Promise => {
diff --git a/packages/components/inputs/SelectInput.tsx b/packages/components/inputs/SelectInput.tsx
new file mode 100644
index 0000000000..8356088657
--- /dev/null
+++ b/packages/components/inputs/SelectInput.tsx
@@ -0,0 +1,232 @@
+import React, { ReactElement, useState } from "react";
+import {
+ View,
+ ScrollView,
+ StyleSheet,
+ StyleProp,
+ ViewStyle,
+} from "react-native";
+
+import { Label } from "./TextInputCustom";
+import chevronDownSVG from "../../../assets/icons/chevron-down.svg";
+import chevronUpSVG from "../../../assets/icons/chevron-up.svg";
+import lockSVG from "../../../assets/icons/lock.svg";
+import {
+ neutral00,
+ neutral33,
+ neutral77,
+ neutralA3,
+ secondaryColor,
+} from "../../utils/style/colors";
+import { fontMedium13, fontSemibold14 } from "../../utils/style/fonts";
+import { layout } from "../../utils/style/layout";
+import { BrandText } from "../BrandText";
+import { SVG } from "../SVG";
+import { CustomPressable } from "../buttons/CustomPressable";
+import { SpacerColumn, SpacerRow } from "../spacer";
+
+export type SelectInputDataValue = string | number;
+
+export type SelectInputData = {
+ label: string;
+ value: SelectInputDataValue;
+ iconComponent?: ReactElement;
+};
+
+type Props = {
+ data: SelectInputData[];
+ placeHolder?: string;
+ selectedData: SelectInputData;
+ setData: (data: SelectInputData) => void;
+ disabled?: boolean;
+ style?: StyleProp;
+ boxStyle?: StyleProp;
+ label?: string;
+ isRequired?: boolean;
+};
+
+export const SelectInput: React.FC = ({
+ data,
+ placeHolder,
+ selectedData,
+ setData,
+ disabled,
+ style,
+ boxStyle,
+ label,
+ isRequired,
+}) => {
+ const [openMenu, setOpenMenu] = useState(false);
+ const [hoveredIndex, setHoveredIndex] = useState(0);
+ const [hovered, setHovered] = useState(false);
+
+ const getScrollViewStyle = () => {
+ if (data.length > 5) {
+ return [styles.dropdownMenu, { height: 200 }];
+ }
+ return styles.dropdownMenu;
+ };
+
+ return (
+ setHovered(true)}
+ onHoverOut={() => setHovered(false)}
+ onPress={() => {
+ if (!disabled || data.length) setOpenMenu((value) => !value);
+ }}
+ disabled={disabled || !data.length}
+ >
+ {label && (
+ <>
+
+
+ >
+ )}
+
+
+
+ {selectedData.iconComponent && (
+ <>
+ {selectedData.iconComponent}
+
+ >
+ )}
+
+
+ {selectedData?.label ? selectedData.label : placeHolder}
+
+
+
+
+
+
+ {/*TODO: If the opened menu appears under other elements, you'll may need to set zIndex:-1 or something to these elements*/}
+ {openMenu && (
+
+ {data.map((item, index) => (
+ {
+ setHoveredIndex(index + 1);
+ setHovered(true);
+ }}
+ onHoverOut={() => {
+ setHoveredIndex(0);
+ setHovered(false);
+ }}
+ onPress={() => {
+ setData(item);
+ setOpenMenu(false);
+ }}
+ key={index}
+ style={styles.dropdownMenuRow}
+ >
+
+ {item.iconComponent && (
+ <>
+ {item.iconComponent}
+
+ >
+ )}
+
+
+ {item.label}
+
+
+
+ ))}
+
+ )}
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ selectInputLabel: StyleSheet.flatten([fontSemibold14, { color: neutralA3 }]),
+ selectInput: {
+ backgroundColor: neutral00,
+ fontSize: 14,
+ fontWeight: 600,
+ color: secondaryColor,
+ borderColor: neutral33,
+ borderWidth: 1,
+ borderRadius: 12,
+ padding: layout.padding_x1_5,
+ flexDirection: "row",
+ justifyContent: "space-between",
+ alignItems: "center",
+ },
+ inputContainer: {
+ backgroundColor: neutral00,
+ borderWidth: 1,
+ borderColor: neutral33,
+ borderRadius: 12,
+ paddingHorizontal: layout.padding_x1_5,
+ },
+ inputItemStyle: {
+ backgroundColor: "#292929",
+ color: neutralA3,
+ paddingVertical: layout.padding_x1_5,
+ paddingHorizontal: layout.padding_x1,
+ },
+ iconLabel: {
+ flexDirection: "row",
+ alignItems: "center",
+ },
+
+ dropdownMenu: {
+ backgroundColor: "#292929",
+ borderWidth: 1,
+ borderColor: neutral33,
+ borderRadius: 12,
+ padding: layout.padding_x1,
+ position: "absolute",
+ top: 52,
+ width: "100%",
+ zIndex: 10,
+ },
+ dropdownMenuText: StyleSheet.flatten([fontMedium13]),
+ dropdownMenuRow: {
+ borderRadius: 6,
+ padding: layout.padding_x1,
+ },
+ // dropdownMenuRow: {
+ // backgroundColor: neutral00,
+ // borderRadius: 6,
+ // padding: layout.padding_x1,
+ // },
+});
diff --git a/packages/components/inputs/TextInputCustom.tsx b/packages/components/inputs/TextInputCustom.tsx
index 84a9681655..9f91a19b8f 100644
--- a/packages/components/inputs/TextInputCustom.tsx
+++ b/packages/components/inputs/TextInputCustom.tsx
@@ -6,6 +6,7 @@ import React, {
useEffect,
useMemo,
useRef,
+ useState,
} from "react";
import {
RegisterOptions,
@@ -16,7 +17,7 @@ import {
FieldValues,
} from "react-hook-form";
import {
- Pressable,
+ ActivityIndicator,
StyleProp,
StyleSheet,
TextInput,
@@ -27,7 +28,6 @@ import {
} from "react-native";
import { SvgProps } from "react-native-svg";
-// import { TextInputLabelProps } from "./TextInputOutsideLabel";
import { DEFAULT_FORM_ERRORS } from "../../utils/errors";
import { handleKeyPress } from "../../utils/keyboard";
import {
@@ -39,22 +39,19 @@ import {
neutralA3,
secondaryColor,
} from "../../utils/style/colors";
-import {
- fontMedium10,
- fontSemibold14,
- fontSemibold20,
-} from "../../utils/style/fonts";
+import { fontMedium10, fontSemibold14 } from "../../utils/style/fonts";
import { layout } from "../../utils/style/layout";
import { BrandText } from "../BrandText";
import { ErrorText } from "../ErrorText";
import { SVG } from "../SVG";
import { TertiaryBox } from "../boxes/TertiaryBox";
+import { CustomPressable } from "../buttons/CustomPressable";
import { SpacerColumn, SpacerRow } from "../spacer";
export interface TextInputCustomProps
extends Omit {
label: string;
- variant?: "regular" | "labelOutside" | "noCropBorder" | "noStyle";
+ variant?: "regular" | "labelOutside" | "noStyle";
iconSVG?: React.FC;
placeHolder?: string;
squaresBackgroundColor?: string;
@@ -72,7 +69,9 @@ export interface TextInputCustomProps
defaultValue?: PathValue>;
subtitle?: React.ReactElement;
hideLabel?: boolean;
+ errorStyle?: ViewStyle;
valueModifier?: (value: string) => string;
+ isLoading?: boolean;
labelStyle?: TextStyle;
containerStyle?: ViewStyle;
boxMainContainerStyle?: ViewStyle;
@@ -84,21 +83,30 @@ export interface TextInputCustomProps
export const Label: React.FC<{
children: string;
- style?: TextStyle;
+ style?: StyleProp;
isRequired?: boolean;
-}> = ({ children, style, isRequired }) => (
+ hovered?: boolean;
+}> = ({ children, style, isRequired, hovered }) => (
-
+
{children}
{!!isRequired && children && (
@@ -123,6 +131,7 @@ export const TextInputCustom = ({
width,
height,
variant = "regular",
+ noBrokenCorners,
name,
control,
defaultValue,
@@ -130,10 +139,10 @@ export const TextInputCustom = ({
subtitle,
labelStyle,
iconSVG,
- noBrokenCorners,
- // isAsterickSign,
hideLabel,
valueModifier,
+ errorStyle,
+ isLoading,
containerStyle,
boxMainContainerStyle,
error,
@@ -141,14 +150,13 @@ export const TextInputCustom = ({
setRef,
...restProps
}: TextInputCustomProps) => {
- // variables
const { field, fieldState } = useController({
name,
control,
rules,
- defaultValue,
});
const inputRef = useRef(null);
+ const [hovered, setHovered] = useState(false);
// Passing ref to parent since I didn't find a pattern to handle generic argument AND forwardRef
useEffect(() => {
if (inputRef.current && setRef) {
@@ -158,7 +166,7 @@ export const TextInputCustom = ({
useEffect(() => {
if (defaultValue) {
- handleChangeText(defaultValue);
+ handleChangeText(defaultValue || "");
}
// handleChangeText changes on every render and we want to call handleChangeText only when default value changes so we disable exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -218,25 +226,35 @@ export const TextInputCustom = ({
);
return (
-
- {variant === "labelOutside" && (
+ setHovered(true)}
+ onHoverOut={() => setHovered(false)}
+ onPress={() => inputRef?.current?.focus()}
+ disabled={disabled}
+ >
+ {variant === "labelOutside" && !hideLabel && (
<>
-
-
+
>
)}
-
({
)}
{!variant ||
- (!["labelOutside", "noCropBorder"].includes(variant) &&
- !hideLabel && (
- inputRef.current?.focus()}>
-
- {label}
-
-
-
- ))}
+ (variant !== "labelOutside" && !hideLabel && (
+ <>
+
+ {label}
+
+
+ >
+ ))}
handleKeyPress({ event, onPressEnter })}
- placeholderTextColor={neutralA3}
+ placeholderTextColor={neutral77}
value={field.value}
style={[styles.textInput, textInputStyle]}
{...restProps}
/>
- <>{children}>
+ {isLoading ? (
+
+ ) : (
+ <>{children}>
+ )}
{error || fieldError}
-
+
);
};
const styles = StyleSheet.create({
- rowEnd: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "flex-end",
- },
mainContainer: {
alignItems: "flex-start",
paddingHorizontal: 12,
@@ -304,7 +320,7 @@ const styles = StyleSheet.create({
paddingVertical: layout.padding_x1_5,
},
labelText: {
- color: neutral77,
+ color: neutralA3,
},
textInput: {
fontSize: 14,
@@ -317,4 +333,9 @@ const styles = StyleSheet.create({
alignItems: "center",
width: "100%",
},
+ rowEnd: {
+ flexDirection: "row",
+ justifyContent: "space-between",
+ alignItems: "flex-end",
+ },
});
diff --git a/packages/components/socialFeed/NewsFeed/NewsFeed.type.ts b/packages/components/socialFeed/NewsFeed/NewsFeed.type.ts
index 11e839ecd9..12d9baf14b 100644
--- a/packages/components/socialFeed/NewsFeed/NewsFeed.type.ts
+++ b/packages/components/socialFeed/NewsFeed/NewsFeed.type.ts
@@ -1,6 +1,6 @@
import { Post } from "../../../api/feed/v1/feed";
import { PostResult } from "../../../contracts-clients/teritori-social-feed/TeritoriSocialFeed.types";
-import { LocalFileData, RemoteFileData } from "../../../utils/types/feed";
+import { LocalFileData, RemoteFileData } from "../../../utils/types/files";
export enum PostCategory {
Reaction,
diff --git a/packages/components/socialFeed/NewsFeed/NewsFeedInput.tsx b/packages/components/socialFeed/NewsFeed/NewsFeedInput.tsx
index cdc90b4cd7..9ba9a8b4b3 100644
--- a/packages/components/socialFeed/NewsFeed/NewsFeedInput.tsx
+++ b/packages/components/socialFeed/NewsFeed/NewsFeedInput.tsx
@@ -10,6 +10,7 @@ import {
useWindowDimensions,
} from "react-native";
import Animated, { useSharedValue } from "react-native-reanimated";
+import { useSelector } from "react-redux";
import { v4 as uuidv4 } from "uuid";
import {
@@ -18,11 +19,7 @@ import {
ReplyToType,
SocialFeedMetadata,
} from "./NewsFeed.type";
-import {
- generatePostMetadata,
- getPostCategory,
- uploadPostFilesToPinata,
-} from "./NewsFeedQueries";
+import { generatePostMetadata, getPostCategory } from "./NewsFeedQueries";
import { NotEnoughFundModal } from "./NotEnoughFundModal";
import audioSVG from "../../../../assets/icons/audio.svg";
import cameraSVG from "../../../../assets/icons/camera.svg";
@@ -46,9 +43,11 @@ import {
getUserId,
mustGetCosmosNetwork,
} from "../../../networks";
+import { selectNFTStorageAPI } from "../../../store/slices/settings";
import { prettyPrice } from "../../../utils/coins";
import { defaultSocialFeedFee } from "../../../utils/fee";
-import { adenaVMCall } from "../../../utils/gno";
+import { adenaDoContract } from "../../../utils/gno";
+import { generateIpfsKey, uploadFilesToPinata } from "../../../utils/ipfs";
import {
AUDIO_MIME_TYPES,
IMAGE_MIME_TYPES,
@@ -58,7 +57,6 @@ import {
SOCIAL_FEED_ARTICLE_MIN_CHARS_LIMIT,
hashtagMatch,
mentionMatch,
- generateIpfsKey,
replaceFileInArray,
removeFileFromArray,
} from "../../../utils/social-feed";
@@ -83,7 +81,7 @@ import {
SOCIAL_FEED_BREAKPOINT_M,
} from "../../../utils/style/layout";
import { replaceBetweenString } from "../../../utils/text";
-import { LocalFileData, RemoteFileData } from "../../../utils/types/feed";
+import { LocalFileData, RemoteFileData } from "../../../utils/types/files";
import { BrandText } from "../../BrandText";
import { FilesPreviewsContainer } from "../../FilePreview/FilesPreviewsContainer";
import FlexRow from "../../FlexRow";
@@ -194,7 +192,7 @@ export const NewsFeedInput = React.forwardRef<
}
);
const formValues = watch();
-
+ const userIPFSKey = useSelector(selectNFTStorageAPI);
const { postFee } = useUpdatePostFee(
selectedNetworkId,
getPostCategory(formValues)
@@ -247,9 +245,10 @@ export const NewsFeedInput = React.forwardRef<
let files: RemoteFileData[] = [];
if (formValues.files?.length) {
- const pinataJWTKey = await generateIpfsKey(selectedNetworkId, userId);
+ const pinataJWTKey =
+ userIPFSKey || (await generateIpfsKey(selectedNetworkId, userId));
if (pinataJWTKey) {
- files = await uploadPostFilesToPinata({
+ files = await uploadFilesToPinata({
files: formValues.files,
pinataJWTKey,
});
@@ -332,12 +331,17 @@ export const NewsFeedInput = React.forwardRef<
msg.metadata,
],
};
- const tx = await adenaVMCall(vmCall, {
- gasWanted: 2_000_000,
- });
+
+ const txHash = await adenaDoContract(
+ selectedNetworkId,
+ [{ type: "/vm.m_call", value: vmCall }],
+ {
+ gasWanted: 1_000_000,
+ }
+ );
const provider = new GnoJSONRPCProvider(selectedNetwork.endpoint);
- await provider.waitForTransaction(tx.data.hash);
+ await provider.waitForTransaction(txHash);
onPostCreationSuccess();
} else {
const client = await signingSocialFeedClient({
@@ -591,7 +595,7 @@ export const NewsFeedInput = React.forwardRef<
: `The cost for this ${type} is ${prettyPrice(
selectedNetworkId,
postFee.toString(),
- "utori"
+ selectedNetwork?.currencies?.[0].denom || "utori"
)}`}
diff --git a/packages/components/socialFeed/NewsFeed/NewsFeedQueries.ts b/packages/components/socialFeed/NewsFeed/NewsFeedQueries.ts
index a698cbe135..ba4a759f6e 100644
--- a/packages/components/socialFeed/NewsFeed/NewsFeedQueries.ts
+++ b/packages/components/socialFeed/NewsFeed/NewsFeedQueries.ts
@@ -1,21 +1,24 @@
import { coin } from "@cosmjs/amino";
-import { omit } from "lodash";
import { v4 as uuidv4 } from "uuid";
import {
- PostCategory,
NewPostFormValues,
+ PostCategory,
SocialFeedMetadata,
} from "./NewsFeed.type";
-import { pinataPinFileToIPFS } from "../../../candymachine/pinata-upload";
import {
nonSigningSocialFeedClient,
signingSocialFeedClient,
} from "../../../client-creators/socialFeedClient";
import { Wallet } from "../../../context/WalletsProvider";
+import { mustGetNetwork, NetworkKind } from "../../../networks";
import { defaultSocialFeedFee } from "../../../utils/fee";
-import { ipfsURLToHTTPURL } from "../../../utils/ipfs";
-import { LocalFileData, RemoteFileData } from "../../../utils/types/feed";
+import { ipfsURLToHTTPURL, uploadFilesToPinata } from "../../../utils/ipfs";
+import { RemoteFileData } from "../../../utils/types/files";
+import { GNO_SOCIAL_FEEDS_PKG_PATH, TERITORI_FEED_ID } from "../const";
+import { adenaDoContract } from "../../../utils/gno";
+import { GnoJSONRPCProvider } from "@gnolang/gno-js-client";
+
interface GetAvailableFreePostParams {
networkId: string;
wallet?: Wallet;
@@ -124,15 +127,10 @@ export const createPost = async ({
return;
}
- const client = await signingSocialFeedClient({
- networkId,
- walletAddress: wallet.address,
- });
-
let files: RemoteFileData[] = [];
if (formValues.files?.length && pinataJWTKey) {
- files = await uploadPostFilesToPinata({
+ files = await uploadFilesToPinata({
files: formValues.files,
pinataJWTKey,
});
@@ -163,62 +161,59 @@ export const createPost = async ({
mentions: formValues.mentions || [],
});
- await client.createPost(
- {
+ const network = mustGetNetwork(networkId);
+
+ if (network.kind === NetworkKind.Gno) {
+ const msg = {
category,
- identifier: identifier || uuidv4(),
+ identifier,
metadata: JSON.stringify(metadata),
parentPostIdentifier: parentId,
- },
- defaultSocialFeedFee,
- "",
- freePostCount ? undefined : [coin(fee, "utori")]
- );
- return true;
-};
-
-interface UploadPostFilesToPinataParams {
- files: LocalFileData[];
- pinataJWTKey: string;
-}
+ };
+
+ const vmCall = {
+ caller: wallet.address,
+ send: "",
+ pkg_path: GNO_SOCIAL_FEEDS_PKG_PATH,
+ func: "CreatePost",
+ args: [
+ TERITORI_FEED_ID,
+ msg.parentPostIdentifier || "0",
+ msg.category.toString(),
+ msg.metadata,
+ ],
+ };
+
+ const txHash = await adenaDoContract(
+ networkId,
+ [{ type: "/vm.m_call", value: vmCall }],
+ {
+ gasWanted: 2_000_000,
+ }
+ );
-export const uploadPostFilesToPinata = async ({
- files,
- pinataJWTKey,
-}: UploadPostFilesToPinataParams): Promise => {
- const storedFile = async (file: LocalFileData): Promise => {
- const fileData = await pinataPinFileToIPFS({
- file,
- pinataJWTKey,
+ const provider = new GnoJSONRPCProvider(network.endpoint);
+ await provider.waitForTransaction(txHash);
+ } else {
+ const client = await signingSocialFeedClient({
+ networkId,
+ walletAddress: wallet.address,
});
- if (file.thumbnailFileData) {
- const thumbnailData = await pinataPinFileToIPFS({
- file: file.thumbnailFileData,
- pinataJWTKey,
- });
-
- return {
- ...omit(file, "file"),
- url: fileData?.IpfsHash || "",
- thumbnailFileData: {
- ...omit(file.thumbnailFileData, "file"),
- url: thumbnailData?.IpfsHash || "",
- },
- };
- } else {
- return {
- ...omit(file, "file"),
- url: fileData?.IpfsHash || "",
- };
- }
- };
- const queries = [];
- for (const file of files) {
- const storedFileQuery = storedFile(file);
- queries.push(storedFileQuery);
+ await client.createPost(
+ {
+ category,
+ identifier: identifier || uuidv4(),
+ metadata: JSON.stringify(metadata),
+ parentPostIdentifier: parentId,
+ },
+ defaultSocialFeedFee,
+ "",
+ freePostCount ? undefined : [coin(fee, "utori")]
+ );
}
- return await Promise.all(queries);
+
+ return true;
};
interface GeneratePostMetadataParams {
diff --git a/packages/components/socialFeed/RichText/RichText.type.ts b/packages/components/socialFeed/RichText/RichText.type.ts
index 9fd0949420..527b8ee727 100644
--- a/packages/components/socialFeed/RichText/RichText.type.ts
+++ b/packages/components/socialFeed/RichText/RichText.type.ts
@@ -1,6 +1,6 @@
import { EntityInstance } from "draft-js";
-import { LocalFileData, RemoteFileData } from "../../../utils/types/feed";
+import { LocalFileData, RemoteFileData } from "../../../utils/types/files";
export type PublishValues = {
hashtags: string[];
diff --git a/packages/components/socialFeed/RichText/RichText.web.tsx b/packages/components/socialFeed/RichText/RichText.web.tsx
index 63d5fb2d58..ac940817a5 100644
--- a/packages/components/socialFeed/RichText/RichText.web.tsx
+++ b/packages/components/socialFeed/RichText/RichText.web.tsx
@@ -81,7 +81,7 @@ import {
import { neutral77 } from "../../../utils/style/colors";
import { fontSemibold14 } from "../../../utils/style/fonts";
import { layout, SOCIAL_FEED_BREAKPOINT_M } from "../../../utils/style/layout";
-import { LocalFileData } from "../../../utils/types/feed";
+import { LocalFileData } from "../../../utils/types/files";
import { BrandText } from "../../BrandText";
import { AudioView } from "../../FilePreview/AudioView";
import { EditableAudioPreview } from "../../FilePreview/EditableAudioPreview";
diff --git a/packages/components/socialFeed/SocialThread/ArticleRenderer.tsx b/packages/components/socialFeed/SocialThread/ArticleRenderer.tsx
index ce37069805..5deee2f0df 100644
--- a/packages/components/socialFeed/SocialThread/ArticleRenderer.tsx
+++ b/packages/components/socialFeed/SocialThread/ArticleRenderer.tsx
@@ -1,11 +1,11 @@
import React from "react";
import { Image } from "react-native";
+import { ipfsURLToHTTPURL } from "../../../utils/ipfs";
import { ARTICLE_COVER_IMAGE_HEIGHT } from "../../../utils/social-feed";
import { layout } from "../../../utils/style/layout";
-import { RemoteFileData } from "../../../utils/types/feed";
+import { RemoteFileData } from "../../../utils/types/files";
import { BrandText } from "../../BrandText";
-import { ipfsURLToHTTPURL } from "../../FilePreview/ipfs";
import { SocialFeedMetadata } from "../NewsFeed/NewsFeed.type";
import { RichText } from "../RichText";
diff --git a/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx b/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx
index e1f5a4e846..f39e3cb337 100644
--- a/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx
+++ b/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx
@@ -1,6 +1,7 @@
import React, { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { View } from "react-native";
+import { useSelector } from "react-redux";
import priceSVG from "../../../assets/icons/price.svg";
import { BrandText } from "../../components/BrandText";
@@ -27,16 +28,18 @@ import { useUpdateAvailableFreePost } from "../../hooks/feed/useUpdateAvailableF
import { useUpdatePostFee } from "../../hooks/feed/useUpdatePostFee";
import { useBalances } from "../../hooks/useBalances";
import { useIsMobile } from "../../hooks/useIsMobile";
-import { useSelectedNetworkId } from "../../hooks/useSelectedNetwork";
+import {
+ useSelectedNetworkId,
+ useSelectedNetworkInfo,
+} from "../../hooks/useSelectedNetwork";
import useSelectedWallet from "../../hooks/useSelectedWallet";
-import { getUserId, NetworkKind } from "../../networks";
+import { getUserId, NetworkFeature, NetworkKind } from "../../networks";
+import { selectNFTStorageAPI } from "../../store/slices/settings";
import { prettyPrice } from "../../utils/coins";
+import { generateIpfsKey } from "../../utils/ipfs";
import { IMAGE_MIME_TYPES } from "../../utils/mime";
import { ScreenFC, useAppNavigation } from "../../utils/navigation";
-import {
- ARTICLE_COVER_IMAGE_HEIGHT,
- generateIpfsKey,
-} from "../../utils/social-feed";
+import { ARTICLE_COVER_IMAGE_HEIGHT } from "../../utils/social-feed";
import {
neutral00,
neutral11,
@@ -51,7 +54,8 @@ import { pluralOrNot } from "../../utils/text";
export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => {
const isMobile = useIsMobile();
- const selectedNetworkId = useSelectedNetworkId();
+ const selectNetworkInfo = useSelectedNetworkInfo();
+ const selectedNetworkId = selectNetworkInfo?.id || "";
const wallet = useSelectedWallet();
const { postFee } = useUpdatePostFee(selectedNetworkId, PostCategory.Article);
const { freePostCount } = useUpdateAvailableFreePost(
@@ -61,6 +65,7 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => {
);
const [isNotEnoughFundModal, setNotEnoughFundModal] = useState(false);
const [loading, setLoading] = useState(false);
+ const userIPFSKey = useSelector(selectNFTStorageAPI);
const { setToastSuccess, setToastError } = useFeedbacks();
const navigation = useAppNavigation();
@@ -113,7 +118,8 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => {
}
let pinataJWTKey = undefined;
if (files?.length) {
- pinataJWTKey = await generateIpfsKey(selectedNetworkId, userId);
+ pinataJWTKey =
+ userIPFSKey || (await generateIpfsKey(selectedNetworkId, userId));
}
const result = await createPost({
@@ -178,7 +184,7 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => {
return (
= () => {
: `The cost for this Article is ${prettyPrice(
selectedNetworkId,
postFee.toString(),
- "utori"
+ selectNetworkInfo?.currencies?.[0].denom || "utori"
)}`}
@@ -268,31 +274,32 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => {
borderRadius: 12,
}}
/>
- Article content
-
- {/**@ts-ignore error:TS2589: Type instantiation is excessively deep and possibly infinite. */}
- (
-
- )}
- />
+
+ Article content
+
+ (
+
+ )}
+ />
+
);
diff --git a/packages/screens/Organizations/components/ConfigureVotingSection.tsx b/packages/screens/Organizations/components/ConfigureVotingSection.tsx
index 1ab0124e5d..57f8caa5f9 100644
--- a/packages/screens/Organizations/components/ConfigureVotingSection.tsx
+++ b/packages/screens/Organizations/components/ConfigureVotingSection.tsx
@@ -76,7 +76,7 @@ export const ConfigureVotingSection: React.FC = ({
name="days"
- variant="noCropBorder"
+ noBrokenCorners
hideLabel
control={control}
label=""
@@ -89,7 +89,7 @@ export const ConfigureVotingSection: React.FC = ({
name="hours"
- variant="noCropBorder"
+ noBrokenCorners
hideLabel
control={control}
label=""
@@ -103,7 +103,7 @@ export const ConfigureVotingSection: React.FC = ({
name="minutes"
- variant="noCropBorder"
+ noBrokenCorners
hideLabel
control={control}
label=""
diff --git a/packages/screens/Organizations/components/CreateDAOSection.tsx b/packages/screens/Organizations/components/CreateDAOSection.tsx
index 6871ce06fb..7e48beecf2 100644
--- a/packages/screens/Organizations/components/CreateDAOSection.tsx
+++ b/packages/screens/Organizations/components/CreateDAOSection.tsx
@@ -63,43 +63,44 @@ export const CreateDAOSection: React.FC = ({
+ noBrokenCorners
+ variant="labelOutside"
control={control}
- variant="noCropBorder"
label="Organization's name"
placeHolder="Type organization's name here"
name="organizationName"
rules={{ required: true }}
- // isAsterickSign
/>
+ noBrokenCorners
+ variant="labelOutside"
control={control}
- variant="noCropBorder"
label="Associated Teritori Name Service"
placeHolder="your-organization.tori"
name="associatedTeritoriNameService"
rules={{ required: true }}
- // isAsterickSign
/>
+ noBrokenCorners
control={control}
- variant="noCropBorder"
+ variant="labelOutside"
label="Organization's image url"
placeHolder="https://example.com/preview.png"
name="imageUrl"
rules={{ required: true }}
- // isAsterickSign
/>
+ noBrokenCorners
+ variant="labelOutside"
control={control}
- variant="noCropBorder"
label="Organization's description"
placeHolder="Type organization's description here"
name="organizationDescription"
diff --git a/packages/screens/Organizations/components/MemberSettingsSection.tsx b/packages/screens/Organizations/components/MemberSettingsSection.tsx
index c514cc3513..d61fbb698f 100644
--- a/packages/screens/Organizations/components/MemberSettingsSection.tsx
+++ b/packages/screens/Organizations/components/MemberSettingsSection.tsx
@@ -54,11 +54,10 @@ export const MemberSettingsSection: React.FC = ({
name={`members.${index}.addr`}
- variant="noCropBorder"
+ noBrokenCorners
label="Member Address"
hideLabel={index > 0}
control={control}
- // isAsterickSign
rules={{ required: true, validate: validateAddress }}
placeHolder="Account address"
iconSVG={walletInputSVG}
@@ -75,11 +74,10 @@ export const MemberSettingsSection: React.FC = ({
name={`members.${index}.weight`}
- variant="noCropBorder"
+ noBrokenCorners
label="Weight"
hideLabel={index > 0}
control={control}
- // isAsterickSign
rules={{ required: true, pattern: patternOnlyNumbers }}
placeHolder="1"
/>
diff --git a/packages/screens/Organizations/components/TokenSettingsSection.tsx b/packages/screens/Organizations/components/TokenSettingsSection.tsx
index 0dfe358e59..970ae4581d 100644
--- a/packages/screens/Organizations/components/TokenSettingsSection.tsx
+++ b/packages/screens/Organizations/components/TokenSettingsSection.tsx
@@ -56,10 +56,9 @@ export const TokenSettingsSection: React.FC = ({
name="tokenName"
- variant="noCropBorder"
+ noBrokenCorners
label="Token name"
control={control}
- // isAsterickSign
rules={{ required: true }}
placeHolder="My Organization Token"
/>
@@ -68,10 +67,9 @@ export const TokenSettingsSection: React.FC = ({
name="tokenSymbol"
- variant="noCropBorder"
+ noBrokenCorners
label="Token Symbol"
control={control}
- // isAsterickSign
valueModifier={(value) => value.toUpperCase()}
rules={{ required: true, pattern: patternOnlyLetters }}
placeHolder="ABC"
@@ -85,11 +83,10 @@ export const TokenSettingsSection: React.FC = ({
name={`tokenHolders.${index}.address`}
- variant="noCropBorder"
+ noBrokenCorners
label="Token Holders"
hideLabel={index > 0}
control={control}
- // isAsterickSign
rules={{ required: true, validate: validateAddress }}
placeHolder="Account address"
iconSVG={walletInputSVG}
@@ -106,11 +103,10 @@ export const TokenSettingsSection: React.FC = ({
name={`tokenHolders.${index}.balance`}
- variant="noCropBorder"
+ noBrokenCorners
label="Balances"
hideLabel={index > 0}
control={control}
- // isAsterickSign
rules={{ required: true, pattern: patternOnlyNumbers }}
placeHolder="0"
/>
diff --git a/packages/screens/Settings/SettingsScreen.tsx b/packages/screens/Settings/SettingsScreen.tsx
index c9986b7d7a..012bf6e64c 100644
--- a/packages/screens/Settings/SettingsScreen.tsx
+++ b/packages/screens/Settings/SettingsScreen.tsx
@@ -25,7 +25,7 @@ import { neutralA3, primaryColor } from "../../utils/style/colors";
import { fontSemibold14 } from "../../utils/style/fonts";
const NFTAPIKeyInput: React.FC = () => {
- const NFTApiKey = useSelector(selectNFTStorageAPI);
+ const userIPFSKey = useSelector(selectNFTStorageAPI);
const dispatch = useAppDispatch();
const commonStyles = useCommonStyles();
@@ -40,11 +40,12 @@ const NFTAPIKeyInput: React.FC = () => {
},
]}
>
- NFT.Storage/Pinata.cloud API key (for Social Feed)
+ app.pinata.cloud JWT key (For file upload)
- dispatch(setNFTStorageAPI(process.env.NFT_STORAGE_API || ""))
+ // We ask key at each upload for now (Don't have Teritori's key for now)
+ dispatch(setNFTStorageAPI(""))
}
>
@@ -55,7 +56,7 @@ const NFTAPIKeyInput: React.FC = () => {
dispatch(setNFTStorageAPI(value))}
/>
diff --git a/packages/utils/ipfs.ts b/packages/utils/ipfs.ts
index 837241c733..f792d182fb 100644
--- a/packages/utils/ipfs.ts
+++ b/packages/utils/ipfs.ts
@@ -1,3 +1,87 @@
+import { omit } from "lodash";
+
+import { mustGetFeedClient } from "./backend";
+import { LocalFileData, RemoteFileData } from "./types/files";
+import { pinataPinFileToIPFS } from "../candymachine/pinata-upload";
+
+interface UploadPostFilesToPinataParams {
+ files: LocalFileData[];
+ pinataJWTKey: string;
+}
+
+export const uploadFilesToPinata = async ({
+ files,
+ pinataJWTKey,
+}: UploadPostFilesToPinataParams): Promise => {
+ const storedFile = async (file: LocalFileData): Promise => {
+ const fileData = await pinataPinFileToIPFS({
+ file,
+ pinataJWTKey,
+ });
+ if (file.thumbnailFileData) {
+ const thumbnailData = await pinataPinFileToIPFS({
+ file: file.thumbnailFileData,
+ pinataJWTKey,
+ });
+
+ return {
+ ...omit(file, "file"),
+ url: fileData?.IpfsHash || "",
+ thumbnailFileData: {
+ ...omit(file.thumbnailFileData, "file"),
+ url: thumbnailData?.IpfsHash || "",
+ },
+ };
+ } else {
+ return {
+ ...omit(file, "file"),
+ url: fileData?.IpfsHash || "",
+ };
+ }
+ };
+
+ const queries = [];
+ for (const file of files) {
+ const storedFileQuery = storedFile(file);
+ queries.push(storedFileQuery);
+ }
+ return await Promise.all(queries);
+};
+
+export const generateIpfsKey = async (networkId: string, userId: string) => {
+ try {
+ const backendClient = mustGetFeedClient(networkId);
+ const response = await backendClient.IPFSKey({ userId });
+ return response.jwt;
+ } catch (e) {
+ console.error("ERROR WHILE GENERATING IPFSKey : ", e);
+ return undefined;
+ }
+};
+
+// Get IPFS Key and upload files.
+// But you can do separately generateIpfsKey then uploadFilesToPinata (Ex in NewsFeedInput.tsx)
+export const uploadFileToIPFS = async (
+ file: LocalFileData,
+ networkId: string,
+ userId: string,
+ userKey?: string
+) => {
+ let uploadedFiles: RemoteFileData[] = [];
+ const pinataJWTKey = userKey || (await generateIpfsKey(networkId, userId));
+
+ if (pinataJWTKey) {
+ uploadedFiles = await uploadFilesToPinata({
+ files: [file],
+ pinataJWTKey,
+ });
+ }
+ if (!uploadedFiles.find((file) => file.url)) {
+ console.error("upload file err : Fail to pin to IPFS");
+ } else return uploadedFiles[0];
+};
+
+// Used to get a correct image URL for displaying or storing
export const ipfsURLToHTTPURL = (ipfsURL: string | undefined) => {
if (!ipfsURL) {
return "";
diff --git a/packages/utils/social-feed.ts b/packages/utils/social-feed.ts
index 78b7c60b47..ff3238a4ca 100644
--- a/packages/utils/social-feed.ts
+++ b/packages/utils/social-feed.ts
@@ -1,7 +1,6 @@
-import { mustGetFeedClient } from "./backend";
import { GIF_MIME_TYPE } from "./mime";
import { HASHTAG_REGEX, MENTION_REGEX, URL_REGEX } from "./regex";
-import { LocalFileData } from "./types/feed";
+import { LocalFileData } from "./types/files";
import { Post, Reaction } from "../api/feed/v1/feed";
import {
PostCategory,
@@ -100,17 +99,6 @@ export const postResultToPost = (
return post;
};
-export const generateIpfsKey = async (networkId: string, userId: string) => {
- try {
- const backendClient = mustGetFeedClient(networkId);
- const response = await backendClient.IPFSKey({ userId });
- return response.jwt;
- } catch (e) {
- console.error("ERROR WHILE GENERATING IPFSKey : ", e);
- return undefined;
- }
-};
-
export const replaceFileInArray = (
files: LocalFileData[],
newFile: LocalFileData
diff --git a/packages/utils/types/feed.ts b/packages/utils/types/files.ts
similarity index 100%
rename from packages/utils/types/feed.ts
rename to packages/utils/types/files.ts
diff --git a/packages/utils/waveform/waveform.web.ts b/packages/utils/waveform/waveform.web.ts
index 696067f606..8b8fa12531 100644
--- a/packages/utils/waveform/waveform.web.ts
+++ b/packages/utils/waveform/waveform.web.ts
@@ -1,7 +1,7 @@
import WaveformData from "waveform-data";
import { BAR_LENGTH } from "./constants";
-import { AudioFileMetadata } from "../types/feed";
+import { AudioFileMetadata } from "../types/files";
//@ts-ignore
window.AudioContext = window.AudioContext || window?.webkitAudioContext;