website-theme/layouts/partials/highfive.html
2025-06-15 22:32:52 -04:00

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>