Skip to content

Commit c11b701

Browse files
amritbejAmrit
andauthored
feat: add copy profile link button (#120)
Co-authored-by: Amrit <amrit@example.com>
1 parent 4493b31 commit c11b701

1 file changed

Lines changed: 92 additions & 2 deletions

File tree

apps/web/src/routes/u/[username]/+page.svelte

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,46 @@
1515
};
1616
1717
let mounted = $state(false);
18+
let copyMessage = $state('');
19+
let copyStatus = $state<'success' | 'error'>('success');
20+
let copyMessageTimeout: ReturnType<typeof setTimeout> | undefined;
21+
1822
onMount(() => {
1923
mounted = true;
24+
25+
return () => {
26+
if (copyMessageTimeout) {
27+
clearTimeout(copyMessageTimeout);
28+
}
29+
};
2030
});
31+
32+
function showCopyMessage(message: string, status: 'success' | 'error') {
33+
copyMessage = message;
34+
copyStatus = status;
35+
36+
if (copyMessageTimeout) {
37+
clearTimeout(copyMessageTimeout);
38+
}
39+
40+
copyMessageTimeout = setTimeout(() => {
41+
copyMessage = '';
42+
}, 3000);
43+
}
44+
45+
async function copyProfileUrl() {
46+
if (!navigator.clipboard?.writeText) {
47+
showCopyMessage('Clipboard API unavailable. Copy the URL from your address bar.', 'error');
48+
return;
49+
}
50+
51+
try {
52+
await navigator.clipboard.writeText(window.location.href);
53+
showCopyMessage('Profile link copied.', 'success');
54+
} catch {
55+
showCopyMessage('Could not copy link. Copy the URL from your address bar.', 'error');
56+
}
57+
}
2158
</script>
2259

2360
<svelte:head>
@@ -96,7 +133,15 @@
96133

97134
<div class="get-your-own">
98135
<p>Want a card like this?</p>
99-
<a href="/" class="gradient-text">Create your DevCard ⚡</a>
136+
<div class="profile-actions">
137+
<a href="/" class="gradient-text get-devcard-link">Create your DevCard ⚡</a>
138+
<button type="button" class="copy-link-button" onclick={copyProfileUrl}>
139+
Copy Link
140+
</button>
141+
</div>
142+
<p class="copy-message {copyStatus}" aria-live="polite">
143+
{copyMessage}
144+
</p>
100145
</div>
101146
{/if}
102147
</main>
@@ -301,11 +346,56 @@
301346
margin-bottom: 0.5rem;
302347
}
303348
304-
.get-your-own a {
349+
.profile-actions {
350+
display: flex;
351+
flex-wrap: wrap;
352+
align-items: center;
353+
justify-content: center;
354+
gap: 0.75rem;
355+
}
356+
357+
.get-devcard-link {
305358
font-weight: 700;
306359
font-size: 1.1rem;
307360
}
308361
362+
.copy-link-button {
363+
border: 1px solid var(--border-glass);
364+
border-radius: var(--radius);
365+
background: rgba(255, 255, 255, 0.08);
366+
color: var(--text-primary);
367+
cursor: pointer;
368+
font: inherit;
369+
font-weight: 700;
370+
padding: 0.65rem 1rem;
371+
transition: all 0.2s ease;
372+
}
373+
374+
.copy-link-button:hover {
375+
background: rgba(255, 255, 255, 0.15);
376+
transform: translateY(-1px);
377+
}
378+
379+
.copy-link-button:focus-visible {
380+
outline: 2px solid var(--accent);
381+
outline-offset: 3px;
382+
}
383+
384+
.copy-message {
385+
min-height: 1.2rem;
386+
margin-top: 0.75rem;
387+
margin-bottom: 0;
388+
font-size: 0.85rem;
389+
}
390+
391+
.copy-message.success {
392+
color: var(--text-secondary);
393+
}
394+
395+
.copy-message.error {
396+
color: #ef4444;
397+
}
398+
309399
.error-glass {
310400
text-align: center;
311401
padding: 4rem;

0 commit comments

Comments
 (0)