Skip to content

Commit e80b68e

Browse files
Enhance mobile navigation and layout: implement responsive views for folders, emails, and email viewer with back navigation buttons
Signed-off-by: Shahm Najeeb <Shahm_Najeeb@outlook.com>
1 parent bbd2c38 commit e80b68e

6 files changed

Lines changed: 178 additions & 53 deletions

File tree

app/globals.css

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,35 @@
173173
:root:not(.dark) *::-webkit-scrollbar-thumb:hover {
174174
background-color: oklch(0.439 0 0);
175175
}
176+
177+
/* Mobile-specific styles */
178+
@media (max-width: 768px) {
179+
/* Thinner scrollbars on mobile */
180+
.dark *::-webkit-scrollbar,
181+
:root:not(.dark) *::-webkit-scrollbar {
182+
width: 6px;
183+
height: 6px;
184+
}
185+
186+
.dark *::-webkit-scrollbar-thumb,
187+
:root:not(.dark) *::-webkit-scrollbar-thumb {
188+
border-width: 1px;
189+
}
190+
}
191+
192+
/* Prevent text selection on mobile UI elements */
193+
@media (max-width: 768px) {
194+
button, .mobile-nav-item {
195+
-webkit-tap-highlight-color: transparent;
196+
user-select: none;
197+
}
198+
}
199+
200+
/* Touch-friendly sizing */
201+
@media (max-width: 768px) {
202+
button, a {
203+
min-height: 44px;
204+
min-width: 44px;
205+
}
206+
}
176207
}

app/layout.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type React from "react"
2-
import type {Metadata} from "next"
2+
import type {Metadata, Viewport} from "next"
33
import {Analytics} from "@vercel/analytics/next"
44
import "./globals.css"
55

@@ -25,6 +25,17 @@ export const metadata: Metadata = {
2525
},
2626
}
2727

