303 lines
13 KiB
TypeScript
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>
|
|
);
|
|
}
|