Skip to content
Merged

Demo #379

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions apps/mail/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@
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 (
<div className="relative h-screen min-h-screen w-full overflow-hidden bg-black">
<div className="relative h-screen min-h-screen w-full overflow-auto bg-black">
{/* <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="h-[500px] w-[500px] rounded-full bg-gradient-to-r from-orange-500/10 via-pink-500/10 to-purple-500/10 blur-[120px]" />
</div> */}
<div className="relative mx-auto mb-4 flex flex-col">
<Navbar />
<Hero />
<HeroImage />
<div className="container mx-auto md:block hidden">
<DemoMailLayout />
</div>
<div className="container mx-auto">
<p className="text-xs text-shinyGray mt-8">Zero Email Inc, 4409 Verbena Street, Midlothian, Texas, 76065, United States</p>
</div>
</div>
</div>
);
Expand Down
111 changes: 111 additions & 0 deletions apps/mail/components/mail/demo.json
Original file line number Diff line number Diff line change
@@ -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": "<p>Hey... so, I think I caught feelings. Is there a cure, or am I doomed? 😳</p>"
},
{
"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
}
]
44 changes: 39 additions & 5 deletions apps/mail/components/mail/mail-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;
Expand All @@ -34,6 +35,7 @@ type ThreadProps = {
selectMode: MailSelectMode;
onSelect: (message: InitialThread) => void;
isCompact?: boolean;
demo?: boolean;
};

const highlightText = (text: string, highlight: string) => {
Expand All @@ -56,32 +58,33 @@ 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();
const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
const isHovering = useRef<boolean>(false);
const hasPrefetched = useRef<boolean>(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);
}
}
};

const handleMouseEnter = () => {
if (demo) return;
isHovering.current = true;

// Prefetch only in single select mode
Expand Down Expand Up @@ -236,6 +239,37 @@ const StreamingText = ({ text }: { text: string }) => {
);
};

export function MailListDemo({ isCompact }: MailListProps) {
return <ScrollArea
className="h-full pb-2"
type="scroll"
>
<div
className={cn(
"relative min-h-[calc(100vh-4rem)] w-full",

)}
>
<div
className="absolute left-0 top-0 w-full p-[8px]"
>
{items.map((item) => {
return item ? (
<Thread
demo
key={item.id}
message={item}
selectMode={'single'}
onSelect={() => console.log('Selected')}
isCompact={isCompact}
/>
) : null;
})}
</div>
</div>
</ScrollArea>
}

export function MailList({ isCompact }: MailListProps) {
const { folder } = useParams<{ folder: string }>()
const [mail, setMail] = useMail();
Expand Down
112 changes: 110 additions & 2 deletions apps/mail/components/mail/mail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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 <TooltipProvider delayDuration={0}>
<div className="rounded-inherit flex">
<ResizablePanelGroup
direction="horizontal"
autoSaveId="mail-panel-layout"
className="rounded-inherit gap-1.5 overflow-hidden"
>
<ResizablePanel
className={cn(
"border-none !bg-transparent",
mail?.selected ? "md:hidden lg:block" : "", // Hide on md, but show again on lg and up
)}
defaultSize={isMobile ? 100 : 25}
minSize={isMobile ? 100 : 25}
>
<div className="bg-offsetLight dark:bg-offsetDark flex-1 flex-col overflow-y-auto shadow-inner md:flex md:rounded-2xl md:border md:shadow-sm">
<div className={cn("compose-gradient h-0.5 w-full transition-opacity", isValidating ? "opacity-50" : "opacity-0")} />
<div
className={cn(
"sticky top-0 z-10 flex items-center justify-between gap-1.5 p-2 transition-colors",
)}
>
<SidebarToggle className="h-fit px-2" />
<Button
variant="ghost"
className="md:h-fit md:px-2"
onClick={() => setIsCompact(!isCompact)}
>
<AlignVerticalSpaceAround />
</Button>
</div>
<div className="h-[calc(100dvh-56px)] overflow-hidden pt-0 md:h-[calc(100dvh-(8px+8px+14px+44px))]">
{isLoading ? (
<div className="flex flex-col">
{[...Array(8)].map((_, i) => (
<div key={i} className="flex flex-col px-4 py-3">
<div className="flex w-full items-center justify-between">
<div className="flex items-center gap-2">
<Skeleton className="h-4 w-24" />
</div>
<Skeleton className="h-3 w-12" />
</div>
<Skeleton className="mt-2 h-3 w-32" />
<Skeleton className="mt-2 h-3 w-full" />
<div className="mt-2 flex gap-2">
<Skeleton className="h-4 w-16 rounded-full" />
<Skeleton className="h-4 w-16 rounded-full" />
</div>
</div>
))}
</div>
) : (
<MailListDemo
isCompact={isCompact}
/>
)}
</div>
</div>
</ResizablePanel>

{isDesktop && mail.selected && (
<>
<ResizableHandle className="opacity-0" />
<ResizablePanel
className="shadow-sm md:flex md:rounded-2xl md:border md:shadow-sm bg-offsetLight dark:bg-offsetDark"
defaultSize={75}
minSize={25}
>
<div className="hidden h-[calc(100vh-(12px+14px))] flex-1 md:block relative">
<ThreadDemo mail={[items[0]]} onClose={handleClose} />
</div>
</ResizablePanel>
</>
)}
</ResizablePanelGroup>

{/* Mobile Drawer */}
{isMobile && (
<Drawer open={open} onOpenChange={setOpen}>
<DrawerContent className="bg-offsetLight dark:bg-offsetDark h-[calc(100vh-3rem)] overflow-hidden p-0">
<DrawerHeader className="sr-only">
<DrawerTitle>Email Details</DrawerTitle>
</DrawerHeader>
<div className="flex h-full flex-col overflow-hidden">
<div className="flex-1 overflow-hidden">
<ThreadDisplay mail={mail.selected} onClose={handleClose} isMobile={true} />
</div>
</div>
</DrawerContent>
</Drawer>
)}
</div>
</TooltipProvider>
}

export function MailLayout() {
const { folder } = useParams<{ folder: string }>()
Expand Down
Loading
Loading