✨ Features added: - 🔍 Implement functional search in header navigation - ⚙️ Add basic user settings page - 📱 Make dashboard fully responsive 🔧 Enhancements: - 🎭 Standardize CSS with consistent theming across components - 🧹 Remove unused CSS for better performance - 📊 Improve dashboard layout and visual hierarchy - 📦 Redesign last block widget for better usability 💅 This commit introduces a cohesive design system with functional design-token components for a more ✨ polished user experience.
364 lines
8.9 KiB
TypeScript
364 lines
8.9 KiB
TypeScript
import type { NDKUserProfile } from '@nostr-dev-kit/ndk';
|
|
import { LitElement, css, html } from 'lit';
|
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
import { when } from 'lit/directives/when.js';
|
|
import { getUserProfile } from '../ndk';
|
|
|
|
import '@components/General/Card';
|
|
|
|
@customElement('arx-profile-route')
|
|
export class NostrProfile extends LitElement {
|
|
@property({ type: String })
|
|
npub = '';
|
|
|
|
@state()
|
|
private profile: NDKUserProfile | undefined;
|
|
|
|
@state()
|
|
private error: string | null = null;
|
|
|
|
static override styles = css`
|
|
:host {
|
|
display: block;
|
|
}
|
|
|
|
.banner-container {
|
|
position: relative;
|
|
height: 20rem;
|
|
overflow: hidden;
|
|
border-radius: var(--radius-box) var(--radius-box) 0 0;
|
|
border: var(--border) solid var(--color-base-300);
|
|
border-bottom: none;
|
|
}
|
|
|
|
.banner-image {
|
|
position: absolute;
|
|
inset: 0;
|
|
transform: scale(1);
|
|
transition: transform 700ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.banner-image:hover {
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.banner-image img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.banner-overlay {
|
|
position: absolute;
|
|
inset: 0;
|
|
background: linear-gradient(
|
|
to bottom,
|
|
transparent,
|
|
oklch(from var(--color-base-content) l c h / 0.2),
|
|
oklch(from var(--color-base-content) l c h / 0.4)
|
|
);
|
|
}
|
|
|
|
.profile-container {
|
|
max-width: 64rem;
|
|
margin: 0 auto;
|
|
padding: 0 1rem;
|
|
position: relative;
|
|
z-index: 10;
|
|
}
|
|
|
|
.profile-container.with-banner {
|
|
margin-top: -8rem;
|
|
}
|
|
|
|
.profile-container.no-banner {
|
|
margin-top: 4rem;
|
|
}
|
|
|
|
.profile-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1.5rem;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.profile-content {
|
|
flex-direction: row;
|
|
align-items: flex-start;
|
|
}
|
|
}
|
|
|
|
.profile-image-container {
|
|
position: relative;
|
|
}
|
|
|
|
.profile-image {
|
|
width: 10rem;
|
|
height: 10rem;
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
border: calc(var(--border) * 2) solid var(--color-base-100);
|
|
box-shadow: calc(var(--depth) * 2px) calc(var(--depth) * 2px)
|
|
calc(var(--depth) * 4px)
|
|
oklch(from var(--color-base-content) l c h / 0.15);
|
|
transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.profile-image:hover {
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.profile-image-placeholder {
|
|
width: 10rem;
|
|
height: 10rem;
|
|
border-radius: 50%;
|
|
background: linear-gradient(
|
|
135deg,
|
|
var(--color-base-200),
|
|
var(--color-base-300)
|
|
);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: calc(var(--border) * 2) solid var(--color-base-100);
|
|
box-shadow: calc(var(--depth) * 2px) calc(var(--depth) * 2px)
|
|
calc(var(--depth) * 4px)
|
|
oklch(from var(--color-base-content) l c h / 0.15);
|
|
}
|
|
|
|
.placeholder-icon {
|
|
width: 5rem;
|
|
height: 5rem;
|
|
color: var(--color-secondary);
|
|
}
|
|
|
|
.profile-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.profile-header {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.profile-header {
|
|
flex-direction: row;
|
|
align-items: center;
|
|
}
|
|
}
|
|
|
|
.display-name {
|
|
font-size: 1.875rem;
|
|
font-weight: bold;
|
|
color: var(--color-base-content);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
margin: 0;
|
|
}
|
|
|
|
.verified-icon {
|
|
color: var(--color-accent);
|
|
}
|
|
|
|
.nip05 {
|
|
color: var(--color-secondary);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
margin-top: 0.25rem;
|
|
}
|
|
|
|
.action-buttons {
|
|
display: flex;
|
|
gap: 0.75rem;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.action-buttons {
|
|
margin-top: 0;
|
|
}
|
|
}
|
|
|
|
.links-section {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
gap: 1.5rem;
|
|
margin-top: 2rem;
|
|
padding-top: 1.5rem;
|
|
border-top: var(--border) solid var(--color-base-300);
|
|
}
|
|
|
|
.link-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 1rem;
|
|
border-radius: var(--radius-field);
|
|
background-color: var(--color-base-200);
|
|
transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
text-decoration: none;
|
|
color: var(--color-base-content);
|
|
}
|
|
|
|
.link-item:hover {
|
|
background-color: var(--color-base-300);
|
|
transform: translateY(-2px);
|
|
box-shadow: calc(var(--depth) * 2px) calc(var(--depth) * 2px)
|
|
calc(var(--depth) * 4px)
|
|
oklch(from var(--color-base-content) l c h / 0.1);
|
|
}
|
|
|
|
.link-icon {
|
|
width: 1.5rem;
|
|
height: 1.5rem;
|
|
}
|
|
|
|
.link-icon.website {
|
|
color: var(--color-accent);
|
|
}
|
|
|
|
.link-icon.lightning {
|
|
color: var(--color-warning);
|
|
}
|
|
|
|
.bio {
|
|
white-space: pre-line;
|
|
font-size: 0.9rem;
|
|
color: var(--color-secondary);
|
|
margin: 1rem 0;
|
|
padding: 1rem;
|
|
line-height: 1.6;
|
|
background-color: var(--color-base-200);
|
|
border-radius: var(--radius-field);
|
|
border-left: 3px solid var(--color-accent);
|
|
}
|
|
`;
|
|
|
|
protected override async firstUpdated() {
|
|
try {
|
|
this.profile = await getUserProfile(this.npub);
|
|
} catch (err) {
|
|
this.error = 'Failed to load profile';
|
|
console.error(err);
|
|
}
|
|
}
|
|
|
|
private get displayName() {
|
|
if (!this.profile) return this.npub;
|
|
return this.profile.displayName || this.profile.name || this.npub.substring(0, 8);
|
|
}
|
|
|
|
override render() {
|
|
if (this.error) {
|
|
return html`<arx-error-view .error=${this.error}></arx-error-view>`;
|
|
}
|
|
|
|
if (!this.profile) {
|
|
return html`<arx-loading-view></arx-loading-view>`;
|
|
}
|
|
|
|
return html`
|
|
${when(
|
|
this.profile.banner,
|
|
() => html`
|
|
<div class="banner-container">
|
|
<div class="banner-image">
|
|
<img src=${this.profile!.banner} alt="Banner" />
|
|
</div>
|
|
<div class="banner-overlay"></div>
|
|
</div>
|
|
`,
|
|
)}
|
|
|
|
<div
|
|
class=${this.profile.banner ? 'profile-container with-banner' : 'profile-container no-banner'}
|
|
>
|
|
<arx-card>
|
|
<div class="profile-content">
|
|
<div class="profile-image-container">
|
|
${when(
|
|
this.profile.image,
|
|
() =>
|
|
html`<img
|
|
src=${this.profile!.image}
|
|
alt="Profile"
|
|
class="profile-image"
|
|
/>`,
|
|
() => html`
|
|
<div class="profile-image-placeholder">
|
|
<svg-icon
|
|
icon="mdi:account"
|
|
class="placeholder-icon"
|
|
></svg-icon>
|
|
</div>
|
|
`,
|
|
)}
|
|
</div>
|
|
|
|
<div class="profile-info">
|
|
<div class="profile-header">
|
|
<div>
|
|
<h1 class="display-name">
|
|
${this.displayName}
|
|
${when(
|
|
this.profile.verified,
|
|
() => html`
|
|
<span class="verified-icon">
|
|
<svg-icon icon="mdi:check-decagram"></svg-icon>
|
|
</span>
|
|
`,
|
|
)}
|
|
</h1>
|
|
${when(
|
|
this.profile.nip05,
|
|
() => html`
|
|
<p class="nip05">
|
|
<svg-icon icon="mdi:at"></svg-icon>
|
|
${this.profile!.nip05}
|
|
</p>
|
|
`,
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
${when(this.profile.about, () => html` <p class="bio">${this.profile!.about}</p> `)}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="links-section">
|
|
${when(
|
|
this.profile.website,
|
|
() => html`
|
|
<a
|
|
href=${this.profile!.website}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="link-item"
|
|
>
|
|
<svg-icon icon="mdi:web" class="link-icon website"></svg-icon>
|
|
<span>${this.profile!.website}</span>
|
|
</a>
|
|
`,
|
|
)}
|
|
${when(
|
|
this.profile.lud16,
|
|
() => html`
|
|
<a href="lightning:${this.profile!.lud16}" class="link-item">
|
|
<svg-icon
|
|
icon="mdi:lightning-bolt"
|
|
class="link-icon lightning"
|
|
></svg-icon>
|
|
<span>${this.profile!.lud16}</span>
|
|
</a>
|
|
`,
|
|
)}
|
|
</div>
|
|
</arx-card>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|