@@ -7,6 +7,13 @@ import { SourceCard } from "@/components/library/source-card";
77import { api } from "@/lib/api" ;
88import type { MemorySource , SourceType } from "@/lib/types" ;
99
10+ // Tab consolidations
11+ import { Tabs , TabsContent , TabsList , TabsTrigger } from "@/components/ui/tabs" ;
12+ import { JournalPage } from "@/components/journal/journal-page" ;
13+ import { VoicePage } from "@/components/voice/voice-page" ;
14+ import { MailShell } from "@/components/mail/mail-shell" ;
15+ import { MemoryInboxPage } from "@/components/inbox/memory-inbox-page" ;
16+
1017function normalizeSource ( raw : any ) : MemorySource {
1118 return {
1219 id : raw . id ?? crypto . randomUUID ( ) ,
@@ -50,108 +57,109 @@ export function LibraryPage() {
5057 } ) ;
5158 } , [ sources , activeType , search , sortMode ] ) ;
5259
53- const header = (
54- < div className = "px-6 pt-6 pb-3" >
55- < h1 className = "text-2xl font-bold text-foreground font-[var(--font-nunito)]" >
56- Library
57- </ h1 >
58- < p className = "text-sm text-muted-foreground mt-1" >
59- Everything you've captured, in one place.
60- </ p >
61- </ div >
62- ) ;
63-
64- // Only show loading skeletons if there's no data already cached
65- if ( isLoading && sources . length === 0 ) {
66- return (
67- < div className = "flex flex-col h-full" >
68- { header }
69- < div className = "px-6 pb-4" >
70- < FilterBar
71- search = { search }
72- onSearchChange = { setSearch }
73- activeType = { activeType }
74- onTypeChange = { setActiveType }
75- viewMode = { viewMode }
76- onViewModeChange = { setViewMode }
77- sortMode = { sortMode }
78- onSortModeChange = { setSortMode }
79- />
80- </ div >
81- < div className = "flex-1 overflow-y-auto px-6 pb-6" >
82- < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3" >
83- { [ 0 , 1 , 2 , 3 , 4 , 5 ] . map ( ( i ) => (
84- < div
85- key = { i }
86- className = "rounded-2xl border-2 border-border bg-card p-4 h-36 animate-pulse"
87- />
88- ) ) }
89- </ div >
90- </ div >
91- </ div >
92- ) ;
93- }
94-
95- if ( error && sources . length === 0 ) {
96- return (
97- < div className = "flex flex-col h-full" >
98- { header }
99- < div className = "flex-1 flex items-center justify-center" >
100- < div className = "flex flex-col items-center text-center gap-3" >
101- < div className = "size-10 rounded-xl bg-accent flex items-center justify-center" >
102- < FileTextIcon />
103- </ div >
104- < p className = "text-xs text-muted-foreground max-w-[28ch]" >
105- Could not load sources. Make sure the API is running.
106- </ p >
107- </ div >
108- </ div >
109- </ div >
110- ) ;
111- }
60+ // Loading indicator for Timeline only (since other tabs manage their own loading state)
61+ const timelineLoading = isLoading && sources . length === 0 ;
11262
11363 return (
11464 < div className = "flex flex-col h-full" >
115- { header }
116- < div className = "px-6 pb-4" >
117- < FilterBar
118- search = { search }
119- onSearchChange = { setSearch }
120- activeType = { activeType }
121- onTypeChange = { setActiveType }
122- viewMode = { viewMode }
123- onViewModeChange = { setViewMode }
124- sortMode = { sortMode }
125- onSortModeChange = { setSortMode }
126- />
65+ < div className = "px-6 pt-6 pb-3 border-b border-border bg-card/40" >
66+ < h1 className = "text-2xl font-bold text-foreground font-[var(--font-nunito)]" >
67+ Memory Hub
68+ </ h1 >
69+ < p className = "text-xs text-muted-foreground mt-0.5" >
70+ Your centralized workspace for Timeline, Journals, Voice recordings, Mail sync, and Inbox review.
71+ </ p >
12772 </ div >
12873
129- < div className = "flex-1 overflow-y-auto px-6 pb-6" >
130- { filtered . length === 0 ? (
131- < div className = "flex flex-col items-center justify-center py-16 text-center gap-3" >
132- < div className = "size-10 rounded-xl bg-accent flex items-center justify-center" >
133- < FileTextIcon />
134- </ div >
135- < p className = "text-xs text-muted-foreground max-w-[28ch]" >
136- { search || activeType !== "all"
137- ? "No sources match your filters."
138- : "Capture a journal, voice note, or link to start your library." }
139- </ p >
140- </ div >
141- ) : viewMode === "grid" ? (
142- < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3" >
143- { filtered . map ( ( source : MemorySource ) => (
144- < SourceCard key = { source . id } source = { source } variant = "grid" />
145- ) ) }
74+ < Tabs defaultValue = "timeline" className = "flex-1 flex flex-col min-h-0" >
75+ < div className = "px-6 border-b border-border bg-card/20 py-2 shrink-0" >
76+ < TabsList className = "bg-muted/50 p-1 rounded-xl" >
77+ < TabsTrigger value = "timeline" className = "rounded-lg text-xs font-bold px-4" > Timeline</ TabsTrigger >
78+ < TabsTrigger value = "journal" className = "rounded-lg text-xs font-bold px-4" > Journal</ TabsTrigger >
79+ < TabsTrigger value = "voice" className = "rounded-lg text-xs font-bold px-4" > Voice Notes</ TabsTrigger >
80+ < TabsTrigger value = "mail" className = "rounded-lg text-xs font-bold px-4" > Debo Mail</ TabsTrigger >
81+ < TabsTrigger value = "inbox" className = "rounded-lg text-xs font-bold px-4" > Inbox Review</ TabsTrigger >
82+ </ TabsList >
83+ </ div >
84+
85+ < TabsContent value = "timeline" className = "flex-1 flex flex-col min-h-0 focus-visible:outline-none focus-visible:ring-0" >
86+ < div className = "px-6 py-4" >
87+ < FilterBar
88+ search = { search }
89+ onSearchChange = { setSearch }
90+ activeType = { activeType }
91+ onTypeChange = { setActiveType }
92+ viewMode = { viewMode }
93+ onViewModeChange = { setViewMode }
94+ sortMode = { sortMode }
95+ onSortModeChange = { setSortMode }
96+ />
14697 </ div >
147- ) : (
148- < div className = "space-y-2" >
149- { filtered . map ( ( source : MemorySource ) => (
150- < SourceCard key = { source . id } source = { source } variant = "list" />
151- ) ) }
98+
99+ < div className = "flex-1 overflow-y-auto px-6 pb-6" >
100+ { timelineLoading ? (
101+ < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3" >
102+ { [ 0 , 1 , 2 , 3 , 4 , 5 ] . map ( ( i ) => (
103+ < div
104+ key = { i }
105+ className = "rounded-2xl border-2 border-border bg-card p-4 h-36 animate-pulse"
106+ />
107+ ) ) }
108+ </ div >
109+ ) : error && sources . length === 0 ? (
110+ < div className = "flex-1 flex items-center justify-center" >
111+ < div className = "flex flex-col items-center text-center gap-3" >
112+ < div className = "size-10 rounded-xl bg-accent flex items-center justify-center" >
113+ < FileTextIcon />
114+ </ div >
115+ < p className = "text-xs text-muted-foreground max-w-[28ch]" >
116+ Could not load sources. Make sure the API is running.
117+ </ p >
118+ </ div >
119+ </ div >
120+ ) : filtered . length === 0 ? (
121+ < div className = "flex flex-col items-center justify-center py-16 text-center gap-3" >
122+ < div className = "size-10 rounded-xl bg-accent flex items-center justify-center" >
123+ < FileTextIcon />
124+ </ div >
125+ < p className = "text-xs text-muted-foreground max-w-[28ch]" >
126+ { search || activeType !== "all"
127+ ? "No sources match your filters."
128+ : "Capture a journal, voice note, or link to start your library." }
129+ </ p >
130+ </ div >
131+ ) : viewMode === "grid" ? (
132+ < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3" >
133+ { filtered . map ( ( source : MemorySource ) => (
134+ < SourceCard key = { source . id } source = { source } variant = "grid" />
135+ ) ) }
136+ </ div >
137+ ) : (
138+ < div className = "space-y-2" >
139+ { filtered . map ( ( source : MemorySource ) => (
140+ < SourceCard key = { source . id } source = { source } variant = "list" />
141+ ) ) }
142+ </ div >
143+ ) }
152144 </ div >
153- ) }
154- </ div >
145+ </ TabsContent >
146+
147+ < TabsContent value = "journal" className = "flex-1 overflow-y-auto focus-visible:outline-none focus-visible:ring-0" >
148+ < JournalPage />
149+ </ TabsContent >
150+
151+ < TabsContent value = "voice" className = "flex-1 overflow-y-auto focus-visible:outline-none focus-visible:ring-0" >
152+ < VoicePage />
153+ </ TabsContent >
154+
155+ < TabsContent value = "mail" className = "flex-1 overflow-hidden focus-visible:outline-none focus-visible:ring-0" >
156+ < MailShell />
157+ </ TabsContent >
158+
159+ < TabsContent value = "inbox" className = "flex-1 overflow-y-auto focus-visible:outline-none focus-visible:ring-0" >
160+ < MemoryInboxPage />
161+ </ TabsContent >
162+ </ Tabs >
155163 </ div >
156164 ) ;
157165}
0 commit comments