2024-01-20 16:16:14 +00:00
'use client' ;
import { IVtuber } from "@/lib/vtubers" ;
2024-02-02 23:50:24 +00:00
import { IStream } from "@/lib/streams" ;
2024-01-20 16:16:14 +00:00
import { useSearchParams } from 'next/navigation' ;
import React , { useContext , useState , useEffect } from 'react' ;
import { UppyContext } from 'app/uppy' ;
import { LoginButton , useAuth } from '@/components/auth' ;
import { Dashboard } from '@uppy/react' ;
import styles from '@/assets/styles/fp.module.css'
import { projektMelodyEpoch } from "@/lib/constants" ;
import add from "date-fns/add" ;
import sub from "date-fns/sub" ;
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" ;
2024-02-02 23:50:24 +00:00
import { faCheckCircle , faEraser , faPaperPlane , faSpinner , faX , faXmark } from "@fortawesome/free-solid-svg-icons" ;
2024-01-20 16:16:14 +00:00
import { useForm , useFieldArray , ValidationMode } from 'react-hook-form' ;
import { yupResolver } from '@hookform/resolvers/yup' ;
import * as Yup from 'yup' ;
2024-02-02 23:50:24 +00:00
import qs from 'qs' ;
import { toast } from "react-toastify" ;
import { ErrorMessage } from "@hookform/error-message"
2024-01-20 16:16:14 +00:00
interface IUploadFormProps {
vtubers : IVtuber [ ] ;
}
interface IValidationResults {
valid : boolean ;
issues : string [ ] | null ;
}
interface IFormSchema extends Yup . InferType < typeof validationSchema > { } ;
const validationSchema = Yup . object ( ) . shape ( {
vtuber : Yup.number ( )
. required ( 'VTuber is required' ) ,
2024-02-02 23:50:24 +00:00
streamCuid : Yup.string ( ) . optional ( ) ,
2024-01-20 16:16:14 +00:00
date : Yup.date ( )
. typeError ( 'Invalid date' ) // https://stackoverflow.com/a/72985532/1004931
. min ( sub ( projektMelodyEpoch , { days : 1 } ) , 'Date must be after February 7 2020' )
. max ( add ( new Date ( ) , { days : 1 } ) , 'Date cannot be in the future' )
. required ( 'Date is required' ) ,
notes : Yup.string ( ) . optional ( ) ,
attribution : Yup.boolean ( ) . optional ( ) ,
files : Yup.array ( )
. of (
Yup . object ( ) . shape ( {
key : Yup.string ( ) . required ( 'key is required' ) ,
uploadId : Yup.string ( ) . required ( 'uploadId is required' )
} ) ,
)
. min ( 1 , 'At least one file is required' ) ,
} ) ;
export default function UploadForm ( { vtubers } : IUploadFormProps ) {
const searchParams = useSearchParams ( ) ;
const cuid = searchParams . get ( 'cuid' ) ;
const uppy = useContext ( UppyContext ) ;
const { authData } = useAuth ( ) ;
2024-02-02 23:50:24 +00:00
2024-01-20 16:16:14 +00:00
const formOptions = {
resolver : yupResolver ( validationSchema ) ,
mode : 'onChange' as keyof ValidationMode ,
} ;
const {
register ,
handleSubmit ,
2024-02-02 23:50:24 +00:00
setError ,
clearErrors ,
2024-01-20 16:16:14 +00:00
formState : {
errors ,
2024-02-02 23:50:24 +00:00
isValid ,
isSubmitted ,
isSubmitSuccessful ,
isSubmitting
2024-01-20 16:16:14 +00:00
} ,
setValue ,
watch ,
2024-02-02 23:50:24 +00:00
reset
2024-01-20 16:16:14 +00:00
} = useForm ( formOptions ) ;
2024-02-02 23:50:24 +00:00
// useEffect(() => {
// if (!cuid) return;
// (async () => {
// console.log('query')
// const query = qs.stringify({
// filters: {
// cuid: {
// '$eq': cuid
// }
// }
// });
// const res = await fetch(`${process.env.NEXT_PUBLIC_STRAPI_URL}/api/streams?${query}`);
// if (!res.ok) return;
// const matchingStream = (await res.json()).data as IStream;
// console.log(matchingStream);
// setValue('vtuber', matchingStream.attributes.vtuber.data.id);
// })();
// }, [cuid]);
// setValue('streamCuid', cuid||'');
2024-01-20 16:16:14 +00:00
const files = watch ( 'files' ) ;
async function createUSC ( data : IFormSchema ) {
2024-02-02 23:50:24 +00:00
try {
const res = await fetch ( ` ${ process . env . NEXT_PUBLIC_STRAPI_URL } /api/user-submitted-contents/createFromUppy ` , {
method : 'POST' ,
headers : {
'authorization' : ` Bearer ${ authData ? . accessToken } ` ,
'content-type' : 'application/json' ,
'accept' : 'application/json'
} ,
body : JSON.stringify ( {
data : {
files : data.files ,
attribution : data.attribution ,
notes : data.notes ,
vtuber : data.vtuber ,
date : data.date ,
streamCuid : cuid
}
} )
} ) ;
if ( ! res . ok ) {
console . error ( 'failed to fetch /api/user-submitted-contents/createFromUppy' ) ;
const body = await res . json ( ) ;
const error = body . error ;
toast . error ( ` ${ error . type } ${ error . message } ` , { theme : 'dark' } ) ;
setError ( 'root.serverError' , {
type : error . type ,
message : error.message
} )
}
} catch ( e ) {
if ( e instanceof Error ) {
toast . error ( ` ${ e . message } ` , { theme : 'dark' } ) ;
setError ( 'root.serverError' , {
type : "remote" ,
message : e.message ,
} ) ;
} else {
toast . error ( ` Something went wrong. Please try again. ` , { theme : 'dark' } ) ;
setError ( 'root.serverError' , {
type : 'remote' ,
message : 'Something went wrong. Please try again.'
} )
}
2024-01-20 16:16:14 +00:00
}
}
uppy . on ( 'complete' , async ( result : any ) = > {
let files = result . successful . map ( ( f : any ) = > ( { key : f.s3Multipart.key , uploadId : f.s3Multipart.uploadId } ) ) ;
setValue ( 'files' , files ) ;
} ) ;
2024-03-29 07:28:02 +00:00
return ( < div className = "notification is-secondary" >
< h1 className = "title" > VOD uploads < / h1 >
< p >
< i > coming soon ! ! < / i >
< / p >
< hr > < / hr >
< p > Track progress on the < a href = "/goals" > Goals Page < / a > < / p >
< / div > )
// return (
// <>
// <div className='section'>
// <h2 className='title is-2'>Upload VOD</h2>
// <p className="mb-5"><i>Together we can archive all lewdtuber livestreams!</i></p>
// {(!authData?.accessToken)
// ?
// <>
// <aside className='notification is-danger'><p>Please log in to upload VODs</p></aside>
// <LoginButton />
// </>
// : (
// <div className='columns is-multiline'>
// <form id="vod-details" onSubmit={handleSubmit(createUSC)}>
// {(!isSubmitSuccessful) && <div className='column is-full'>
// <section className="hero is-info mb-3">
// <div className="hero-body">
// <p className="title">
// Step 1
// </p>
// <p className="subtitle">
// Upload the file
// </p>
// </div>
// </section>
// <section className="section mb-5">
// <Dashboard
// uppy={uppy}
// theme='dark'
// proudlyDisplayPoweredByUppy={false}
// />
// <input
// required
// hidden={true}
// style={{ display: 'none' }}
// className="input" type="text"
// {...register('files')}
// ></input>
// {errors.files && <p className="help is-danger">{errors.files.message?.toString()}</p>}
// </section>
// </div>}
// {(!isSubmitSuccessful) && <div className='column is-full '>
// {/* {(!cuid) && <aside className='notification is-info'>Hint: Some of these fields are filled out automatically when uploading from a <Link href="/streams">stream</Link> page.</aside>} */}
// <section className="hero is-info mb-3">
// <div className="hero-body">
// <p className="title">
// Step 2
// </p>
// <p className="subtitle">
// Tell us about the VOD
// </p>
// </div>
// </section>
// <section className="section">
// {/* <input
// required
// // hidden={false}
// // style={{ display: 'none' }}
// className="input" type="text"
// {...register('streamCuid')}
// ></input> */}
// <div className="field">
// <label className="label">VTuber</label>
// <div className="select">
// <select
// required
// // value={vtuber}
// // onChange={(evt) => setVtuber(parseInt(evt.target.value))}
// {...register('vtuber')}
// >
// {vtubers.map((vtuber: IVtuber) => (
// <option key={vtuber.id} value={vtuber.id}>{vtuber.attributes.displayName}</option>
// ))}
// </select>
// </div>
// <p className="help is-info">Choose the VTuber this VOD belongs to. (More VTubers will be added when storage/bandwidth funding is secured.)</p>
// {errors.vtuber && <p className="help is-danger">vtuber error</p>}
// </div>
// <div className="field">
// <label className="label">Stream Date</label>
// <input
// required
// className="input" type="date"
// {...register('date')}
// // onChange={(evt) => setDate(evt.target.value)}
// ></input>
// <p className="help is-info">The date when the VOD was originally streamed.</p>
// {errors.date && <p className="help is-danger">{errors.date.message?.toString()}</p>}
// </div>
// <div className="field">
// <label className="label">Notes</label>
// <textarea
// className="textarea"
// placeholder="e.g. Missing first 10 minutes of stream"
// // onChange={(evt) => setNote(evt.target.value)}
// {...register('notes')}
// ></textarea>
// <p className="help is-info">If there are any issues with the VOD, put a note here. If there are no VOD issues, leave this field blank.</p>
// </div>
// <div className="field">
// <label className="label">Attribution</label>
// <label className="checkbox">
// <input
// type="checkbox"
// // onChange={(evt) => setAttribution(evt.target.checked)}
// {...register('attribution')}
// />
// <span className={`ml-2 ${styles.noselect}`}>Credit {authData.user?.username} for the upload.</span>
// <p className="help is-info">Check this box if you want your username displayed on the website. Thank you for uploading!</p>
// </label>
// </div>
// </section>
// </div>}
// <div className="column is-full">
// <section className="hero is-info">
// <div className="hero-body">
// <p className="title">
// Step 3
// </p>
// <p className="subtitle">
// Send the form
// </p>
// </div>
// </section>
// <section className="section">
// {errors.root?.serverError && (
// <div className="notification">
// <button className="delete" onClick={() => clearErrors()}></button>
// <ErrorMessage name="root" errors={errors} ></ErrorMessage>
// </div>
// )}
// {!isSubmitSuccessful && (
// <button className="button is-primary is-large mt-5">
// <span className="icon is-small">
// <FontAwesomeIcon icon={faPaperPlane}></FontAwesomeIcon>
// </span>
// <span>Send</span>
// </button>
// )}
// {isSubmitting && (
// <p>
// <FontAwesomeIcon className="mt-5 fa-spin-pulse" icon={faSpinner} ></FontAwesomeIcon>
// </p>
// )}
// {isSubmitSuccessful && (
// <>
// <aside className="notification mt-5 is-success">Thank you for uploading! </aside>
// <button onClick={() => {
// reset(); // reset form
// const files = uppy.getFiles()
// for (const file of files) {
// uppy.removeFile(file.id); // reset uppy
// }
// }} className="button is-primary">
// <span className="icon is-small">
// <FontAwesomeIcon icon={faEraser}></FontAwesomeIcon>
// </span>
// <span>Reset form</span>
// </button>
// </>
// )}
// </section>
// </div>
// </form>
// </div>
// )
// }
// </div>
// </>
// )
2024-01-20 16:16:14 +00:00
}