Moved the upload folder out of public, an api endpoint to server the files, and a middleware to make it seemlessly pull the uploads from the api without api in the name. Further styling on the expand/collapse podcast card logic to have it show new lines and to fade the collapsed paragraph. Publish page now shows that it's 'loading' in the submit button and alerts the user when done. After success upload it'll redirect to the podcasts listing page.
This commit is contained in:
parent
8e9ef164a9
commit
5559e85588
2
.gitignore
vendored
2
.gitignore
vendored
@ -19,7 +19,7 @@
|
||||
|
||||
# production
|
||||
/build
|
||||
/public/uploads/
|
||||
/uploads/
|
||||
db.json
|
||||
|
||||
# misc
|
||||
|
||||
@ -25,11 +25,13 @@ export default function PodcastCard({
|
||||
|
||||
<div className="p-4 flex-grow flex flex-col bg-purple-200">
|
||||
<h2 className="text-lg font-bold text-gray-800">{title}</h2>
|
||||
<div
|
||||
className={`text-sm text-gray-600 overflow-hidden transition-all duration-300 ${descriptionExpanded ? 'max-h-full' : 'max-h-[30px]'
|
||||
}`}
|
||||
>
|
||||
{description}
|
||||
<div className="relative">
|
||||
<div className={`text-sm text-gray-600 overflow-hidden transition-all duration-300 whitespace-pre-wrap ${descriptionExpanded ? 'max-h-full' : 'max-h-[60px]'}`}>
|
||||
{description}
|
||||
</div>
|
||||
{!descriptionExpanded && (
|
||||
<div className="absolute bottom-0 left-0 w-full h-6 bg-gradient-to-t from-purple-200 to-transparent pointer-events-none"></div>
|
||||
)}
|
||||
</div>
|
||||
<button onClick={toggleExpand} className="mt-2 text-center text-blue-500 hover:underline text-sm self-center">
|
||||
{descriptionExpanded ? 'Show Less' : 'Show More'}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useForm, SubmitHandler } from "react-hook-form";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
type PodcastDto = {
|
||||
imageUrl?: FileList;
|
||||
@ -12,9 +14,12 @@ type PodcastDto = {
|
||||
};
|
||||
|
||||
export default function StreamPublishForm({ stream }: { stream?: string | string[] }) {
|
||||
const { register, handleSubmit, formState: { errors } } = useForm<PodcastDto>();
|
||||
const { register, handleSubmit, formState: { errors } } = useForm<PodcastDto>(),
|
||||
router = useRouter(),
|
||||
[submitting, setSubmitting] = useState(false);
|
||||
|
||||
const onSubmit: SubmitHandler<PodcastDto> = (data) => {
|
||||
setSubmitting(true);
|
||||
const formData = new FormData();
|
||||
formData.append("image", data.imageUrl?.[0] as File);
|
||||
formData.append("title", data.title);
|
||||
@ -29,10 +34,14 @@ export default function StreamPublishForm({ stream }: { stream?: string | string
|
||||
body: formData,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((result) => {
|
||||
console.log("Podcast created:", result);
|
||||
.then(() => {
|
||||
alert(`Podcast uploaded: ${data.title}`);
|
||||
router.push(`/${stream}/podcasts`);
|
||||
})
|
||||
.catch((err) => console.error("Error creating podcast:", err));
|
||||
.catch((err) => {
|
||||
alert(`Error creating podcast: JSON.string${err}`);
|
||||
setSubmitting(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@ -102,8 +111,9 @@ export default function StreamPublishForm({ stream }: { stream?: string | string
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-purple-600 text-purple-200 py-2 px-4 rounded-md hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
|
||||
disabled={submitting}
|
||||
>
|
||||
Submit
|
||||
{submitting ? 'Uploading...' : 'Upload'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -21,7 +21,7 @@ export const convertUrlToPublic = (url?: string) => {
|
||||
};
|
||||
|
||||
export const preparePodcastItem = (podcast: PodcastDto) => {
|
||||
const fileSize = getFileSize(path.join(process.cwd(), `public/uploads/${podcast.url}`)),
|
||||
const fileSize = getFileSize(path.join(process.cwd(), `uploads/${podcast.url}`)),
|
||||
fileUrl = convertUrlToPublic(podcast.url);
|
||||
|
||||
return {
|
||||
|
||||
15
src/middleware.ts
Normal file
15
src/middleware.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export function middleware(req: NextRequest) {
|
||||
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||
const url = req.nextUrl.clone();
|
||||
|
||||
const adjustedPathname = url.pathname.replace(basePath, '');
|
||||
|
||||
if (adjustedPathname.startsWith('/uploads/')) {
|
||||
url.pathname = `/api${adjustedPathname}`;
|
||||
return NextResponse.rewrite(url);
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
}
|
||||
@ -27,7 +27,7 @@ const post = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
return res.status(400).json({ error: 'Invalid stream parameter' });
|
||||
}
|
||||
|
||||
const uploadDir = path.join(process.cwd(), 'public/uploads');
|
||||
const uploadDir = path.join(process.cwd(), 'uploads');
|
||||
await fs.mkdir(uploadDir, { recursive: true });
|
||||
const form = new multiparty.Form({
|
||||
uploadDir
|
||||
@ -80,37 +80,6 @@ const post = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// try {
|
||||
// const { title, description, author, image, file } = req.body;
|
||||
|
||||
// const uploadDir = path.join(process.cwd(), 'public/uploads');
|
||||
// const imageFileName = `${uuidv4()}${path.extname(image.name)}`;
|
||||
// const imageFilePath = path.join(uploadDir, imageFileName);
|
||||
// await fs.writeFile(imageFilePath, Buffer.from(image.data, 'base64'));
|
||||
|
||||
// const podcastFileName = `${uuidv4()}${path.extname(file.name)}`;
|
||||
// const podcastFilePath = path.join(uploadDir, podcastFileName);
|
||||
// await fs.writeFile(podcastFilePath, Buffer.from(file.data, 'base64'));
|
||||
|
||||
// const podcastData: PodcastDto = {
|
||||
// podcastId: uuidv4(),
|
||||
// streamId: stream,
|
||||
// title,
|
||||
// description,
|
||||
// uploadDate: Date.now().toString(),
|
||||
// author,
|
||||
// imageUrl: imageFileName,
|
||||
// url: podcastFileName,
|
||||
// };
|
||||
|
||||
// await publishPodcast(podcastData);
|
||||
|
||||
// res.status(201).json({ message: 'Podcast created successfully' });
|
||||
// } catch (error) {
|
||||
// console.error('Error handling request:', error);
|
||||
// res.status(500).json({ error: 'Internal server error' });
|
||||
// }
|
||||
};
|
||||
|
||||
export default function handler(
|
||||
|
||||
16
src/pages/api/uploads/[file].ts
Normal file
16
src/pages/api/uploads/[file].ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { file } = req.query;
|
||||
|
||||
const filePath = path.join(process.cwd(), 'uploads', file as string);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return res.status(404).json({ error: 'File not found' });
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'application/octet-stream');
|
||||
fs.createReadStream(filePath).pipe(res);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user