Аватар

This commit is contained in:
Халимов Рустам
2026-04-05 01:27:58 +03:00
parent 53f193970c
commit e11240f78f
13 changed files with 133 additions and 47 deletions

View File

@@ -39,7 +39,7 @@ export class UserApi {
});
}
static async cropAvatar(file: File, cropData: { x: number; y: number; width: number; height: number }) {
static async cropAvatar(file: File, cropData: { x: number; y: number; width: number; height: number; sw: number; sh: number }) {
// Конечная точка в новом бэкенде: POST /api/profiles/avatar/crop
const formData = new FormData();
formData.append('avatar', file);
@@ -47,6 +47,8 @@ export class UserApi {
formData.append('y', cropData.y.toString());
formData.append('width', cropData.width.toString());
formData.append('height', cropData.height.toString());
formData.append('sw', cropData.sw.toString());
formData.append('sh', cropData.sh.toString());
return httpClient.request<User>('/profiles/avatar/crop', {
method: 'POST',

View File

@@ -6,7 +6,7 @@ import { useLang } from '../../../../core/infrastructure/i18n';
interface AvatarCropModalProps {
image: string;
onCrop: (cropData: Area) => void;
onCrop: (cropData: Area, pixelCropData: Area) => void;
onClose: () => void;
}
@@ -14,15 +14,18 @@ export default function AvatarCropModal({ image, onCrop, onClose }: AvatarCropMo
const { t } = useLang();
const [crop, setCrop] = useState<Point>({ x: 0, y: 0 });
const [zoom, setZoom] = useState(1);
const [croppedArea, setCroppedArea] = useState<Area | null>(null);
const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null);
const onCropComplete = useCallback((_croppedArea: Area, croppedAreaPixels: Area) => {
const onCropComplete = useCallback((croppedArea: Area, croppedAreaPixels: Area) => {
setCroppedArea(croppedArea);
setCroppedAreaPixels(croppedAreaPixels);
}, []);
const handleSave = () => {
if (croppedAreaPixels) {
onCrop(croppedAreaPixels);
if (croppedArea && croppedAreaPixels) {
onCrop(croppedArea, croppedAreaPixels);
}
};

View File

@@ -13,6 +13,7 @@ import { getMediaUrl, getInitials } from '../../../../core/utils/utils';
import DatePicker from '../../../../core/presentation/components/ui/DatePicker';
import AvatarCropModal from './AvatarCropModal';
import { Area } from 'react-easy-crop';
import { getCroppedImg } from '../../../../core/utils/cropImage';
export default function SettingsPage() {
const { user, updateUser, logout } = useAuthStore();
@@ -27,7 +28,11 @@ export default function SettingsPage() {
const [saving, setSaving] = useState(false);
// Avatar cropping state
const [cropModal, setCropModal] = useState<{ open: boolean, image: string, file: File | null }>({ open: false, image: '', file: null });
const [cropModal, setCropModal] = useState<{
open: boolean,
image: string,
file: File | null
}>({ open: false, image: '', file: null });
const [uploadingAvatar, setUploadingAvatar] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
@@ -63,26 +68,30 @@ export default function SettingsPage() {
if (file) {
const reader = new FileReader();
reader.onload = () => {
setCropModal({ open: true, image: reader.result as string, file });
setCropModal({
open: true,
image: reader.result as string,
file
});
};
reader.readAsDataURL(file);
}
e.target.value = '';
};
const onCropConfirm = async (cropData: Area) => {
if (!cropModal.file) return;
const onCropConfirm = async (_area: Area, pixelArea: Area) => {
if (!cropModal.file || !cropModal.image) return;
setCropModal(prev => ({ ...prev, open: false }));
setUploadingAvatar(true);
try {
const updatedUser = await UserApi.cropAvatar(cropModal.file, {
x: Math.round(cropData.x),
y: Math.round(cropData.y),
width: Math.round(cropData.width),
height: Math.round(cropData.height)
});
const croppedBlob = await getCroppedImg(cropModal.image, pixelArea);
if (!croppedBlob) throw new Error('Failed to crop image');
const croppedFile = new File([croppedBlob], 'avatar.jpg', { type: 'image/jpeg' });
const updatedUser = await UserApi.uploadAvatar(croppedFile);
updateUser(updatedUser);
} catch (err) {
console.error(err);
console.error('Failed to update avatar:', err);
} finally {
setUploadingAvatar(false);
}