Skip to content

Commit bd2ae43

Browse files
authored
sticky scroll position for mailbox navigation (#378)
1 parent 9d21696 commit bd2ae43

File tree

13 files changed

+125
-99
lines changed

13 files changed

+125
-99
lines changed

apps/web/app/dashboard/(unified)/(mail)/layout.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { SidebarInset } from "@/components/ui/sidebar";
22
import { AppSidebar } from "@/components/ui/dashboards/unified/default/app-sidebar";
33
import {
44
fetchIdentityMailboxList,
5-
fetchMailboxUnreadCounts, fetchScheduledCount,
5+
fetchMailboxUnreadCounts, fetchScheduledDraftCounts,
66
} from "@/lib/actions/mailbox";
77
import {fetchLabelsWithCounts} from "@/lib/actions/labels";
88
import { getPublicEnv, LabelScope } from "@schema";
@@ -19,13 +19,13 @@ export default async function DashboardLayout({
1919
children: React.ReactNode;
2020
}) {
2121
const publicConfig = getPublicEnv();
22-
const [identityMailboxes, unreadCounts, user, globalLabels, scheduledCounts] =
22+
const [identityMailboxes, unreadCounts, user, globalLabels, scheduledDrafts] =
2323
await Promise.all([
2424
fetchIdentityMailboxList(),
2525
fetchMailboxUnreadCounts(),
2626
isSignedIn(),
2727
fetchLabelsWithCounts(),
28-
fetchScheduledCount()
28+
fetchScheduledDraftCounts()
2929
]);
3030

3131
return (
@@ -46,7 +46,7 @@ export default async function DashboardLayout({
4646
<IdentityMailboxesList
4747
identityMailboxes={identityMailboxes}
4848
unreadCounts={unreadCounts}
49-
scheduledCounts={scheduledCounts}
49+
scheduledDrafts={scheduledDrafts}
5050
/>
5151
<DynamicContextProvider
5252
initialState={{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import Loading from "@/app/loading";
2+
export default Loading;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React from "react";
2+
import { fetchMailbox, fetchWebMailThreadDetail } from "@/lib/actions/mailbox";
3+
import ThreadItem from "@/components/mailbox/default/thread-item";
4+
import { Divider } from "@mantine/core";
5+
6+
async function Page({
7+
params,
8+
}: {
9+
params: Promise<{
10+
identityPublicId: string;
11+
mailboxSlug: string;
12+
threadId: string;
13+
}>;
14+
}) {
15+
16+
const { threadId, identityPublicId, mailboxSlug } = await params;
17+
const { activeMailbox, mailboxSync } = await fetchMailbox(
18+
identityPublicId,
19+
mailboxSlug,
20+
);
21+
const activeThread = await fetchWebMailThreadDetail(threadId);
22+
23+
return (
24+
<>
25+
{activeThread?.messages.map((message, threadIndex) => {
26+
return (
27+
<div key={message.id}>
28+
<ThreadItem
29+
message={message}
30+
threadIndex={threadIndex}
31+
numberOfMessages={activeThread.messages.length}
32+
threadId={threadId}
33+
activeMailboxId={activeMailbox.id}
34+
markSmtp={!!mailboxSync}
35+
/>
36+
<Divider className={"opacity-50 mb-6"} ml={"xl"} mr={"xl"} />
37+
</div>
38+
);
39+
})}
40+
</>
41+
);
42+
}
43+
44+
export default Page;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Default() {
2+
return null;
3+
}

apps/web/app/dashboard/(unified)/(mail)/mail/[identityPublicId]/[mailboxSlug]/layout.tsx

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,38 @@ import React, { ReactNode } from "react";
55
import { isSignedIn } from "@/lib/actions/auth";
66

77
type LayoutProps = {
8-
children: ReactNode;
9-
params: Promise<{
10-
identityPublicId: string;
11-
mailboxSlug: string;
12-
}>;
8+
children: ReactNode;
9+
thread: ReactNode;
10+
params: Promise<{
11+
identityPublicId: string;
12+
mailboxSlug: string;
13+
}>;
1314
};
1415

15-
export default async function DashboardLayout({
16-
children,
17-
params,
18-
}: LayoutProps) {
19-
const { identityPublicId, mailboxSlug } = await params;
16+
export default async function DashboardLayout({ children, thread, params }: LayoutProps) {
17+
const { identityPublicId, mailboxSlug } = await params;
18+
const user = await isSignedIn();
2019

21-
const user = await isSignedIn();
2220

23-
return (
24-
<>
25-
<header className="bg-background sticky top-0 flex shrink-0 items-center gap-2 border-b p-4">
26-
<SidebarTrigger className="-ml-1" />
27-
<Separator
28-
orientation="vertical"
29-
className="mr-2 data-[orientation=vertical]:h-4"
30-
/>
21+
return (
22+
<>
23+
<header className="bg-background sticky top-0 flex shrink-0 items-center gap-2 border-b p-4">
24+
<SidebarTrigger className="-ml-1" />
25+
<Separator
26+
orientation="vertical"
27+
className="mr-2 data-[orientation=vertical]:h-4"
28+
/>
3129

32-
<MailboxSearch
33-
user={user}
34-
publicId={identityPublicId}
35-
mailboxSlug={mailboxSlug}
36-
/>
37-
</header>
30+
<MailboxSearch
31+
user={user}
32+
publicId={identityPublicId}
33+
mailboxSlug={mailboxSlug}
34+
/>
35+
</header>
3836

39-
{children}
40-
</>
41-
);
37+
{thread}
38+
{children}
39+
40+
</>
41+
);
4242
}

apps/web/app/dashboard/(unified)/(mail)/mail/[identityPublicId]/[mailboxSlug]/page.tsx

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,21 @@ async function Page({
1515
params: { identityPublicId: string; mailboxSlug?: string };
1616
searchParams: { page?: string };
1717
}) {
18-
const { page } = await searchParams;
19-
const { identityPublicId, mailboxSlug } = await params;
20-
const { activeMailbox, count, mailboxSync } = await fetchMailbox(
21-
identityPublicId,
22-
mailboxSlug,
23-
);
2418

25-
// TODO: We have IMAP IDLE detection support in the worker now. We probably don't need this?
26-
// if (mailboxSync) {
27-
// if (mailboxSync.phase === "IDLE") {
28-
// await deltaFetch({ identityId: activeMailbox.identityId });
29-
// }
30-
// }
19+
const { page } = await searchParams;
20+
const { identityPublicId, mailboxSlug } = await params;
21+
const publicConfig = getPublicEnv();
22+
const { activeMailbox, count, mailboxSync } = await fetchMailbox(
23+
identityPublicId,
24+
mailboxSlug,
25+
);
3126

32-
const publicConfig = getPublicEnv();
3327
const mailboxThreads = await fetchMailboxThreads(
3428
identityPublicId,
3529
String(mailboxSlug),
3630
Number(page),
3731
);
32+
3833
const labelsByThreadId = await fetchMailboxThreadLabels(mailboxThreads);
3934
const identityMailboxes = await fetchIdentityMailboxList();
4035
const globalLabels = await fetchLabels();

apps/web/components/dashboard/identity-mailboxes-list.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
FetchMailboxUnreadCountsResult,
2222
} from "@/lib/actions/mailbox";
2323
import { MailboxKind } from "@schema";
24-
import { IdentityEntity, MailboxEntity } from "@db";
24+
import {DraftMessageEntity, IdentityEntity, MailboxEntity} from "@db";
2525
import AddNewFolder from "@/components/mailbox/default/add-new-folder";
2626
import { Menu } from "@mantine/core";
2727
import DeleteMailboxFolder from "@/components/mailbox/default/delete-folder";
@@ -115,12 +115,12 @@ function buildTree(
115115
export default function IdentityMailboxesList({
116116
identityMailboxes,
117117
unreadCounts,
118-
scheduledCounts,
118+
scheduledDrafts,
119119
onComplete,
120120
}: {
121121
identityMailboxes: FetchIdentityMailboxListResult;
122122
unreadCounts: FetchMailboxUnreadCountsResult;
123-
scheduledCounts: number;
123+
scheduledDrafts: DraftMessageEntity[];
124124
onComplete?: () => void;
125125
}) {
126126
const pathname = usePathname();
@@ -176,7 +176,7 @@ export default function IdentityMailboxesList({
176176
<div className="flex w-full items-start">
177177
<Link
178178
href={href}
179-
onClick={onComplete ? () => onComplete() : undefined}
179+
onClick={onComplete ? () => onComplete() : undefined}
180180
aria-disabled={!m.selectable}
181181
className={cn(
182182
"flex min-w-0 flex-1 items-center gap-2 rounded-md py-1.5 pl-2 text-sm",
@@ -247,6 +247,7 @@ export default function IdentityMailboxesList({
247247
{identityMailboxes.map(({ identity, mailboxes }) => {
248248
const tree = buildTree(mailboxes as MailboxEntity[], unreadCounts);
249249

250+
const scheduledCounts = scheduledDrafts.filter(draft => draft.identityId === identity.id).length;
250251
return (
251252
<div key={identity.id}>
252253
<div className="px-1 mb-1 mt-2 text-xs font-semibold text-sidebar-foreground/60 flex items-center gap-1">
@@ -263,10 +264,10 @@ export default function IdentityMailboxesList({
263264
/>
264265
))}
265266
</div>
266-
<Link href={`/dashboard/mail/${params.identityPublicId}/scheduled`} className={`my-2 rounded hover:dark:bg-neutral-800 ${currentSlug === "scheduled" ? "dark:bg-neutral-800 dark:text-brand-foreground bg-brand-200 text-brand" : ""} flex justify-start gap-1 w-full p-1.5`}>
267+
{scheduledCounts > 0 && <Link href={`/dashboard/mail/${params.identityPublicId}/scheduled`} className={`my-2 rounded hover:dark:bg-neutral-800 ${currentSlug === "scheduled" ? "dark:bg-neutral-800 dark:text-brand-foreground bg-brand-200 text-brand" : ""} flex justify-start gap-1 w-full p-1.5`}>
267268
<IconMailFast size={22}/>
268269
<span className={"font-normal text-sm"}>Scheduled ({scheduledCounts})</span>
269-
</Link>
270+
</Link>}
270271

271272
</div>
272273
);

apps/web/components/mailbox/default/mail-list-header.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"use client";
12
import React, { useRef, useEffect, useState } from "react";
23
import { MailOpen, RotateCw, Trash2 } from "lucide-react";
34
import { useDynamicContext } from "@/hooks/use-dynamic-context";
@@ -158,13 +159,6 @@ function MailListHeader({
158159
variant="subtle"
159160
onClick={reload}
160161
title="Sync"
161-
// disabled={
162-
// mailboxSync
163-
// ? !identityIdRef.current ||
164-
// reloading ||
165-
// mailboxSync?.phase !== "IDLE"
166-
// : !identityIdRef.current || reloading
167-
// }
168162
className="h-8 w-8"
169163
>
170164
<RotateCw className={reloading ? "animate-spin" : ""} size={16} />

apps/web/components/mailbox/default/mail-pagination.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22
import React, { useState } from "react";
33
import { Pagination } from "@mantine/core";
4-
import { usePathname, useRouter } from "next/navigation";
4+
import {useParams, usePathname, useRouter} from "next/navigation";
55

66
function MailPagination({
77
count,
@@ -17,6 +17,7 @@ function MailPagination({
1717
const [activePage, setPage] = useState(page || 1);
1818
const router = useRouter();
1919
const pathname = usePathname();
20+
const params = useParams();
2021

2122
const updatePageNumber = async (number: number) => {
2223
if (number < 1) return;
@@ -32,13 +33,13 @@ function MailPagination({
3233
setPage(number);
3334
};
3435

35-
return (
36+
return <div className={params?.threadId ? "hidden" : ""}>
3637
<Pagination
3738
value={activePage}
3839
onChange={updatePageNumber}
3940
total={count > 0 ? count / 50 : 0}
4041
/>
41-
);
42+
</div>
4243
}
4344

4445
export default MailPagination;

apps/web/components/mailbox/default/webmail-list.tsx

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@ import {
1212
} from "@/lib/actions/labels";
1313
import MailListHeader from "@/components/mailbox/default/mail-list-header";
1414
import WebmailListItem from "@/components/mailbox/default/webmail-list-item";
15-
import { useEffect } from "react";
1615
import { DynamicContextProvider } from "@/hooks/use-dynamic-context";
17-
import { toast } from "sonner";
1816
import { useMediaQuery } from "@mantine/hooks";
1917
import WebmailListItemMobile from "@/components/mailbox/default/webmail-list-item-mobile";
18+
import {useParams} from "next/navigation";
2019

2120
type WebListProps = {
2221
mailboxThreads: FetchMailboxThreadsResult;
@@ -39,21 +38,13 @@ export default function WebmailList({
3938
globalLabels,
4039
labelsByThreadId,
4140
}: WebListProps) {
42-
// Disable mailbox busy toast for now
43-
// useEffect(() => {
44-
// if (mailboxSync) {
45-
// if (mailboxSync?.phase !== "IDLE") {
46-
// toast.info("Mailbox Busy", {
47-
// description: "Mailbox is currently syncing",
48-
// });
49-
// }
50-
// }
51-
// }, [mailboxSync, mailboxSync?.phase]);
5241

5342
const isMobile = useMediaQuery("(max-width: 768px)");
43+
const params = useParams();
44+
5445

5546
return (
56-
<>
47+
<div className={params?.threadId ? "hidden" : ""}>
5748
<DynamicContextProvider
5849
initialState={{
5950
selectedThreadIds: new Set(),
@@ -76,7 +67,7 @@ export default function WebmailList({
7667
activeMailbox={activeMailbox}
7768
/>
7869

79-
<ul role="list" className="divide-y rounded-4xl">
70+
<ul role="list" className={`divide-y rounded-4xl`}>
8071
{mailboxThreads.map((mailboxThreadItem) =>
8172
isMobile ? (
8273
<WebmailListItemMobile
@@ -107,6 +98,6 @@ export default function WebmailList({
10798
</div>
10899
)}
109100
</DynamicContextProvider>
110-
</>
101+
</div>
111102
);
112103
}

0 commit comments

Comments
 (0)