forked from RiseUP/riseup-squad23
309 lines
8.3 KiB
JavaScript
309 lines
8.3 KiB
JavaScript
import React, { useState, useEffect, useCallback } from "react";
|
||
import { useLocation, useNavigate } from "react-router-dom";
|
||
import "./style/ProfilePage.css";
|
||
|
||
|
||
const MOCK_API_BASE_URL = "https://mock.apidog.com/m1/1053378-0-default";
|
||
|
||
|
||
const getLocalAvatar = () => localStorage.getItem('user_avatar');
|
||
const setLocalAvatar = (avatarData) => localStorage.setItem('user_avatar', avatarData);
|
||
const clearLocalAvatar = () => localStorage.removeItem('user_avatar');
|
||
|
||
const ROLES = {
|
||
ADMIN: "Administrador",
|
||
SECRETARY: "Secretária",
|
||
DOCTOR: "Médico",
|
||
FINANCIAL: "Financeiro"
|
||
};
|
||
|
||
const ProfilePage = () => {
|
||
const location = useLocation();
|
||
const navigate = useNavigate();
|
||
|
||
const getRoleFromPath = useCallback(() => {
|
||
const path = location.pathname;
|
||
if (path.includes("/admin")) return ROLES.ADMIN;
|
||
if (path.includes("/secretaria")) return ROLES.SECRETARY;
|
||
if (path.includes("/medico")) return ROLES.DOCTOR;
|
||
if (path.includes("/financeiro")) return ROLES.FINANCIAL;
|
||
return "Usuário";
|
||
}, [location.pathname]);
|
||
|
||
const userRole = getRoleFromPath();
|
||
|
||
const [userName, setUserName] = useState("Admin Padrão");
|
||
const [userEmail, setUserEmail] = useState("admin@squad23.com");
|
||
const [avatarUrl, setAvatarUrl] = useState(null);
|
||
const [isEditingName, setIsEditingName] = useState(false);
|
||
const [isUploading, setIsUploading] = useState(false);
|
||
const [error, setError] = useState(null);
|
||
|
||
|
||
useEffect(() => {
|
||
const handleEscKey = (event) => {
|
||
if (event.keyCode === 27) handleClose();
|
||
};
|
||
|
||
document.addEventListener('keydown', handleEscKey);
|
||
return () => document.removeEventListener('keydown', handleEscKey);
|
||
}, []);
|
||
|
||
|
||
useEffect(() => {
|
||
const loadProfileData = () => {
|
||
|
||
const localAvatar = getLocalAvatar();
|
||
if (localAvatar) {
|
||
setAvatarUrl(localAvatar);
|
||
}
|
||
};
|
||
|
||
loadProfileData();
|
||
}, []);
|
||
|
||
const handleNameSave = () => {
|
||
if (userName.trim() === "") {
|
||
setError("Nome não pode estar vazio");
|
||
return;
|
||
}
|
||
|
||
setIsEditingName(false);
|
||
setError(null);
|
||
};
|
||
|
||
const handleNameKeyDown = (event) => {
|
||
if (event.key === "Enter") handleNameSave();
|
||
if (event.key === "Escape") {
|
||
setUserName("Admin Padrão");
|
||
setIsEditingName(false);
|
||
setError(null);
|
||
}
|
||
};
|
||
|
||
const handleClose = () => navigate(-1);
|
||
|
||
|
||
const handleAvatarUpload = async (event) => {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
setError(null);
|
||
|
||
|
||
const MAX_FILE_SIZE = 5 * 1024 * 1024;
|
||
const ACCEPTED_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||
|
||
if (file.size > MAX_FILE_SIZE) {
|
||
setError("Arquivo muito grande. Máximo 5MB.");
|
||
return;
|
||
}
|
||
|
||
if (!ACCEPTED_TYPES.includes(file.type)) {
|
||
setError("Tipo de arquivo não suportado. Use JPEG, PNG, GIF ou WebP.");
|
||
return;
|
||
}
|
||
|
||
setIsUploading(true);
|
||
|
||
try {
|
||
|
||
try {
|
||
const result = await uploadAvatarToMockAPI(file);
|
||
|
||
const newAvatarUrl = result.url || result.avatarUrl;
|
||
if (newAvatarUrl) {
|
||
setAvatarUrl(newAvatarUrl);
|
||
setLocalAvatar(newAvatarUrl);
|
||
console.log('Avatar enviado para API com sucesso');
|
||
return;
|
||
}
|
||
} catch (apiError) {
|
||
|
||
console.log('API não disponível, salvando localmente...');
|
||
}
|
||
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = (e) => {
|
||
const imageDataUrl = e.target.result;
|
||
setLocalAvatar(imageDataUrl);
|
||
setAvatarUrl(imageDataUrl);
|
||
console.log('Avatar salvo localmente');
|
||
};
|
||
reader.readAsDataURL(file);
|
||
|
||
} catch (error) {
|
||
|
||
console.error('Erro no processamento:', error);
|
||
const reader = new FileReader();
|
||
reader.onload = (e) => {
|
||
const imageDataUrl = e.target.result;
|
||
setLocalAvatar(imageDataUrl);
|
||
setAvatarUrl(imageDataUrl);
|
||
};
|
||
reader.readAsDataURL(file);
|
||
} finally {
|
||
setIsUploading(false);
|
||
event.target.value = '';
|
||
}
|
||
};
|
||
|
||
|
||
const uploadAvatarToMockAPI = async (file) => {
|
||
const formData = new FormData();
|
||
formData.append("avatar", file);
|
||
|
||
const response = await fetch(`${MOCK_API_BASE_URL}/storage/v1/object/avatars/[path]`, {
|
||
method: "POST",
|
||
body: formData
|
||
});
|
||
|
||
if (!response.ok) {
|
||
|
||
return null;
|
||
}
|
||
|
||
return await response.json();
|
||
};
|
||
|
||
|
||
const clearAvatar = () => {
|
||
|
||
fetch(`${MOCK_API_BASE_URL}/storage/v1/object/avatars/[path]`, {
|
||
method: "DELETE"
|
||
}).catch(() => {
|
||
|
||
});
|
||
|
||
|
||
clearLocalAvatar();
|
||
setAvatarUrl(null);
|
||
};
|
||
|
||
return (
|
||
<div className="profile-overlay" role="dialog" aria-modal="true">
|
||
<div className="profile-modal">
|
||
<button
|
||
className="profile-close"
|
||
onClick={handleClose}
|
||
aria-label="Fechar Perfil"
|
||
>
|
||
×
|
||
</button>
|
||
|
||
<div className="profile-content">
|
||
<div className="profile-left">
|
||
<div className="avatar-wrapper">
|
||
<div className="avatar-square">
|
||
{avatarUrl ? (
|
||
<img
|
||
src={avatarUrl}
|
||
alt="Avatar do usuário"
|
||
className="avatar-img"
|
||
onError={() => {
|
||
setAvatarUrl(null);
|
||
clearLocalAvatar();
|
||
}}
|
||
/>
|
||
) : (
|
||
<div className="avatar-placeholder">
|
||
{userName.split(' ').map(n => n[0]).join('').toUpperCase()}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<label
|
||
className={`avatar-edit-btn ${isUploading ? 'uploading' : ''}`}
|
||
title="Alterar foto de perfil"
|
||
>
|
||
{isUploading ? 'Enviando...' : 'Alterar Foto'}
|
||
<input
|
||
type="file"
|
||
accept="image/*"
|
||
onChange={handleAvatarUpload}
|
||
disabled={isUploading}
|
||
style={{ display: "none" }}
|
||
/>
|
||
</label>
|
||
|
||
{isUploading && (
|
||
<p className="upload-status">
|
||
Processando imagem...
|
||
</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="profile-right">
|
||
<div className="profile-name-row">
|
||
{isEditingName ? (
|
||
<div className="name-edit-wrapper">
|
||
<input
|
||
className="profile-name-input"
|
||
value={userName}
|
||
onChange={(e) => setUserName(e.target.value)}
|
||
onBlur={handleNameSave}
|
||
onKeyDown={handleNameKeyDown}
|
||
autoFocus
|
||
maxLength={50}
|
||
/>
|
||
<div className="name-edit-hint">
|
||
Pressione Enter para salvar, ESC para cancelar
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<h2 className="profile-username">
|
||
{userName}
|
||
</h2>
|
||
)}
|
||
|
||
<button
|
||
className="profile-edit-inline"
|
||
onClick={() => setIsEditingName(!isEditingName)}
|
||
type="button"
|
||
aria-label={isEditingName ? 'Cancelar edição' : 'Editar nome'}
|
||
>
|
||
{isEditingName ? 'Cancelar' : 'Editar'}
|
||
</button>
|
||
</div>
|
||
|
||
{error && (
|
||
<div className="error-message">
|
||
{error}
|
||
</div>
|
||
)}
|
||
|
||
<div className="profile-info">
|
||
<p className="profile-email">
|
||
<span>Email:</span>
|
||
<strong>{userEmail}</strong>
|
||
</p>
|
||
|
||
<p className="profile-role">
|
||
<span>Cargo:</span>
|
||
<strong>{userRole}</strong>
|
||
</p>
|
||
</div>
|
||
|
||
<div className="profile-actions">
|
||
{avatarUrl && (
|
||
<button onClick={clearAvatar} className="btn btn-clear">
|
||
Remover Avatar
|
||
</button>
|
||
)}
|
||
<button
|
||
className="btn btn-close"
|
||
onClick={handleClose}
|
||
>
|
||
Fechar Perfil
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default ProfilePage; |