aboutsummaryrefslogtreecommitdiffstats
path: root/frontend/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components')
-rw-r--r--frontend/src/components/FeedList.css183
-rw-r--r--frontend/src/components/FeedList.test.tsx14
-rw-r--r--frontend/src/components/FeedList.tsx82
3 files changed, 135 insertions, 144 deletions
diff --git a/frontend/src/components/FeedList.css b/frontend/src/components/FeedList.css
index fa0278a..1b83aed 100644
--- a/frontend/src/components/FeedList.css
+++ b/frontend/src/components/FeedList.css
@@ -1,169 +1,134 @@
.feed-list {
- /* Removed card styling */
- padding: 0;
- background: transparent;
+ padding: 1rem;
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ color: var(--text-color);
}
-.search-section {
- margin-bottom: 1.5rem;
+.feed-list h1.logo {
+ font-size: 3rem;
+ margin: 0 0 1rem 0;
+ line-height: 1;
+ cursor: pointer;
}
-.search-form {
- display: flex;
+.search-section {
+ margin-bottom: 1.5rem;
}
.search-input {
width: 100%;
- padding: 0.5rem;
+ padding: 0.25rem;
border: 1px solid var(--border-color, #999);
background: var(--bg-color);
color: var(--text-color);
- font-size: 1rem;
+ font-size: 0.9rem;
font-family: inherit;
}
-.search-input:focus {
- outline: none;
- background: var(--bg-color);
- border-color: var(--link-color);
-}
-
-.feed-list h2,
-.feed-section-header {
- font-size: 1.2rem;
- margin-bottom: 0.5rem;
- border-bottom: 1px solid var(--border-color, #999);
- padding-bottom: 0.25rem;
- text-transform: uppercase;
- letter-spacing: 1px;
+.section-header {
+ font-size: 1rem;
+ font-weight: bold;
+ margin: 1.5rem 0 0.5rem 0;
cursor: pointer;
user-select: none;
- display: flex;
- align-items: center;
-}
-
-.toggle-indicator {
- font-size: 0.8rem;
- margin-right: 0.5rem;
- display: inline-block;
- width: 1rem;
- text-align: center;
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ color: var(--text-color);
}
-.feed-list-items,
+.filter-list,
.tag-list-items,
-.filter-list {
+.feed-list-items,
+.nav-list {
list-style: none;
padding: 0;
margin: 0;
}
-.sidebar-feed-item {
- padding: 0.25rem 0;
- border-bottom: none;
- /* Clean look */
- display: flex;
- justify-content: space-between;
- align-items: center;
+.filter-list li,
+.nav-list li {
+ margin-bottom: 0.25rem;
}
-.feed-title {
- color: var(--link-color);
- text-decoration: none;
- font-size: 0.9rem;
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
-}
-
-.feed-title:hover {
- text-decoration: underline;
- color: var(--link-color);
-}
-
-.feed-category {
- display: none;
- /* Hide category in sidebar list to save space */
-}
-
-.tag-section {
- margin-top: 2rem;
-}
-
-.tag-link {
- color: var(--link-color);
+.filter-list a,
+.nav-list a,
+.tag-link,
+.feed-title,
+.logout-link {
text-decoration: none;
+ color: var(--link-color, blue);
font-size: 0.9rem;
display: block;
- padding: 0.1rem 0;
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ cursor: pointer;
+ background: none;
+ border: none;
+ padding: 0;
+ font-family: inherit;
+ font-variant: small-caps;
+ text-transform: lowercase;
}
-.tag-link:hover {
+.filter-list a:hover,
+.nav-list a:hover,
+.tag-link:hover,
+.feed-title:hover,
+.logout-link:hover {
text-decoration: underline;
- background: transparent;
- color: var(--link-color);
}
-.filter-section {
- margin-bottom: 2rem;
+.filter-list a.active,
+.tag-link.active,
+.feed-title.active {
+ font-weight: bold;
+ color: var(--text-color);
}
-.filter-list {
- display: block;
- list-style: none;
- padding: 0;
- margin: 0;
+.tag-item,
+.sidebar-feed-item {
+ margin-bottom: 0.1rem;
}
-.filter-list li a {
- text-decoration: none;
- color: var(--text-color);
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
- font-variant: small-caps;
- text-transform: lowercase;
- font-size: 1.1rem;
- display: block;
- margin-bottom: 0.5rem;
+.feed-category {
+ display: none;
}
-.filter-list li a:hover {
- color: blue;
- background-color: transparent;
- text-decoration: underline;
+.nav-section {
+ margin-top: 2rem;
+ border-top: 1px solid var(--border-color, #ccc);
+ padding-top: 1rem;
}
-.feed-title.active,
-.tag-link.active,
-.filter-list li a.active,
-.theme-selector button.active {
- font-weight: bold !important;
+.logout-link {
+ text-align: left;
+ width: 100%;
}
.theme-section {
- margin-top: 2rem;
- padding-bottom: 2rem;
+ margin-top: 1rem;
}
.theme-selector {
display: flex;
- justify-content: space-between;
- gap: 5px;
+ gap: 0.5rem;
}
.theme-selector button {
- font-size: 1.2rem;
- padding: 0.5rem;
- width: 48%;
- background: var(--sidebar-bg);
+ background: transparent;
border: 1px solid var(--border-color, #ccc);
+ cursor: pointer;
+ padding: 0.25rem 0.5rem;
+ font-size: 1rem;
border-radius: 4px;
}
-.theme-selector button:hover {
- background: var(--bg-color);
+.theme-selector button.active {
+ background: var(--border-color, #ccc);
}
-.theme-selector button.active {
- background: var(--bg-color);
- border-color: var(--link-color);
- box-shadow: 0 0 5px var(--link-color);
+/* Scrollbar styling for webkit */
+.dashboard-sidebar::-webkit-scrollbar {
+ width: 6px;
+}
+
+.dashboard-sidebar::-webkit-scrollbar-thumb {
+ background-color: var(--border-color, #ccc);
} \ No newline at end of file
diff --git a/frontend/src/components/FeedList.test.tsx b/frontend/src/components/FeedList.test.tsx
index daa4d69..059d8a4 100644
--- a/frontend/src/components/FeedList.test.tsx
+++ b/frontend/src/components/FeedList.test.tsx
@@ -13,11 +13,11 @@ describe('FeedList Component', () => {
});
it('renders loading state initially', () => {
- (global.fetch as any).mockImplementation(() => new Promise(() => {}));
+ (global.fetch as any).mockImplementation(() => new Promise(() => { }));
render(
<BrowserRouter>
{/* @ts-ignore */}
- <FeedList theme="light" setTheme={() => {}} />
+ <FeedList theme="light" setTheme={() => { }} />
</BrowserRouter>
);
expect(screen.getByText(/loading feeds/i)).toBeInTheDocument();
@@ -60,7 +60,7 @@ describe('FeedList Component', () => {
render(
<BrowserRouter>
{/* @ts-ignore */}
- <FeedList theme="light" setTheme={() => {}} />
+ <FeedList theme="light" setTheme={() => { }} />
</BrowserRouter>
);
@@ -69,7 +69,7 @@ describe('FeedList Component', () => {
});
// Expand feeds
- fireEvent.click(screen.getByText(/feeds/i, { selector: 'h2' }));
+ fireEvent.click(screen.getByText(/feeds/i, { selector: 'h4' }));
await waitFor(() => {
expect(screen.getByText('Feed One')).toBeInTheDocument();
@@ -85,7 +85,7 @@ describe('FeedList Component', () => {
render(
<BrowserRouter>
{/* @ts-ignore */}
- <FeedList theme="light" setTheme={() => {}} />
+ <FeedList theme="light" setTheme={() => { }} setSidebarVisible={() => { }} />
</BrowserRouter>
);
@@ -114,7 +114,7 @@ describe('FeedList Component', () => {
render(
<BrowserRouter>
{/* @ts-ignore */}
- <FeedList theme="light" setTheme={() => {}} />
+ <FeedList theme="light" setTheme={() => { }} setSidebarVisible={() => { }} />
</BrowserRouter>
);
@@ -123,7 +123,7 @@ describe('FeedList Component', () => {
});
// Expand feeds
- fireEvent.click(screen.getByText(/feeds/i, { selector: 'h2' }));
+ fireEvent.click(screen.getByText(/feeds/i, { selector: 'h4' }));
await waitFor(() => {
expect(screen.getByText(/no feeds found/i)).toBeInTheDocument();
diff --git a/frontend/src/components/FeedList.tsx b/frontend/src/components/FeedList.tsx
index 1cd1bfd..4ce2d71 100644
--- a/frontend/src/components/FeedList.tsx
+++ b/frontend/src/components/FeedList.tsx
@@ -7,9 +7,11 @@ import { apiFetch } from '../utils';
export default function FeedList({
theme,
setTheme,
+ setSidebarVisible,
}: {
theme: string;
setTheme: (t: string) => void;
+ setSidebarVisible: (visible: boolean) => void;
}) {
const [feeds, setFeeds] = useState<Feed[]>([]);
const [tags, setTags] = useState<Category[]>([]);
@@ -62,42 +64,70 @@ export default function FeedList({
if (loading) return <div className="feed-list-loading">Loading feeds...</div>;
if (error) return <div className="feed-list-error">Error: {error}</div>;
+ const handleLogout = () => {
+ apiFetch('/api/logout', { method: 'POST' }).then(() => (window.location.href = '/v2/login'));
+ };
+
return (
<div className="feed-list">
+ <h1 className="logo" onClick={() => setSidebarVisible(false)}>
+ 🐱
+ </h1>
+
<div className="search-section">
<form onSubmit={handleSearch} className="search-form">
<input
type="search"
- placeholder="Search items..."
+ placeholder="search..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="search-input"
/>
</form>
</div>
+
<div className="filter-section">
<ul className="filter-list">
- <li>
+ <li className="unread_filter">
<Link to="/?filter=unread" className={currentFilter === 'unread' ? 'active' : ''}>
- Unread
+ unread
</Link>
</li>
- <li>
+ <li className="all_filter">
<Link to="/?filter=all" className={currentFilter === 'all' ? 'active' : ''}>
- All
+ all
</Link>
</li>
- <li>
+ <li className="starred_filter">
<Link to="/?filter=starred" className={currentFilter === 'starred' ? 'active' : ''}>
- Starred
+ starred
</Link>
</li>
</ul>
</div>
+
+ <div className="tag-section">
+ <h4 onClick={() => { }} className="section-header">
+ Tags
+ </h4>
+ <ul className="tag-list-items">
+ {tags.map((tag) => (
+ <li key={tag.title} className="tag-item">
+ <Link
+ to={`/tag/${encodeURIComponent(tag.title)}`}
+ className={`tag-link ${tagName === tag.title ? 'active' : ''}`}
+ >
+ {tag.title}
+ </Link>
+ </li>
+ ))}
+ </ul>
+ </div>
+
<div className="feed-section">
- <h2 onClick={toggleFeeds} className="feed-section-header">
- <span className="toggle-indicator">{feedsExpanded ? '▼' : '▶'}</span> Feeds
- </h2>
+ <h4 onClick={toggleFeeds} className="section-header">
+ Feeds
+ </h4>
{feedsExpanded &&
(feeds.length === 0 ? (
<p>No feeds found.</p>
@@ -111,30 +141,26 @@ export default function FeedList({
>
{feed.title || feed.url}
</Link>
- {feed.category && <span className="feed-category">{feed.category}</span>}
</li>
))}
</ul>
))}
</div>
- {tags && tags.length > 0 && (
- <div className="tag-section">
- <h2>Tags</h2>
- <ul className="tag-list-items">
- {tags.map((tag) => (
- <li key={tag.title} className="tag-item">
- <Link
- to={`/tag/${encodeURIComponent(tag.title)}`}
- className={`tag-link ${tagName === tag.title ? 'active' : ''}`}
- >
- {tag.title}
- </Link>
- </li>
- ))}
- </ul>
- </div>
- )}
+ <div className="nav-section">
+ <ul className="nav-list">
+ <li>
+ <Link to="/settings" className="nav-link">
+ settings
+ </Link>
+ </li>
+ <li>
+ <button onClick={handleLogout} className="logout-link">
+ logout
+ </button>
+ </li>
+ </ul>
+ </div>
<div className="theme-section">
<div className="theme-selector">