Files
my_openplace/frontend-src/src/lib/components/user/UserProfileButton.svelte
T
2025-10-04 01:41:35 -07:00

409 lines
16 KiB
Svelte

<script lang="ts">
import { currentUser, userLevel } from '$lib/stores/user';
import { getLevelProgress } from '$lib/utils/level';
import { minidenticon } from 'minidenticons';
let dropdownOpen = false;
let showEditProfile = false;
let isMuted = false;
$: levelProgress = $currentUser ? getLevelProgress($currentUser.pixelsPainted) : 0;
$: svgURI = $currentUser
? 'data:image/svg+xml;utf8,' + encodeURIComponent(minidenticon($currentUser.name, 90, 50))
: '';
function toggleDropdown() {
dropdownOpen = !dropdownOpen;
}
function closeDropdown() {
dropdownOpen = false;
}
function toggleMute() {
isMuted = !isMuted;
}
</script>
{#if $currentUser}
<div class="relative dropdown dropdown-end">
<!-- Profile Button -->
<button
on:click={toggleDropdown}
class="btn size-12 p-0 shadow-md bg-primary"
title="Show profile"
>
<div class="relative w-max">
<!-- Background circle -->
<div class="bg-base-content/20 size-12 rounded-full"></div>
<!-- Level progress ring -->
<div
class="level-fill center-absolute absolute size-12 rotate-[215deg] rounded-full"
style="--angle: {(levelProgress / 100) * 360}deg; --color: var(--color-secondary);"
></div>
<!-- Avatar -->
<div class="avatar center-absolute absolute">
<div class="size-10 rounded-full">
<div class="bg-base-200 minidenticon">
{@html minidenticon($currentUser.name, 90, 50)}
</div>
</div>
<!-- Edit profile button -->
<button
class="btn btn-circle btn-sm absolute -bottom-1 -right-1"
on:click|stopPropagation={() => (showEditProfile = true)}
title="Edit profile"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 -960 960 960"
fill="currentColor"
class="size-4"
>
<path
d="M200-200h57l391-391-57-57-391 391v57Zm-80 80v-170l528-527q12-11 26.5-17t30.5-6q16 0 31 6t26 18l55 56q12 11 17.5 26t5.5 30q0 16-5.5 30.5T817-647L290-120H120Zm640-584-56-56 56 56Zm-141 85-28-29 57 57-29-28Z"
/>
</svg>
</button>
</div>
<!-- Level badge -->
<div
class="text-primary-content bg-secondary absolute bottom-0 left-0 -left-1 flex items-center justify-center rounded-full px-[5px] py-0 text-xs font-bold"
>
{$userLevel}
</div>
</div>
</button>
<!-- Dropdown Menu -->
{#if dropdownOpen}
<div
class="dropdown-content menu bg-base-100 rounded-box border-base-300 z-50 relative right-1 w-[min(100vw-24px,400px)] translate-y-2 border p-4 shadow-md"
>
<!-- Close button -->
<button on:click={closeDropdown} class="btn btn-ghost btn-circle absolute right-2 top-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 -960 960 960"
fill="currentColor"
class="size-5"
>
<path
d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"
/>
</svg>
</button>
<!-- Profile Info -->
<section class="flex gap-2">
<div class="relative">
<div class="avatar relative rounded-full border-3 border-primary">
<div class="border-base-300 size-20 rounded-full border-2">
<div class="bg-base-200 minidenticon">
{@html minidenticon($currentUser.name, 90, 50)}
</div>
</div>
</div>
</div>
<div>
<div class="flex items-center gap-1.5 pr-8 text-lg font-medium">
<h3 class="line-clamp-1 text-ellipsis text-lg" title={$currentUser.name}>
{$currentUser.name}
</h3>
<span class="text-rose-500">#{$currentUser.id}</span>
{#if $currentUser.country}
<span class="tooltip font-flag ml-0.5" data-tip={$currentUser.country}>
{$currentUser.country === 'US' ? '🇺🇸' : '🇧🇷'}
</span>
{/if}
</div>
{#if $currentUser.discord}
<div class="mt-1">
<span class="tooltip h-4">
<div class="tooltip-content">
<span>Discord: {$currentUser.discord}</span>
</div>
<button class="flex items-center gap-1 text-sm">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 127.14 96.36"
fill="currentColor"
class="size-4"
>
<path
d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"
/>
</svg>
</button>
</span>
</div>
{/if}
<div class="flex items-center gap-1">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 -960 960 960"
fill="currentColor"
class="inline size-4"
>
<path
d="M240-120q-45 0-89-22t-71-58q26 0 53-20.5t27-59.5q0-50 35-85t85-35q50 0 85 35t35 85q0 66-47 113t-113 47Zm230-240L360-470l358-358q11-11 27.5-11.5T774-828l54 54q12 12 12 28t-12 28L470-360Z"
/>
</svg>
<span
>Pixels painted: <span class="text-primary font-semibold"
>{$currentUser.pixelsPainted.toLocaleString()}</span
></span
>
</div>
<div class="flex items-center gap-1">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 -960 960 960"
fill="currentColor"
class="inline size-4"
>
<path
d="M440-160v-487L216-423l-56-57 320-320 320 320-56 57-224-224v487h-80Z"
/>
</svg>
<span class="text-secondary">
<span class="font-semibold">Level {$userLevel}</span> ({levelProgress.toFixed(0)}%)
</span>
</div>
</div>
</section>
<!-- Menu Items -->
<section class="mt-3 flex flex-col gap-2">
<div class="mb-1 flex items-center justify-between">
<h3 class="text-lg font-semibold">Menu</h3>
<div class="flex items-center gap-1">
<!-- Language selector -->
<div class="dropdown dropdown-end">
<button class="btn btn-sm btn-circle" tabindex="0" title="Language">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 -960 960 960"
fill="currentColor"
class="size-5"
>
<path
d="M480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-82q26-36 45-75t31-83H404q12 44 31 83t45 75Zm-104-16q-18-33-31.5-68.5T322-320H204q29 50 72.5 87t99.5 55Zm208 0q56-18 99.5-55t72.5-87H638q-9 38-22.5 73.5T584-178ZM170-400h136q-3-20-4.5-39.5T300-480q0-21 1.5-40.5T306-560H170q-5 20-7.5 39.5T160-480q0 21 2.5 40.5T170-400Zm216 0h188q3-20 4.5-39.5T580-480q0-21-1.5-40.5T574-560H386q-3 20-4.5 39.5T380-480q0 21 1.5 40.5T386-400Zm268 0h136q5-20 7.5-39.5T800-480q0-21-2.5-40.5T790-560H654q3 20 4.5 39.5T660-480q0 21-1.5 40.5T654-400Zm-16-240h118q-29-50-72.5-87T584-782q18 33 31.5 68.5T638-640Zm-234 0h152q-12-44-31-83t-45-75q-26 36-45 75t-31 83Zm-200 0h118q9-38 22.5-73.5T376-782q-56 18-99.5 55T204-640Z"
/>
</svg>
</button>
<ul
tabindex="0"
class="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 border border-base-300 p-2 shadow"
>
<li>
<button class="font-flag">🇺🇸 English</button>
</li>
<li>
<button class="font-flag">🇧🇷 Português</button>
</li>
</ul>
</div>
<!-- Mute button -->
<button
class="btn btn-sm btn-circle"
on:click={toggleMute}
title={isMuted ? 'Unmute' : 'Mute'}
>
{#if isMuted}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 -960 960 960"
fill="currentColor"
class="size-5"
>
<path
d="M792-56 671-177q-25 16-53 27.5T560-131v-82q14-5 27.5-10t25.5-12L480-368v208L280-360H120v-240h128L56-792l56-56 736 736-56 56Zm-8-232-58-58q17-31 25.5-65t8.5-70q0-94-55-168T560-749v-82q124 28 202 125.5T840-481q0 53-14.5 102T784-288ZM650-422l-90-90v-130q47 22 73.5 66t26.5 96q0 15-2.5 29.5T650-422ZM480-592 376-696l104-104v208Zm-80 238v-94l-72-72H200v80h114l86 86Zm-36-130Z"
/>
</svg>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 -960 960 960"
fill="currentColor"
class="size-5"
>
<path
d="M560-131v-82q90-26 145-100t55-168q0-94-55-168T560-749v-82q124 28 202 125.5T840-481q0 127-78 224.5T560-131ZM120-360v-240h160l200-200v640L280-360H120Zm440 40v-322q47 22 73.5 66t26.5 96q0 51-26.5 94.5T560-320ZM400-606l-86 86H200v80h114l86 86v-252ZM300-480Z"
/>
</svg>
{/if}
</button>
</div>
</div>
<a class="btn w-full" href="/profile">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 -960 960 960"
fill="currentColor"
class="size-5"
>
<path
d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm246-164q-59 0-99.5-40.5T340-580q0-59 40.5-99.5T480-720q59 0 99.5 40.5T620-580q0 59-40.5 99.5T480-440Zm0 360q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q53 0 100-15.5t86-44.5q-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160Zm0-360q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm0-60Zm0 360Z"
/>
</svg>
Profile
</a>
<a class="btn w-full" href="/alliance">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 -960 960 960"
fill="currentColor"
class="size-5"
>
<path
d="M40-160v-160q0-34 23.5-57t56.5-23h131q20 0 38 10t29 27q29 39 71.5 61t90.5 22q49 0 91.5-22t70.5-61q13-17 30.5-27t36.5-10h131q34 0 57 23t23 57v160H640v-91q-35 25-75.5 38T480-200q-43 0-84-13.5T320-252v92H40Zm440-160q-38 0-72-17.5T351-386q-17-25-42.5-39.5T253-440q22-37 93-58.5T480-520q63 0 134 21.5t93 58.5q-29 0-55 14.5T609-386q-22 32-56 49t-73 17Z"
/>
</svg>
Alliance
</a>
<a class="btn w-full" href="/store">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 -960 960 960"
fill="currentColor"
class="size-5"
>
<path
d="M280-80q-33 0-56.5-23.5T200-160q0-33 23.5-56.5T280-240q33 0 56.5 23.5T360-160q0 33-23.5 56.5T280-80Zm400 0q-33 0-56.5-23.5T600-160q0-33 23.5-56.5T680-240q33 0 56.5 23.5T760-160q0 33-23.5 56.5T680-80ZM246-720l96 200h280l110-200H246Zm-38-80h590q23 0 35 20.5t1 41.5L692-482q-11 20-29.5 31T622-440H324l-44 80h480v80H280q-45 0-68-39.5t-2-78.5l54-98-144-304H40v-80h130l38 80Z"
/>
</svg>
Store
</a>
<a class="btn w-full" href="/leaderboard">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 -960 960 960"
fill="currentColor"
class="size-5"
>
<path
d="M160-200h160v-320H160v320Zm240 0h160v-560H400v560Zm240 0h160v-240H640v240ZM80-120v-480h240v-240h320v320h240v400H80Z"
/>
</svg>
Leaderboard
</a>
<!-- Social Media Buttons -->
<a
class="btn w-full"
href="https://www.twitch.tv/directory/category/wplace"
target="_blank"
rel="noopener noreferrer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 256 268"
fill="currentColor"
class="size-5"
>
<path
d="M17.458 0L0 46.556v186.201h63.983v34.934h34.931l34.898-34.934h52.36L256 162.954V0H17.458zm23.259 23.263H232.73v128.029l-40.739 40.741H128L93.113 226.92v-34.886H40.717V23.263zm64.008 116.405H128V69.844h-23.275v69.824zm63.997 0h23.27V69.844h-23.27v69.824z"
/>
</svg>
Livestreams
</a>
<a
class="btn w-full"
href="https://discord.gg/wplace"
target="_blank"
rel="noopener noreferrer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 127.14 96.36"
fill="currentColor"
class="size-5"
>
<path
d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"
/>
</svg>
Discord
</a>
<a
class="btn w-full"
href="https://www.reddit.com/r/wplace_unofficial"
target="_blank"
rel="noopener noreferrer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-5"
>
<path
d="M12 0C5.373 0 0 5.373 0 12c0 3.314 1.343 6.314 3.515 8.485l-2.286 2.286C.775 23.225 1.097 24 1.738 24H12c6.627 0 12-5.373 12-12S18.627 0 12 0zm4.388 3.199c.605 0 1.126.21 1.551.628.426.418.64.948.64 1.59 0 .613-.213 1.128-.64 1.546-.425.418-.946.628-1.551.628-.578 0-1.075-.21-1.486-.628-.412-.418-.619-.933-.619-1.546 0-.642.207-1.172.619-1.59.411-.418.908-.628 1.486-.628zM5.423 9.636c.853 0 1.573.3 2.16.902.586.6.879 1.334.879 2.201 0 .866-.293 1.6-.879 2.2-.587.6-1.307.9-2.16.9-.852 0-1.573-.3-2.159-.9-.587-.6-.88-1.334-.88-2.2 0-.867.293-1.601.88-2.2.586-.602 1.307-.903 2.159-.903zm13.154 0c.853 0 1.574.3 2.161.902.586.6.879 1.334.879 2.201 0 .866-.293 1.6-.879 2.2-.587.6-1.308.9-2.161.9-.852 0-1.573-.3-2.159-.9-.587-.6-.88-1.334-.88-2.2 0-.867.293-1.601.88-2.2.586-.602 1.307-.903 2.159-.903zM12 15.314c1.777 0 3.309.48 4.597 1.44.964.72 1.446 1.584 1.446 2.592H5.957c0-1.008.482-1.872 1.445-2.592 1.289-.96 2.821-1.44 4.598-1.44z"
/>
</svg>
Reddit
</a>
<button class="btn w-full" on:click={() => currentUser.logout()}>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 -960 960 960"
fill="currentColor"
class="size-5"
>
<path
d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h280v80H200Zm440-160-55-58 102-102H360v-80h327L585-622l55-58 200 200-200 200Z"
/>
</svg>
Log Out
</button>
</section>
</div>
{/if}
</div>
{:else}
<a href="/join" class="btn btn-primary shadow-md">Login</a>
{/if}
<style>
.level-fill {
background: conic-gradient(
var(--color) 0deg,
var(--color) var(--angle),
transparent var(--angle)
);
-webkit-mask: radial-gradient(
farthest-side,
transparent calc(100% - 3px),
white calc(100% - 2px)
);
mask: radial-gradient(farthest-side, transparent calc(100% - 3px), white calc(100% - 2px));
}
.center-absolute {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.minidenticon :global(svg) {
width: 100%;
height: 100%;
}
</style>