Last active
December 6, 2022 20:39
-
-
Save dblodorn/08f82d84329bd62b5275afc6a0e0c92c to your computer and use it in GitHub Desktop.
Big ass nft storage form
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** 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(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://auction.holly.plus/submit