ImageUploadField
This commit is contained in:
45
src/blog/components/ImageUploadField.tsx
Normal file
45
src/blog/components/ImageUploadField.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { Box, Button, Avatar, CircularProgress } from "@mui/material";
|
||||||
|
import { ImageUploadFieldProps } from "../types/props";
|
||||||
|
|
||||||
|
export default function ImageUploadField({
|
||||||
|
label = "Upload Image",
|
||||||
|
value,
|
||||||
|
uploading = false,
|
||||||
|
onUpload,
|
||||||
|
size = 64,
|
||||||
|
}: ImageUploadFieldProps) {
|
||||||
|
|
||||||
|
const imgSrc = value
|
||||||
|
? import.meta.env.VITE_API_BASE_URL.replace(/\/+$/, "") +
|
||||||
|
"/" +
|
||||||
|
value.replace(/^\/+/, "")
|
||||||
|
: "";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", gap: 2, mb: 3 }}>
|
||||||
|
<Avatar
|
||||||
|
src={imgSrc}
|
||||||
|
sx={{ width: size, height: size, borderRadius: 2 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
component="label"
|
||||||
|
disabled={uploading}
|
||||||
|
startIcon={uploading && <CircularProgress size={16} />}
|
||||||
|
>
|
||||||
|
{uploading ? "Uploading..." : label}
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
hidden
|
||||||
|
onChange={(e) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (file) onUpload(file);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6,11 +6,11 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
IconButton,
|
IconButton,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Avatar,
|
|
||||||
Alert,
|
Alert,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded';
|
import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded';
|
||||||
import { useAuth } from '../providers/Author';
|
import { useAuth } from '../providers/Author';
|
||||||
|
import ImageUploadField from './ImageUploadField';
|
||||||
|
|
||||||
interface ProfileProps {
|
interface ProfileProps {
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
@@ -120,31 +120,14 @@ export default function Profile({ onBack }: ProfileProps) {
|
|||||||
Profile
|
Profile
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 3 }}>
|
<ImageUploadField
|
||||||
<Avatar
|
label="Upload Avatar"
|
||||||
src={
|
value={formData.avatar}
|
||||||
(import.meta.env.VITE_API_BASE_URL.replace(/\/+$/, "") +
|
uploading={uploadingAvatar}
|
||||||
"/" +
|
onUpload={handleAvatarUpload}
|
||||||
(formData.avatar?.replace(/^\/+/, "") || "")
|
size={64}
|
||||||
)
|
|
||||||
}
|
|
||||||
alt={formData.name || formData.username}
|
|
||||||
sx={{ width: 64, height: 64 }}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button variant="outlined" component="label">
|
|
||||||
{uploadingAvatar ? "Uploading..." : "Upload Avatar"}
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
accept="image/*"
|
|
||||||
hidden
|
|
||||||
onChange={(e) => {
|
|
||||||
if (e.target.files?.[0]) handleAvatarUpload(e.target.files[0]);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Username"
|
label="Username"
|
||||||
|
|||||||
@@ -38,3 +38,11 @@ export interface ArticleGridProps {
|
|||||||
md4?: number; // default 4 (third-width)
|
md4?: number; // default 4 (third-width)
|
||||||
nested?: 1 | 2; // number of stacked cards in a nested column
|
nested?: 1 | 2; // number of stacked cards in a nested column
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ImageUploadFieldProps {
|
||||||
|
label?: string;
|
||||||
|
value?: string;
|
||||||
|
uploading?: boolean;
|
||||||
|
onUpload: (file: File) => void;
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user