refactor: move Bytescale upload logic to backend for security
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useState, useRef } from "react";
|
||||
import * as Bytescale from "@bytescale/sdk";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { User, X, Loader2 } from "lucide-react";
|
||||
import {useState, useRef} from "react";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {User, X, Loader2} from "lucide-react";
|
||||
import axios from "axios";
|
||||
|
||||
interface BytescaleUploaderProps {
|
||||
value: string;
|
||||
@@ -9,11 +9,11 @@ interface BytescaleUploaderProps {
|
||||
folderPath: "/doctors" | "/departments" | "/news";
|
||||
}
|
||||
|
||||
const uploadManager = new Bytescale.UploadManager({
|
||||
apiKey: "public_223k2cv3MyaAxBeyALCfJa8EMLek",
|
||||
});
|
||||
|
||||
export function BytescaleUploader({ value, onChange }: BytescaleUploaderProps) {
|
||||
export function BytescaleUploader({
|
||||
value,
|
||||
onChange,
|
||||
folderPath,
|
||||
}: BytescaleUploaderProps) {
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
@@ -21,19 +21,35 @@ export function BytescaleUploader({ value, onChange }: BytescaleUploaderProps) {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
setIsUploading(true);
|
||||
try {
|
||||
const { fileUrl } = await uploadManager.upload({
|
||||
data: file,
|
||||
path: {
|
||||
folderPath: "/doctors",
|
||||
},
|
||||
});
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
alert("File is too large (Max 5MB)");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsUploading(true);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("folderPath", folderPath);
|
||||
|
||||
try {
|
||||
const response = await axios.post(
|
||||
"http://localhost:3000/api/upload",
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const {fileUrl} = response.data;
|
||||
onChange(fileUrl);
|
||||
} catch (e: any) {
|
||||
console.error("Upload Error:", e);
|
||||
alert(`Error:\n${e.message}`);
|
||||
const errorMessage =
|
||||
e.response?.data?.error || e.message || "Upload failed";
|
||||
alert(`Upload Error: ${errorMessage}`);
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
||||
@@ -49,7 +65,7 @@ export function BytescaleUploader({ value, onChange }: BytescaleUploaderProps) {
|
||||
<img
|
||||
src={value}
|
||||
className="w-16 h-16 rounded-full object-cover border-2 border-primary/20"
|
||||
alt="Doctor Profile"
|
||||
alt="Preview"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -100,8 +116,8 @@ export function BytescaleUploader({ value, onChange }: BytescaleUploaderProps) {
|
||||
|
||||
{value && (
|
||||
<p className="text-xs text-amber-600 pl-[72px]">
|
||||
⚠️ Make sure to save the changes by clicking the "Save Changes" button
|
||||
at the bottom.
|
||||
⚠️ Make sure to save the changes by clicking the "Save Changes"
|
||||
button.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { AxiosError } from "axios";
|
||||
import { BytescaleUploader } from "@/components/BytescaleUploader/BytescaleUploader";
|
||||
import {useState, useEffect, useCallback} from "react";
|
||||
import {AxiosError} from "axios";
|
||||
import {BytescaleUploader} from "@/components/BytescaleUploader/BytescaleUploader";
|
||||
|
||||
import {
|
||||
getDepartmentsApi,
|
||||
@@ -18,8 +18,8 @@ import {
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card";
|
||||
import {Button} from "@/components/ui/button";
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
@@ -29,8 +29,8 @@ import {
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {Input} from "@/components/ui/input";
|
||||
import {Textarea} from "@/components/ui/textarea";
|
||||
|
||||
import {
|
||||
Loader2,
|
||||
@@ -122,7 +122,7 @@ export default function DepartmentPage() {
|
||||
);
|
||||
|
||||
function handleChange(e: any) {
|
||||
setForm({ ...form, [e.target.name]: e.target.value });
|
||||
setForm({...form, [e.target.name]: e.target.value});
|
||||
}
|
||||
|
||||
function truncate(text: string, limit = 60) {
|
||||
@@ -159,7 +159,7 @@ export default function DepartmentPage() {
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
if (editing) {
|
||||
const { departmentId, ...updateData } = form;
|
||||
const {departmentId, ...updateData} = form;
|
||||
await updateDepartmentApi(editing.departmentId, updateData);
|
||||
} else {
|
||||
await createDepartmentApi(form);
|
||||
@@ -402,7 +402,7 @@ export default function DepartmentPage() {
|
||||
<BytescaleUploader
|
||||
value={form.image}
|
||||
folderPath="/departments"
|
||||
onChange={(url) => setForm({ ...form, image: url })}
|
||||
onChange={(url) => setForm({...form, image: url})}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
@@ -457,7 +457,7 @@ export default function DepartmentPage() {
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleSubmit}>
|
||||
{editing ? "Update" : "Create"}
|
||||
{editing ? "Save Changes" : "Create"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
Reference in New Issue
Block a user