diff --git a/components/CreateNftPage/ChooseCollection/index.tsx b/components/CreateNftPage/ChooseCollection/index.tsx deleted file mode 100644 index 827b342..0000000 --- a/components/CreateNftPage/ChooseCollection/index.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from "react"; -import { VPlusCircleFill } from "@components/modules/__modules__/_vectors"; - -const ChooseCollection = () => { - return ( -
-

Choose Collection

- -
- ); -}; - -export default ChooseCollection; diff --git a/components/CreateNftPage/CreateDropModal/CreateDropProcessingModal/index.tsx b/components/CreateNftPage/CreateDropModal/CreateDropProcessingModal/index.tsx new file mode 100644 index 0000000..c511d9b --- /dev/null +++ b/components/CreateNftPage/CreateDropModal/CreateDropProcessingModal/index.tsx @@ -0,0 +1,64 @@ +import { + VSpinner, + VCheckFill, + VPlusCircleFill, +} from "@components/modules/__modules__/_vectors"; +import React from "react"; + +interface IProps { + errorWhileSendingTransaction: boolean; + createDropProcessing: boolean; + dropSavingOnDatabaseFailed: boolean; + sendStorageFeeProcessing: boolean; + setIsCreateDropProcessingModal: (value: boolean) => void; +} + +const CreateDropProcessingModal = ({ + errorWhileSendingTransaction, + createDropProcessing, + dropSavingOnDatabaseFailed, + sendStorageFeeProcessing, + setIsCreateDropProcessingModal, +}: IProps) => { + const onCloseDropProcessingModal = () => { + setIsCreateDropProcessingModal(false); + }; + + return ( +
+
+
+ {sendStorageFeeProcessing ? ( + + ) : !errorWhileSendingTransaction ? ( + + ) : ( + + )} +

Waiting of Storage Fee

+
+ +
+ {createDropProcessing ? ( + + ) : !dropSavingOnDatabaseFailed ? ( + + ) : ( + + )} +
+

Save Drop

+
+
+ +
+
+ ); +}; + +export default CreateDropProcessingModal; diff --git a/components/CreateNftPage/CreateDropModal/index.tsx b/components/CreateNftPage/CreateDropModal/index.tsx new file mode 100644 index 0000000..c4c10ae --- /dev/null +++ b/components/CreateNftPage/CreateDropModal/index.tsx @@ -0,0 +1,249 @@ +/* eslint-disable @next/next/no-img-element */ +import React, { ChangeEvent, useRef, useState } from "react"; +import { NoCoverImg } from "@lib/Resources"; +import { CrossVector } from "@components/modules/__modules__/_vectors"; +import { ICreateDrop } from "@lib/models/GeneralModel"; +import { saveFileWithWeb3Storage } from "@lib/web3StorageClient"; +import { useRecoilValue } from "recoil"; +import { currentAccountState } from "@lib/atoms"; +import { Web3Service } from "@lib/web3"; +import LocalStorage from "@lib/helper/LocalStorage"; +import { backendApiService } from "@lib/services/BackendApiService"; +import ShowWidget from "@components/modules/__modules__/ShowWidget"; +import CreateDropProcessingModal from "./CreateDropProcessingModal"; +import UploadFileProcessing from "@components/modules/__modules__/Card/UploadFileProcessing"; +import imageResizer from "@lib/helper/ImageResizer"; +import { generateTokenUri as generateDropId } from "@lib/Utils"; + +interface IProps { + setIsCreateDropModal: (value: boolean) => void; +} + +const DEFAULT_DROP_DATA: ICreateDrop = { + creationFeeTransactionHash: "", + creatorId: 0, + creatorAddress: "", + creatorUsername: "", + description: "", + dropID: "", + imageUrl: "", + imageUrlThumbnail: "", + title: "", + collection: true, +}; + +const CreateDropModal = ({ setIsCreateDropModal }: IProps) => { + const [previewUrl, setPreviewUrl] = useState(""); + const [newDropIsLoading, setNewDropImageIsLoading] = useState(false); + const [fileSavingFailed, setFileSavingFailed] = useState(false); + const [errorwhileSendingTransaction, setErrorWhileSendingTransaction] = + useState(false); + const [createDropProcessing, setCreateDropProcessing] = useState(false); + const [dropSavingOnDatabaseFailed, setDropSavingOnDatabasefailed] = + useState(false); + const [sendStorageFeeProcessing, setSendStorageFeeProcessing] = + useState(false); + const [isCreateDropProcessingModal, setIsCreateDropProcessingModal] = + useState(false); + + const [dropData, setDropData] = useState(DEFAULT_DROP_DATA); + + const userAccount = useRecoilValue(currentAccountState); + + const inputFileRef = useRef(null); + + const onCloseCreateDropModal = () => { + setIsCreateDropModal(false); + setIsCreateDropProcessingModal(false); + }; + + const onChooseFile = () => { + inputFileRef.current?.click(); + }; + + const onFileChange = async (event) => { + const { files } = event.target; + + setNewDropImageIsLoading(true); + const imageUrl = await saveFileWithWeb3Storage([files[0]]); + + if (!imageUrl) { + setFileSavingFailed(true); + return; + } + + imageResizer(files[0], async (resizedFile) => { + const imageUrlThumbnail = await saveFileWithWeb3Storage([resizedFile]); + setNewDropImageIsLoading(false); + + if (imageUrlThumbnail) { + setDropData({ ...dropData, imageUrl, imageUrlThumbnail }); + } + }); + + setFileSavingFailed(false); + + const previewUrl = URL.createObjectURL(files[0]); + setPreviewUrl(previewUrl); + }; + + const onDropDetailsChange = ( + event: ChangeEvent + ) => { + const { value, name } = event.target; + + setDropData({ ...dropData, [name]: value }); + }; + + const onCreateDrop = async () => { + if ( + !dropData.description || + !dropData.title || + !dropData.imageUrl || + !dropData.imageUrlThumbnail || + !userAccount + ) + return; + + setIsCreateDropProcessingModal(true); + const web3Services = new Web3Service(); + + setSendStorageFeeProcessing(true); + const { transactionHash } = await web3Services.sendStorageFee(); + setSendStorageFeeProcessing(false); + + if (!transactionHash) { + setErrorWhileSendingTransaction(true); + return; + } + + setErrorWhileSendingTransaction(false); + + const drop: ICreateDrop = { + ...dropData, + creationFeeTransactionHash: transactionHash, + creatorAddress: `${ + userAccount?.walletAddress || + LocalStorage.getItem("ongama_signer_address") + }`, + creatorId: Number(userAccount?.id), + creatorUsername: `${userAccount?.username}`, + dropID: generateDropId(), + }; + + setCreateDropProcessing(true); + const response = await backendApiService.createDrop(drop); + setCreateDropProcessing(false); + + if (!response) { + setDropSavingOnDatabasefailed(true); + return; + } + setDropSavingOnDatabasefailed(false); + + setDropData(DEFAULT_DROP_DATA); + }; + + return ( +
+ +
+ + } + > +

+ Create drop collection +

+
+
+
+ } + > + drop image + +
+
+

