hackatime/app/views/users/wakatime_setup.html.erb
Mahad Kalam 146ea37822
Add new setup commands (#902)
Co-authored-by: Echo <github@3kh0.net>
2026-02-04 11:29:23 -05:00

355 lines
16 KiB
Text

<div class="min-h-screen text-white">
<div class="max-w-6xl mx-auto p-4">
<div class="text-center mb-4">
<h1 class="text-4xl font-bold text-primary mb-2">Hackatime Setup</h1>
<div class="flex items-center justify-center gap-2 mb-4">
<div class="w-8 h-8 bg-primary rounded-full flex items-center justify-center text-sm font-bold">1</div>
<div class="w-16 h-1 bg-darkless"></div>
<div class="w-8 h-8 bg-darkless rounded-full flex items-center justify-center text-sm">2</div>
<div class="w-16 h-1 bg-darkless"></div>
<div class="w-8 h-8 bg-darkless rounded-full flex items-center justify-center text-sm">3</div>
<div class="w-16 h-1 bg-darkless"></div>
<div class="w-8 h-8 bg-darkless rounded-full flex items-center justify-center text-sm">4</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="lg:col-span-2">
<div class="setup-instructions">
<div id="mac-linux" class="bg-dark rounded-lg p-6" style="display: none;">
<h3 class="text-2xl font-bold text-green mb-4">🍎 Mac/Linux/Codespaces</h3>
<details class="mb-4 group">
<summary class="cursor-pointer text-blue hover:text-cyan flex items-center gap-2 border border-blue/30 rounded-lg p-3 bg-blue/10">
<svg class="w-4 h-4 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
<span class="font-semibold">On GitHub Codespaces? Click here first!</span>
</summary>
<div class="mt-3 pl-6 border-l-2 border-blue/30">
<p class="text-md mb-2">Here's how to find your terminal:</p>
<ol class="list-decimal list-inside text-md space-y-1">
<li>Look at the bottom of your Codespaces window for a panel</li>
<li>Click the <strong>"Terminal"</strong> tab (it's usually already open!)</li>
<li>If you don't see it, press <kbd class="bg-darkless px-1 rounded text-sm">Ctrl+`</kbd> (or <kbd class="bg-darkless px-1 rounded text-sm">Cmd+`</kbd> on Mac)</li>
</ol>
</div>
</details>
<div class="space-y-4 mb-4">
<ol class="list-decimal list-inside text-lg space-y-3">
<li>
<strong>Find your terminal:</strong>
<ul class="list-disc list-inside ml-4 mt-1 text-md space-y-1">
<li><strong>Mac:</strong> Open the "Terminal" app (search in Spotlight)</li>
<li><strong>Linux:</strong> Open your terminal emulator</li>
</ul>
</li>
<li><strong>Copy the command below</strong> (click the Copy button!)</li>
<li><strong>Paste it in your terminal</strong> and press <i>Enter</i></li>
</ol>
</div>
<div class="code-block bg-darkless rounded-lg p-4 mb-4">
<code class="text-cyan text-sm">curl -fsSL https://hack.club/setup/install.sh | bash -s -- <%= @current_user_api_key %></code>
<button class="copy-button bg-primary hover:bg-primary/75 border-0 text-white px-6 py-2 rounded transition-colors cursor-pointer font-semibold" onclick="copy(this)">Copy</button>
</div>
<details class="mb-4 group">
<summary class="cursor-pointer text-secondary hover:text-white flex items-center gap-2">
<svg class="w-4 h-4 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
Watch a video tutorial
</summary>
<div class="mt-3 rounded-lg">
<iframe width="100%" height="250" src="https://www.youtube.com/embed/QTwhJy7nT_w?loop=1&playlist=QTwhJy7nT_w&modestbranding=1&rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen class="rounded"></iframe>
</div>
</details>
<div class="flex gap-3 flex-wrap">
<button class="bg-blue hover:bg-blue/75 text-white border-0 px-4 py-2 rounded transition-colors cursor-pointer text-sm" onclick="toggleSection('windows')">Using Windows?</button>
<button class="bg-purple hover:bg-purple/75 text-white border-0 px-4 py-2 rounded transition-colors cursor-pointer text-sm" onclick="toggleSection('advanced')">Custom Setup</button>
</div>
</div>
<div id="windows" class="bg-dark rounded-lg p-6" style="display: none;">
<h3 class="text-2xl font-bold text-blue mb-4">🪟 Windows</h3>
<div class="space-y-4 mb-4">
<ol class="list-decimal list-inside text-lg space-y-3">
<li><strong>Open PowerShell:</strong> Press <kbd class="bg-darkless px-2 py-1 rounded text-md">Windows + R</kbd>, type "powershell", press Enter</li>
<li><strong>Copy the command below</strong> (click the Copy button!)</li>
<li><strong>Paste it</strong> (right-click in PowerShell, or <kbd class="bg-darkless px-1 rounded text-md">Ctrl+V</kbd>)</li>
</ol>
</div>
<div class="code-block bg-darkless rounded-lg p-4 mb-4">
<code class="text-cyan text-sm">& ([scriptblock]::Create((irm https://hack.club/setup/install.ps1))) -ApiKey <%= @current_user_api_key %></code>
<button class="copy-button bg-primary hover:bg-primary/75 border-0 text-white px-6 py-2 rounded transition-colors cursor-pointer font-semibold" onclick="copy(this)">Copy</button>
</div>
<details class="mb-4 group">
<summary class="cursor-pointer text-secondary hover:text-white flex items-center gap-2">
<svg class="w-4 h-4 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
Watch a video tutorial
</summary>
<div class="mt-3 rounded-lg">
<iframe width="100%" height="250" src="https://www.youtube.com/embed/fX9tsiRvzhg?loop=1&playlist=fX9tsiRvzhg&modestbranding=1&rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen class="rounded"></iframe>
</div>
</details>
<div class="flex gap-3 flex-wrap">
<button class="bg-green hover:bg-green/75 text-white border-0 px-4 py-2 rounded transition-colors cursor-pointer text-sm" onclick="toggleSection('mac-linux')">Using Mac/Linux?</button>
<button class="bg-purple hover:bg-purple/75 text-white border-0 px-4 py-2 rounded transition-colors cursor-pointer text-sm" onclick="toggleSection('advanced')">Custom Setup</button>
</div>
</div>
<div id="advanced" class="bg-dark rounded-lg p-6" style="display: none;">
<h3 class="text-2xl font-bold text-purple mb-4">⚙️ Custom Setup</h3>
<div class="bg-purple/10 border border-purple/30 rounded-lg p-4 mb-4">
<p class="text-sm">For advanced users who want to manually configure their setup.</p>
</div>
<p class="text-lg mb-4">Create or edit <span class="bg-darkless mx-2 px-2 py-1 rounded text-cyan text-md">~/.wakatime.cfg</span> with:</p>
<div class="code-block bg-darkless rounded-lg p-4 mb-4">
<code class="text-cyan text-sm">[settings]&#10;api_url = <%= api_hackatime_v1_url %>&#10;api_key = <%= @current_user_api_key %>&#10;heartbeat_rate_limit_seconds = 30</code>
<button class="copy-button bg-primary hover:bg-primary/75 border-0 text-white px-6 py-2 rounded transition-colors cursor-pointer font-semibold" onclick="copy(this)">Copy</button>
</div>
<div class="flex gap-3 flex-wrap">
<button class="bg-green hover:bg-green/75 text-white border-0 px-4 py-2 rounded transition-colors cursor-pointer text-sm" onclick="toggleSection('mac-linux')">Using Mac/Linux?</button>
<button class="bg-blue hover:bg-blue/75 text-white border-0 px-4 py-2 rounded transition-colors cursor-pointer text-sm" onclick="toggleSection('windows')">Using Windows?</button>
</div>
</div>
</div>
</div>
<div class="lg:col-span-1">
<div class="lg:sticky lg:top-4">
<section id="status-panel" class="bg-dark rounded-lg p-6 border-2 border-darkless">
<div id="waiting-state">
<div class="text-center mb-4">
<div class="w-16 h-16 mx-auto mb-3 rounded-full bg-primary/20 flex items-center justify-center">
<svg class="w-8 h-8 text-primary animate-pulse" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
<h4 class="text-xl font-bold text-white mb-2">Waiting for you to run the setup command...</h4>
<p class="text-secondary text-sm" id="status-message">Copy the command on the left and run it in your terminal!</p>
</div>
<div class="bg-darkless rounded-lg p-3">
<div class="flex items-center justify-center gap-2 text-secondary">
<div class="progress-indicator"></div>
<span class="text-sm" id="poll-status">Listening for heartbeats...</span>
</div>
</div>
</div>
<div id="success-state" class="hidden">
<div class="text-center mb-4">
<div class="w-16 h-16 mx-auto mb-3 rounded-full bg-green/20 flex items-center justify-center">
<svg class="w-8 h-8 text-green" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
</div>
<h4 class="text-xl font-bold text-green mb-2">Setup complete <span id="heartbeat-time-ago"></span>!</h4>
<p class="text-secondary text-sm">Hackatime is configured and ready to go.</p>
</div>
<%= link_to my_wakatime_setup_step_2_path, class: "block w-full bg-primary hover:bg-primary/75 text-white text-center px-6 py-3 rounded-lg font-semibold transition-colors" do %>
Continue →
<% end %>
</div>
</section>
<p class="text-center text-secondary text-xs mt-3">Already configured? <a href="<%= my_wakatime_setup_step_2_path %>" class="text-cyan hover:underline">Skip to next step</a></p>
</div>
</div>
</div>
</div>
</div>
<script>
function a() {
const ua = window.navigator.userAgent;
const mac = document.getElementById("mac-linux");
const windows = document.getElementById("windows");
mac.style.display = "none";
windows.style.display = "none";
if (ua.indexOf("Windows") !== -1) {
windows.style.display = "block";
} else {
mac.style.display = "block";
}
}
document.addEventListener("turbo:load", function () {
a();
window.toggleSection = function (section) {
const mac = document.getElementById("mac-linux");
const windows = document.getElementById("windows");
const advanced = document.getElementById("advanced");
mac.style.display = "none";
windows.style.display = "none";
advanced.style.display = "none";
if (section === "windows") {
windows.style.display = "block";
} else if (section === "advanced") {
advanced.style.display = "block";
} else {
mac.style.display = "block";
}
};
const waitingState = document.getElementById("waiting-state");
const successState = document.getElementById("success-state");
const statusMessage = document.getElementById("status-message");
const pollStatus = document.getElementById("poll-status");
const statusPanel = document.getElementById("status-panel");
let checkCount = 0;
const maxChecks = 120;
const msg = ["Copy the command on the left and run it in your terminal!", "Paste the command and press Enter...", "The script will configure everything automatically!", "Almost there - just run the command!", "We'll detect it as soon as the script runs!"];
function showSuccess(timeAgo) {
waitingState.classList.add("hidden");
successState.classList.remove("hidden");
statusPanel.classList.remove("border-darkless");
statusPanel.classList.add("border-green");
document.getElementById("heartbeat-time-ago").textContent = timeAgo;
}
function check() {
fetch(<%== api_v1_my_heartbeats_most_recent_path(source_type: "test_entry").to_json %>, {
headers: {
Authorization: "Bearer " + <%== @current_user_api_key.to_json %>,
},
})
.then((response) => response.json())
.then((data) => {
if (data.has_heartbeat) {
const heartbeatTime = new Date(data.heartbeat.created_at);
const now = new Date();
const secondsAgo = (now - heartbeatTime) / 1000;
const recentThreshold = 300;
if (secondsAgo <= recentThreshold) {
showSuccess(data.time_ago);
return;
}
}
throw new Error("No heartbeats yet");
})
.catch((error) => {
checkCount++;
if (checkCount % 3 === 0) {
const msgIndex = Math.floor(checkCount / 3) % msg.length;
statusMessage.textContent = msg[msgIndex];
}
pollStatus.textContent = `Checked ${checkCount} time${checkCount === 1 ? "" : "s"}...`;
if (checkCount >= maxChecks) {
pollStatus.textContent = "Still waiting... Make sure you've run the command!";
return;
}
setTimeout(check, 5000);
});
}
check();
window.skipToNext = function () {
window.location.href = <%== my_wakatime_setup_step_2_path.to_json %>;
};
});
function copy(button) {
const codeBlock = button.previousElementSibling;
const text = codeBlock.textContent;
navigator.clipboard.writeText(text).then(() => {
const originalText = button.textContent;
button.textContent = "✅ Copied!";
button.classList.add("bg-green");
button.classList.remove("bg-primary");
setTimeout(() => {
button.textContent = originalText;
button.classList.remove("bg-green");
button.classList.add("bg-primary");
}, 2000);
});
}
</script>
<style>
.progress-indicator {
width: 16px;
height: 16px;
border: 2px solid var(--color-darkless);
border-top: 2px solid var(--color-primary);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.setup-instructions {
width: 100%;
}
.code-block {
display: flex;
align-items: flex-start;
gap: 1rem;
flex-wrap: wrap;
}
.code-block code {
flex: 1;
min-width: 0;
white-space: pre-wrap;
word-break: break-all;
font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
line-height: 1.4;
}
.copy-button {
flex-shrink: 0;
cursor: pointer;
font-size: 0.875rem;
}
@media (max-width: 768px) {
.code-block {
flex-direction: column;
align-items: stretch;
}
.code-block code {
margin-bottom: 1rem;
}
iframe {
height: 200px;
}
}
</style>