mirror of
https://github.com/dergigi/boris.git
synced 2025-12-19 07:34:28 +01:00
feat: add /me page to show user's recent highlights
- Create Me component to display logged-in user's highlights - Add /me route to App routing - Update SidebarHeader to navigate to /me when clicking profile avatar - Integrate Me page in ThreePaneLayout (same as Settings/Explore) - Show user profile info and highlight count - List all highlights created by the user Clicking the profile picture now takes you to your personal highlights page.
This commit is contained in:
@@ -69,6 +69,15 @@ function AppRoutes({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/me"
|
||||||
|
element={
|
||||||
|
<Bookmarks
|
||||||
|
relayPool={relayPool}
|
||||||
|
onLogout={handleLogout}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route path="/" element={<Navigate to={`/a/${DEFAULT_ARTICLE}`} replace />} />
|
<Route path="/" element={<Navigate to={`/a/${DEFAULT_ARTICLE}`} replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { useRelayStatus } from '../hooks/useRelayStatus'
|
|||||||
import { useOfflineSync } from '../hooks/useOfflineSync'
|
import { useOfflineSync } from '../hooks/useOfflineSync'
|
||||||
import ThreePaneLayout from './ThreePaneLayout'
|
import ThreePaneLayout from './ThreePaneLayout'
|
||||||
import Explore from './Explore'
|
import Explore from './Explore'
|
||||||
|
import Me from './Me'
|
||||||
import { classifyHighlights } from '../utils/highlightClassification'
|
import { classifyHighlights } from '../utils/highlightClassification'
|
||||||
|
|
||||||
export type ViewMode = 'compact' | 'cards' | 'large'
|
export type ViewMode = 'compact' | 'cards' | 'large'
|
||||||
@@ -35,13 +36,14 @@ const Bookmarks: React.FC<BookmarksProps> = ({ relayPool, onLogout }) => {
|
|||||||
|
|
||||||
const showSettings = location.pathname === '/settings'
|
const showSettings = location.pathname === '/settings'
|
||||||
const showExplore = location.pathname === '/explore'
|
const showExplore = location.pathname === '/explore'
|
||||||
|
const showMe = location.pathname === '/me'
|
||||||
|
|
||||||
// Track previous location for going back from settings
|
// Track previous location for going back from settings/me/explore
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showSettings) {
|
if (!showSettings && !showMe && !showExplore) {
|
||||||
previousLocationRef.current = location.pathname
|
previousLocationRef.current = location.pathname
|
||||||
}
|
}
|
||||||
}, [location.pathname, showSettings])
|
}, [location.pathname, showSettings, showMe, showExplore])
|
||||||
|
|
||||||
const activeAccount = Hooks.useActiveAccount()
|
const activeAccount = Hooks.useActiveAccount()
|
||||||
const accountManager = Hooks.useAccountManager()
|
const accountManager = Hooks.useAccountManager()
|
||||||
@@ -202,6 +204,7 @@ const Bookmarks: React.FC<BookmarksProps> = ({ relayPool, onLogout }) => {
|
|||||||
isSidebarOpen={isSidebarOpen}
|
isSidebarOpen={isSidebarOpen}
|
||||||
showSettings={showSettings}
|
showSettings={showSettings}
|
||||||
showExplore={showExplore}
|
showExplore={showExplore}
|
||||||
|
showMe={showMe}
|
||||||
bookmarks={bookmarks}
|
bookmarks={bookmarks}
|
||||||
bookmarksLoading={bookmarksLoading}
|
bookmarksLoading={bookmarksLoading}
|
||||||
viewMode={viewMode}
|
viewMode={viewMode}
|
||||||
@@ -257,6 +260,9 @@ const Bookmarks: React.FC<BookmarksProps> = ({ relayPool, onLogout }) => {
|
|||||||
explore={showExplore ? (
|
explore={showExplore ? (
|
||||||
relayPool ? <Explore relayPool={relayPool} /> : null
|
relayPool ? <Explore relayPool={relayPool} /> : null
|
||||||
) : undefined}
|
) : undefined}
|
||||||
|
me={showMe ? (
|
||||||
|
relayPool ? <Me relayPool={relayPool} /> : null
|
||||||
|
) : undefined}
|
||||||
toastMessage={toastMessage ?? undefined}
|
toastMessage={toastMessage ?? undefined}
|
||||||
toastType={toastType}
|
toastType={toastType}
|
||||||
onClearToast={clearToast}
|
onClearToast={clearToast}
|
||||||
|
|||||||
113
src/components/Me.tsx
Normal file
113
src/components/Me.tsx
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import React, { useState, useEffect } from 'react'
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import { faSpinner, faExclamationCircle, faUser, faHighlighter } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { Hooks } from 'applesauce-react'
|
||||||
|
import { RelayPool } from 'applesauce-relay'
|
||||||
|
import { useEventModel } from 'applesauce-react/hooks'
|
||||||
|
import { Models } from 'applesauce-core'
|
||||||
|
import { Highlight } from '../types/highlights'
|
||||||
|
import { HighlightItem } from './HighlightItem'
|
||||||
|
import { fetchHighlights } from '../services/highlightService'
|
||||||
|
|
||||||
|
interface MeProps {
|
||||||
|
relayPool: RelayPool
|
||||||
|
}
|
||||||
|
|
||||||
|
const Me: React.FC<MeProps> = ({ relayPool }) => {
|
||||||
|
const activeAccount = Hooks.useActiveAccount()
|
||||||
|
const [highlights, setHighlights] = useState<Highlight[]>([])
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const profile = useEventModel(Models.ProfileModel, activeAccount ? [activeAccount.pubkey] : null)
|
||||||
|
|
||||||
|
const getUserDisplayName = () => {
|
||||||
|
if (!activeAccount) return 'Unknown User'
|
||||||
|
if (profile?.name) return profile.name
|
||||||
|
if (profile?.display_name) return profile.display_name
|
||||||
|
if (profile?.nip05) return profile.nip05
|
||||||
|
return `${activeAccount.pubkey.slice(0, 8)}...${activeAccount.pubkey.slice(-8)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadHighlights = async () => {
|
||||||
|
if (!activeAccount) {
|
||||||
|
setError('Please log in to view your highlights')
|
||||||
|
setLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
setError(null)
|
||||||
|
|
||||||
|
// Fetch highlights created by the user
|
||||||
|
const userHighlights = await fetchHighlights(
|
||||||
|
relayPool,
|
||||||
|
activeAccount.pubkey
|
||||||
|
)
|
||||||
|
|
||||||
|
if (userHighlights.length === 0) {
|
||||||
|
setError('No highlights yet. Start highlighting content to see them here!')
|
||||||
|
}
|
||||||
|
|
||||||
|
setHighlights(userHighlights)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load highlights:', err)
|
||||||
|
setError('Failed to load highlights. Please try again.')
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadHighlights()
|
||||||
|
}, [relayPool, activeAccount])
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="explore-container">
|
||||||
|
<div className="explore-loading">
|
||||||
|
<FontAwesomeIcon icon={faSpinner} spin size="2x" />
|
||||||
|
<p>Loading your highlights...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="explore-container">
|
||||||
|
<div className="explore-error">
|
||||||
|
<FontAwesomeIcon icon={faExclamationCircle} size="2x" />
|
||||||
|
<p>{error}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="explore-container">
|
||||||
|
<div className="explore-header">
|
||||||
|
<h1>
|
||||||
|
<FontAwesomeIcon icon={faUser} />
|
||||||
|
{getUserDisplayName()}
|
||||||
|
</h1>
|
||||||
|
<p className="explore-subtitle">
|
||||||
|
<FontAwesomeIcon icon={faHighlighter} /> {highlights.length} highlight{highlights.length !== 1 ? 's' : ''}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="highlights-list me-highlights-list">
|
||||||
|
{highlights.map((highlight) => (
|
||||||
|
<HighlightItem
|
||||||
|
key={highlight.id}
|
||||||
|
highlight={highlight}
|
||||||
|
relayPool={relayPool}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Me
|
||||||
|
|
||||||
@@ -90,8 +90,12 @@ const SidebarHeader: React.FC<SidebarHeaderProps> = ({ onToggleCollapse, onLogou
|
|||||||
<div
|
<div
|
||||||
className="profile-avatar"
|
className="profile-avatar"
|
||||||
title={activeAccount ? getUserDisplayName() : "Login"}
|
title={activeAccount ? getUserDisplayName() : "Login"}
|
||||||
onClick={!activeAccount ? (isConnecting ? () => {} : handleLogin) : undefined}
|
onClick={
|
||||||
style={{ cursor: !activeAccount ? 'pointer' : 'default' }}
|
activeAccount
|
||||||
|
? () => navigate('/me')
|
||||||
|
: (isConnecting ? () => {} : handleLogin)
|
||||||
|
}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
>
|
>
|
||||||
{profileImage ? (
|
{profileImage ? (
|
||||||
<img src={profileImage} alt={getUserDisplayName()} />
|
<img src={profileImage} alt={getUserDisplayName()} />
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ interface ThreePaneLayoutProps {
|
|||||||
isSidebarOpen: boolean
|
isSidebarOpen: boolean
|
||||||
showSettings: boolean
|
showSettings: boolean
|
||||||
showExplore?: boolean
|
showExplore?: boolean
|
||||||
|
showMe?: boolean
|
||||||
|
|
||||||
// Bookmarks pane
|
// Bookmarks pane
|
||||||
bookmarks: Bookmark[]
|
bookmarks: Bookmark[]
|
||||||
@@ -81,6 +82,9 @@ interface ThreePaneLayoutProps {
|
|||||||
|
|
||||||
// Optional Explore content
|
// Optional Explore content
|
||||||
explore?: React.ReactNode
|
explore?: React.ReactNode
|
||||||
|
|
||||||
|
// Optional Me content
|
||||||
|
me?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const ThreePaneLayout: React.FC<ThreePaneLayoutProps> = (props) => {
|
const ThreePaneLayout: React.FC<ThreePaneLayoutProps> = (props) => {
|
||||||
@@ -288,6 +292,11 @@ const ThreePaneLayout: React.FC<ThreePaneLayoutProps> = (props) => {
|
|||||||
<>
|
<>
|
||||||
{props.explore}
|
{props.explore}
|
||||||
</>
|
</>
|
||||||
|
) : props.showMe && props.me ? (
|
||||||
|
// Render Me inside the main pane to keep side panels
|
||||||
|
<>
|
||||||
|
{props.me}
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<ContentPanel
|
<ContentPanel
|
||||||
loading={props.readerLoading}
|
loading={props.readerLoading}
|
||||||
|
|||||||
Reference in New Issue
Block a user