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