28+
export const viewport: Viewport = {
29+
width: 'device-width',
30+
initialScale: 1,
31+
maximumScale: 5,
32+
userScalable: true,
33+
themeColor: [
34+
{media: "(prefers-color-scheme: light)", color: "white"},
35+
{media: "(prefers-color-scheme: dark)", color: "black"},
36+
],
37+
}
38+
2839
export default function RootLayout({
2940
children,
3041
}: Readonly<{

components/dashboard-client.tsx

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ import {EmailList} from "./email-list"
1111
import {EmailViewer} from "./email-viewer"
1212
import {DashboardHeader} from "./dashboard-header"
1313
import {Alert, AlertDescription} from "@/components/ui/alert"
14-
import {AlertCircle} from "lucide-react"
14+
import {AlertCircle, ArrowLeft, FileText, Folder, Mail} from "lucide-react"
1515
import {Account} from "@/types";
1616
import {Cache} from "@/lib/cache"
1717
import {UISettings} from "@/lib/settings"
18+
import {Button} from "@/components/ui/button"
1819

1920
export function DashboardClient() {
2021
const [accounts, setAccounts] = useState<Account[]>([])
@@ -25,6 +26,8 @@ export function DashboardClient() {
2526
const [error, setError] = useState<string | null>(null)
2627
const [switchingAccount, setSwitchingAccount] = useState(false)
2728
const [foldersLoaded, setFoldersLoaded] = useState(false)
29+
// Mobile view state: 'folders' | 'emails' | 'viewer'
30+
const [mobileView, setMobileView] = useState<'folders' | 'emails' | 'viewer'>('folders')
2831

2932
// Handle account change with loading state
3033
const handleAccountChange = (accountId: string) => {
@@ -152,9 +155,47 @@ export function DashboardClient() {
152155
disabled={switchingAccount}
153156
/>
154157

155-
<div className="flex h-[calc(100vh-4rem)]">
158+
{/* Mobile Navigation */}
159+
<div className="md:hidden border-b bg-background">
160+
<div className="flex items-center">
161+
<button
162+
className={`flex-1 flex items-center justify-center gap-2 py-3 border-r transition-colors ${
163+
mobileView === 'folders' ? 'bg-muted text-foreground' : 'text-muted-foreground'
164+
}`}
165+
onClick={() => setMobileView('folders')}
166+
>
167+
<Folder className="h-4 w-4"/>
168+
<span className="text-sm font-medium">Folders</span>
169+
</button>
170+
<button
171+
className={`flex-1 flex items-center justify-center gap-2 py-3 border-r transition-colors ${
172+
mobileView === 'emails' ? 'bg-muted text-foreground' : 'text-muted-foreground'
173+
}`}
174+
onClick={() => setMobileView('emails')}
175+
disabled={!selectedFolder}
176+
>
177+
<Mail className="h-4 w-4"/>
178+
<span className="text-sm font-medium">Emails</span>
179+
</button>
180+
<button
181+
className={`flex-1 flex items-center justify-center gap-2 py-3 transition-colors ${
182+
mobileView === 'viewer' ? 'bg-muted text-foreground' : 'text-muted-foreground'
183+
}`}
184+
onClick={() => setMobileView('viewer')}
185+
disabled={!selectedEmail}
186+
>
187+
<FileText className="h-4 w-4"/>
188+
<span className="text-sm font-medium">View</span>
189+
</button>
190+
</div>
191+
</div>
192+
193+
{/* Desktop: 3-column layout, Mobile: single column based on mobileView */}
194+
<div className="flex h-[calc(100vh-4rem)] md:h-[calc(100vh-4rem)]">
156195
{/* Folder sidebar */}
157-
<div className="border-r bg-muted/30 overflow-y-auto" style={{width: `${UISettings.sidebarWidth}px`}}>
196+
<div className={`border-r bg-muted/30 overflow-y-auto ${
197+
mobileView === 'folders' ? 'w-full md:w-auto' : 'hidden md:block'
198+
}`} style={{width: mobileView === 'folders' ? '100%' : `${UISettings.sidebarWidth}px`}}>
158199
{selectedAccount && (
159200
<FolderList
160201
accountId={selectedAccount}
@@ -166,30 +207,56 @@ export function DashboardClient() {
166207
}
167208
setSelectedFolder(folder)
168209
setSelectedEmail(null)
210+
setMobileView('emails') // Switch to emails view on mobile
169211
}}
170212
onLoadingComplete={() => setFoldersLoaded(true)}
171213
/>
172214
)}
173215
</div>
174216

175217
{/* Email list */}
176-
<div className="border-r bg-background overflow-y-auto"
177-
style={{width: `${UISettings.emailListWidth}px`}}>
218+
<div className={`border-r bg-background overflow-y-auto ${
219+
mobileView === 'emails' ? 'w-full md:w-auto' : 'hidden md:block'
220+
}`} style={{width: mobileView === 'emails' ? '100%' : `${UISettings.emailListWidth}px`}}>
178221
{selectedAccount && selectedFolder && (
179-
<EmailList
180-
accountId={selectedAccount}
181-
folder={selectedFolder}
182-
selectedUid={selectedEmail?.uid}
183-
onEmailSelect={(uid) => setSelectedEmail({uid, folder: selectedFolder})}
184-
/>
222+
<>
223+
{/* Mobile back button */}
224+
<div className="md:hidden border-b p-2 flex items-center gap-2">
225+
<Button variant="ghost" size="sm" onClick={() => setMobileView('folders')}>
226+
<ArrowLeft className="h-4 w-4 mr-2"/>
227+
Back to Folders
228+
</Button>
229+
</div>
230+
<EmailList
231+
accountId={selectedAccount}
232+
folder={selectedFolder}
233+
selectedUid={selectedEmail?.uid}
234+
onEmailSelect={(uid) => {
235+
setSelectedEmail({uid, folder: selectedFolder})
236+
setMobileView('viewer') // Switch to viewer on mobile
237+
}}
238+
/>
239+
</>
185240
)}
186241
</div>
187242

188243
{/* Email viewer */}
189-
<div className="flex-1 overflow-y-auto bg-background">
244+
<div className={`flex-1 overflow-y-auto bg-background ${
245+
mobileView === 'viewer' ? 'w-full' : 'hidden md:block'
246+
}`}>
190247
{selectedEmail ? (
191-
<EmailViewer accountId={selectedAccount!} folder={selectedEmail.folder}
192-
uid={selectedEmail.uid}/>
248+
<>
249+
{/* Mobile back button */}
250+
<div
251+
className="md:hidden border-b p-2 flex items-center gap-2 sticky top-0 bg-background z-10">
252+
<Button variant="ghost" size="sm" onClick={() => setMobileView('emails')}>
253+
<ArrowLeft className="h-4 w-4 mr-2"/>
254+
Back to Emails
255+
</Button>
256+
</div>
257+
<EmailViewer accountId={selectedAccount!} folder={selectedEmail.folder}
258+
uid={selectedEmail.uid}/>
259+
</>
193260
) : (
194261
<div className="h-full flex items-center justify-center text-muted-foreground">
195262
<p>Select an email to view</p>

components/dashboard-header.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,14 @@ export function DashboardHeader({accounts, selectedAccount, onAccountChange, dis
3939
}
4040

4141
return (
42-
<header className="h-16 border-b bg-background flex items-center justify-between px-6">
43-
<div className="flex items-center gap-4">
44-
<div className="flex items-center gap-2">
45-
<Shield className="h-5 w-5 text-primary"/>
46-
<h1 className="text-lg font-semibold">Email Supervision Dashboard</h1>
42+
<header className="h-16 border-b bg-background flex items-center justify-between px-4 md:px-6">
43+
<div className="flex items-center gap-2 md:gap-4 flex-1 min-w-0">
44+
<div className="flex items-center gap-2 min-w-0">
45+
<Shield className="h-5 w-5 text-primary shrink-0"/>
46+
<h1 className="text-sm md:text-lg font-semibold truncate">
47+
<span className="hidden sm:inline">Email Supervision Dashboard</span>
48+
<span className="sm:hidden">Email Dashboard</span>
49+
</h1>
4750
</div>
4851

4952
{accounts.length > 0 && (
@@ -55,10 +58,10 @@ export function DashboardHeader({accounts, selectedAccount, onAccountChange, dis
5558
<PopoverTrigger asChild>
5659
<Button
5760
variant="outline"
58-
className="w-70 justify-start bg-transparent"
61+
className="w-auto md:w-70 justify-start bg-transparent min-w-0 hidden sm:flex"
5962
disabled={disabled}
6063
>
61-
<Search className="h-4 w-4 mr-2 text-muted-foreground"/>
64+
<Search className="h-4 w-4 mr-2 text-muted-foreground shrink-0"/>
6265
<span className="truncate">
6366
{disabled ? 'Loading...' : selectedLabel}
6467
</span>
@@ -93,9 +96,9 @@ export function DashboardHeader({accounts, selectedAccount, onAccountChange, dis
9396
)}
9497
</div>
9598

96-
<Button variant="outline" size="sm" onClick={handleLogout}>
97-
<LogOut className="h-4 w-4 mr-2"/>
98-
Logout
99+
<Button variant="outline" size="sm" onClick={handleLogout} className="shrink-0">
100+
<LogOut className="h-4 w-4 md:mr-2"/>
101+
<span className="hidden md:inline">Logout</span>
99102
</Button>
100103
</header>
101104
)

components/email-viewer.tsx

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -172,28 +172,29 @@ export function EmailViewer({accountId, folder, uid}: EmailViewerProps) {
172172
}
173173

174174
return (
175-
<div className="p-6 max-w-4xl mx-auto">
176-
<div className="mb-6">
177-
<h1 className="text-2xl font-bold mb-4">{email.subject || "(No subject)"}</h1>
175+
<div className="p-4 md:p-6 max-w-4xl mx-auto">
176+
<div className="mb-4 md:mb-6">
177+
<h1 className="text-lg md:text-2xl font-bold mb-3 md:mb-4 wrap-break-word">{email.subject || "(No subject)"}</h1>
178178

179179
<div className="space-y-2 text-sm">
180180
<div className="flex gap-2">
181-
<span className="text-muted-foreground w-16">From:</span>
182-
<span className="font-medium">{formatAddress(email.from)}</span>
181+
<span className="text-muted-foreground w-14 md:w-16 shrink-0">From:</span>
182+
<span className="font-medium wrap-break-word">{formatAddress(email.from)}</span>
183183
</div>
184184
<div className="flex gap-2">
185-
<span className="text-muted-foreground w-16">To:</span>
186-
<span>{formatAddress(email.to)}</span>
185+
<span className="text-muted-foreground w-14 md:w-16 shrink-0">To:</span>
186+
<span className="wrap-break-word">{formatAddress(email.to)}</span>
187187
</div>
188188
{email.cc && email.cc.length > 0 && (
189189
<div className="flex gap-2">
190-
<span className="text-muted-foreground w-16">CC:</span>
191-
<span>{formatAddress(email.cc)}</span>
190+
<span className="text-muted-foreground w-14 md:w-16 shrink-0">CC:</span>
191+
<span className="wrap-break-word">{formatAddress(email.cc)}</span>
192192
</div>
193193
)}
194194
<div className="flex gap-2">
195-
<span className="text-muted-foreground w-16">Date:</span>
196-
<span>{email.date ? format(new Date(email.date), EmailDisplaySettings.dateFormat) : "Unknown"}</span>
195+
<span className="text-muted-foreground w-14 md:w-16 shrink-0">Date:</span>
196+
<span
197+
className="wrap-break-word">{email.date ? format(new Date(email.date), EmailDisplaySettings.dateFormat) : "Unknown"}</span>
197198
</div>
198199
</div>
199200

@@ -204,11 +205,11 @@ export function EmailViewer({accountId, folder, uid}: EmailViewerProps) {
204205
key={i}
205206
variant="secondary"
206207
size="sm"
207-
className="gap-2"
208+
className="gap-2 text-xs md:text-sm"
208209
onClick={() => downloadAttachment(att)}
209210
>
210211
<Paperclip className="h-3 w-3"/>
211-
<span>{att.filename || "Attachment"}</span>
212+
<span className="truncate max-w-30 md:max-w-none">{att.filename || "Attachment"}</span>
212213
{att.size && <span className="text-xs">({(att.size / 1024).toFixed(1)} KB)</span>}
213214
<Download className="h-3 w-3 ml-1"/>
214215
</Button>
@@ -217,21 +218,21 @@ export function EmailViewer({accountId, folder, uid}: EmailViewerProps) {
217218
)}
218219
</div>
219220

220-
<Separator className="my-6"/>
221+
<Separator className="my-4 md:my-6"/>
221222

222223
<Tabs defaultValue={defaultTab}>
223224
{email.html && email.text && (
224-
<TabsList className="mb-4">
225-
<TabsTrigger value="html">HTML</TabsTrigger>
226-
<TabsTrigger value="text">Plain Text</TabsTrigger>
225+
<TabsList className="mb-4 w-full md:w-auto">
226+
<TabsTrigger value="html" className="flex-1 md:flex-none">HTML</TabsTrigger>
227+
<TabsTrigger value="text" className="flex-1 md:flex-none">Plain Text</TabsTrigger>
227228
</TabsList>
228229
)}
229230

230231
{email.html && (
231232
<TabsContent value="html">
232-
<Card className="p-6">
233+
<Card className="p-4 md:p-6">
233234
<div
234-
className="prose prose-sm max-w-none email-content"
235+
className="prose prose-sm max-w-none email-content overflow-x-auto"
235236
dangerouslySetInnerHTML={{__html: email.html}}
236237
style={{
237238
whiteSpace: 'pre-wrap',
@@ -241,6 +242,7 @@ export function EmailViewer({accountId, folder, uid}: EmailViewerProps) {
241242
<style jsx>{`
242243
.email-content :global(*) {
243244
color: inherit !important;
245+
max-width: 100% !important;
244246
}
245247
246248
.email-content :global(a) {
@@ -259,16 +261,27 @@ export function EmailViewer({accountId, folder, uid}: EmailViewerProps) {
259261
.email-content :global([style*="color: #ffffff"]) {
260262
color: inherit !important;
261263
}
264+
265+
.email-content :global(img) {
266+
max-width: 100% !important;
267+
height: auto !important;
268+
}
269+
270+
.email-content :global(table) {
271+
max-width: 100% !important;
272+
overflow-x: auto !important;
273+
display: block !important;
274+
}
262275
`}</style>
263276
</Card>
264277
</TabsContent>
265278
)}
266279

267280
{email.text && (
268281
<TabsContent value="text">
269-
<Card className="p-6">
282+
<Card className="p-4 md:p-6">
270283
<div
271-
className="whitespace-pre-wrap font-sans text-sm prose prose-sm max-w-none"
284+
className="whitespace-pre-wrap font-sans text-sm prose prose-sm max-w-none overflow-x-auto"
272285
dangerouslySetInnerHTML={{__html: formatPlainText(email.text)}}
273286
/>
274287
</Card>

0 commit comments

Comments
 (0)