mirror of
https://github.com/Brandon-Rozek/website-theme.git
synced 2025-07-29 13:12:00 +00:00
238 lines
6 KiB
HTML
238 lines
6 KiB
HTML
<style>
|
|
.highfive-container {
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
.highfive-message {
|
|
margin-left: 1rem;
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 3px;
|
|
font-size: 0.9em;
|
|
display: none;
|
|
}
|
|
|
|
.highfive-message.success {
|
|
background-color: #d4edda;
|
|
color: #155724;
|
|
border: 1px solid #c3e6cb;
|
|
}
|
|
|
|
.highfive-message.error {
|
|
background-color: #f8d7da;
|
|
color: #721c24;
|
|
border: 1px solid #f5c6cb;
|
|
}
|
|
|
|
.highfive-btn {
|
|
transition: opacity 0.2s ease;
|
|
}
|
|
|
|
.highfive-btn.no-js {
|
|
opacity: 0.6;
|
|
pointer-events: none;
|
|
cursor: not-allowed;
|
|
}
|
|
</style>
|
|
|
|
{{ $apiUrl := printf "https://api.brandonrozek.com/highfive%s" .RelPermalink }}
|
|
{{ $initialCount := 0 }}
|
|
{{ with try (resources.GetRemote $apiUrl) }}
|
|
{{ with .Err }}
|
|
{{ warnf "Failed to fetch highfive data from %s" $apiUrl }}
|
|
{{ else }}
|
|
{{ $resource := .Value }}
|
|
{{ $highfiveData := $resource | transform.Unmarshal }}
|
|
{{ if $highfiveData.count }}
|
|
{{ $initialCount = $highfiveData.count }}
|
|
{{ end }}
|
|
{{ end }}
|
|
{{ end }}
|
|
|
|
<div class="highfive-container">
|
|
<p>
|
|
Give me a high five (it's free):
|
|
<a href="#" id="highfive-btn" class="reply-button highfive-btn no-js" role="button" aria-label="Give high five">
|
|
<i class="fa fa-hand-paper" style="margin-right: 0.5rem;" aria-hidden="true"></i>(<span id="highfive-count" aria-live="polite">{{ $initialCount }}</span>)
|
|
</a>
|
|
<span id="highfive-message" class="highfive-message" role="status" aria-live="polite"></span>
|
|
</p>
|
|
</div>
|
|
|
|
<script>
|
|
(function() {
|
|
'use strict';
|
|
|
|
// Configuration
|
|
const CONFIG = {
|
|
API_BASE: 'https://api.brandonrozek.com/highfive',
|
|
TIMEOUTS: {
|
|
SUCCESS_MESSAGE: 3000,
|
|
ERROR_MESSAGE: 3000
|
|
},
|
|
SELECTORS: {
|
|
BUTTON: '#highfive-btn',
|
|
COUNT: '#highfive-count',
|
|
MESSAGE: '#highfive-message'
|
|
}
|
|
};
|
|
|
|
// DOM elements
|
|
const elements = {};
|
|
|
|
// State
|
|
let isLoading = false;
|
|
let currentCount = {{ $initialCount }};
|
|
|
|
/**
|
|
* Initialize the high five functionality
|
|
*/
|
|
function init() {
|
|
// Cache DOM elements
|
|
elements.button = document.querySelector(CONFIG.SELECTORS.BUTTON);
|
|
elements.count = document.querySelector(CONFIG.SELECTORS.COUNT);
|
|
elements.message = document.querySelector(CONFIG.SELECTORS.MESSAGE);
|
|
|
|
if (!elements.button || !elements.count || !elements.message) {
|
|
console.error('High five: Required DOM elements not found');
|
|
return;
|
|
}
|
|
|
|
// Enable button now that JavaScript is available
|
|
elements.button.classList.remove('no-js');
|
|
elements.button.addEventListener('click', handleHighFiveClick);
|
|
|
|
// Load initial count
|
|
loadHighFiveCount();
|
|
}
|
|
|
|
/**
|
|
* Make API request with error handling
|
|
*/
|
|
async function makeApiRequest(method = 'GET') {
|
|
const response = await fetch(`${CONFIG.API_BASE}${window.location.pathname}`, {
|
|
method,
|
|
mode: 'cors',
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
|
|
// Handle 409 Conflict (already high-fived) as a special case
|
|
if (response.status === 409) {
|
|
const data = await response.json();
|
|
return { ...data, alreadyHighFived: true };
|
|
}
|
|
|
|
if (response.status === 400) {
|
|
return {count: 0}
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
/**
|
|
* Load current high five count
|
|
*/
|
|
async function loadHighFiveCount() {
|
|
try {
|
|
const data = await makeApiRequest('GET');
|
|
currentCount = data.count || currentCount;
|
|
updateButtonContent('hand-paper');
|
|
} catch (error) {
|
|
console.error('Failed to load high five count:', error);
|
|
updateButtonContent('hand-paper');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle high five button click
|
|
*/
|
|
async function handleHighFiveClick(event) {
|
|
event.preventDefault();
|
|
|
|
if (isLoading) return;
|
|
|
|
setButtonState('loading');
|
|
|
|
try {
|
|
const data = await makeApiRequest('POST');
|
|
const newCount = data.count || currentCount;
|
|
currentCount = newCount;
|
|
|
|
// Check if user already gave a high five (HTTP 409 response)
|
|
if (data.alreadyHighFived) {
|
|
updateButtonContent('check');
|
|
showMessage('👋 Already high-fived!', 'success');
|
|
setButtonState('disabled');
|
|
} else {
|
|
updateButtonContent('check');
|
|
showMessage('🎉 Thanks!', 'success');
|
|
setButtonState('disabled');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Failed to send high five:', error);
|
|
|
|
showMessage('❌ Failed to send', 'error', CONFIG.TIMEOUTS.ERROR_MESSAGE);
|
|
updateButtonContent('hand-paper');
|
|
setButtonState('disabled');
|
|
|
|
setTimeout(() => {
|
|
setButtonState('enabled');
|
|
}, CONFIG.TIMEOUTS.ERROR_MESSAGE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update button content with icon and count
|
|
*/
|
|
function updateButtonContent(icon) {
|
|
elements.count.textContent = currentCount;
|
|
const iconClass = icon === 'loading' ? 'fa-spinner fa-spin' : `fa-${icon}`;
|
|
const text = icon === 'loading' ? 'Sending...' : `(${currentCount})`;
|
|
|
|
elements.button.innerHTML = `<i class="fa ${iconClass}" style="margin-right: 0.5rem;" aria-hidden="true"></i>${text}`;
|
|
}
|
|
|
|
/**
|
|
* Set button state (enabled, disabled, loading)
|
|
*/
|
|
function setButtonState(state) {
|
|
isLoading = state === 'loading';
|
|
|
|
const isInteractive = state === 'enabled';
|
|
elements.button.style.pointerEvents = isInteractive ? 'auto' : 'none';
|
|
elements.button.style.opacity = isInteractive ? '1' : '0.6';
|
|
|
|
if (state === 'loading') {
|
|
updateButtonContent('loading');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show message with optional auto-hide
|
|
*/
|
|
function showMessage(text, type, timeout) {
|
|
elements.message.textContent = text;
|
|
elements.message.className = `highfive-message ${type}`;
|
|
elements.message.style.display = 'inline';
|
|
|
|
if (timeout) {
|
|
setTimeout(() => {
|
|
elements.message.style.display = 'none';
|
|
elements.message.className = 'highfive-message';
|
|
}, timeout);
|
|
}
|
|
}
|
|
|
|
// Initialize when DOM is ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
|
|
})();
|
|
</script>
|