diff --git a/apps/mail/app/page.tsx b/apps/mail/app/page.tsx index a8ec359ca..a5e25d746 100644 --- a/apps/mail/app/page.tsx +++ b/apps/mail/app/page.tsx @@ -3,6 +3,7 @@ import HeroImage from "@/components/home/hero-image"; import Navbar from "@/components/home/navbar"; import Hero from "@/components/home/hero"; +import { DemoMailLayout } from "@/components/mail/mail"; export default function Home() { return ( @@ -13,7 +14,9 @@ export default function Home() {
- +
+ +
); diff --git a/apps/mail/components/mail/demo.json b/apps/mail/components/mail/demo.json new file mode 100644 index 000000000..e5cbd2520 --- /dev/null +++ b/apps/mail/components/mail/demo.json @@ -0,0 +1,111 @@ +[ + { + "id": "love-001", + "threadId": "love-001", + "tags": [ + "inbox", + "personal" + ], + "title": "🚨 EMERGENCY: I Might Be in Love with You 🚨", + "body": "Hey... so, I think I caught feelings. Is there a cure, or am I doomed? 😳", + "receivedOn": "2024-02-14", + "sender": { + "name": "Secret Admirer", + "email": "mystery@unknown.com" + }, + "unread": true, + "subject": "HELP: Symptoms Include Butterflies and Overthinking", + "totalReplies": 3, + "decodedBody": "

Hey... so, I think I caught feelings. Is there a cure, or am I doomed? 😳

" + }, + { + "id": "job-003", + "threadId": "job-003", + "tags": [ + "inbox", + "work" + ], + "title": "⚡ URGENT: Someone Wants to Pay You to Exist", + "body": "Okay, not quite, but we do have a job for you. Are you in?", + "receivedOn": "2024-03-05", + "sender": { + "name": "Tech Recruiter", + "email": "hr@desperateforhelp.com" + }, + "unread": false, + "subject": "Please Work for Us, We Are Begging", + "totalReplies": 4 + }, + { + "id": "job-001", + "threadId": "job-001", + "tags": [ + "inbox", + "work" + ], + "title": "🎉 You Got a Job! (Just Kidding, But Let’s Talk)", + "body": "Hey, you look like someone who needs a paycheck. Want a job?", + "receivedOn": "2024-03-01", + "sender": { + "name": "HR Recruiter", + "email": "hireme@company.com" + }, + "unread": false, + "subject": "Work 9-5, Make Money, Repeat", + "totalReplies": 5 + }, + { + "id": "love-002", + "threadId": "love-002", + "tags": [ + "inbox", + "personal" + ], + "title": "💔 I Wrote You a Poem (And It’s Terrible)", + "body": "Roses are red, violets are blue, coffee this weekend, or should I be sad forever? ☕", + "receivedOn": "2024-02-10", + "sender": { + "name": "Emma", + "email": "emma@romantic.com" + }, + "unread": true, + "subject": "Worst Poem Ever, But With Love", + "totalReplies": 2 + }, + { + "id": "job-002", + "threadId": "job-002", + "tags": [ + "inbox", + "work" + ], + "title": "💰 Work From Home & Become a Millionaire*", + "body": "*Okay, maybe not a millionaire, but you’ll at least afford coffee. Interested?", + "receivedOn": "2024-03-04", + "sender": { + "name": "John Doe", + "email": "john.doe@scamfreejobs.com" + }, + "unread": false, + "subject": "No Boss, No Office, Just You & A Laptop", + "totalReplies": 1 + }, + { + "id": "love-003", + "threadId": "love-003", + "tags": [ + "inbox", + "personal" + ], + "title": "📅 Our First Date Was a Simulation (Or Was It?)", + "body": "I had an amazing time! Unless it was all a dream. Let’s do it again to confirm.", + "receivedOn": "2024-02-20", + "sender": { + "name": "Lily", + "email": "lily@maybearealperson.com" + }, + "unread": true, + "subject": "Glitch in the Matrix? Or Just a Great Night?", + "totalReplies": 1 + } +] \ No newline at end of file diff --git a/apps/mail/components/mail/mail-list.tsx b/apps/mail/components/mail/mail-list.tsx index 99b76e303..17b343b37 100644 --- a/apps/mail/components/mail/mail-list.tsx +++ b/apps/mail/components/mail/mail-list.tsx @@ -3,7 +3,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { ComponentProps, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { EmptyState, type FolderType } from "@/components/mail/empty-state"; -import { preloadThread, useThread, useThreads } from "@/hooks/use-threads"; +import { preloadThread, useThreads } from "@/hooks/use-threads"; import { useSearchValue } from "@/hooks/use-search-value"; import { markAsRead, markAsUnread } from "@/actions/mail"; import { ScrollArea } from "@/components/ui/scroll-area"; @@ -20,6 +20,7 @@ import { toast } from "sonner"; import { ThreadContextMenu } from "../context/thread-context"; import { useParams } from "next/navigation"; import { useSummary } from "@/hooks/use-summary"; +import items from './demo.json' interface MailListProps { isCompact?: boolean; @@ -34,6 +35,7 @@ type ThreadProps = { selectMode: MailSelectMode; onSelect: (message: InitialThread) => void; isCompact?: boolean; + demo?: boolean; }; const highlightText = (text: string, highlight: string) => { @@ -56,7 +58,7 @@ const highlightText = (text: string, highlight: string) => { }); }; -const Thread = ({ message, selectMode, onSelect, isCompact }: ThreadProps) => { +const Thread = ({ message, selectMode, onSelect, isCompact, demo }: ThreadProps) => { const { folder } = useParams<{ folder: string }>() const [mail] = useMail(); const { data: session } = useSession(); @@ -64,17 +66,17 @@ const Thread = ({ message, selectMode, onSelect, isCompact }: ThreadProps) => { const isHovering = useRef(false); const hasPrefetched = useRef(false); const [searchValue] = useSearchValue(); - const { mutate } = useThreads(folder, undefined, searchValue.value, 20); - const { data } = useSummary(message.id) + const { mutate } = demo ? { mutate: async () => { } } : useThreads(folder, undefined, searchValue.value, 20); const isMailSelected = message.id === mail.selected; const isMailBulkSelected = mail.bulkSelected.includes(message.id); const handleMailClick = async () => { + if (demo) return; onSelect(message); if ((!selectMode || selectMode === 'single') && !isMailSelected && message.unread) { try { - await markAsRead({ ids: [message.id] }).then(() => mutate()).catch(console.error); + await markAsRead({ ids: [message.id] }).then(() => mutate() as any).catch(console.error); } catch (error) { console.error("Error marking message as read:", error); } @@ -82,6 +84,7 @@ const Thread = ({ message, selectMode, onSelect, isCompact }: ThreadProps) => { }; const handleMouseEnter = () => { + if (demo) return; isHovering.current = true; // Prefetch only in single select mode @@ -236,6 +239,37 @@ const StreamingText = ({ text }: { text: string }) => { ); }; +export function MailListDemo({ isCompact }: MailListProps) { + return +
+
+ {items.map((item) => { + return item ? ( + console.log('Selected')} + isCompact={isCompact} + /> + ) : null; + })} +
+
+
+} + export function MailList({ isCompact }: MailListProps) { const { folder } = useParams<{ folder: string }>() const [mail, setMail] = useMail(); diff --git a/apps/mail/components/mail/mail.tsx b/apps/mail/components/mail/mail.tsx index 4ac9352cc..103c01a93 100644 --- a/apps/mail/components/mail/mail.tsx +++ b/apps/mail/components/mail/mail.tsx @@ -5,10 +5,10 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/componen import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from "@/components/ui/drawer"; import { AlignVerticalSpaceAround, ArchiveX, BellOff, SearchIcon, X } from "lucide-react"; import { useState, useCallback, useMemo, useEffect, ReactNode } from "react"; -import { ThreadDisplay } from "@/components/mail/thread-display"; +import { ThreadDisplay, ThreadDemo } from "@/components/mail/thread-display"; import { useMediaQuery } from "../../hooks/use-media-query"; import { useSearchValue } from "@/hooks/use-search-value"; -import { MailList } from "@/components/mail/mail-list"; +import { MailList, MailListDemo } from "@/components/mail/mail-list"; import { useMail } from "@/components/mail/use-mail"; import { SidebarToggle } from "../ui/sidebar-toggle"; import { Skeleton } from "@/components/ui/skeleton"; @@ -21,6 +21,114 @@ import { useSession } from "@/lib/auth-client"; import { useRouter } from "next/navigation"; import { SearchBar } from "./search-bar"; import { cn } from "@/lib/utils"; +import items from './demo.json' + +export function DemoMailLayout() { + const mail = { + selected: 'demo' + } + const isMobile = false + const isValidating = false + const isLoading = false + const isDesktop = true + const [isCompact, setIsCompact] = useState(false) + const [open, setOpen] = useState(false) + const handleClose = () => setOpen(false) + + return +
+ + +
+
+
+ + +
+
+ {isLoading ? ( +
+ {[...Array(8)].map((_, i) => ( +
+
+
+ +
+ +
+ + +
+ + +
+
+ ))} +
+ ) : ( + + )} +
+
+ + + {isDesktop && mail.selected && ( + <> + + +
+ +
+
+ + )} + + + {/* Mobile Drawer */} + {isMobile && ( + + + + Email Details + +
+
+ +
+
+
+
+ )} +
+ +} export function MailLayout() { const { folder } = useParams<{ folder: string }>() diff --git a/apps/mail/components/mail/thread-display.tsx b/apps/mail/components/mail/thread-display.tsx index 9a5363ba9..d22bc0f73 100644 --- a/apps/mail/components/mail/thread-display.tsx +++ b/apps/mail/components/mail/thread-display.tsx @@ -27,11 +27,141 @@ import { cn } from "@/lib/utils"; import React from "react"; interface ThreadDisplayProps { - mail: string | null; + mail: any; onClose?: () => void; isMobile?: boolean; } +export function ThreadDemo({ mail: emailData, onClose, isMobile }: ThreadDisplayProps) { + const isFullscreen = false + return
+
+
+
+ + + + + Close + +
+
+ + + + + + {isFullscreen ? "Exit fullscreen" : "Enter fullscreen"} + + + + + + + Archive + + + + + + Reply + + + + + + + + Move to spam + + + Reply all + + + Forward + + Mark as unread + Add label + Mute thread + + +
+
+
+ +
+ {[...(emailData || [])].reverse().map((message, index) => ( +
0 && "border-border border-t", + )} + > + +
+ ))} +
+
+
+ { }} /> +
+
+
+
+} + export function ThreadDisplay({ mail, onClose, isMobile }: ThreadDisplayProps) { const [, setMail] = useMail(); const { data: emailData, isLoading } = useThread(mail ?? ""); diff --git a/apps/mail/lib/email-utils.ts b/apps/mail/lib/email-utils.ts index 01fa5a448..740415cb2 100644 --- a/apps/mail/lib/email-utils.ts +++ b/apps/mail/lib/email-utils.ts @@ -2,6 +2,7 @@ import { EMAIL_HTML_TEMPLATE } from "./constants"; import Color from "color"; export const template = (html: string) => { + if (typeof DOMParser === "undefined") return html; const htmlParser = new DOMParser(); const doc = htmlParser.parseFromString(html, "text/html"); const template = htmlParser.parseFromString(EMAIL_HTML_TEMPLATE, "text/html");