Skip to content

Instantly share code, notes, and snippets.

@dblodorn
Last active December 6, 2022 20:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dblodorn/08f82d84329bd62b5275afc6a0e0c92c to your computer and use it in GitHub Desktop.
Save dblodorn/08f82d84329bd62b5275afc6a0e0c92c to your computer and use it in GitHub Desktop.
Big ass nft storage form
import React, { useCallback, useEffect } from 'react'
import { css } from '@emotion/react'
import { useDropzone } from 'react-dropzone'
interface DropzoneProps {
value?: any
description?: string
accepted?: string
onDrop: (acceptedFiles: File) => any
}
const Dropzone: React.FC<DropzoneProps> = ({
value,
accepted,
description = "Drag 'n' drop some files here, or click to select files",
onDrop,
}) => {
const handleOnDrops = useCallback(
(acceptedFiles: File[]) => {
console.log('on drop', acceptedFiles)
if (!acceptedFiles || acceptedFiles.length > 1) {
throw new Error('Missing File')
}
return onDrop(acceptedFiles[0])
},
[onDrop]
)
/* MAX SIZE = 50mb */
const maxSize = 1048576 * 50
const { getRootProps, getInputProps } = useDropzone({
onDrop: handleOnDrops,
maxFiles: 1,
minSize: 0,
maxSize: maxSize,
...(accepted && { accept: accepted }),
})
return (
<div {...getRootProps()} className="drop-wrapper">
<input multiple={false} {...getInputProps()} required/>
<p>{description}</p>
</div>
)
}
export { Dropzone }
import { NFTStorage } from 'nft.storage'
const client = new NFTStorage({ token: process.env.NEXT_PUBLIC_NFT_STORAGE_KEY as string })
type nftStorageProps = {
thumbnail: any,
name: any,
description: any,
nftFile: any
}
export async function nftStorage({thumbnail, name, description, nftFile}: nftStorageProps) {
try {
const metadata = await client.store({
name: name,
description: description,
image: thumbnail,
properties: {
custom: 'nft file',
file: nftFile
}
})
return metadata
} catch (err) {
console.error(err)
}
}
/** THIS WOULD BE THE MINT CALL */
import getConfig from 'next/config'
const { publicRuntimeConfig } = getConfig()
export async function postSubmission(formData?: any) {
console.log('formdata', JSON.stringify(formData))
const res = await fetch('https://hollyplussubmissions.up.railway.app/api/submit', {
method: 'POST',
body: JSON.stringify(formData),
headers: {
'content-type': 'application/json',
accept: 'application/json'
}
});
return res.json();
}
import { useState } from 'react'
import { css } from '@emotion/react'
import styled from '@emotion/styled'
import { Formik, Form, Field, ErrorMessage } from 'formik';
import Link from "next/link";
import { mediaType } from './../../utils/mediaTypes'
import { Dropzone } from './Dropzone'
import HollysHead from '../head-thumbnail/HollysHead'
import ErrorBoundary from '../ErrorBoundary'
import * as mixins from '../../styles/mixins'
import { Spinner } from '../../styles/components'
import { nftStorage } from './../../services/nftStorage'
import { postSubmission } from '../../services/postSubmission'
interface SubmitFormValues {
creator: string;
title: string;
thumbnail: string;
// thumbBase: string;
description: string;
twitter: string;
instagram: string;
email: string;
file: string;
wallet: string;
acceptTerms: boolean;
}
function onKeyDown(keyEvent: any) {
if ((keyEvent.charCode || keyEvent.keyCode) === 13) {
keyEvent.preventDefault();
}
}
const uploadDisclaimer = `You will not be able to submit until uploading to IPFS has been completed. Note that this can take some time, especially for larger files sizes.`
const adBlockDisclaimer = `If you are using an adblocker such as uBlock Origin the submission button may not work`
export const SubmissionForm = () => {
const [thumb, setThumb] = useState<any | undefined>();
const [nftFile, setNftFile] = useState<any | undefined>();
const [canSubmit, setCanSubmit] = useState<boolean | false>();
const [uploading, setUploading] = useState<boolean | false>();
const [canUpload, setCanUpload] = useState<boolean | false>();
const [uploaded, setUploaded] = useState<boolean | false>();
const [submitting, setSubmitting] = useState<boolean | false>();
const [submitted, setSubmitted] = useState<boolean | false>();
const initialValues: SubmitFormValues = {
creator: '',
title: '',
thumbnail: '',
description: '',
twitter: '',
instagram: '',
email: '',
file: '',
wallet: '',
acceptTerms: false
};
return (
<>
{submitted
? <div className="submitted-wrapper">
<p>Thanks for submitting to Holly+</p>
</div>
: <Formik
initialValues={initialValues}
onSubmit={async (values) => {
setSubmitting(true)
await new Promise((resolve) => setTimeout(resolve, 500));
console.log(values)
postSubmission(values).then((response) => {
console.log(response)
setSubmitted(true)
response.json().then((data: any) => {
console.log(data)
})
})
.catch(error => console.error('Error!', error.message))
}}
>
{({ setFieldValue, values }) => (
<Form css={SubmitForm} onKeyDown={onKeyDown}>
<div className="form-inner">
<FormElement>
<Field id="creator" name="creator" placeholder="Your Name" />
<Field id="title" name="title" placeholder="NFT Title" />
<Field id="description" name="description" placeholder="NFT Description..." />
</FormElement>
{/* NFT */}
<div css={DropzoneWrapper} className={uploaded || uploading ? 'submitted' : ''}>
{/* NFT FILE */}
<FormElement>
<label htmlFor="fileName">NFT:</label>
<Dropzone
description={nftFile === undefined ? mediaType('file')?.description : nftFile.file.name}
accepted={mediaType('file')?.accepted}
onDrop={(acceptedFiles: any) => {
const reader = new FileReader()
reader.onloadend = () => {
const result = {
'file': acceptedFiles,
'preview': reader.result,
'uploaded': true,
}
setNftFile(result as any)
}
reader.readAsDataURL(acceptedFiles)
}}
/>
</FormElement>
{/* THUMBNAIL */}
<FormElement>
<label htmlFor="thumbnail">Thumbnail:</label>
<Dropzone
description={(thumb === undefined) ? mediaType('thumbnail')?.description : thumb.file.name}
accepted={mediaType('thumbnail')?.accepted}
onDrop={(acceptedFiles: any) => {
const reader = new FileReader()
reader.onloadend = () => {
const result = {
'file': acceptedFiles,
'preview': reader.result,
'uploaded': true
}
setThumb(result as any)
setCanUpload(true)
}
reader.readAsDataURL(acceptedFiles)
}}
/>
</FormElement>
</div>
{!canSubmit
? <ButtonWrapper className={uploading ? 'upload uploading' : 'upload'}>
{!uploading && !uploaded ?
<>
<button
className={canUpload ? '' : 'fade'}
onClick={(event) => {
setUploading(true)
event.preventDefault()
nftStorage({ thumbnail: thumb.file, name: values.title, description: values.description, nftFile: nftFile.file }).then((metaData: any) => {
setFieldValue('thumbnail', metaData.data.image)
// setFieldValue('thumbBase', thumb.preview)
setFieldValue('file', metaData.data.properties.file)
setCanSubmit(true)
setUploading(false)
setUploaded(true)
})
}}
>Upload your files to IPFS:</button>
<p className="micro-type">*{uploadDisclaimer}</p>
</>
: <p className="button-style">Uploading to IPFS ~ Please be patient ⏳...</p>
}
</ButtonWrapper>
: <ButtonWrapper className='upload uploading'>
<p className="button-style">Your Files have been uploaded</p>
</ButtonWrapper>
}
{/* CONTACT FIELDS */}
<FormElement className="contact-fields">
<label htmlFor="contact">Please add at least one contact:</label>
<Field id="email" name="email" type="email" placeholder="email" />
<Field id="twitter" name="twitter" placeholder="twitter" />
<Field id="instagram" name="instagram" placeholder="instagram" />
</FormElement>
<FormElement className="contact-fields">
<label htmlFor="contact">Please enter your ethereum wallet address:</label>
<Field id="wallet" name="wallet" placeholder="your ethereum wallet" />
</FormElement>
<FormElement>
<div className="terms-wrapper">
<Field type="checkbox" name="acceptTerms" required />
<label htmlFor="acceptTerms" className="form-check-label">You’ve read Holly+ <Link passHref href="/terms"><a>Terms and Conditions.</a></Link></label>
</div>
</FormElement>
</div>
{!submitted &&
<>
{!submitting
? <ButtonWrapper>
<button type="submit" className={canSubmit ? '' : 'fade'}>Submit</button>
<p className="micro-type">*{adBlockDisclaimer}</p>
</ButtonWrapper>
: <ButtonWrapper className={'upload uploading'}>
<p className="button-style">Submitting...</p>
</ButtonWrapper>
}
</>
}
</Form>
)}
</Formik>
}
<div css={PreviewWrapper}>
{uploading && <Spinner><div className="icon"/></Spinner>}
<div className="head-wrapper">
{thumb !== undefined &&
<ErrorBoundary>
<HollysHead thumbnailImage={thumb.preview} />
</ErrorBoundary>
}
</div>
{nftFile !== undefined &&
<div className="audio-wrapper">
<audio controls src={nftFile.preview} />
</div>
}
</div>
</>
)
}
const DropzoneWrapper = css`
width: 100%;
position: relative;
&.submitted {
pointer-events: none;
opacity: .7;
}
`
const PreviewWrapper = css`
width: 100%;
height: 100%;
position: relative;
border-top: var(--border-black);
border-bottom: var(--border-black);
display: flex;
flex-direction: column;
.head-wrapper {
width: 100%;
height: 0;
overflow-y: visible;
padding-bottom: 100%;
position: relative;
border-bottom: var(--border-black);
}
.audio-wrapper {
width: 100%;
height: 6rem;
position: relative;
z-index: 3;
padding: var(--space-sm);
display: flex;
align-items: center;
audio {
width: 100%;
height: 3rem;
}
}
${mixins.media.laptop`
border-top: 0;
border-bottom: 0;
`}
`
const SubmitForm = css`
width: 100%;
${mixins.media.laptop`
border-right: var(--border-black);
`}
input,
.file-name,
.drop-wrapper {
font-family: var(--font-a);
font-size: var(--text-01);
width: 100%;
margin-top: var(--space-sm);
border: 1px solid var(--black);
border-radius: 0;
color: var(--black);
-webkit-text-fill-color: var(--black);
background-color: var(--blue);
padding: calc(var(--space-sm) / 2);
text-transform: none;
* {
text-transform: none;
}
}
input[type=email]:focus,
input[type=text]:focus {
outline: none!important;
box-shadow: 0;
background-color: var(--blue);
}
`
const FormElement = styled.div`
width: 100%;
padding: 0 var(--space-sm) var(--space-sm);
border-bottom: var(--border-black);
&:last-child {
border-bottom: 0;
}
label {
font-size: var(--text-01);
margin-top: var(--space-sm);
}
input,
label {
display: block;
position: relative;
}
.invalid-message {
padding-top: calc(var(--text-micro) / 2);
font-size: var(--text-micro);
}
.terms-wrapper {
display: flex;
flex-direction: row;
align-items: center;
padding-top: var(--space-sm);
input {
width: 1rem;
height: 1rem;
margin: 0;
}
label {
margin-top: 0!important;
text-transform: none;
padding-left: calc(var(--space-sm) / 2);
}
}
`
const ButtonWrapper = styled.div`
width: 100%;
padding: var(--space-sm);
&.upload {
border-bottom: var(--border-black);
}
&.uploading {
background-color: var(--black);
}
button,
.button-style {
${mixins.largePillButton};
border: var(--border-black);
&.fade {
opacity: 0.125;
pointer-events: none;
}
&:hover {
background-color: var(--blue);
}
}
.button-style {
background-color: var(--black);
pointer-events: none;
color: var(--green)!important;
}
`
@dblodorn
Copy link
Author

dblodorn commented Dec 6, 2022

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment