import { Alert, Box, Button, FormControl, FormHelperText, FormLabel, HStack, Icon, Image, Input, Link, Modal, ModalBody, ModalContent, ModalOverlay, ModalProps, Progress, Text, useDisclosure, VStack } from "@chakra-ui/react"; import { yupResolver } from "@hookform/resolvers/yup"; import { useWallet } from "@solana/wallet-adapter-react"; import { ChatSdk, IdentifierType, randomizeFileName, uploadFiles } from "@strata-foundation/chat"; import { truncatePubkey, useErrorHandler, useProvider } from "@strata-foundation/react"; import { sendMultipleInstructions } from "@strata-foundation/spl-utils"; import React, { useCallback, useEffect, useState } from "react"; import { useAsyncCallback } from "react-async-hook"; import { FormProvider, useForm } from "react-hook-form"; import { RiCheckFill } from "react-icons/ri"; import * as yup from "yup"; import { STRATA_KEY } from "../constants/globals"; import { useChatSdk } from "../contexts/chatSdk"; import { useAnalyticsEventTracker } from "../hooks/useAnalyticsEventTracker"; import { useChatStorageAccountKey } from "../hooks/useChatStorageAccountKey"; import { useLoadDelegate } from "../hooks/useLoadDelegate"; import { useUsernameFromIdentifierCertificate } from "../hooks/useUsernameFromIdentifierCertificate"; import { useWalletFromUsernameIdentifier } from "../hooks/useWalletFromUsernameIdentifier"; import { useWalletProfile } from "../hooks/useWalletProfile"; import { FormControlWithError } from "./form/FormControlWithError"; import { LoadWalletModal } from "./LoadWalletModal"; interface IProfileProps { username: string; image?: File; imageUrl?: string; } const validationSchema = yup.object({ username: yup .string() .required() .max(28) .matches( /^[a-zA-Z0-9_\-]*$/, "Must only contain alphanumeric characters, underscores, or dashes." ), image: yup.mixed(), imageUrl: yup.string(), }); async function createProfile( chatSdk: ChatSdk | undefined, args: IProfileProps, setProgress: (step: string) => void ): Promise { if (chatSdk) { let imageUrl: string | undefined = args.imageUrl; setProgress("Creating your Profile..."); const { instructions: claimInstructions, signers: claimSigners, output: { certificateMint }, } = await chatSdk.claimIdentifierInstructions({ type: IdentifierType.User, identifier: args.username, }); const { instructions, signers } = await chatSdk.initializeProfileInstructions({ identifierCertificateMint: certificateMint, imageUrl, identifier: args.username, }); await sendMultipleInstructions( chatSdk.errors || new Map(), chatSdk.provider, [claimInstructions[0], [...claimInstructions[1], ...instructions]], [claimSigners[0], [...claimSigners[1], ...signers]] ); } } export function CreateProfileModal(props: Partial) { const formProps = useForm({ resolver: yupResolver(validationSchema), defaultValues: {}, }); const { publicKey } = useWallet(); const { register, handleSubmit, watch, clearErrors, setValue, setError, formState: { errors, isSubmitting }, } = formProps; const [step, setStep] = useState(""); const { execute, loading, error } = useAsyncCallback(createProfile); const { chatSdk } = useChatSdk(); const { awaitingApproval } = useProvider(); const { handleErrors } = useErrorHandler(); const { isOpen: loadWalletIsOpen, onClose, onOpen, } = useDisclosure({ defaultIsOpen: true, }); const { delegateWallet, needsInit, error: delegateError, loadingNeeds, loading: loadingDelegate, } = useLoadDelegate(); const gaEventTracker = useAnalyticsEventTracker(); const { username, image } = watch(); const { account: profileAccount, info: profile, loading: loadingProfile, } = useWalletProfile(publicKey || undefined); const { wallet } = useWalletFromUsernameIdentifier(username); const { username: existingUsername } = useUsernameFromIdentifierCertificate( profile?.identifierCertificateMint, profile?.ownerWallet ); const [isUploading, setIsUploading] = useState(false); useEffect(() => { if (profile) { setValue("imageUrl", profile.imageUrl); } }, [profile, setValue]); useEffect(() => { if (existingUsername) setValue("username", existingUsername); }, [existingUsername, setValue]); const userError = wallet && publicKey && !wallet.equals(publicKey) && ( Username is already in owned by{" "} {truncatePubkey(wallet)} ); handleErrors(error, delegateError); async function onSubmit(args: IProfileProps): Promise { if (!publicKey?.equals(STRATA_KEY) && args.username.length < 6 && !wallet) { setError("username", { message: "Username must be at least 6 characters.", }); return; } await execute(chatSdk, args, setStep); if (props.onClose) { props.onClose(); } gaEventTracker({ action: "Create Profile", }); } useEffect(() => { if (props.isOpen && !loadingNeeds && !needsInit) { onClose(); } }, [loadingDelegate, props.isOpen, needsInit, onClose, loadingNeeds]); const onCloseCallback = useCallback(() => { props.onClose && props.onClose(); }, [props.onClose]); const { result: chatStorage } = useChatStorageAccountKey(); const hiddenFileInput = React.useRef(null); const handleImageChange = (e: React.ChangeEvent) => { const file = e.target.files![0]; // @ts-ignore setValue("image", file || null); // @ts-ignore clearErrors("image"); }; const [imgUrl, setImgUrl] = useState(); useEffect(() => { (async () => { if (image) { const reader = new FileReader(); reader.onload = (event) => { setImgUrl((event.target?.result as string) || ""); }; reader.readAsDataURL(image); if (!imgUrl) { setIsUploading(true); randomizeFileName(image); let innerImageUploaded = false; try { const uri = await uploadFiles( chatSdk!.provider, [image], delegateWallet ); if (uri && uri.length > 0) { setValue("imageUrl", uri[0]); innerImageUploaded = true; } } catch (e) { handleErrors(e as Error); } finally { setIsUploading(false); if (!innerImageUploaded) { setValue("imageUrl", undefined); setValue("image", undefined); setImgUrl(undefined); setError("image", { message: "Image failed to upload, please try again", }); if (hiddenFileInput.current) { hiddenFileInput.current.value = ""; } } } } } else { setImgUrl(undefined); } })(); }, [image]); if (props.isOpen && loadWalletIsOpen) { return ( { props.onClose && props.onClose(); onClose(); }} onLoaded={() => { onClose(); }} /> ); } return (
Setup your Profile {userError && {userError}} Upload Picture {image && ( {image?.name} {image?.name} )} {errors.image?.message || `The image that will be displayed as your pfp. Note that your first upload to SHDW can take up to 3 minutes depending on Solana confirmation times.`} {isUploading && ( )}
); }