diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 2524b9f..f0bc19a 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useRef, useState } from "react"; import { LayoutGrid, List, @@ -6,6 +6,7 @@ import { Moon, Palette, Search, + Shield, Sun, } from "lucide-react"; import { SCHEMES, type Scheme, type ThemePrefs } from "../lib/theme"; @@ -129,19 +130,78 @@ export default function Header({ )} -
- {me.avatar_url ? ( - - ) : ( -
- {me.email[0]?.toUpperCase()} -
- )} - - - +
+
); } + +function Avatar({ me, className = "" }: { me: Me; className?: string }) { + return me.avatar_url ? ( + + ) : ( +
+ {(me.display_name?.[0] ?? me.email[0] ?? "?").toUpperCase()} +
+ ); +} + +function AccountMenu({ me, logout }: { me: Me; logout: () => void }) { + const [open, setOpen] = useState(false); + const closeTimer = useRef | null>(null); + + function openNow() { + if (closeTimer.current) clearTimeout(closeTimer.current); + setOpen(true); + } + function closeSoon() { + closeTimer.current = setTimeout(() => setOpen(false), 220); + } + + return ( +
+ + + {open && ( +
+
+ +
+
+ {me.display_name ?? me.email.split("@")[0]} +
+
{me.email}
+
+
+ + {me.role === "admin" && ( +
+ + Admin +
+ )} + + +
+ )} +
+ ); +} diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index dcc1a0a..24944d0 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -1,7 +1,27 @@ +import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { X } from "lucide-react"; import { api, type FeedFilters, type Tag } from "../lib/api"; +const DATE_PRESETS: { days: number; label: string }[] = [ + { days: 1, label: "24h" }, + { days: 7, label: "1 week" }, + { days: 30, label: "1 month" }, + { days: 180, label: "6 months" }, + { days: 365, label: "1 year" }, +]; + +// Filter values owned by the sidebar (everything except the header search `q`). +const DEFAULT_SIDEBAR_FILTERS: Omit = { + tags: [], + tagMode: "or", + sort: "newest", + includeNormal: true, + includeShorts: false, + includeLive: false, + show: "unwatched", +}; + const SORTS = [ { id: "newest", label: "Newest" }, { id: "oldest", label: "Oldest" }, @@ -56,6 +76,7 @@ export default function Sidebar({ const tags = tagsQuery.data ?? []; const languages = tags.filter((t) => t.category === "language"); const topics = tags.filter((t) => t.category === "topic"); + const [customDates, setCustomDates] = useState(false); function toggleTag(id: number) { const has = filters.tags.includes(id); @@ -65,19 +86,44 @@ export default function Sidebar({ }); } - const active = - filters.tags.length > 0 || - !filters.includeNormal || - filters.includeShorts || - filters.includeLive || - filters.show !== "unwatched" || - filters.sort !== "newest" || - !!filters.channelId || - !!filters.dateFrom || - !!filters.dateTo; + const dateActive = !!filters.maxAgeDays || !!filters.dateFrom || !!filters.dateTo; + const contentChanged = + !filters.includeNormal || filters.includeShorts || filters.includeLive; + const activeCount = + filters.tags.length + + (filters.show !== "unwatched" ? 1 : 0) + + (filters.sort !== "newest" ? 1 : 0) + + (contentChanged ? 1 : 0) + + (filters.channelId ? 1 : 0) + + (dateActive ? 1 : 0); + const active = activeCount > 0; + + function clearAll() { + setCustomDates(false); + setFilters({ + ...DEFAULT_SIDEBAR_FILTERS, + q: filters.q, + }); + } return ( ); }