+ We recommend an image of at least 300x300. Gifs work too. Max + 5mb. +

+ +

+ File saving Failed. Try again +

+
+ + +
+
+
+
+ + +
+
+ + +
+ +
+
+
+
+
+ ); +}; + +export default CreateDropModal; diff --git a/components/CreateNftPage/index.tsx b/components/CreateNftPage/index.tsx index e5e4c58..25769b9 100644 --- a/components/CreateNftPage/index.tsx +++ b/components/CreateNftPage/index.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from "react"; import { CrossVector, + VPlusCircleFill, VQuestionMark, } from "@components/modules/__modules__/_vectors"; import { useRecoilValue, useSetRecoilState } from "recoil"; @@ -12,7 +13,6 @@ import DetailsForm from "./DetailsForm"; import AdvancedSettingForm from "./AdvanceSettingForm"; import Header from "@components/modules/__noAuth/Header"; import PutOnMarketMenu from "./PutOnMarketMenu"; -import ChooseCollection from "./ChooseCollection"; import NftPreview from "./NftPreview"; import WalletInfoCard from "@components/modules/__modules__/Card/WalletInfoCard"; import ProfileMenu from "@components/modules/__secured/ProfileMenu"; @@ -28,6 +28,8 @@ import LocalStorage from "@lib/helper/LocalStorage"; import UploadFileProcessing from "@components/modules/__modules__/Card/UploadFileProcessing"; import UploadFileErrorCard from "@components/modules/__modules__/Card/UploadFileErrorCard"; import { saveFileWithWeb3Storage } from "@lib/web3StorageClient"; +import ShowWidget from "@components/modules/__modules__/ShowWidget"; +import CreateDropModal from "./CreateDropModal"; const CreateNftPage = () => { const [nftData, setNftData] = useState({ @@ -57,9 +59,11 @@ const CreateNftPage = () => { const [isFreeMinting, setIsFreeMinting] = useState(true); const [isAdvancedForm, setIsAdvancedForm] = useState(false); - const [previewUrl, setPreviewUrl] = useState(""); const [isImage, setIsImage] = useState(true); const [isCreateNftProcessModal, setIsCreateNftProcessModal] = useState(false); + const [isCreateDropModal, setIsCreateDropModal] = useState(false); + + const [previewUrl, setPreviewUrl] = useState(""); const [uploadFileProcessing, setUploadFileProcessing] = useState(false); const [fileUploadingError, setFileUploadingError] = useState(false); const [mintEroor, setMintError] = useState(false); @@ -113,7 +117,7 @@ const CreateNftPage = () => { setIsImage((files[0].type as string).includes("image")); const filePreviewUrl = URL.createObjectURL(files[0]); try { - const uploadResult = await uploadFileOnWeb3Storage(files); + const uploadResult = await uploadFileOnWeb3Storage([files[0]]); if (uploadResult) setPreviewUrl(filePreviewUrl); } catch (err) { @@ -221,6 +225,10 @@ const CreateNftPage = () => { } }; + const toggleCreateDropModal = () => { + setIsCreateDropModal(!isCreateDropModal); + }; + return (
@@ -299,7 +307,19 @@ const CreateNftPage = () => { } nftPrice={nftData.price.toString()} /> - +
+

Choose Collection

+ +

@@ -354,7 +374,7 @@ const CreateNftPage = () => {

- {isCreateNftProcessModal && ( + { onMintAgain={onPutOnMarket} mintError={mintEroor} /> - )} + + + + ); }; diff --git a/components/EditProfilePage/index.tsx b/components/EditProfilePage/index.tsx index d615355..b1eef49 100644 --- a/components/EditProfilePage/index.tsx +++ b/components/EditProfilePage/index.tsx @@ -58,7 +58,7 @@ const EditProfile = () => { const previewUrl = URL.createObjectURL(files[0]); setIsUserAvatarUploading(true); - const fileUrl = await saveFileWithWeb3Storage(files); + const fileUrl = await saveFileWithWeb3Storage([files[0]]); setIsUserAvatarUploading(false); if (fileUrl) { diff --git a/components/modules/__modules__/Card/AvatartAndCoverCard/index.tsx b/components/modules/__modules__/Card/AvatartAndCoverCard/index.tsx index 04720fb..d649571 100644 --- a/components/modules/__modules__/Card/AvatartAndCoverCard/index.tsx +++ b/components/modules/__modules__/Card/AvatartAndCoverCard/index.tsx @@ -77,7 +77,7 @@ const AvatarAndCoverCard = ({ isEditable, user }: IProps) => { setIsAddCover(false); setIsUpdateModal(!isUpdateModal); setIsUpdatePending(true); - const fileUrl = await saveFileWithWeb3Storage(files); + const fileUrl = await saveFileWithWeb3Storage([files[0]]); if (fileUrl) { const signature = await getSignature(fileUrl); diff --git a/components/modules/__noAuth/Header/SearchInputBar/index.tsx b/components/modules/__noAuth/Header/SearchInputBar/index.tsx index 92e1983..5960568 100644 --- a/components/modules/__noAuth/Header/SearchInputBar/index.tsx +++ b/components/modules/__noAuth/Header/SearchInputBar/index.tsx @@ -41,9 +41,9 @@ const SearchInputBar = () => { className="w-full py-3 bg-transparent placeholder:text-gray-500 outline-none px-2 font-ibmPlexSans " /> {inputValue && ( -
+
+ )} ); diff --git a/lib/helper/ImageResizer.ts b/lib/helper/ImageResizer.ts new file mode 100644 index 0000000..a93b7c2 --- /dev/null +++ b/lib/helper/ImageResizer.ts @@ -0,0 +1,50 @@ +const dataUrlToFileConverter = async ( + dataUrl: string, + fileName: string +): Promise => { + const res: Response = await fetch(dataUrl); + const blob: Blob = await res.blob(); + return new File([blob], fileName, { type: "image/png" }); +}; + +const imageResizer = (file: File, callback: (resizedFile: File) => void) => { + if (!file) return; + + const reader = new FileReader(); + + reader.readAsDataURL(file); + + reader.onload = (event) => { + const imageElement: HTMLImageElement = document.createElement("img"); + imageElement.src = `${event.target?.result}`; + + imageElement.onload = async (e) => { + const imageTarget = e.target as HTMLImageElement; + const { width, height } = imageTarget; + + const canvas = document.createElement("canvas"); + const maxWidth = 400; + + const scaleSize = maxWidth / width; + + canvas.width = maxWidth; + canvas.height = height * scaleSize; + + const canvasContext = canvas.getContext("2d"); + canvasContext?.drawImage(imageTarget, 0, 0, canvas.width, canvas.height); + + if (canvasContext) { + const srcEncoded = canvasContext.canvas.toDataURL( + imageTarget.src, + "image/jpeg" + ); + + const resizedFile = await dataUrlToFileConverter(srcEncoded, file.name); + + callback(resizedFile); + } + }; + }; +}; + +export default imageResizer; diff --git a/lib/models/GeneralModel.ts b/lib/models/GeneralModel.ts index 699a279..b4e7677 100644 --- a/lib/models/GeneralModel.ts +++ b/lib/models/GeneralModel.ts @@ -15,6 +15,19 @@ export interface IGetRequestNFTsParams { walletAddress?: string; } +export interface ICreateDrop { + creationFeeTransactionHash: string; + creatorId: number; + creatorAddress: string; + creatorUsername: string; + description: string; + dropID: string; + imageUrl: string; + imageUrlThumbnail: string; + title: string; + collection: boolean; +} + export interface NFTMetaData { page: number; totalPages: number; diff --git a/lib/services/BackendApiService.ts b/lib/services/BackendApiService.ts index e9da4c0..5ecb9c0 100644 --- a/lib/services/BackendApiService.ts +++ b/lib/services/BackendApiService.ts @@ -4,11 +4,13 @@ import * as Sentry from "@sentry/nextjs"; import { orderObject } from "@lib/Utils"; import { IUpdateProfile } from "@lib/@Types"; import { + ICreateDrop, IGetRequestNFTsParams, NftCardData, NFTData, NFTMetaData, } from "@lib/models/GeneralModel"; +import { AxiosError } from "axios"; class BackendApiService { async findAccountWhereAddressOrUsername( @@ -92,6 +94,18 @@ class BackendApiService { return null; } } + + async createDrop(drop: ICreateDrop) { + try { + const createDropEndpoint = "/nfts-drops"; + const response = await http.post(createDropEndpoint, orderObject(drop)); + + return response; + } catch (e) { + Sentry.captureException(e); + return null; + } + } } export const backendApiService = new BackendApiService(); diff --git a/lib/web3StorageClient.ts b/lib/web3StorageClient.ts index 20f634b..6c46e2f 100644 --- a/lib/web3StorageClient.ts +++ b/lib/web3StorageClient.ts @@ -4,7 +4,7 @@ const storageClient = new Web3Storage({ token: `${process.env.NEXT_PUBLIC_WEB3_STORAGE_TOKEN}`, }); -export const saveFileWithWeb3Storage = async (file: FileList) => { +export const saveFileWithWeb3Storage = async (file: File[]) => { if (!file) return; const fileIdenfier = await storageClient.put(file);