Olá\! Vamos analisar esses novos erros. A boa notícia é que estamos chegando perto da solução. Os erros atuais são muito específicos e nos dão um caminho claro para a correção.
Basicamente, temos dois problemas principais acontecendo:
1. **Erro de Configuração no Google Cloud:** O erro `The given origin is not allowed for the given client ID` persiste. Isso é 100% um problema de configuração na sua conta Google e é o bloqueio principal do login.
2. **Erro de Código no `index.html`:** O erro `Uncaught ReferenceError: jwt_decode is not defined` indica que a biblioteca para decodificar o token do Google não está sendo carregada corretamente na sua página de login.
Vamos resolver isso e, em seguida, organizar a estrutura do seu código para que fique mais limpa e fácil de manter.
-----
### Análise e Diagnóstico Detalhado
#### 1\. ERRO CRÍTICO: `The given origin is not allowed for the given client ID` (Erro 403)
Como mencionei antes, este erro não pode ser corrigido com código. Ele significa que o Google está rejeitando a autenticação porque o endereço de onde ela parte (`http://127.0.0.1:xxxx` ou `http://localhost:xxxx`) não está na "lista de permissões" do seu projeto.
**Ação Obrigatória (Verifique Novamente):**
1. Abra sua aplicação no navegador e copie **exatamente** o endereço que aparece na barra de URL. Por exemplo: `http://127.0.0.1:5500`.
2. Vá para o [Google Cloud Console](https://console.cloud.google.com/).
3. Navegue até **APIs e Serviços \> Credenciais**.
4. Clique no nome do seu **ID do Cliente OAuth 2.0**.
5. Na seção **"Origens JavaScript autorizadas"**, confirme que o endereço que você copiou no passo 1 está na lista. Se não estiver, clique em **"ADICIONAR URI"** e adicione-o.
6. Clique em **"Salvar"**. **Aguarde 2 a 5 minutos** para que a alteração seja propagada pelos servidores do Google.
#### 2\. ERRO CRÍTICO: `Uncaught ReferenceError: jwt_decode is not defined`
Este erro acontece no seu arquivo `index.html` (a página de login). A função `handleCredentialResponse` é chamada, mas quando ela tenta executar `jwt_decode(...)`, o navegador diz "eu não conheço essa função".
* **Causa:** A biblioteca `jwt-decode` não foi carregada antes da função ser chamada. Isso acontece porque a tag `<script>` que importa essa biblioteca está faltando no seu arquivo `index.html`.
-----
### Solução Corrigida e Código Refatorado
Para resolver o erro de código e também melhorar a organização, vamos seguir a arquitetura de duas páginas que discutimos, mas com o código corrigido e uma separação mais limpa entre HTML e JavaScript.
#### Passo 1: Corrija o arquivo `index.html` (Página de Login)
Substitua o conteúdo do seu `index.html` por este. Ele agora inclui a tag `<script>` que estava faltando para a biblioteca `jwt-decode`.
```html
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Dashboard de Produtividade (D2P)</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f2f5;
text-align: center;
}
.container {
padding: 40px;
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
h1 { color: #333; }
p { color: #555; }
</style>
<script src="https://unpkg.com/jwt-decode/build/jwt-decode.js"></script>
</head>
<body>
<div class="container">
<h1>Dashboard de Produtividade Pessoal (D2P)</h1>
<p>Faça o login com sua conta Google para continuar.</p>
<div id="g_id_onload"
data-client_id="756336753138-q6bta7q070rrfmdqp92tqk0jdd0ilpct.apps.googleusercontent.com"
data-callback="handleCredentialResponse"
data-auto_prompt="false">
</div>
<div class="g_id_signin"
data-type="standard"
data-size="large"
data-theme="outline"
data-text="sign_in_with"
data-shape="rectangular"
data-logo_alignment="left">
</div>
</div>
<script src="https://accounts.google.com/gsi/client" async defer></script>
<script>
function handleCredentialResponse(response) {
// Agora a função jwt_decode será encontrada!
const responsePayload = jwt_decode(response.credential);
console.log("Login bem-sucedido. Redirecionando...");
localStorage.setItem('usuarioNome', responsePayload.name);
localStorage.setItem('usuarioEmail', responsePayload.email);
window.location.href = 'd2p.html';
}
</script>
</body>
</html>
```
#### Passo 2: Limpe o arquivo `d2p.html` (Estrutura do Dashboard)
Vamos transformar seu `d2p.html` em um arquivo "casca", contendo apenas o HTML. Toda a lógica de JavaScript será movida para o arquivo `dashboard.js`. Isso torna o código muito mais organizado.
Substitua o conteúdo do seu `d2p.html` por este:
```html
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard de Produtividade Pessoal</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://accounts.google.com/gsi/client" async defer></script>
<style>
body { font-family: 'Inter', sans-serif; background-color: #f0f2f5; }
.btn { transition: all 0.2s ease-in-out; }
.btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.task-active { box-shadow: 0 0 0 3px #3b82f6; transform: scale(1.02); }
</style>
</head>
<body class="bg-gray-100 text-gray-800 p-4 md:p-8">
<div class="max-w-7xl mx-auto">
<header class="mb-8 flex justify-between items-center">
<div>
<h1 class="text-3xl md:text-4xl font-bold text-gray-900">Dashboard de Produtividade</h1>
<p id="welcome-message" class="text-gray-600 mt-1">Carregando dados do usuário...</p>
</div>
<button id="logoutButton" class="btn bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded-lg">Sair</button>
</header>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div class="bg-white p-6 rounded-xl shadow-md">
<h2 class="text-xl font-semibold mb-4">Jornada do Dia</h2>
<div class="flex items-center justify-between mb-4">
<button id="btnStartWorkday" class="btn bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded-lg w-full mr-2">Iniciar Jornada</button>
<button id="btnEndWorkday" class="btn bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded-lg w-full ml-2" disabled>Finalizar Jornada</button>
</div>
<div class="text-sm text-gray-500">
<p>Início: <span id="workdayStartTime" class="font-medium">--:--:--</span></p>
<p>Duração Total: <span id="workdayDuration" class="font-medium">0h 0m 0s</span></p>
</div>
</div>
<div class="bg-white p-6 rounded-xl shadow-md text-center">
<h2 class="text-xl font-semibold mb-2">Produtividade</h2>
<p class="text-4xl font-bold text-blue-600"><span id="productivityRate">0.00</span></p>
<p class="text-gray-500">Pontos / Hora</p>
</div>
<div class="bg-white p-6 rounded-xl shadow-md text-center">
<h2 class="text-xl font-semibold mb-2">Pontos Concluídos</h2>
<p class="text-4xl font-bold text-green-600"><span id="totalPointsCompleted">0</span></p>
<p class="text-gray-500">Total de pontos de tarefas finalizadas</p>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-8">
<div class="lg:col-span-2 bg-white p-6 rounded-xl shadow-md">
<h2 class="text-xl font-semibold mb-4">Plano do Dia</h2>
<form id="formAddTask" class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6 items-end">
<div class="md:col-span-2">
<label for="taskName" class="block text-sm font-medium text-gray-700">Nome da Tarefa</label>
<input type="text" id="taskName" required class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label for="taskCode" class="block text-sm font-medium text-gray-700">Código</label>
<input type="text" id="taskCode" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label for="taskPoints" class="block text-sm font-medium text-gray-700">Pontos</label>
<input type="number" id="taskPoints" required min="1" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500">
</div>
<button type="submit" class="btn md:col-start-4 bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-lg">Adicionar Tarefa</button>
</form>
<div id="taskList" class="space-y-3">
<p class="text-gray-500 text-center py-4">Nenhuma tarefa adicionada ainda.</p>
</div>
</div>
<div class="space-y-8">
<div class="bg-white p-6 rounded-xl shadow-md">
<h2 class="text-xl font-semibold mb-4">Tarefa em Execução</h2>
<div id="currentTaskDisplay" class="bg-gray-50 p-4 rounded-lg mb-4 text-center">
<h3 id="currentTaskName" class="text-lg font-medium text-gray-800">Nenhuma tarefa iniciada</h3>
<p id="currentTaskTimer" class="text-5xl font-bold tracking-tight my-2">00:00:00</p>
<p class="text-sm text-gray-500">Duração Efetiva</p>
</div>
<div class="grid grid-cols-2 gap-4">
<button id="btnPauseResumeTask" class="btn bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 rounded-lg" disabled>Pausar</button>
<button id="btnStopTask" class="finalizar-btn bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded-lg" disabled>Finalizar</button>
</div>
</div>
</div>
</div>
</div>
<script src="dashboard.js" defer></script>
</body>
</html>
```
#### Passo 3: Crie o arquivo `dashboard.js` (Lógica do Dashboard)
Agora, crie um arquivo chamado `dashboard.js` e coloque **toda a lógica** nele. Este código unifica tudo: a verificação de login, a função de logout e toda a lógica do seu dashboard que estava antes no `d2p.html`.
```javascript
document.addEventListener('DOMContentLoaded', function() {
// ---- VERIFICAÇÃO DE LOGIN E AUTENTICAÇÃO ----
const nomeUsuario = localStorage.getItem('usuarioNome');
const emailUsuario = localStorage.getItem('usuarioEmail');
if (!nomeUsuario || !emailUsuario) {
alert("Acesso negado. Por favor, faça o login.");
window.location.href = 'index.html';
return;
}
// Popula a mensagem de boas-vindas
document.getElementById('welcome-message').textContent = `Bem-vindo(a), ${nomeUsuario}!`;
// Configura o botão de logout
document.getElementById('logoutButton').addEventListener('click', () => {
if (typeof google !== 'undefined') {
google.accounts.id.disableAutoSelect();
}
localStorage.clear();
window.location.href = 'index.html';
});
// ---- A PARTIR DAQUI, COMEÇA A LÓGICA DO DASHBOARD ----
// --- ELEMENTOS DO DOM ---
const formAddTask = document.getElementById('formAddTask');
const taskListEl = document.getElementById('taskList');
const btnStartWorkday = document.getElementById('btnStartWorkday');
const btnEndWorkday = document.getElementById('btnEndWorkday');
const workdayStartTimeEl = document.getElementById('workdayStartTime');
const workdayDurationEl = document.getElementById('workdayDuration');
const productivityRateEl = document.getElementById('productivityRate');
const totalPointsCompletedEl = document.getElementById('totalPointsCompleted');
const currentTaskNameEl = document.getElementById('currentTaskName');
const currentTaskTimerEl = document.getElementById('currentTaskTimer');
const btnPauseResumeTask = document.getElementById('btnPauseResumeTask');
const btnStopTask = document.getElementById('btnStopTask');
// --- ESTADO DA APLICAÇÃO ---
let state = {
tasks: [],
workday: {
startTime: null,
endTime: null,
durationTimer: null,
totalSeconds: 0,
totalEffectiveSeconds: 0,
},
currentTask: {
id: null,
timer: null,
isPaused: false,
effectiveSeconds: 0,
},
nextTaskId: 1,
};
// --- FUNÇÕES DE UTILIDADE ---
const formatTime = (totalSeconds) => {
if (isNaN(totalSeconds) || totalSeconds < 0) return "00:00:00";
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = Math.floor(totalSeconds % 60);
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
};
const formatTimeHours = (totalSeconds) => {
if (isNaN(totalSeconds) || totalSeconds <= 0) return 0;
return totalSeconds / 3600;
};
// --- RENDERIZAÇÃO ---
const renderTasks = () => {
if (state.tasks.length === 0) {
taskListEl.innerHTML = '<p class="text-gray-500 text-center py-4">Nenhuma tarefa adicionada ainda.</p>';
return;
}
taskListEl.innerHTML = '';
state.tasks.forEach(task => {
const taskEl = document.createElement('div');
taskEl.className = `p-4 border rounded-lg flex items-center justify-between transition-all duration-300 ${task.status === 'completed' ? 'bg-green-50 border-green-200' : 'bg-white'} ${state.currentTask.id === task.id ? 'task-active' : ''}`;
taskEl.id = `task-${task.id}`;
let statusIndicator = '';
if (task.status === 'completed') {
statusIndicator = '<span class="text-xs font-medium bg-green-100 text-green-800 py-1 px-2 rounded-full">Concluída</span>';
} else if (task.status === 'in-progress') {
statusIndicator = '<span class="text-xs font-medium bg-blue-100 text-blue-800 py-1 px-2 rounded-full">Em Progresso</span>';
}
taskEl.innerHTML = `
<div>
<p class="font-semibold">${task.name} <span class="text-sm font-normal text-gray-500">(${task.code || 'N/C'})</span></p>
<p class="text-sm text-gray-600">Pontos: ${task.points} | Duração Efetiva: ${formatTime(task.effectiveDuration)}</p>
</div>
<div class="flex items-center space-x-2">
${statusIndicator}
<button data-task-id="${task.id}" class="btn-start-task bg-blue-500 hover:bg-blue-600 text-white p-2 rounded-full disabled:bg-gray-300 disabled:cursor-not-allowed" ${task.status !== 'pending' || !state.workday.startTime || state.currentTask.id ? 'disabled' : ''} title="Iniciar Tarefa">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
</button>
</div>
`;
taskListEl.appendChild(taskEl);
});
document.querySelectorAll('.btn-start-task').forEach(btn => {
btn.addEventListener('click', () => handleStartTask(parseInt(btn.dataset.taskId)));
});
};
const updateUI = () => {
renderTasks();
btnStartWorkday.disabled = !!state.workday.startTime;
btnEndWorkday.disabled = !state.workday.startTime || !!state.workday.endTime || !!state.currentTask.id;
const hasActiveTask = !!state.currentTask.id;
btnPauseResumeTask.disabled = !hasActiveTask;
btnStopTask.disabled = !hasActiveTask;
btnPauseResumeTask.textContent = state.currentTask.isPaused ? 'Retomar' : 'Pausar';
btnPauseResumeTask.className = `btn font-bold py-2 px-4 rounded-lg ${state.currentTask.isPaused ? 'bg-green-500 hover:bg-green-600' : 'bg-yellow-500 hover:bg-yellow-600'} text-white`;
const totalCompletedPoints = state.tasks.filter(t => t.status === 'completed').reduce((sum, t) => sum + t.points, 0);
totalPointsCompletedEl.textContent = totalCompletedPoints;
const hoursWorked = formatTimeHours(state.workday.totalEffectiveSeconds);
const productivity = hoursWorked > 0 ? (totalCompletedPoints / hoursWorked).toFixed(2) : '0.00';
productivityRateEl.textContent = productivity;
};
// --- LÓGICA DE NEGÓCIO ---
const handleStartWorkday = () => {
state.workday.startTime = new Date();
workdayStartTimeEl.textContent = state.workday.startTime.toLocaleTimeString();
state.workday.durationTimer = setInterval(() => {
state.workday.totalSeconds = Math.floor((new Date() - state.workday.startTime) / 1000);
workdayDurationEl.textContent = formatTime(state.workday.totalSeconds);
if (!state.currentTask.isPaused && state.currentTask.id) {
state.workday.totalEffectiveSeconds++;
updateUI();
}
}, 1000);
updateUI();
};
const handleAddTask = (e) => {
e.preventDefault();
const taskName = document.getElementById('taskName').value;
const taskCode = document.getElementById('taskCode').value;
const taskPoints = parseInt(document.getElementById('taskPoints').value);
if (taskName && taskPoints) {
const newTask = {
id: state.nextTaskId++,
name: taskName,
code: taskCode,
points: taskPoints,
status: 'pending',
effectiveDuration: 0,
};
state.tasks.push(newTask);
formAddTask.reset();
updateUI();
}
};
const handleStartTask = (taskId) => {
if (state.currentTask.id) {
alert('Já existe uma tarefa em andamento. Finalize-a primeiro.');
return;
}
const task = state.tasks.find(t => t.id === taskId);
if (task) {
task.status = 'in-progress';
state.currentTask.id = taskId;
state.currentTask.effectiveSeconds = task.effectiveDuration;
state.currentTask.isPaused = false;
currentTaskNameEl.textContent = task.name;
currentTaskTimerEl.textContent = formatTime(state.currentTask.effectiveSeconds);
state.currentTask.timer = setInterval(() => {
if (!state.currentTask.isPaused) {
state.currentTask.effectiveSeconds++;
currentTaskTimerEl.textContent = formatTime(state.currentTask.effectiveSeconds);
}
}, 1000);
updateUI();
}
};
const handlePauseResumeTask = () => {
state.currentTask.isPaused = !state.currentTask.isPaused;
updateUI();
};
const handleStopTask = () => {
clearInterval(state.currentTask.timer);
const task = state.tasks.find(t => t.id === state.currentTask.id);
if(task) {
task.status = 'completed';
task.effectiveDuration = state.currentTask.effectiveSeconds;
}
state.currentTask = { id: null, timer: null, isPaused: false, effectiveSeconds: 0 };
currentTaskNameEl.textContent = 'Nenhuma tarefa iniciada';
currentTaskTimerEl.textContent = '00:00:00';
updateUI();
};
// --- INICIALIZAÇÃO ---
const init = () => {
formAddTask.addEventListener('submit', handleAddTask);
btnStartWorkday.addEventListener('click', handleStartWorkday);
btnPauseResumeTask.addEventListener('click', handlePauseResumeTask);
btnStopTask.addEventListener('click', handleStopTask);
updateUI();
};
init();
});
```
### Resumo das Ações Finais
1. **Corrija a Configuração do Google Cloud:** Este é o passo mais importante. Certifique-se de que a origem (`http://127.0.0.1:xxxx`) está autorizada.
2. **Substitua `index.html`:** Use o novo código fornecido, que agora inclui a biblioteca `jwt-decode`.
3. **Substitua `d2p.html`:** Use a nova versão "limpa" que apenas estrutura o HTML e chama o `dashboard.js`.
4. **Crie/Substitua `dashboard.js`:** Use o código completo que unifica toda a lógica do seu aplicativo.
5. **Estrutura de Arquivos:** Garanta que `index.html`, `d2p.html`, e `dashboard.js` estejam todos na mesma pasta no seu projeto.
Depois de fazer essas alterações, o fluxo de login deve funcionar sem erros e a sua aplicação ficará muito mais organizada.
Nenhum comentário:
Postar um comentário