import React, { useCallback, useContext, useEffect, useState } from 'react'; import { AppCircle, AppCircleContainer, AppCircleLabel, AppDownloadButton, AppDownloadButtonText, AppInfoAppName, AppInfoSnippetContainer, AppInfoSnippetLeft, AppInfoSnippetMiddle, AppInfoSnippetRight, AppInfoUserName, AppLibrarySubTitle, AppPublishTagsContainer, AppsLibraryContainer, AppsParent, AppsWidthLimiter, PublishQAppCTAButton, PublishQAppChoseFile, PublishQAppInfo, } from './Apps-styles'; import { InputBase, InputLabel, MenuItem, Select, useTheme, } from '@mui/material'; import { styled } from '@mui/system'; import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded'; import { Add } from '@mui/icons-material'; import { MyContext, getBaseApiReact } from '../../App'; import LogoSelected from '../../assets/svgs/LogoSelected.svg'; import { Spacer } from '../../common/Spacer'; import { executeEvent } from '../../utils/events'; import { useDropzone } from 'react-dropzone'; import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { getFee } from '../../background'; import { fileToBase64 } from '../../utils/fileReading'; const CustomSelect = styled(Select)({ border: '0.5px solid var(--50-white, #FFFFFF80)', padding: '0px 15px', borderRadius: '5px', height: '36px', width: '100%', maxWidth: '450px', '& .MuiSelect-select': { padding: '0px', }, '&:hover': { borderColor: 'none', // Border color on hover }, '&.Mui-focused .MuiOutlinedInput-notchedOutline': { borderColor: 'none', // Border color when focused }, '&.Mui-disabled': { opacity: 0.5, // Lower opacity when disabled }, '& .MuiSvgIcon-root': { color: 'var(--50-white, #FFFFFF80)', }, }); const CustomMenuItem = styled(MenuItem)({ // backgroundColor: '#1f1f1f', // Background for dropdown items // color: '#ccc', // '&:hover': { // backgroundColor: '#333', // Darker background on hover // }, }); export const AppPublish = ({ categories, myAddress, myName }) => { const [names, setNames] = useState([]); const [name, setName] = useState(''); const [title, setTitle] = useState(''); const [description, setDescription] = useState(''); const [category, setCategory] = useState(''); const [appType, setAppType] = useState('APP'); const [file, setFile] = useState(null); const { show } = useContext(MyContext); const theme = useTheme(); const [tag1, setTag1] = useState(''); const [tag2, setTag2] = useState(''); const [tag3, setTag3] = useState(''); const [tag4, setTag4] = useState(''); const [tag5, setTag5] = useState(''); const [openSnack, setOpenSnack] = useState(false); const [infoSnack, setInfoSnack] = useState(null); const [isLoading, setIsLoading] = useState(''); const maxFileSize = appType === 'APP' ? 50 * 1024 * 1024 : 400 * 1024 * 1024; // 50MB or 400MB const { getRootProps, getInputProps } = useDropzone({ accept: { 'application/zip': ['.zip'], // Only accept zip files }, maxSize: maxFileSize, // Set the max size based on appType multiple: false, // Disable multiple file uploads onDrop: (acceptedFiles) => { if (acceptedFiles.length > 0) { setFile(acceptedFiles[0]); // Set the file name } }, onDropRejected: (fileRejections) => { fileRejections.forEach(({ file, errors }) => { errors.forEach((error) => { if (error.code === 'file-too-large') { console.error( `File ${file.name} is too large. Max size allowed is ${ maxFileSize / (1024 * 1024) } MB.` ); } }); }); }, }); const getQapp = React.useCallback(async (name, appType) => { try { setIsLoading('Loading app information'); const url = `${getBaseApiReact()}/arbitrary/resources/search?service=${appType}&mode=ALL&name=${name}&includemetadata=true`; const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); if (!response?.ok) return; const responseData = await response.json(); if (responseData?.length > 0) { const myApp = responseData[0]; setTitle(myApp?.metadata?.title || ''); setDescription(myApp?.metadata?.description || ''); setCategory(myApp?.metadata?.category || ''); setTag1(myApp?.metadata?.tags[0] || ''); setTag2(myApp?.metadata?.tags[1] || ''); setTag3(myApp?.metadata?.tags[2] || ''); setTag4(myApp?.metadata?.tags[3] || ''); setTag5(myApp?.metadata?.tags[4] || ''); } } catch (error) { } finally { setIsLoading(''); } }, []); useEffect(() => { if (!name || !appType) return; getQapp(name, appType); }, [name, appType]); const getNames = useCallback(async () => { if (!myAddress) return; try { setIsLoading('Loading names'); const res = await fetch( `${getBaseApiReact()}/names/address/${myAddress}` ); const data = await res.json(); setNames(data?.map((item) => item.name)); } catch (error) { console.error(error); } finally { setIsLoading(''); } }, [myAddress]); useEffect(() => { getNames(); }, [getNames]); const publishApp = async () => { try { const data = { name, title, description, category, appType, file, }; const requiredFields = [ 'name', 'title', 'description', 'category', 'appType', 'file', ]; const missingFields: string[] = []; requiredFields.forEach((field) => { if (!data[field]) { missingFields.push(field); } }); if (missingFields.length > 0) { const missingFieldsString = missingFields.join(', '); const errorMsg = `Missing fields: ${missingFieldsString}`; throw new Error(errorMsg); } const fee = await getFee('ARBITRARY'); await show({ message: 'Would you like to publish this app?', publishFee: fee.fee + ' QORT', }); setIsLoading('Publishing... Please wait.'); const fileBase64 = await fileToBase64(file); await new Promise((res, rej) => { window .sendMessage('publishOnQDN', { data: fileBase64, service: appType, title, name, description, category, tag1, tag2, tag3, tag4, tag5, uploadType: 'zip', }) .then((response) => { if (!response?.error) { res(response); return; } rej(response.error); }) .catch((error) => { rej(error.message || 'An error occurred'); }); }); setInfoSnack({ type: 'success', message: 'Successfully published. Please wait a couple minutes for the network to propogate the changes.', }); setOpenSnack(true); const dataObj = { name: name, service: appType, metadata: { title: title, description: description, category: category, }, created: Date.now(), }; executeEvent('addTab', { data: dataObj, }); } catch (error) { setInfoSnack({ type: 'error', message: error?.message || 'Unable to publish app', }); setOpenSnack(true); } finally { setIsLoading(''); } }; return ( Create Apps! Note: Currently, only one App and Website is allowed per Name. Name/App setName(event?.target.value)} > Select Name/App {/* This is the placeholder item */} {names.map((name) => { return {name}; })} App service type setAppType(event?.target.value)} > Select App Type App Website Title setTitle(e.target.value)} sx={{ border: `0.5px solid ${theme.palette.action.disabled}`, padding: '0px 15px', borderRadius: '5px', height: '36px', width: '100%', maxWidth: '450px', }} placeholder="Title" inputProps={{ 'aria-label': 'Title', fontSize: '14px', fontWeight: 400, }} /> Description setDescription(e.target.value)} sx={{ border: `0.5px solid ${theme.palette.action.disabled}`, padding: '0px 15px', borderRadius: '5px', height: '36px', width: '100%', maxWidth: '450px', }} placeholder="Description" inputProps={{ 'aria-label': 'Description', fontSize: '14px', fontWeight: 400, }} /> Category setCategory(event?.target.value)} > Select Category {categories?.map((category) => { return ( {category?.name} ); })} Tags setTag1(e.target.value)} sx={{ border: `0.5px solid ${theme.palette.action.disabled}`, padding: '0px 15px', borderRadius: '5px', height: '36px', width: '100px', }} placeholder="Tag 1" inputProps={{ 'aria-label': 'Tag 1', fontSize: '14px', fontWeight: 400, }} /> setTag2(e.target.value)} sx={{ border: `0.5px solid ${theme.palette.action.disabled}`, padding: '0px 15px', borderRadius: '5px', height: '36px', width: '100px', }} placeholder="Tag 2" inputProps={{ 'aria-label': 'Tag 2', fontSize: '14px', fontWeight: 400, }} /> setTag3(e.target.value)} sx={{ border: `0.5px solid ${theme.palette.action.disabled}`, padding: '0px 15px', borderRadius: '5px', height: '36px', width: '100px', }} placeholder="Tag 3" inputProps={{ 'aria-label': 'Tag 3', fontSize: '14px', fontWeight: 400, }} /> setTag4(e.target.value)} sx={{ border: `0.5px solid ${theme.palette.action.disabled}`, padding: '0px 15px', borderRadius: '5px', height: '36px', width: '100px', }} placeholder="Tag 4" inputProps={{ 'aria-label': 'Tag 4', fontSize: '14px', fontWeight: 400, }} /> setTag5(e.target.value)} sx={{ border: `0.5px solid ${theme.palette.action.disabled}`, padding: '0px 15px', borderRadius: '5px', height: '36px', width: '100px', }} placeholder="Tag 5" inputProps={{ 'aria-label': 'Tag 5', fontSize: '14px', fontWeight: 400, }} /> Select .zip file containing static content:{' '} {`(${ appType === 'APP' ? '50mb' : '400mb' } MB maximum)`} {file && ( <> {`Selected: (${file?.name})`} )} {' '} Choose File Publish ); };