fp/packages/next/app/components/upload-form.tsx

394 lines
18 KiB
TypeScript
Raw Normal View History

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
}