From 0a8ebfb050caf2cf692fd2410e7413a943d0b02f Mon Sep 17 00:00:00 2001 From: Anna Murphy Date: Wed, 8 May 2024 16:12:34 -0400 Subject: [PATCH] update upload to use shared form and include on submit feedback --- README.md | 2 +- src/admin-portal/components/EpisodeForm.tsx | 35 ++- src/admin-portal/components/index.ts | 1 + src/admin-portal/index.tsx | 21 +- src/admin-portal/views/EditEpisode.tsx | 13 +- src/admin-portal/views/EpisodeView.tsx | 9 +- src/admin-portal/views/Upload.tsx | 232 +++----------------- src/admin-portal/views/index.ts | 1 + 8 files changed, 95 insertions(+), 219 deletions(-) diff --git a/README.md b/README.md index a429a37..1a0642e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Capes in the Dark Web App -This Firebase + Astro web application is used to hold the web platform for the RPG *Capes in the Dark*. The Capes in the West March podcast feed is also hosted through this application, and managed through the `/admin` panel. +This Firebase + Astro web application is used to hold the web platform for the RPG _Capes in the Dark_. The Capes in the West March podcast feed is also hosted through this application, and managed through the `/admin` panel. To access the site itself, go to [https://capes-in-the-dark.web.app](https://capes-in-the-dark.web.app). diff --git a/src/admin-portal/components/EpisodeForm.tsx b/src/admin-portal/components/EpisodeForm.tsx index ed61924..790929b 100644 --- a/src/admin-portal/components/EpisodeForm.tsx +++ b/src/admin-portal/components/EpisodeForm.tsx @@ -12,7 +12,12 @@ import { Timestamp } from "firebase/firestore"; interface EpisodeFormPromps { episodeData?: PodcastEpisode; - parseFile?: (file: File) => Promise<{ downloadUrl: string; size: number }>; + parseFile?: ( + file: File, + season: number, + episode: number, + title: string, + ) => Promise<{ downloadUrl: string; size: number }>; submit: (episode: PodcastEpisode) => void; submitLabel: string; } @@ -44,7 +49,12 @@ export function EpisodeForm({ if (parseFile === undefined) return editedEpisodeData.file; if (uploadedFile === undefined) throw new Error("Don't forget to upload an episode!"); - return await parseFile(uploadedFile); + return await parseFile( + uploadedFile, + editedEpisodeData.season, + editedEpisodeData.episode, + editedEpisodeData.title, + ); }, [parseFile, editedEpisodeData], ); @@ -56,12 +66,18 @@ export function EpisodeForm({ event.preventDefault(); try { const { downloadUrl, size } = await doParseFile(file); - const publishDate = episodeData === undefined ? Timestamp.fromDate(new Date()) : episodeData.publishDate as unknown as Timestamp; + const publishDate = + episodeData === undefined + ? Timestamp.fromDate(new Date()) + : (episodeData.publishDate as unknown as Timestamp); submit( - makeEpisodeData({ - ...editedEpisodeData, - file: { downloadUrl, size }, - }, publishDate), + makeEpisodeData( + { + ...editedEpisodeData, + file: { downloadUrl, size }, + }, + publishDate, + ), ); } catch (ex) { setError((ex as { message: string }).message); @@ -185,7 +201,10 @@ function startingEpisodeData( }; } -function makeEpisodeData(editedData: EditedEpisodeData, publishDate: Timestamp): PodcastEpisode { +function makeEpisodeData( + editedData: EditedEpisodeData, + publishDate: Timestamp, +): PodcastEpisode { return { feed: "Capes in the West March", title: editedData.title, diff --git a/src/admin-portal/components/index.ts b/src/admin-portal/components/index.ts index e69de29..160cf4c 100644 --- a/src/admin-portal/components/index.ts +++ b/src/admin-portal/components/index.ts @@ -0,0 +1 @@ +export * from "./EpisodeForm"; diff --git a/src/admin-portal/index.tsx b/src/admin-portal/index.tsx index 453dd61..10672fb 100644 --- a/src/admin-portal/index.tsx +++ b/src/admin-portal/index.tsx @@ -1,9 +1,7 @@ import React from "react"; - -import { EpisodeView, Login, Upload } from "./views"; - import type { UserCredential } from "firebase/auth"; -import { EditEpisode } from "./views/EditEpisode"; + +import { EpisodeView, EditEpisode, Login, Upload } from "./views"; const PAGES = ["EPISODES", "EDIT", "UPLOAD"] as const; type Page = (typeof PAGES)[number]; @@ -53,9 +51,20 @@ export function AdminPortal(): JSX.Element { /> ) : undefined} {page === "EDIT" && editId !== undefined ? ( - + { + setPage("EPISODES"); + }} + /> + ) : undefined} + {page === "UPLOAD" ? ( + { + setPage("EPISODES"); + }} + /> ) : undefined} - {page === "UPLOAD" ? : undefined} ) : undefined} diff --git a/src/admin-portal/views/EditEpisode.tsx b/src/admin-portal/views/EditEpisode.tsx index d7d38fa..d29874f 100644 --- a/src/admin-portal/views/EditEpisode.tsx +++ b/src/admin-portal/views/EditEpisode.tsx @@ -2,13 +2,14 @@ import React from "react"; import { doc, getDoc, setDoc } from "firebase/firestore"; import { firestore } from "@admin-portal/utils/firebase"; -import { EpisodeForm } from "@admin-portal/components/EpisodeForm"; +import { EpisodeForm } from "@admin-portal/components"; interface EditEpisodeProps { id: string; + onFinish: () => void; } -export function EditEpisode({ id }: EditEpisodeProps): JSX.Element { +export function EditEpisode({ id, onFinish }: EditEpisodeProps): JSX.Element { const documentReference = React.useMemo( () => doc(firestore, `api/v1/episodes/${id}`), [id], @@ -32,7 +33,13 @@ export function EditEpisode({ id }: EditEpisodeProps): JSX.Element { const update = React.useCallback( (editedEpisode: PodcastEpisode) => { - void setDoc(documentReference, editedEpisode); + setDoc(documentReference, editedEpisode) + .then(() => { + onFinish(); + }) + .catch((ex) => { + setError((ex as { message: string }).message); + }); }, [documentReference], ); diff --git a/src/admin-portal/views/EpisodeView.tsx b/src/admin-portal/views/EpisodeView.tsx index 4b46c8d..7c623a6 100644 --- a/src/admin-portal/views/EpisodeView.tsx +++ b/src/admin-portal/views/EpisodeView.tsx @@ -63,5 +63,12 @@ export function EpisodeView({ goToEpisode }: EpisodeViewProps): JSX.Element { } function formatEpisodeTitle(episode: PodcastEpisode): JSX.Element { - return <>{episode.title} ({episode.metadata.season}-{episode.metadata.episode}); + return ( + <> + {episode.title}{" "} + + ({episode.metadata.season}-{episode.metadata.episode}) + + + ); } diff --git a/src/admin-portal/views/Upload.tsx b/src/admin-portal/views/Upload.tsx index 836be01..f38e291 100644 --- a/src/admin-portal/views/Upload.tsx +++ b/src/admin-portal/views/Upload.tsx @@ -1,65 +1,47 @@ import React from "react"; -/* -import { EpisodeForm } from "@admin-portal/components/EpisodeForm"; -import { firestore, storage } from "@admin-portal/utils/firebase"; -import { getDownloadURL, getMetadata, ref, uploadBytes } from "firebase/storage"; import { addDoc, collection } from "firebase/firestore"; +import { + getDownloadURL, + getMetadata, + ref, + uploadBytes, +} from "firebase/storage"; -function pad(num: number, padSize = 3): string { - return (Array.from(Array(padSize), () => "0").join("") + num).slice(-1 * padSize); -} +import { EpisodeForm } from "@admin-portal/components"; +import { firestore, storage } from "../utils/firebase"; -async function uploadAudio( - audioFile: File, - season: number, - episode: number, - title: string, -): Promise<{ downloadUrl: string; size: number }> { - const splitOnDot = audioFile.name.split("."); - const path = `recordings/citwm-s${pad(season, 3)}-e${pad(episode, 3)}-${title.replaceAll(" ", "_")}.${splitOnDot[splitOnDot.length - 1]}`; - const audioRef = ref(storage, path); - await uploadBytes(audioRef, audioFile, { - customMetadata: { - metadata: JSON.stringify({ season, episode, title }), - }, - }); - const downloadUrl = await getDownloadURL(audioRef); - const metadata = await getMetadata(audioRef); - return { downloadUrl, size: metadata.size }; +interface UploadProps { + onFinish: () => void; } -async function makeEpisodeDocument(episode: PodcastEpisode): Promise { - const collectionRef = collection(firestore, "api/v1/episodes"); - await addDoc(collectionRef, episode); -} +export function Upload({ onFinish }: UploadProps): JSX.Element { + const [error, setError] = React.useState(""); + + const doUpload = React.useCallback( + (podcastEpisode: PodcastEpisode) => { + makeEpisodeDocument(podcastEpisode) + .then(() => { + onFinish(); + }) + .catch((ex) => { + setError((ex as { message: string }).message); + }); + }, + [onFinish, setError], + ); -export function Upload(): JSX.Element { return ( <> -

Upload an Episode

- +

Upload

+ {error !== "" ?

{error}

: undefined} + ); } -*/ - -import { - FileInput, - NumberInput, - TextInput, - SelectInput, - CheckboxInput, - ParagraphInput, - SeasonSelect, -} from "@admin-portal/components/FormInputs"; -import { firestore, storage } from "../utils/firebase"; -import { - getDownloadURL, - getMetadata, - ref, - uploadBytes, -} from "firebase/storage"; -import { Timestamp, addDoc, collection } from "firebase/firestore"; async function uploadAudio( audioFile: File, @@ -80,157 +62,7 @@ async function uploadAudio( return { downloadUrl, size: metadata.size }; } -function makeEpisodeData( - title: string, - duration: number, - fileUrl: string, - fileSize: number, - episodeType: EpisodeType, - explicit: boolean, - description: string, - seasonNumber: number, - episodeNumber: number, -): PodcastEpisode { - const data: PodcastEpisode = { - feed: "Capes in the West March", - title, - description, - // Save this as a firebase Timestamp to upload to the database. - publishDate: Timestamp.fromDate(new Date()) as unknown as Date, - imageLink: "", - metadata: { - season: seasonNumber, - episode: episodeNumber, - type: episodeType, - explicit, - }, - fileData: { - url: fileUrl, - size: fileSize, - duration, - }, - }; - return data; -} - async function makeEpisodeDocument(episode: PodcastEpisode): Promise { const collectionRef = collection(firestore, "api/v1/episodes"); await addDoc(collectionRef, episode); } - -export function Upload(): JSX.Element { - const [title, setTitle] = React.useState(""); - const [duration, setDuration] = React.useState(0); - const [seasonNumber, setSeasonNumber] = React.useState(1); - const [episodeNumber, setEpisodeNumber] = React.useState(1); - const [file, setFile] = React.useState(); - const [episodeType, setEpisodeType] = React.useState("full"); - const [explicit, setExplicit] = React.useState(true); - const [description, setDescription] = React.useState(""); - - const [error, setError] = React.useState(""); - - const submit = React.useCallback( - async (event: React.FormEvent) => { - event.preventDefault(); - if (file === undefined) { - setError("Choose a file to upload"); - return; - } - try { - const { downloadUrl, size } = await uploadAudio( - file, - seasonNumber, - episodeNumber, - title, - ); - const episodeData = makeEpisodeData( - title, - duration, - downloadUrl, - size, - episodeType, - explicit, - description, - seasonNumber, - episodeNumber, - ); - await makeEpisodeDocument(episodeData); - } catch (ex) { - setError((ex as { message: string }).message); - } - }, - [ - title, - duration, - seasonNumber, - episodeNumber, - file, - episodeType, - explicit, - description, - setError, - ], - ); - // Title - // File - // Duration - // Image (choose from list?) - // Explicit (default yes) - // Description - // Type (default full) - - return ( - <> -

Upload an Episode

-
- - { - setSeasonNumber(Number(season.toString())); - }} - /> - - - - { - setEpisodeType(value as EpisodeType); - }} - /> - - - - - {error !== "" ?

{error}

: undefined} - - ); -} diff --git a/src/admin-portal/views/index.ts b/src/admin-portal/views/index.ts index b1deb9b..2319678 100644 --- a/src/admin-portal/views/index.ts +++ b/src/admin-portal/views/index.ts @@ -1,3 +1,4 @@ export * from "./Login"; export * from "./Upload"; export * from "./EpisodeView"; +export * from "./EditEpisode";