WIP PWA for Grain
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add follow/unfollow button to profile header

+57 -1
+57 -1
src/components/organisms/grain-profile-header.js
··· 2 2 import { router } from '../../router.js'; 3 3 import { auth } from '../../services/auth.js'; 4 4 import { share } from '../../services/share.js'; 5 + import { mutations } from '../../services/mutations.js'; 5 6 import '../atoms/grain-avatar.js'; 6 7 import '../atoms/grain-icon.js'; 7 8 import '../atoms/grain-toast.js'; ··· 13 14 profile: { type: Object }, 14 15 _showFullscreen: { state: true }, 15 16 _user: { state: true }, 16 - _menuOpen: { state: true } 17 + _menuOpen: { state: true }, 18 + _followLoading: { state: true } 17 19 }; 18 20 19 21 static styles = css` ··· 95 97 cursor: pointer; 96 98 color: var(--color-text-secondary); 97 99 } 100 + .follow-button { 101 + background: var(--color-accent); 102 + color: white; 103 + border: none; 104 + border-radius: 8px; 105 + padding: 6px 16px; 106 + font-size: var(--font-size-sm); 107 + font-weight: var(--font-weight-semibold); 108 + cursor: pointer; 109 + } 110 + .follow-button.following { 111 + background: transparent; 112 + border: 1px solid var(--color-border); 113 + color: var(--color-text-primary); 114 + } 115 + .follow-button:disabled { 116 + opacity: 0.5; 117 + } 98 118 `; 99 119 100 120 constructor() { ··· 102 122 this._showFullscreen = false; 103 123 this._user = auth.user; 104 124 this._menuOpen = false; 125 + this._followLoading = false; 105 126 } 106 127 107 128 connectedCallback() { ··· 158 179 this._showFullscreen = false; 159 180 } 160 181 182 + async #handleFollowClick() { 183 + if (!this._user || this._followLoading || !this.profile) return; 184 + 185 + this._followLoading = true; 186 + try { 187 + const update = await mutations.toggleFollow( 188 + this.profile.handle, 189 + this.profile.did, 190 + this.profile.viewerIsFollowing, 191 + this.profile.viewerFollowUri, 192 + this.profile.followerCount || 0 193 + ); 194 + this.profile = { 195 + ...this.profile, 196 + viewerIsFollowing: update.viewerIsFollowing, 197 + viewerFollowUri: update.viewerFollowUri, 198 + followerCount: update.followerCount 199 + }; 200 + } catch (err) { 201 + console.error('Failed to toggle follow:', err); 202 + this.shadowRoot.querySelector('grain-toast').show('Failed to update'); 203 + } finally { 204 + this._followLoading = false; 205 + } 206 + } 207 + 161 208 render() { 162 209 if (!this.profile) return null; 163 210 ··· 181 228 <div class="right-column"> 182 229 <div class="handle-row"> 183 230 <span class="handle">${handle}</span> 231 + ${!this.#isOwnProfile && this._user ? html` 232 + <button 233 + class="follow-button ${this.profile.viewerIsFollowing ? 'following' : ''}" 234 + ?disabled=${this._followLoading} 235 + @click=${this.#handleFollowClick} 236 + > 237 + ${this.profile.viewerIsFollowing ? 'Following' : 'Follow'} 238 + </button> 239 + ` : ''} 184 240 <button class="menu-button" @click=${this.#openMenu}> 185 241 <grain-icon name="ellipsisVertical" size="20"></grain-icon> 186 242 </button>