centralized state in the header component

This commit is contained in:
miggymofongo 2024-12-07 16:53:16 -04:00
parent 9fd3d47d25
commit 2dfbc08788
3 changed files with 98 additions and 73 deletions

View file

@ -63,62 +63,56 @@ export class AppHeader extends LitElement {
} }
} }
`; `;
/** the connected callback method one of the lifecycle methods that runs once the element is added
* to the dom. there is no disconnectedCallback because the header is never removed from the DOM.
* i centralize state in this component because it's where the sign in and out button resides.
*
*
*/
connectedCallback(): void { connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
//is there a stored pubkey in localStorage already? // Check for stored pubkey and fetch metadata if available
const storedPubkey = localStorage.getItem('pubkey'); const storedPubkey = localStorage.getItem('pubkey');
if (storedPubkey) { if (storedPubkey) {
this.isSignedIn = false; this.isSignedIn = true;
this.fetchProfileMetadata(storedPubkey); this.fetchProfileMetadata(storedPubkey);
} else { } else {
// Show guest view by default
this.displayGuestView(); this.displayGuestView();
}} }
}
displayGuestView() { displayGuestView() {
// Set initial values for guest view // this method displays a guest view when the user is signed out
this.nostrAddy = ''; this.nostrAddy = '';
this.bio = 'Welcome, guest! Please sign in with a browser extension to view your profile.'; this.bio = 'Welcome, guest! Please sign in with a browser extension to view your profile.';
this.profilePic = '/public/assets/img/default_pfp.png'; // Could be a placeholder image for guests this.profilePic = '/public/assets/img/default_pfp.png'; // a placeholder profile picture
this.isSignedIn = false; this.isSignedIn = false;
this.dispatchUserStateChanged();
} }
async fetchProfileMetadata(pubkey: string) { async fetchProfileMetadata(pubkey: string) {
try {
const relay = await Relay.connect('wss://notes.miguelalmodo.com'); const relay = await Relay.connect('wss://notes.miguelalmodo.com');
console.log(`connected to ${relay.url}`); const sub = relay.subscribe(
[{ kinds: [0], authors: [pubkey] }],
// subscribe to my profile metadata from the relay
const sub = relay.subscribe([
{ {
kinds: [0], // profile metadata
authors: [pubkey], //will insert the pubkey of the user who signed in
}
], {
onevent: (event) => { onevent: (event) => {
const profileData = JSON.parse(event.content); const profileData = JSON.parse(event.content);
this.profilePic = profileData.picture || '/assets/img/default_pfp.png'; this.profilePic = profileData.picture || '/assets/img/default_pfp.png';
this.nostrAddy = profileData.nip05 || 'no addy available'; this.nostrAddy = profileData.nip05 || 'no addy available';
this.bio = profileData.about || 'bio not available'; this.bio = profileData.about || 'bio not available';
this.dispatchEvent(new CustomEvent('profile-updated' , { this.dispatchUserStateChanged();
detail: { nostrAddy: this.nostrAddy, bio: this.bio, profilePic: this.profilePic },
bubbles: true,
composed: true
}));
console.log('Profile updated event dispatched:', { nostrAddy: this.nostrAddy, bio: this.bio, profilePic: this.profilePic });
}, },
oneose: () => { oneose: () => sub.close(),
sub.close();
} }
}); );
} catch (error: any) { } catch (error) {
console.error('Failed to fetch profile metadata:', error); console.error('Failed to fetch profile metadata:', error);
this.profilePic = '/assets/img/default_pfp.png'; this.displayGuestView();
this.nostrAddy = 'failed to fetch address'; }
this.bio = 'failed to fetch profile'
} }
async signInWithNostr() { async signInWithNostr() {
@ -140,19 +134,26 @@ export class AppHeader extends LitElement {
} }
} }
/* this sign out method clears the public key from local storage and sets the profile view
back to guest view */
signOut() { signOut() {
// clear pubkey from localStorage and reset to guest view
localStorage.removeItem('pubkey'); localStorage.removeItem('pubkey');
this.displayGuestView(); this.displayGuestView();
}
this.isSignedIn = false; dispatchUserStateChanged() {
this.dispatchEvent(
console.log('signed out') new CustomEvent('user-state-changed', {
detail: {
this.dispatchEvent( new CustomEvent('user-signed-out', { isSignedIn: this.isSignedIn,
nostrAddy: this.nostrAddy,
bio: this.bio,
profilePic: this.profilePic,
},
bubbles: true, bubbles: true,
composed: true composed: true,
})); })
);
} }
render() { render() {

View file

@ -117,18 +117,18 @@ Profile Picture Container
connectedCallback(): void { connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
window.addEventListener('profile-updated', this.updateProfileFromEvent.bind(this));
this.addEventListener('user-signed-out', this.handlesSignOut); this.addEventListener('user-state-changed', (event: Event) => {
const storedPubkey = localStorage.getItem('pubkey'); const detail = (event as CustomEvent).detail;
if (storedPubkey) { this.nostrAddy = detail.nostrAddy;
// manually fetch profile metadata on mount this.bio = detail.bio;
this.nostrAddy = 'loading...'; this.profilePic = detail.profilePic;
this.bio = 'loading profile...'; this.isSignedIn = detail.isSignedIn;
this.profilePic = '/assets/img/default_pfp.png'; });
}
} }
disconnectedCallback(): void { disconnectedCallback(): void {
super.disconnectedCallback(); super.disconnectedCallback();
window.removeEventListener('profile-updated', this.updateProfileFromEvent.bind(this)); window.removeEventListener('profile-updated', this.updateProfileFromEvent.bind(this));
@ -154,12 +154,36 @@ Profile Picture Container
this.requestUpdate(); this.requestUpdate();
} }
async fetchAndDisplayProfile(pubkey: string) {
this.nostrAddy = pubkey;
this.bio = 'Loading profile info...';
this.profilePic = '/assets/img/loading_pfp.png';
try {
// Fetch profile metadata from a relay
const relay = await Relay.connect('wss://notes.miguelalmodo.com'); // Example URL
const sub = relay.subscribe([{ kinds: [0], authors: [pubkey] }], {
onevent: (event) => {
const profileData = JSON.parse(event.content);
this.nostrAddy = profileData.nip05 || 'No address available';
this.bio = profileData.about || 'No bio available';
this.profilePic = profileData.picture || '/assets/img/default_pfp.png';
this.requestUpdate();
},
oneose: () => sub.close(),
});
} catch (error) {
console.error('Error fetching profile:', error);
this.displayGuestView();
}
}
displayGuestView() { displayGuestView() {
// Set initial values for guest view // Set initial values for guest view
this.nostrAddy = ''; this.nostrAddy = '';
this.bio = 'Welcome, guest! Please sign in with a browser extension to view your profile.'; this.bio = 'Welcome, guest! Please sign in with a browser extension to view your profile.';
this.profilePic = ''; // Could be a placeholder image for guests this.profilePic = '/assets/img/default_pfp.png'; // Could be a placeholder image for guests
this.isSignedIn = false; this.isSignedIn = false;
} }

View file

@ -160,6 +160,7 @@ export class NoteWall extends LitElement {
this.fetchNotes(), this.fetchNotes(),
]); ]);
console.log('initial notes:', this.notes);
} }
@ -192,17 +193,12 @@ async fetchNotes() {
} }
], { ], {
onevent: async (event) => { onevent: async (event) => {
/* // Translate the note content to the user's language console.log('Event received:', event);
const userLang = this.getUserLang(); this.notes = [...this.notes, {
// const translatedContent = await this.translateText(event.content, userLang); content: event.content,
this.notes.push({
content: translatedContent,
date: new Date(event.created_at * 1000).toLocaleDateString(), date: new Date(event.created_at * 1000).toLocaleDateString(),
}); */ }];
this.requestUpdate(); this.requestUpdate();
console.log(event);
}, },
oneose: () => { oneose: () => {
sub.close(); sub.close();
@ -243,20 +239,24 @@ async fetchNotes() {
<table> <table>
${this.notes.map(note => { ${this.notes.map(note => {
// Extract URL from note content // extract URL from note content
const urlMatch = note.content.match(/https?:\/\/[^\s]+/); const urlMatch = note.content.match(/https?:\/\/[^\s]+/);
const textContent = note.content.replace(urlMatch?.[0] || '', '').trim(); const textContent = note.content.replace(urlMatch?.[0] || '', '').trim();
// Check for YouTube links and extract video ID // Check for yt links and extract video ID
const youtubeRegex = /(?:https?:\/\/(?:www\.)?youtube\.com\/watch\?v=|https?:\/\/youtu\.be\/)([a-zA-Z0-9_-]{11})/; const youtubeRegex = /(?:https?:\/\/(?:www\.)?youtube\.com\/watch\?v=|https?:\/\/youtu\.be\/)([a-zA-Z0-9_-]{11})/;
const youtubeMatch = urlMatch?.[0].match(youtubeRegex); const youtubeMatch = urlMatch?.[0].match(youtubeRegex);
const youtubeVideoId = youtubeMatch ? youtubeMatch[1] : null; const youtubeVideoId = youtubeMatch ? youtubeMatch[1] : null;
// Construct YouTube thumbnail URL if applicable // build yt thumbnail URL if applicable
const thumbnailUrl = youtubeVideoId const thumbnailUrl = youtubeVideoId
? `https://img.youtube.com/vi/${youtubeVideoId}/maxresdefault.jpg` ? `https://img.youtube.com/vi/${youtubeVideoId}/maxresdefault.jpg`
: null; : null;
//check for image links
const imgRegex = /(https?:\/\/[^\s]+\.(?:jpg|jpeg|png|gif))/i;
const imgMatch = urlMatch?.find(url => imgRegex.test(url));
return html` return html`
<tr> <tr>
<td><h3>${note.date}</h3></td> <td><h3>${note.date}</h3></td>
@ -264,8 +264,8 @@ async fetchNotes() {
<p>${textContent}</p> <p>${textContent}</p>
${thumbnailUrl ${thumbnailUrl
? html`<img src="${thumbnailUrl}" alt="YouTube thumbnail" style="max-width: 30%; height: 30%;">` ? html`<img src="${thumbnailUrl}" alt="YouTube thumbnail" style="max-width: 30%; height: 30%;">`
: urlMatch : imgMatch
? html`<img src="${urlMatch[0]}" alt="Note image" style="max-width: 30%; height: 30%;">` ? html`<img src="${imgMatch}" alt="Note image" style="max-width: 30%; height: 30%;">`
: ''} : ''}