UserMenu.svelte raw

   1  <script>
   2      import { createEventDispatcher } from 'svelte';
   3      import { isStandaloneMode, relayUrl, relayInfo as relayInfoStore, userMenuOpen } from './stores.js';
   4  
   5      export let isLoggedIn = false;
   6      export let userProfile = null;
   7      export let userPubkey = "";
   8      export let userRole = "";
   9      export let currentEffectiveRole = "";
  10      export let isDarkTheme = false;
  11  
  12      const dispatch = createEventDispatcher();
  13  
  14      function getAvailableRoles() {
  15          const allRoles = ["owner", "admin", "write", "read"];
  16          const idx = allRoles.indexOf(userRole);
  17          if (idx === -1) return ["read"];
  18          return allRoles.slice(idx);
  19      }
  20  
  21      function handleLogout() {
  22          userMenuOpen.set(false);
  23          dispatch('logout');
  24      }
  25  
  26      function handleToggleTheme() {
  27          dispatch('toggleTheme');
  28      }
  29  
  30      function handleViewAsRole(role) {
  31          dispatch('setViewAsRole', role === userRole ? "" : role);
  32      }
  33  
  34      function handleChangeRelay() {
  35          userMenuOpen.set(false);
  36          dispatch('openRelayModal');
  37      }
  38  
  39      function closeMenu() {
  40          userMenuOpen.set(false);
  41      }
  42  
  43      function displayName(profile, pubkey) {
  44          if (profile?.name) return profile.name;
  45          if (profile?.display_name) return profile.display_name;
  46          if (pubkey) return pubkey.slice(0, 8) + '...';
  47          return 'Anonymous';
  48      }
  49  </script>
  50  
  51  {#if $userMenuOpen && isLoggedIn}
  52      <!-- svelte-ignore a11y-click-events-have-key-events -->
  53      <!-- svelte-ignore a11y-no-static-element-interactions -->
  54      <div class="menu-overlay" on:click={closeMenu}></div>
  55      <div class="user-menu">
  56          <!-- Profile section -->
  57          <div class="menu-profile">
  58              {#if userProfile?.picture}
  59                  <img src={userProfile.picture} alt="avatar" class="menu-avatar" />
  60              {:else}
  61                  <div class="menu-avatar-placeholder">
  62                      {displayName(userProfile, userPubkey).charAt(0).toUpperCase()}
  63                  </div>
  64              {/if}
  65              <div class="menu-profile-info">
  66                  <div class="menu-display-name">{displayName(userProfile, userPubkey)}</div>
  67                  {#if userProfile?.nip05}
  68                      <div class="menu-nip05">{userProfile.nip05}</div>
  69                  {/if}
  70                  {#if userRole}
  71                      <div class="menu-role">{currentEffectiveRole}</div>
  72                  {/if}
  73              </div>
  74          </div>
  75  
  76          <div class="menu-divider"></div>
  77  
  78          <!-- Theme toggle -->
  79          <button class="menu-item" on:click={handleToggleTheme}>
  80              <span class="menu-item-icon">{isDarkTheme ? '☀️' : '🌙'}</span>
  81              <span>{isDarkTheme ? 'Light Mode' : 'Dark Mode'}</span>
  82          </button>
  83  
  84          <!-- View as role (admin/owner only) -->
  85          {#if userRole && userRole !== "read"}
  86              <div class="menu-section-label">View as Role</div>
  87              {#each getAvailableRoles() as role}
  88                  <button
  89                      class="menu-item"
  90                      class:active={currentEffectiveRole === role}
  91                      on:click={() => handleViewAsRole(role)}
  92                  >
  93                      <span class="menu-item-icon">{currentEffectiveRole === role ? '●' : '○'}</span>
  94                      <span>{role.charAt(0).toUpperCase() + role.slice(1)}{role === userRole ? ' (Default)' : ''}</span>
  95                  </button>
  96              {/each}
  97          {/if}
  98  
  99          <!-- Relay (standalone mode) -->
 100          {#if $isStandaloneMode}
 101              <div class="menu-divider"></div>
 102              <button class="menu-item" on:click={handleChangeRelay}>
 103                  <span class="menu-item-icon">🔗</span>
 104                  <span>Change Relay</span>
 105              </button>
 106          {/if}
 107  
 108          <div class="menu-divider"></div>
 109  
 110          <!-- Logout -->
 111          <button class="menu-item danger" on:click={handleLogout}>
 112              <span class="menu-item-icon">⏻</span>
 113              <span>Log out</span>
 114          </button>
 115      </div>
 116  {/if}
 117  
 118  <style>
 119      .menu-overlay {
 120          position: fixed;
 121          top: 0;
 122          left: 0;
 123          right: 0;
 124          bottom: 0;
 125          z-index: 999;
 126      }
 127  
 128      .user-menu {
 129          position: fixed;
 130          top: 3em;
 131          left: 200px;
 132          width: 240px;
 133          background: var(--card-bg);
 134          border: 1px solid var(--border-color);
 135          border-radius: 8px;
 136          box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
 137          z-index: 1000;
 138          overflow: hidden;
 139      }
 140  
 141      .menu-profile {
 142          display: flex;
 143          align-items: center;
 144          gap: 0.6em;
 145          padding: 0.75em;
 146      }
 147  
 148      .menu-avatar {
 149          width: 40px;
 150          height: 40px;
 151          border-radius: 50%;
 152          object-fit: cover;
 153          flex-shrink: 0;
 154      }
 155  
 156      .menu-avatar-placeholder {
 157          width: 40px;
 158          height: 40px;
 159          border-radius: 50%;
 160          background: var(--primary);
 161          color: #000;
 162          display: flex;
 163          align-items: center;
 164          justify-content: center;
 165          font-weight: bold;
 166          font-size: 1rem;
 167          flex-shrink: 0;
 168      }
 169  
 170      .menu-profile-info {
 171          overflow: hidden;
 172      }
 173  
 174      .menu-display-name {
 175          font-weight: 600;
 176          font-size: 0.85rem;
 177          color: var(--text-color);
 178          white-space: nowrap;
 179          overflow: hidden;
 180          text-overflow: ellipsis;
 181      }
 182  
 183      .menu-nip05 {
 184          font-size: 0.7rem;
 185          color: var(--text-muted);
 186          white-space: nowrap;
 187          overflow: hidden;
 188          text-overflow: ellipsis;
 189      }
 190  
 191      .menu-role {
 192          font-size: 0.65rem;
 193          color: var(--primary);
 194          text-transform: uppercase;
 195          letter-spacing: 0.5px;
 196          font-weight: 600;
 197          margin-top: 0.15em;
 198      }
 199  
 200      .menu-divider {
 201          height: 1px;
 202          background: var(--border-color);
 203          margin: 0.25em 0;
 204      }
 205  
 206      .menu-section-label {
 207          padding: 0.4em 0.75em 0.2em;
 208          font-size: 0.65rem;
 209          color: var(--text-muted);
 210          text-transform: uppercase;
 211          letter-spacing: 0.5px;
 212          font-weight: 500;
 213      }
 214  
 215      .menu-item {
 216          display: flex;
 217          align-items: center;
 218          gap: 0.5em;
 219          width: 100%;
 220          padding: 0.5em 0.75em;
 221          background: none;
 222          border: none;
 223          cursor: pointer;
 224          color: var(--text-color);
 225          font-size: 0.8rem;
 226          text-align: left;
 227          transition: background 0.15s;
 228      }
 229  
 230      .menu-item:hover {
 231          background: var(--button-hover-bg);
 232      }
 233  
 234      .menu-item.active {
 235          color: var(--primary);
 236      }
 237  
 238      .menu-item.danger {
 239          color: var(--danger);
 240      }
 241  
 242      .menu-item.danger:hover {
 243          background: var(--danger-bg);
 244      }
 245  
 246      .menu-item-icon {
 247          width: 1.2em;
 248          text-align: center;
 249          flex-shrink: 0;
 250          font-size: 0.85em;
 251      }
 252  
 253      @media (max-width: 1280px) {
 254          .user-menu {
 255              left: 60px;
 256          }
 257      }
 258  
 259      @media (max-width: 640px) {
 260          .user-menu {
 261              left: 0;
 262              right: 0;
 263              width: auto;
 264              top: auto;
 265              bottom: 0;
 266              border-radius: 12px 12px 0 0;
 267          }
 268      }
 269  </style>
 270