microtecflix/src/components/ProfileView.tsx

303 lines
13 KiB
TypeScript

import React, { useState, useRef } from 'react';
import { User as UserIcon, Shield, CreditCard, Mail, Phone, Calendar, ArrowLeft, Camera, Edit2, Save, X, Loader2 } from 'lucide-react';
import { User } from '../types';
interface ProfileViewProps {
user: User;
onBack: () => void;
onUpdateUser?: (updated: User) => void;
}
export default function ProfileView({ user, onBack, onUpdateUser }: ProfileViewProps) {
const [isEditing, setIsEditing] = useState(false);
const [name, setName] = useState(user.name);
const [whatsapp, setWhatsapp] = useState(user.whatsapp || '');
const [birthDate, setBirthDate] = useState(user.birthDate || '');
const [cpf, setCpf] = useState(user.cpf || '');
const [isSaving, setIsSaving] = useState(false);
const [isUploading, setIsUploading] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const getStatusColor = (status: string) => {
switch (status) {
case 'ACTIVE': return 'bg-emerald-500/20 text-emerald-400 border-emerald-500/50';
case 'TRIAL': return 'bg-amber-500/20 text-amber-400 border-amber-500/50';
case 'OVERDUE': return 'bg-red-500/20 text-red-400 border-red-500/50';
case 'CANCELED': return 'bg-zinc-800 text-zinc-400 border-zinc-700';
default: return 'bg-zinc-800 text-white border-zinc-700';
}
};
const getStatusLabel = (status: string) => {
switch (status) {
case 'ACTIVE': return 'Assinatura Ativa';
case 'TRIAL': return 'Período de Teste';
case 'OVERDUE': return 'Pagamento Pendente';
case 'CANCELED': return 'Assinatura Cancelada';
default: return status;
}
};
const handleSave = async () => {
setIsSaving(true);
try {
const token = localStorage.getItem('devflix_token');
const res = await fetch('/api/users/profile', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` })
},
body: JSON.stringify({ name, whatsapp, birthDate, cpf })
});
const data = await res.json();
if (data.success && onUpdateUser) {
onUpdateUser(data.user);
setIsEditing(false);
} else {
alert(data.error || 'Erro ao atualizar perfil');
}
} catch (err) {
alert('Erro de conexão ao salvar perfil');
} finally {
setIsSaving(false);
}
};
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
setIsUploading(true);
const formData = new FormData();
formData.append('avatar', file);
try {
const token = localStorage.getItem('devflix_token');
const res = await fetch('/api/users/avatar', {
method: 'POST',
headers: {
...(token && { Authorization: `Bearer ${token}` })
},
body: formData
});
const data = await res.json();
if (data.success && onUpdateUser) {
onUpdateUser({ ...user, avatarUrl: data.avatarUrl });
} else {
alert(data.error || 'Erro ao fazer upload da foto');
}
} catch (err) {
alert('Erro de conexão ao enviar foto');
} finally {
setIsUploading(false);
}
};
return (
<div className="space-y-6 max-w-4xl mx-auto pb-12">
<div className="flex items-center justify-between">
<button
onClick={onBack}
className="flex items-center space-x-2 text-zinc-400 hover:text-white transition-colors text-sm font-bold"
>
<ArrowLeft className="w-4 h-4" />
<span>Voltar</span>
</button>
{!isEditing ? (
<button
onClick={() => setIsEditing(true)}
className="flex items-center space-x-2 glass-btn-secondary px-4 py-2 rounded-lg text-sm font-bold text-white hover:text-[#E50914] transition-colors"
>
<Edit2 className="w-4 h-4" />
<span>Editar Perfil</span>
</button>
) : (
<div className="flex items-center space-x-2">
<button
onClick={() => {
setIsEditing(false);
setName(user.name);
setWhatsapp(user.whatsapp || '');
setBirthDate(user.birthDate || '');
setCpf(user.cpf || '');
}}
className="flex items-center space-x-1 px-4 py-2 rounded-lg text-sm font-bold text-zinc-400 hover:text-white transition-colors"
>
<X className="w-4 h-4" />
<span>Cancelar</span>
</button>
<button
onClick={handleSave}
disabled={isSaving}
className="flex items-center space-x-1 glass-btn-primary px-4 py-2 rounded-lg text-sm font-bold text-white transition-colors disabled:opacity-50"
>
{isSaving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
<span>{isSaving ? 'Salvando...' : 'Salvar Alterações'}</span>
</button>
</div>
)}
</div>
<div className="glass-card rounded-2xl overflow-hidden shadow-2xl border border-white/10 p-8 relative">
<div className="flex flex-col md:flex-row items-start gap-8">
{/* Avatar Area */}
<div className="flex flex-col items-center space-y-4">
<div className="relative group cursor-pointer" onClick={() => fileInputRef.current?.click()}>
<div className="w-32 h-32 bg-zinc-800 rounded-full flex items-center justify-center border-4 border-[#E50914]/20 overflow-hidden">
{isUploading ? (
<Loader2 className="w-8 h-8 text-[#E50914] animate-spin" />
) : user.avatarUrl ? (
<img src={user.avatarUrl} alt="Avatar" className="w-full h-full object-cover" />
) : (
<UserIcon className="w-16 h-16 text-zinc-400" />
)}
</div>
<div className="absolute inset-0 bg-black/60 rounded-full opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
<Camera className="w-8 h-8 text-white" />
</div>
<input
type="file"
ref={fileInputRef}
className="hidden"
accept="image/*"
onChange={handleFileChange}
/>
</div>
<div className={`px-3 py-1 rounded-full text-xs font-bold border uppercase tracking-wider ${getStatusColor(user.subscriptionStatus)}`}>
{getStatusLabel(user.subscriptionStatus)}
</div>
</div>
{/* User Info */}
<div className="flex-1 space-y-6 w-full">
<div>
{isEditing ? (
<div className="space-y-1">
<label className="text-[10px] uppercase font-bold text-zinc-500">Nome Completo</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full glass-input rounded-lg p-2 text-xl font-bold text-white focus:outline-none focus:border-[#E50914]"
/>
</div>
) : (
<>
<h2 className="text-2xl font-bold text-white">{user.name}</h2>
<p className="text-zinc-400 text-sm">{user.role === 'admin' ? 'Administrador do Sistema' : 'Aluno'}</p>
</>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="bg-black/30 p-4 rounded-xl border border-white/5 flex flex-col justify-center">
<div className="flex items-center space-x-3 mb-1">
<Shield className="w-4 h-4 text-zinc-500" />
<p className="text-[10px] uppercase font-bold text-zinc-500">CPF</p>
</div>
{isEditing ? (
<div className="pl-7">
<input
type="text"
value={cpf}
onChange={(e) => setCpf(e.target.value.replace(/\D/g, '').replace(/(\d{3})(\d)/, '$1.$2').replace(/(\d{3})(\d)/, '$1.$2').replace(/(\d{3})(\d{1,2})$/, '$1-$2'))}
placeholder="000.000.000-00"
maxLength={14}
className="w-full bg-black/50 border border-white/10 rounded-md p-1.5 text-sm text-white focus:outline-none focus:border-[#E50914]"
/>
</div>
) : (
<p className="text-sm font-medium text-white pl-7">{user.cpf || 'Não informado'}</p>
)}
</div>
<div className="bg-black/30 p-4 rounded-xl border border-white/5 flex flex-col justify-center">
<div className="flex items-center space-x-3 mb-1">
<Mail className="w-4 h-4 text-zinc-500" />
<p className="text-[10px] uppercase font-bold text-zinc-500">Email</p>
</div>
<p className="text-sm font-medium text-white opacity-60 cursor-not-allowed pl-7">{user.email}</p>
</div>
<div className="bg-black/30 p-4 rounded-xl border border-white/5 flex flex-col justify-center">
<div className="flex items-center space-x-3 mb-1">
<Phone className="w-4 h-4 text-zinc-500" />
<p className="text-[10px] uppercase font-bold text-zinc-500">WhatsApp</p>
</div>
{isEditing ? (
<div className="pl-7">
<input
type="text"
value={whatsapp}
onChange={(e) => setWhatsapp(e.target.value)}
placeholder="(11) 99999-9999"
className="w-full bg-black/50 border border-white/10 rounded-md p-1.5 text-sm text-white focus:outline-none focus:border-[#E50914]"
/>
</div>
) : (
<p className="text-sm font-medium text-white pl-7">{user.whatsapp || 'Não informado'}</p>
)}
</div>
<div className="bg-black/30 p-4 rounded-xl border border-white/5 flex flex-col justify-center">
<div className="flex items-center space-x-3 mb-1">
<Calendar className="w-4 h-4 text-zinc-500" />
<p className="text-[10px] uppercase font-bold text-zinc-500">Data de Nascimento</p>
</div>
{isEditing ? (
<div className="pl-7">
<input
type="date"
value={birthDate}
onChange={(e) => setBirthDate(e.target.value)}
className="w-full bg-black/50 border border-white/10 rounded-md p-1.5 text-sm text-white focus:outline-none focus:border-[#E50914] [color-scheme:dark]"
/>
</div>
) : (
<p className="text-sm font-medium text-white pl-7">
{user.birthDate ? new Date(user.birthDate).toLocaleDateString('pt-BR') : 'Não informado'}
</p>
)}
</div>
<div className="bg-black/30 p-4 rounded-xl border border-white/5 flex flex-col justify-center">
<div className="flex items-center space-x-3 mb-1">
<CreditCard className="w-4 h-4 text-zinc-500" />
<p className="text-[10px] uppercase font-bold text-zinc-500">Membro desde</p>
</div>
<p className="text-sm font-medium text-white pl-7">
{user.createdAt ? new Date(user.createdAt).toLocaleDateString('pt-BR') : 'Não disponível'}
</p>
</div>
<div className="bg-black/30 p-4 rounded-xl border border-white/5 flex flex-col justify-center md:col-span-2">
<div className="flex items-center space-x-3 mb-1">
<Shield className="w-4 h-4 text-zinc-500" />
<p className="text-[10px] uppercase font-bold text-zinc-500">CPF</p>
</div>
<p className="text-sm font-medium text-white opacity-60 cursor-not-allowed pl-7">
{user.cpf ? user.cpf.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, "$1.$2.$3-$4") : 'Não informado'}
</p>
</div>
</div>
{user.role === 'admin' && (
<div className="mt-4 p-4 bg-[#E50914]/10 border border-[#E50914]/30 rounded-xl flex items-start space-x-3">
<Shield className="w-5 h-5 text-[#E50914] mt-0.5" />
<div>
<p className="text-sm font-bold text-[#E50914]">Acesso Administrativo</p>
<p className="text-xs text-zinc-300 mt-1">Você tem permissão total para gerenciar cursos, alunos e assinaturas.</p>
</div>
</div>
)}
</div>
</div>
</div>
</div>
);
}