Casas e serviços que guardou para consultar mais tarde.
c.id);
if(!conversationIds.length){
textEl.innerHTML = 'Não tem mensagens novas.Quando receber uma mensagem, ela aparecerá aqui.';
countEl.textContent = '0';
countEl.classList.add('zero');
return;
}
const messagesResult = await supabaseClient
.from('messages')
.select('id', { count: 'exact', head: true })
.in('conversation_id', conversationIds)
.neq('sender_id', userId)
.is('read_at', null);
if(messagesResult.error) throw messagesResult.error;
const unreadCount = messagesResult.count || 0;
if(unreadCount === 0){
textEl.innerHTML = 'Não tem mensagens novas.Todas as suas mensagens estão lidas.';
countEl.textContent = '0';
countEl.classList.add('zero');
}else{
textEl.innerHTML = `${unreadCount === 1 ? 'Tem 1 mensagem nova.' : `Tem ${unreadCount} mensagens novas.`}Abra as mensagens para ler e responder.`;
countEl.textContent = String(unreadCount);
countEl.classList.remove('zero');
}
}catch(error){
console.warn('Não foi possível verificar as mensagens novas:', error.message);
textEl.innerHTML = 'MensagensAbra a caixa de mensagens para consultar as suas conversas.';
countEl.textContent = '—';
countEl.classList.remove('zero');
}
}
async function loadMyHouses(userId){
if(!myHousesList || !userId) return;
myHousesList.innerHTML = 'A carregar…
';
const { data, error } = await supabaseClient
.from('houses')
.select('id, title, status, created_at, updated_at')
.eq('owner_id', userId)
.order('created_at', { ascending: false })
.limit(30);
if(error){
console.warn('Não foi possível carregar os anúncios:', error.message);
myHousesList.innerHTML = 'Não foi possível carregar os seus anúncios neste momento.
';
return;
}
if(!data?.length){
myHousesList.innerHTML = 'Ainda não tem anúncios de casas.
';
return;
}
myHousesList.innerHTML = '';
data.forEach(house => {
const card = document.createElement('article');
card.className = 'house-status-card account-listing-link';
const head = document.createElement('div');
head.className = 'house-status-head';
const title = document.createElement('div');
title.className = 'house-status-title';
title.textContent = house.title || 'Casa sem título';
const date = document.createElement('div');
date.className = 'house-status-date';
date.textContent = house.updated_at ? `Atualizado ${formatDate(house.updated_at)}` : formatDate(house.created_at);
head.append(title, date);
const badge = document.createElement('div');
const cls = house.status === 'pending_approval' ? 'pending' : house.status;
badge.className = `status-badge ${cls || ''}`;
badge.textContent = houseStatusLabel(house.status);
const actions = document.createElement('div');
actions.className = 'listing-card-actions';
const view = document.createElement('a');
view.className = 'listing-edit-btn';
view.href = `property.html?id=${encodeURIComponent(house.id)}`;
view.textContent = 'Ver anúncio';
view.addEventListener('click', event => event.stopPropagation());
const edit = document.createElement('a');
edit.className = 'listing-edit-btn';
edit.href = `edit-house.html?id=${encodeURIComponent(house.id)}`;
edit.textContent = 'Editar';
edit.addEventListener('click', event => event.stopPropagation());
const remove = document.createElement('button');
remove.type = 'button';
remove.className = 'listing-delete-btn';
remove.textContent = 'Apagar';
remove.addEventListener('click', async event => {
event.stopPropagation();
const confirmed = window.confirm(
`Tem a certeza de que pretende apagar a casa "${house.title || 'sem título'}"?\n\n` +
'Esta ação elimina o anúncio e os respetivos dados associados. Não é necessária aprovação do administrador.'
);
if(!confirmed) return;
remove.disabled = true;
remove.textContent = 'A apagar…';
const { data:photos } = await supabaseClient
.from('house_photos')
.select('storage_path')
.eq('house_id', house.id);
const { error:deleteError } = await supabaseClient
.from('houses')
.delete()
.eq('id', house.id)
.eq('owner_id', userId);
if(deleteError){
console.warn('Não foi possível apagar a casa:', deleteError.message);
alert(`Não foi possível apagar a casa.\n\n${deleteError.message}`);
remove.disabled = false;
remove.textContent = 'Apagar';
return;
}
const paths = (photos || []).map(p => p.storage_path).filter(Boolean);
if(paths.length){
const { error:storageError } = await supabaseClient
.storage
.from('house-photos')
.remove(paths);
if(storageError){
console.warn('A casa foi apagada, mas algumas fotografias podem ainda existir no Storage:', storageError.message);
}
}
await loadMyHouses(userId);
});
actions.append(view, edit, remove);
card.append(head, badge, actions);
myHousesList.appendChild(card);
});
}
function serviceStatusLabel(status){
return ({draft:'Rascunho',pending_approval:'A aguardar aprovação',published:'Publicado',hidden:'Oculto',blocked:'Bloqueado'})[status] || status || '—';
}
async function loadMyServices(userId){
if(!myServicesList || !userId) return;
myServicesList.innerHTML = 'A carregar…
';
const { data, error } = await supabaseClient
.from('service_profiles')
.select('id,display_name,status,created_at,updated_at,hourly_min,hourly_max,minimum_hours,provider_type')
.eq('provider_id', userId)
.order('created_at', { ascending: false })
.limit(30);
if(error){
console.warn('Não foi possível carregar os serviços:', error.message);
myServicesList.innerHTML = 'Não foi possível carregar os seus serviços neste momento.
';
return;
}
if(!data?.length){
myServicesList.innerHTML = 'Ainda não tem anúncios de serviços.
';
return;
}
myServicesList.innerHTML = '';
data.forEach(service => {
const card = document.createElement('article');
card.className = 'service-status-card';
const head = document.createElement('div');
head.className = 'service-status-head';
const title = document.createElement('div');
title.className = 'service-status-title';
title.textContent = service.display_name || 'Serviço sem nome';
const date = document.createElement('div');
date.className = 'service-status-date';
date.textContent = service.updated_at ? `Atualizado ${formatDate(service.updated_at)}` : formatDate(service.created_at);
head.append(title, date);
const meta = document.createElement('div');
meta.className = 'service-meta';
const providerType = service.provider_type === 'company' ? 'Empresa' : 'Particular / independente';
const price = service.hourly_min != null ? `€${service.hourly_min}–€${service.hourly_max}/hora` : 'Preço não definido';
const minimum = service.minimum_hours != null ? `Mínimo ${service.minimum_hours} h` : 'Mínimo não definido';
meta.textContent = `${providerType} · ${price} · ${minimum}`;
const badge = document.createElement('span');
const cls = service.status === 'pending_approval' ? 'pending' : service.status;
badge.className = `status-badge ${cls || ''}`;
badge.textContent = serviceStatusLabel(service.status);
const actions = document.createElement('div');
actions.className = 'listing-actions';
const view = document.createElement('a');
view.className = 'listing-btn listing-edit-btn';
view.href = `service.html?id=${encodeURIComponent(service.id)}`;
view.textContent = 'Ver anúncio';
const edit = document.createElement('a');
edit.className = 'listing-btn listing-edit-btn';
edit.href = `edit-service.html?id=${encodeURIComponent(service.id)}`;
edit.textContent = 'Editar';
const remove = document.createElement('button');
remove.type = 'button';
remove.className = 'listing-btn listing-delete-btn';
remove.textContent = 'Apagar';
remove.addEventListener('click', async () => {
const confirmed = window.confirm(
`Tem a certeza de que pretende apagar o serviço "${service.display_name || 'sem nome'}"?\n\n` +
'Esta ação elimina o anúncio e os respetivos dados associados. Não é necessária aprovação do administrador.'
);
if(!confirmed) return;
remove.disabled = true;
remove.textContent = 'A apagar…';
const { data:profile, error:profileError } = await supabaseClient
.from('service_profiles')
.select('id,profile_photo_url')
.eq('id', service.id)
.eq('provider_id', userId)
.maybeSingle();
if(profileError){
alert(`Não foi possível apagar o serviço.\n\n${profileError.message}`);
remove.disabled = false;
remove.textContent = 'Apagar';
return;
}
if(!profile){
alert('Não tem autorização para apagar este serviço.');
remove.disabled = false;
remove.textContent = 'Apagar';
return;
}
const { error:deleteError } = await supabaseClient
.from('service_profiles')
.delete()
.eq('id', service.id)
.eq('provider_id', userId);
if(deleteError){
console.warn('Não foi possível apagar o serviço:', deleteError.message);
alert(`Não foi possível apagar o serviço.\n\n${deleteError.message}`);
remove.disabled = false;
remove.textContent = 'Apagar';
return;
}
const url = profile.profile_photo_url || '';
if(url.includes('/service-profile-photos/')){
const path = decodeURIComponent(url.split('/service-profile-photos/')[1].split('?')[0]);
if(path.startsWith(userId + '/')){
const { error:storageError } = await supabaseClient.storage.from('service-profile-photos').remove([path]);
if(storageError) console.warn('O serviço foi apagado, mas a fotografia pode ainda existir no Storage:', storageError.message);
}
}
await loadMyServices(userId);
});
actions.append(view, edit, remove);
card.append(head, meta, badge, actions);
myServicesList.appendChild(card);
});
}
async function loadFavorites(userId){
if(!favoritesList || !userId) return;
favoritesList.innerHTML = 'A carregar…
';
const { data:favorites, error } = await supabaseClient
.from('favorites')
.select('id,house_id,service_profile_id,created_at')
.eq('user_id', userId)
.order('created_at', { ascending:false })
.limit(100);
if(error){
console.warn('Não foi possível carregar os favoritos:', error.message);
favoritesList.innerHTML = 'Não foi possível carregar os seus favoritos neste momento.
';
return;
}
if(!favorites?.length){
favoritesList.innerHTML = 'Ainda não tem favoritos. Pode guardar casas e serviços através do botão ❤️ nos respetivos anúncios.
';
return;
}
const houseIds = [...new Set(favorites.map(x=>x.house_id).filter(Boolean))];
const serviceIds = [...new Set(favorites.map(x=>x.service_profile_id).filter(Boolean))];
const [housesRes,servicesRes] = await Promise.all([
houseIds.length
? supabaseClient.from('houses').select('id,title,status').in('id',houseIds)
: Promise.resolve({data:[]}),
serviceIds.length
? supabaseClient.from('service_profiles').select('id,display_name,status,hourly_min,hourly_max').in('id',serviceIds)
: Promise.resolve({data:[]})
]);
const houses = Object.fromEntries((housesRes.data||[]).map(x=>[x.id,x]));
const services = Object.fromEntries((servicesRes.data||[]).map(x=>[x.id,x]));
favoritesList.innerHTML = '';
favorites.forEach(fav=>{
const isHouse = !!fav.house_id;
const item = isHouse ? houses[fav.house_id] : services[fav.service_profile_id];
const card = document.createElement('article');
card.className = 'favorites-card';
const head = document.createElement('div');
head.className = 'favorites-head';
const title = document.createElement('div');
title.className = 'favorites-title';
title.textContent = item
? (isHouse ? (item.title || 'Casa de férias') : (item.display_name || 'Serviço'))
: 'Anúncio indisponível';
const date = document.createElement('div');
date.className = 'favorites-date';
date.textContent = formatDate(fav.created_at);
head.append(title,date);
const type = document.createElement('span');
type.className = 'favorite-type';
type.textContent = isHouse ? 'Casa de férias' : 'Serviço';
const meta = document.createElement('div');
meta.className = 'favorites-meta';
if(item){
if(isHouse){
meta.textContent = `Estado: ${houseStatusLabel(item.status)}`;
}else{
const price = item.hourly_min != null
? `€${item.hourly_min}–€${item.hourly_max}/hora`
: 'Preço sob consulta';
meta.textContent = `Estado: ${serviceStatusLabel(item.status)} · ${price}`;
}
}else{
meta.textContent = 'Este anúncio já não está disponível.';
}
const actions = document.createElement('div');
actions.className = 'favorites-actions';
if(item){
const link = document.createElement('a');
link.className = 'button';
link.href = isHouse
? `property.html?id=${encodeURIComponent(fav.house_id)}`
: `service.html?id=${encodeURIComponent(fav.service_profile_id)}`;
link.textContent = 'Ver anúncio';
actions.appendChild(link);
}
const remove = document.createElement('button');
remove.type = 'button';
remove.className = 'favorite-remove';
remove.textContent = 'Remover dos favoritos';
remove.addEventListener('click', async ()=>{
remove.disabled = true;
const {error:deleteError} = await supabaseClient
.from('favorites')
.delete()
.eq('id',fav.id)
.eq('user_id',userId);
if(deleteError){
console.warn('Não foi possível remover o favorito:', deleteError.message);
remove.disabled = false;
return;
}
await loadFavorites(userId);
});
actions.appendChild(remove);
card.append(head,type,meta,actions);
favoritesList.appendChild(card);
});
}
function escapeHtml(value){
return String(value ?? '')
.replace(/&/g,'&')
.replace(//g,'>')
.replace(/"/g,'"')
.replace(/'/g,''');
}
function reviewStars(value){
const n = Math.max(0, Math.min(5, Math.round(Number(value) || 0)));
return '★'.repeat(n) + '☆'.repeat(5 - n);
}
function reviewTargetLabel(row, housesById, servicesById){
if(row.house_id){
const house = housesById.get(row.house_id);
return {type:'Casa', name:house?.title || 'Casa avaliada', href:house?.status === 'published' ? `property.html?id=${encodeURIComponent(row.house_id)}` : ''};
}
if(row.service_profile_id){
const service = servicesById.get(row.service_profile_id);
return {type:'Serviço', name:service?.display_name || 'Serviço avaliado', href:service?.status === 'published' ? `service.html?id=${encodeURIComponent(row.service_profile_id)}` : ''};
}
return {type:'Avaliação',name:'—',href:''};
}
async function loadMyReviews(userId){
if(!myReviewsList || !userId) return;
myReviewsList.innerHTML = 'A carregar…
';
const {data:reviews,error} = await supabaseClient
.from('reviews')
.select('id,house_id,service_profile_id,rating,comment,reviewer_display_name,status,created_at')
.eq('reviewer_id',userId)
.order('created_at',{ascending:false})
.limit(100);
if(error){
console.warn('Não foi possível carregar as suas avaliações:',error.message);
myReviewsList.innerHTML = 'Não foi possível carregar as suas avaliações neste momento.
';
return;
}
const rows = reviews || [];
if(!rows.length){
myReviewsList.innerHTML = 'Ainda não publicou avaliações.
';
return;
}
const houseIds=[...new Set(rows.map(r=>r.house_id).filter(Boolean))];
const serviceIds=[...new Set(rows.map(r=>r.service_profile_id).filter(Boolean))];
const [housesResult,servicesResult] = await Promise.all([
houseIds.length
? supabaseClient.from('houses').select('id,title,status').in('id',houseIds)
: Promise.resolve({data:[],error:null}),
serviceIds.length
? supabaseClient.from('service_profiles').select('id,display_name,status').in('id',serviceIds)
: Promise.resolve({data:[],error:null})
]);
if(housesResult.error) console.warn('Não foi possível carregar os nomes das casas das avaliações:',housesResult.error.message);
if(servicesResult.error) console.warn('Não foi possível carregar os nomes dos serviços das avaliações:',servicesResult.error.message);
const housesById=new Map((housesResult.data||[]).map(x=>[x.id,x]));
const servicesById=new Map((servicesResult.data||[]).map(x=>[x.id,x]));
myReviewsList.innerHTML='';
rows.forEach(row=>{
const target=reviewTargetLabel(row,housesById,servicesById);
const card=document.createElement('article');
card.className='my-review-card';
const head=document.createElement('div');
head.className='my-review-head';
const targetBox=document.createElement('div');
targetBox.innerHTML=`${escapeHtml(target.name)}
${escapeHtml(target.type)}`;
const date=document.createElement('div');
date.className='my-review-date';
date.textContent=formatDate(row.created_at);
head.append(targetBox,date);
const meta=document.createElement('div');
meta.className='my-review-meta';
meta.textContent=`${row.status==='published'?'Publicada':row.status==='hidden'?'Oculta':row.status==='removed'?'Removida':row.status||'—'} · ${row.reviewer_display_name==='initials'?'Iniciais':'Nome completo'}`;
const stars=document.createElement('div');
stars.className='my-review-stars';
stars.textContent=reviewStars(row.rating);
stars.setAttribute('aria-label',`${row.rating} de 5 estrelas`);
const comment=document.createElement('div');
comment.className='my-review-comment';
comment.textContent=row.comment || 'Sem comentário.';
card.append(head,meta,stars,comment);
if(target.href){
const link=document.createElement('a');
link.className='my-review-link';
link.href=target.href;
link.textContent=target.type==='Casa'?'Ver casa →':'Ver serviço →';
card.appendChild(link);
}
myReviewsList.appendChild(card);
});
}
function switchTab(tab){
clearMessage();
const loginActive = tab === 'login';
loginTab.classList.toggle('active', loginActive);
signupTab.classList.toggle('active', !loginActive);
loginForm.classList.toggle('active', loginActive);
signupForm.classList.toggle('active', !loginActive);
}
function displayNameFromUser(user){
const metadataName = user?.user_metadata?.full_name || user?.user_metadata?.name;
return metadataName || (user?.email ? user.email.split('@')[0] : 'utilizador');
}
async function syncProfile(user){
if(!user) return;
// Se o perfil já foi anonimizado, não o atualizar nem tentar recriá-lo.
const { data: existingProfile, error: profileCheckError } = await supabaseClient
.from('profiles')
.select('id,is_anonymized')
.eq('id', user.id)
.maybeSingle();
if(profileCheckError){
console.warn('Não foi possível verificar o perfil:', profileCheckError.message);
return;
}
if(existingProfile?.is_anonymized === true){
console.warn('Perfil anonimizado: sincronização interrompida.');
return;
}
const fullName = String(user.user_metadata?.full_name || user.user_metadata?.name || '').trim();
if(!fullName) return;
const { error } = await supabaseClient.from('profiles').upsert({
id: user.id,
full_name: fullName,
language: 'pt',
updated_at: new Date().toISOString()
}, { onConflict: 'id' });
if(error) console.warn('Não foi possível sincronizar o perfil:', error.message);
}
function showSignedIn(user){
signedOutView.style.display = 'none';
signedInView.style.display = 'block';
const displayName = displayNameFromUser(user);
userName.textContent = displayName;
userEmail.textContent = user?.email || '';
const profileNameSummary = document.getElementById('profileNameSummary');
const profileEmailSummary = document.getElementById('profileEmailSummary');
if(profileNameSummary) profileNameSummary.textContent = displayName;
if(profileEmailSummary) profileEmailSummary.textContent = user?.email || '';
updateAdminLink(user);
updateDeleteAccountButton(user);
loadMessageSummary(user?.id);
loadMyHouses(user?.id);
loadMyServices(user?.id);
loadFavorites(user?.id);
loadMyReviews(user?.id);
}
function showSignedOut(){
signedInView.style.display = 'none';
signedOutView.style.display = 'block';
logoutBtn.style.display = 'none';
if(adminSection) adminSection.style.display = 'none';
if(deleteAccountBtn) deleteAccountBtn.style.display = 'none';
if(deleteAccountNote) deleteAccountNote.style.display = 'none';
}
async function updateAdminLink(user){
if(!adminSection || !user) return;
adminSection.style.display = 'none';
const { data: profile, error } = await supabaseClient
.from('profiles')
.select('role')
.eq('id', user.id)
.maybeSingle();
if(!error && profile?.role === 'admin'){
adminSection.style.display = 'block';
const summary = document.getElementById('adminPendingSummary');
if(summary){
summary.textContent = 'A verificar pedidos novos para aprovação…';
try{
const [houseResult, serviceResult, reviewResult] = await Promise.all([
supabaseClient
.from('houses')
.select('id,status')
.eq('status','pending_approval'),
supabaseClient
.from('service_profiles')
.select('id,status')
.eq('status','pending_approval'),
supabaseClient
.from('reviews')
.select('id')
.is('admin_seen_at', null)
]);
if(houseResult.error) throw houseResult.error;
if(serviceResult.error) throw serviceResult.error;
if(reviewResult.error) throw reviewResult.error;
const houseCount = (houseResult.data || []).length;
const serviceCount = (serviceResult.data || []).length;
const reviewCount = (reviewResult.data || []).length;
const total = houseCount + serviceCount;
if(total === 0){
summary.textContent = 'Não há pedidos novos para aprovar.';
}else{
const houseText = houseCount === 1 ? '1 casa' : `${houseCount} casas`;
const serviceText = serviceCount === 1 ? '1 serviço' : `${serviceCount} serviços`;
const parts = [];
if(houseCount) parts.push(houseText);
if(serviceCount) parts.push(serviceText);
summary.textContent =
`Há ${total} pedido${total === 1 ? '' : 's'} novo${total === 1 ? '' : 's'} para aprovar: ${parts.join(' e ')}.`;
}
if(reviewCount === 1){
summary.textContent += `${summary.textContent.endsWith('.') ? '' : '.'} Há 1 nova avaliação para consultar.`;
}else if(reviewCount > 1){
summary.textContent += `${summary.textContent.endsWith('.') ? '' : '.'} Há ${reviewCount} novas avaliações para consultar.`;
}
}catch(error){
console.warn('Não foi possível calcular os pedidos de aprovação:',error.message);
summary.textContent = 'Não foi possível verificar os pedidos para aprovação.';
}
}
}
}
async function updateDeleteAccountButton(user){
if(!deleteAccountBtn || !user) return;
deleteAccountBtn.style.display = 'none';
if(deleteAccountNote) deleteAccountNote.style.display = 'none';
const { data: profile, error } = await supabaseClient
.from('profiles')
.select('role,is_anonymized')
.eq('id', user.id)
.maybeSingle();
if(error){
console.warn('Não foi possível verificar se a conta pode ser eliminada:', error.message);
return;
}
if(!profile || profile.role === 'admin' || profile.is_anonymized === true){
return;
}
deleteAccountBtn.style.display = 'inline-flex';
if(deleteAccountNote) deleteAccountNote.style.display = 'block';
}
function showDeleteAccountMessage(message, type='info'){
if(!deleteAccountMessage) return;
deleteAccountMessage.textContent = message;
deleteAccountMessage.className = `auth-message ${type}`;
deleteAccountMessage.style.display = 'block';
}
async function deleteOwnAccount(){
if(!deleteAccountBtn) return;
const firstConfirmation = window.confirm(
'Tem a certeza de que pretende eliminar a sua conta do Planeta Mazu?\n\n' +
'Os anúncios serão ocultados e as suas avaliações, favoritos e notificações serão eliminados. As mensagens antigas serão preservadas para a outra pessoa.'
);
if(!firstConfirmation) return;
const secondConfirmation = window.confirm(
'Esta ação é permanente e não pode ser anulada.\n\n' +
'Pretende mesmo continuar?'
);
if(!secondConfirmation) return;
deleteAccountBtn.disabled = true;
deleteAccountBtn.textContent = 'A eliminar a conta…';
if(deleteAccountMessage){
deleteAccountMessage.textContent = '';
deleteAccountMessage.className = 'auth-message';
deleteAccountMessage.style.display = 'none';
}
try{
const { data, error } = await supabaseClient.functions.invoke('delete-account', {
method: 'POST'
});
if(error) throw error;
if(!data || data.success !== true){
throw new Error(data?.error || 'O servidor não confirmou a eliminação da conta.');
}
showDeleteAccountMessage(
'A conta foi eliminada. A sessão será terminada.',
'success'
);
// A conta já foi eliminada no servidor; limpar a sessão local.
await supabaseClient.auth.signOut({ scope: 'local' });
window.setTimeout(() => {
window.location.href = 'index.html';
}, 1800);
}catch(error){
console.error('Erro ao eliminar a conta:', error);
showDeleteAccountMessage(
`Não foi possível confirmar a eliminação. ${error?.message || 'Tenta novamente mais tarde.'}`,
'error'
);
deleteAccountBtn.disabled = false;
deleteAccountBtn.textContent = 'Eliminar a minha conta';
}
}
if(deleteAccountBtn){
deleteAccountBtn.addEventListener('click', deleteOwnAccount);
}
loginTab.addEventListener('click', () => switchTab('login'));
signupTab.addEventListener('click', () => switchTab('signup'));
loginForm.addEventListener('submit', async (event) => {
event.preventDefault();
clearMessage();
const btn = document.getElementById('loginBtn');
btn.disabled = true;
btn.textContent = 'A entrar…';
try {
const email = document.getElementById('loginEmail').value.trim();
const password = document.getElementById('loginPassword').value;
const { data, error } = await supabaseClient.auth.signInWithPassword({ email, password });
if(error) throw error;
await syncProfile(data.user);
showMessage('Login efetuado com sucesso.', 'success');
showSignedIn(data.user);
} catch(error){
console.error(error);
showMessage(error.message || 'Não foi possível iniciar sessão.', 'error');
} finally {
btn.disabled = false;
btn.textContent = 'Entrar';
}
});
signupForm.addEventListener('submit', async (event) => {
event.preventDefault();
clearMessage();
const btn = document.getElementById('signupBtn');
const name = document.getElementById('signupName').value.trim();
const email = document.getElementById('signupEmail').value.trim();
const password = document.getElementById('signupPassword').value;
const password2 = document.getElementById('signupPassword2').value;
if(password !== password2){
showMessage('As palavras-passe não coincidem.', 'error');
return;
}
btn.disabled = true;
btn.textContent = 'A criar conta…';
try {
const redirectTo = `${window.location.origin}/account.html`;
const { data, error } = await supabaseClient.auth.signUp({
email,
password,
options: {
data: { full_name: name },
emailRedirectTo: redirectTo
}
});
if(error) throw error;
if(data.session && data.user){
await syncProfile(data.user);
showSignedIn(data.user);
showMessage('Conta criada com sucesso.', 'success');
} else {
switchTab('login');
showMessage(`Conta criada com sucesso. Enviámos um email para ${email} para validar a sua conta e finalizar o registo. Abra o email e clique em “Confirmar email”. Se não o encontrar, verifique também a pasta de spam.`, 'success');
}
} catch(error){
console.error(error);
showMessage(error.message || 'Não foi possível criar a conta.', 'error');
} finally {
btn.disabled = false;
btn.textContent = 'Criar conta';
}
});
document.getElementById('forgotLink').addEventListener('click', async (event) => {
event.preventDefault();
clearMessage();
const email = document.getElementById('loginEmail').value.trim();
if(!email){
showMessage('Introduza primeiro o seu email.', 'error');
return;
}
try {
const redirectTo = `${window.location.origin}/account.html`;
const { error } = await supabaseClient.auth.resetPasswordForEmail(email, { redirectTo });
if(error) throw error;
showMessage('Se existir uma conta associada a este email, será enviado um link para redefinir a palavra-passe.', 'success');
} catch(error){
console.error(error);
showMessage(error.message || 'Não foi possível iniciar a recuperação da palavra-passe.', 'error');
}
});
logoutBtn.addEventListener('click', async () => {
logoutBtn.disabled = true;
try {
const { error } = await supabaseClient.auth.signOut({ scope: 'local' });
if(error) throw error;
showSignedOut();
switchTab('login');
showMessage('Sessão terminada.', 'success');
} catch(error){
console.error(error);
showMessage(error.message || 'Não foi possível terminar a sessão.', 'error');
} finally {
logoutBtn.disabled = false;
}
});
supabaseClient.auth.onAuthStateChange(async (_event, session) => {
if(session?.user){
await syncProfile(session.user);
showSignedIn(session.user);
} else {
showSignedOut();
}
});
(async () => {
const { data } = await supabaseClient.auth.getSession();
if(data?.session?.user){
await syncProfile(data.session.user);
showSignedIn(data.session.user);
} else {
showSignedOut();
}
})();