Add share link button for room invites

- Add 🔗 button next to room code copy button
- Copies full URL with ?room=XXXX parameter
- On page load, pre-fills room code from URL param
- Works with both logged-in users and guests
- Cleans up URL after extracting room code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee 2026-01-27 16:34:45 -05:00
parent 0b0873350c
commit 0c8d2b4a9c
3 changed files with 104 additions and 37 deletions

View File

@ -33,6 +33,20 @@ class GolfGame {
this.initElements(); this.initElements();
this.initAudio(); this.initAudio();
this.bindEvents(); this.bindEvents();
this.checkUrlParams();
}
checkUrlParams() {
// Handle ?room=XXXX share links
const params = new URLSearchParams(window.location.search);
const roomCode = params.get('room');
if (roomCode) {
this.roomCodeInput.value = roomCode.toUpperCase();
// Focus name input so user can quickly enter name and join
this.playerNameInput.focus();
// Clean up URL without reloading
window.history.replaceState({}, '', window.location.pathname);
}
} }
initAudio() { initAudio() {
@ -140,6 +154,7 @@ class GolfGame {
// Waiting room elements // Waiting room elements
this.displayRoomCode = document.getElementById('display-room-code'); this.displayRoomCode = document.getElementById('display-room-code');
this.copyRoomCodeBtn = document.getElementById('copy-room-code'); this.copyRoomCodeBtn = document.getElementById('copy-room-code');
this.shareRoomLinkBtn = document.getElementById('share-room-link');
this.playersList = document.getElementById('players-list'); this.playersList = document.getElementById('players-list');
this.hostSettings = document.getElementById('host-settings'); this.hostSettings = document.getElementById('host-settings');
this.waitingMessage = document.getElementById('waiting-message'); this.waitingMessage = document.getElementById('waiting-message');
@ -234,6 +249,12 @@ class GolfGame {
this.copyRoomCode(); this.copyRoomCode();
}); });
// Share room link
this.shareRoomLinkBtn.addEventListener('click', () => {
this.playSound('click');
this.shareRoomLink();
});
// Enter key handlers // Enter key handlers
this.playerNameInput.addEventListener('keypress', (e) => { this.playerNameInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') this.createRoomBtn.click(); if (e.key === 'Enter') this.createRoomBtn.click();
@ -511,22 +532,47 @@ class GolfGame {
copyRoomCode() { copyRoomCode() {
if (!this.roomCode) return; if (!this.roomCode) return;
this.copyToClipboard(this.roomCode, this.copyRoomCodeBtn);
}
navigator.clipboard.writeText(this.roomCode).then(() => { shareRoomLink() {
// Show brief visual feedback if (!this.roomCode) return;
const originalText = this.copyRoomCodeBtn.textContent;
this.copyRoomCodeBtn.textContent = '✓'; // Build shareable URL with room code
const url = new URL(window.location.href);
url.search = ''; // Clear existing params
url.hash = ''; // Clear hash
url.searchParams.set('room', this.roomCode);
const shareUrl = url.toString();
this.copyToClipboard(shareUrl, this.shareRoomLinkBtn);
}
copyToClipboard(text, feedbackBtn) {
// Use execCommand which is more reliable across contexts
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.select();
let success = false;
try {
success = document.execCommand('copy');
} catch (err) {
console.error('Copy failed:', err);
}
document.body.removeChild(textarea);
// Show visual feedback
if (success && feedbackBtn) {
const originalText = feedbackBtn.textContent;
feedbackBtn.textContent = '✓';
setTimeout(() => { setTimeout(() => {
this.copyRoomCodeBtn.textContent = originalText; feedbackBtn.textContent = originalText;
}, 1500); }, 1500);
}).catch(err => { }
console.error('Failed to copy room code:', err);
// Fallback: select the text for manual copy
const range = document.createRange();
range.selectNode(this.displayRoomCode);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
});
} }
startGame() { startGame() {

View File

@ -53,7 +53,10 @@
<div class="room-code-banner"> <div class="room-code-banner">
<span class="room-code-label">ROOM CODE</span> <span class="room-code-label">ROOM CODE</span>
<span class="room-code-value" id="display-room-code"></span> <span class="room-code-value" id="display-room-code"></span>
<button class="room-code-copy" id="copy-room-code" title="Copy to clipboard">📋</button> <div class="room-code-buttons">
<button class="room-code-copy" id="copy-room-code" title="Copy code">📋</button>
<button class="room-code-copy" id="share-room-link" title="Copy link">🌐</button>
</div>
</div> </div>
<div class="waiting-layout"> <div class="waiting-layout">

View File

@ -275,50 +275,72 @@ body {
} }
} }
/* Room Code Banner - positioned top-left to avoid auth bar overlap */ /* Room Code Banner - styled as a hanging ribbon/bookmark */
.room-code-banner { .room-code-banner {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 20px;
z-index: 100; z-index: 100;
background: linear-gradient(135deg, rgba(244, 164, 96, 0.9) 0%, rgba(230, 140, 70, 0.95) 100%); background: linear-gradient(180deg, #d4845a 0%, #c4723f 50%, #b8663a 100%);
padding: 10px 15px; padding: 12px 16px 20px;
border-radius: 0 0 12px 0;
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
gap: 10px; gap: 4px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); box-shadow: 2px 4px 12px rgba(0, 0, 0, 0.3);
/* Ribbon forked end (snake tongue style) */
clip-path: polygon(0 0, 100% 0, 100% 100%, 50% calc(100% - 12px), 0 100%);
}
.room-code-banner::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(180deg, rgba(255,255,255,0.3) 0%, transparent 100%);
} }
.room-code-label { .room-code-label {
font-size: 0.65rem; font-size: 0.55rem;
font-weight: 600; font-weight: 700;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.1em; letter-spacing: 0.25em;
color: rgba(26, 71, 42, 0.8); color: rgba(255, 255, 255, 0.85);
text-align: center;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.3);
} }
.room-code-value { .room-code-value {
font-size: 1.5rem; font-size: 1.6rem;
font-weight: 800; font-weight: 800;
font-family: 'Courier New', monospace; font-family: 'Courier New', monospace;
letter-spacing: 0.2em; letter-spacing: 0.2em;
color: #1a472a; color: #fff;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.3); text-shadow: 0 2px 2px rgba(0, 0, 0, 0.3);
padding: 2px 0;
}
.room-code-buttons {
display: flex;
gap: 6px;
margin-top: 2px;
} }
.room-code-copy { .room-code-copy {
background: rgba(26, 71, 42, 0.2); background: rgba(255, 255, 255, 0.85);
border: none; border: none;
border-radius: 6px; border-radius: 4px;
padding: 6px 8px; padding: 4px 8px;
cursor: pointer; cursor: pointer;
font-size: 1rem; font-size: 0.9rem;
transition: all 0.2s; transition: all 0.2s;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
} }
.room-code-copy:hover { .room-code-copy:hover {
background: rgba(26, 71, 42, 0.3); background: rgba(255, 255, 255, 0.35);
transform: scale(1.1); transform: scale(1.1);
} }
@ -326,10 +348,6 @@ body {
transform: scale(0.95); transform: scale(0.95);
} }
.room-code-copy.copied {
background: rgba(26, 71, 42, 0.4);
}
h1 { h1 {
font-size: 3rem; font-size: 3rem;