2025-06-21 20:30:28 -03:00
|
|
|
"use client"
|
|
|
|
import { accounts, mails } from "./data"
|
2025-04-02 20:42:47 -03:00
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
import * as React from "react"
|
|
|
|
import {
|
|
|
|
AlertCircle, Archive, ArchiveX, Clock, File, Forward, Inbox, Mail as MailIcon,
|
|
|
|
MessagesSquare, MoreVertical, Reply, ReplyAll, Search, Send, ShoppingCart,
|
|
|
|
Trash2, Users2, LucideIcon
|
|
|
|
} from "lucide-react"
|
|
|
|
import { useMail } from "./use-mail"
|
|
|
|
import { cn } from "@/lib/utils"
|
|
|
|
import { format, formatDistanceToNow, addDays, addHours, nextSaturday } from "date-fns"
|
|
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
|
|
|
import { Badge } from "@/components/ui/badge"
|
|
|
|
import { Button } from "@/components/ui/button"
|
|
|
|
import { Calendar } from "@/components/ui/calendar"
|
|
|
|
import {
|
|
|
|
DropdownMenu,
|
|
|
|
DropdownMenuContent,
|
|
|
|
DropdownMenuItem,
|
|
|
|
DropdownMenuTrigger,
|
|
|
|
} from "@/components/ui/dropdown-menu"
|
|
|
|
import { Input } from "@/components/ui/input"
|
|
|
|
import { Label } from "@/components/ui/label"
|
|
|
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
|
|
|
import {
|
|
|
|
ResizableHandle,
|
|
|
|
ResizablePanel,
|
|
|
|
ResizablePanelGroup,
|
|
|
|
} from "@/components/ui/resizable"
|
2025-06-28 19:30:35 -03:00
|
|
|
import Footer from '../footer';
|
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
|
|
|
import {
|
|
|
|
Select,
|
|
|
|
SelectContent,
|
|
|
|
SelectItem,
|
|
|
|
SelectTrigger,
|
|
|
|
SelectValue,
|
|
|
|
} from "@/components/ui/select"
|
|
|
|
import { Separator } from "@/components/ui/separator"
|
|
|
|
import { Switch } from "@/components/ui/switch"
|
|
|
|
import {
|
|
|
|
Tabs,
|
|
|
|
TabsContent,
|
|
|
|
TabsList,
|
|
|
|
TabsTrigger,
|
|
|
|
} from "@/components/ui/tabs"
|
|
|
|
import { Textarea } from "@/components/ui/textarea"
|
|
|
|
import {
|
|
|
|
Tooltip,
|
|
|
|
TooltipContent,
|
|
|
|
TooltipTrigger,
|
|
|
|
TooltipProvider,
|
|
|
|
} from "@/components/ui/tooltip"
|
2025-06-21 19:45:21 -03:00
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
interface Account {
|
|
|
|
label: string
|
|
|
|
email: string
|
|
|
|
icon: React.ReactNode
|
|
|
|
}
|
2025-06-21 19:45:21 -03:00
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
interface Mail {
|
|
|
|
id: string
|
|
|
|
name: string
|
|
|
|
email: string
|
|
|
|
subject: string
|
|
|
|
text: string
|
|
|
|
date: string
|
|
|
|
read: boolean
|
|
|
|
labels: string[]
|
|
|
|
}
|
2025-06-21 19:45:21 -03:00
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
interface NavLink {
|
|
|
|
title: string
|
|
|
|
label?: string
|
|
|
|
icon: LucideIcon
|
|
|
|
variant: "default" | "ghost"
|
|
|
|
}
|
2025-06-21 19:45:21 -03:00
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
interface MailProps {
|
|
|
|
accounts: Account[]
|
|
|
|
mails: Mail[]
|
|
|
|
defaultLayout?: number[]
|
|
|
|
defaultCollapsed?: boolean
|
|
|
|
navCollapsedSize: number
|
|
|
|
}
|
2025-06-21 19:45:21 -03:00
|
|
|
|
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
function MailComponent({
|
|
|
|
accounts,
|
|
|
|
mails,
|
|
|
|
defaultLayout = [20, 32, 48],
|
|
|
|
defaultCollapsed = false,
|
|
|
|
navCollapsedSize,
|
|
|
|
}: MailProps) {
|
|
|
|
const [isCollapsed, setIsCollapsed] = React.useState(defaultCollapsed)
|
|
|
|
const [mail, setMail] = useMail()
|
2025-06-21 19:45:21 -03:00
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
const navLinks: NavLink[] = [
|
|
|
|
{ title: "Inbox", label: "128", icon: Inbox, variant: "default" },
|
|
|
|
{ title: "Drafts", label: "9", icon: File, variant: "ghost" },
|
|
|
|
{ title: "Sent", label: "", icon: Send, variant: "ghost" },
|
|
|
|
{ title: "Junk", label: "23", icon: ArchiveX, variant: "ghost" },
|
|
|
|
{ title: "Trash", label: "", icon: Trash2, variant: "ghost" },
|
|
|
|
{ title: "Archive", label: "", icon: Archive, variant: "ghost" },
|
|
|
|
{ title: "Social", label: "972", icon: Users2, variant: "ghost" },
|
|
|
|
{ title: "Updates", label: "342", icon: AlertCircle, variant: "ghost" },
|
|
|
|
{ title: "Forums", label: "128", icon: MessagesSquare, variant: "ghost" },
|
|
|
|
{ title: "Shopping", label: "8", icon: ShoppingCart, variant: "ghost" },
|
|
|
|
{ title: "Promotions", label: "21", icon: Archive, variant: "ghost" }
|
|
|
|
]
|
2025-06-21 19:45:21 -03:00
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
return (
|
|
|
|
<TooltipProvider delayDuration={0}>
|
|
|
|
<ResizablePanelGroup
|
|
|
|
direction="horizontal"
|
|
|
|
className="h-full max-h-[800px] items-stretch"
|
2025-06-21 19:45:21 -03:00
|
|
|
>
|
2025-06-21 20:30:28 -03:00
|
|
|
{/* Left Sidebar */}
|
|
|
|
<ResizablePanel
|
|
|
|
defaultSize={defaultLayout[0]}
|
|
|
|
collapsedSize={navCollapsedSize}
|
|
|
|
collapsible={true}
|
|
|
|
minSize={15}
|
|
|
|
maxSize={20}
|
|
|
|
onCollapse={() => setIsCollapsed(true)}
|
|
|
|
onResize={() => setIsCollapsed(false)}
|
|
|
|
className={cn(isCollapsed && "min-w-[50px] transition-all duration-300 ease-in-out")}
|
|
|
|
>
|
|
|
|
<div className={cn("flex h-[52px] items-center justify-center", isCollapsed ? "h-[52px]" : "px-2")}>
|
|
|
|
<AccountSwitcher isCollapsed={isCollapsed} accounts={accounts} />
|
2025-06-21 19:45:21 -03:00
|
|
|
</div>
|
|
|
|
<Separator />
|
2025-06-21 20:30:28 -03:00
|
|
|
<Nav links={navLinks.slice(0, 6)} isCollapsed={isCollapsed} />
|
|
|
|
<Separator />
|
|
|
|
<Nav links={navLinks.slice(6)} isCollapsed={isCollapsed} />
|
|
|
|
</ResizablePanel>
|
|
|
|
|
|
|
|
<ResizableHandle withHandle />
|
|
|
|
|
|
|
|
{/* Middle Mail List */}
|
|
|
|
<ResizablePanel defaultSize={defaultLayout[1]} minSize={30}>
|
|
|
|
<Tabs defaultValue="all">
|
|
|
|
<div className="flex items-center px-4 py-2">
|
|
|
|
<h1 className="text-xl font-bold">Inbox</h1>
|
|
|
|
<TabsList className="ml-auto">
|
|
|
|
<TabsTrigger value="all" className="text-foreground/80">All mail</TabsTrigger>
|
|
|
|
<TabsTrigger value="unread" className="text-foreground/80">Unread</TabsTrigger>
|
|
|
|
</TabsList>
|
|
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
<div className="bg-background/95 p-4 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
|
|
<form>
|
|
|
|
<div className="relative">
|
|
|
|
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
|
|
|
<Input placeholder="Search" className="pl-8" />
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
<TabsContent value="all" className="m-0">
|
|
|
|
<MailList items={mails} />
|
|
|
|
</TabsContent>
|
|
|
|
<TabsContent value="unread" className="m-0">
|
|
|
|
<MailList items={mails.filter((item) => !item.read)} />
|
|
|
|
</TabsContent>
|
|
|
|
</Tabs>
|
|
|
|
</ResizablePanel>
|
|
|
|
|
|
|
|
<ResizableHandle withHandle />
|
|
|
|
|
|
|
|
{/* Right Mail Display */}
|
|
|
|
<ResizablePanel defaultSize={defaultLayout[2]} minSize={30}>
|
|
|
|
<MailDisplay mail={mails.find((item) => item.id === mail.selected) || null} />
|
|
|
|
</ResizablePanel>
|
|
|
|
</ResizablePanelGroup>
|
|
|
|
</TooltipProvider>
|
|
|
|
)
|
|
|
|
}
|
2025-06-21 19:45:21 -03:00
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
function AccountSwitcher({ isCollapsed, accounts }: { isCollapsed: boolean, accounts: Account[] }) {
|
|
|
|
const [selectedAccount, setSelectedAccount] = React.useState(accounts[0].email)
|
2025-06-21 19:45:21 -03:00
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
return (
|
|
|
|
<Select defaultValue={selectedAccount} onValueChange={setSelectedAccount}>
|
|
|
|
<SelectTrigger
|
|
|
|
className={cn(
|
|
|
|
"flex items-center gap-2 [&>span]:line-clamp-1 [&>span]:flex [&>span]:w-full [&>span]:items-center [&>span]:gap-1 [&>span]:truncate [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0",
|
|
|
|
isCollapsed && "flex h-9 w-9 shrink-0 items-center justify-center p-0 [&>span]:w-auto [&>svg]:hidden"
|
|
|
|
)}
|
|
|
|
aria-label="Select account"
|
|
|
|
>
|
|
|
|
<SelectValue placeholder="Select an account">
|
|
|
|
{accounts.find((account) => account.email === selectedAccount)?.icon}
|
|
|
|
<span className={cn("ml-2", isCollapsed && "hidden")}>
|
|
|
|
{accounts.find((account) => account.email === selectedAccount)?.label}
|
|
|
|
</span>
|
|
|
|
</SelectValue>
|
|
|
|
</SelectTrigger>
|
|
|
|
<SelectContent>
|
|
|
|
{accounts.map((account) => (
|
|
|
|
<SelectItem key={account.email} value={account.email}>
|
|
|
|
<div className="flex items-center gap-3 [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0 [&_svg]:text-foreground">
|
|
|
|
{account.icon}
|
|
|
|
{account.email}
|
|
|
|
</div>
|
|
|
|
</SelectItem>
|
|
|
|
))}
|
|
|
|
</SelectContent>
|
|
|
|
</Select>
|
|
|
|
)
|
|
|
|
}
|
2025-06-21 19:45:21 -03:00
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
function Nav({ links, isCollapsed }: { links: NavLink[], isCollapsed: boolean }) {
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
data-collapsed={isCollapsed}
|
|
|
|
className="group flex flex-col gap-4 py-2 data-[collapsed=true]:py-2"
|
|
|
|
>
|
|
|
|
<nav className="grid gap-1 px-2 group-[[data-collapsed=true]]:justify-center group-[[data-collapsed=true]]:px-2">
|
|
|
|
{links.map((link, index) =>
|
|
|
|
isCollapsed ? (
|
|
|
|
<Tooltip key={index} delayDuration={0}>
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
<a
|
|
|
|
href="#"
|
|
|
|
className={cn(
|
|
|
|
"h-9 w-9 inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
|
|
|
|
link.variant === "default"
|
|
|
|
? "bg-primary text-primary-foreground hover:bg-primary/90"
|
|
|
|
: "hover:bg-secondary hover:text-secondary-foreground",
|
|
|
|
link.variant === "default" && "dark:bg-primary/10 dark:text-primary-foreground"
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
<link.icon className="h-4 w-4" />
|
|
|
|
<span className="sr-only">{link.title}</span>
|
|
|
|
</a>
|
|
|
|
</TooltipTrigger>
|
|
|
|
<TooltipContent side="right" className="flex items-center gap-4">
|
|
|
|
{link.title}
|
|
|
|
{link.label && <span className="ml-auto text-muted-foreground">{link.label}</span>}
|
|
|
|
</TooltipContent>
|
|
|
|
</Tooltip>
|
|
|
|
) : (
|
|
|
|
<a
|
|
|
|
key={index}
|
|
|
|
href="#"
|
|
|
|
className={cn(
|
|
|
|
"inline-flex items-center h-9 px-3 rounded-md text-sm font-medium transition-colors",
|
|
|
|
link.variant === "default"
|
|
|
|
? "bg-primary text-primary-foreground hover:bg-primary/90"
|
|
|
|
: "hover:bg-secondary hover:text-secondary-foreground",
|
|
|
|
link.variant === "default" && "dark:bg-primary/10 dark:text-primary-foreground",
|
|
|
|
"justify-start"
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
<link.icon className="mr-2 h-4 w-4" />
|
2025-06-21 19:45:21 -03:00
|
|
|
{link.title}
|
2025-06-21 20:30:28 -03:00
|
|
|
{link.label && (
|
|
|
|
<span className={cn("ml-auto", link.variant === "default" && "text-primary-foreground")}>
|
|
|
|
{link.label}
|
|
|
|
</span>
|
|
|
|
)}
|
|
|
|
</a>
|
|
|
|
)
|
|
|
|
)}
|
|
|
|
</nav>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
function MailList({ items }: { items: Mail[] }) {
|
|
|
|
const [mail, setMail] = useMail()
|
|
|
|
|
|
|
|
return (
|
|
|
|
<ScrollArea className="h-screen">
|
|
|
|
<div className="flex flex-col gap-2 p-4 pt-0">
|
|
|
|
{items.map((item) => (
|
|
|
|
<button
|
|
|
|
key={item.id}
|
2025-06-21 19:45:21 -03:00
|
|
|
className={cn(
|
2025-06-21 20:30:28 -03:00
|
|
|
"flex flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent",
|
|
|
|
mail.selected === item.id && "bg-muted"
|
2025-06-21 19:45:21 -03:00
|
|
|
)}
|
2025-06-21 20:30:28 -03:00
|
|
|
onClick={() => setMail({ ...mail, selected: item.id })}
|
2025-06-21 19:45:21 -03:00
|
|
|
>
|
2025-06-21 20:30:28 -03:00
|
|
|
<div className="flex w-full flex-col gap-1">
|
|
|
|
<div className="flex items-center">
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
<div className="font-semibold">{item.name}</div>
|
|
|
|
{!item.read && <span className="flex h-2 w-2 rounded-full bg-primary" />}
|
|
|
|
</div>
|
|
|
|
<div className={cn("ml-auto text-xs", mail.selected === item.id ? "text-foreground" : "text-muted-foreground")}>
|
|
|
|
{formatDistanceToNow(new Date(item.date), { addSuffix: true })}
|
|
|
|
</div>
|
2025-06-21 19:45:21 -03:00
|
|
|
</div>
|
2025-06-21 20:30:28 -03:00
|
|
|
<div className="text-xs font-medium">{item.subject}</div>
|
2025-06-21 19:45:21 -03:00
|
|
|
</div>
|
2025-06-21 20:30:28 -03:00
|
|
|
<div className="line-clamp-2 text-xs text-muted-foreground">
|
|
|
|
{item.text.substring(0, 300)}
|
2025-06-21 19:45:21 -03:00
|
|
|
</div>
|
2025-06-21 20:30:28 -03:00
|
|
|
{item.labels.length ? (
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
{item.labels.map((label) => (
|
|
|
|
<Badge key={label} variant={getBadgeVariantFromLabel(label)}>
|
|
|
|
{label}
|
|
|
|
</Badge>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
) : null}
|
|
|
|
</button>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
</ScrollArea>
|
|
|
|
)
|
|
|
|
}
|
2025-06-21 19:45:21 -03:00
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
function MailDisplay({ mail }: { mail: Mail | null }) {
|
|
|
|
const today = new Date()
|
2025-06-21 19:45:21 -03:00
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
return (
|
|
|
|
<div className="flex h-full flex-col">
|
|
|
|
<div className="flex items-center p-2">
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
<Tooltip>
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
<Button variant="ghost" size="icon" disabled={!mail}>
|
|
|
|
<Archive className="h-4 w-4" />
|
|
|
|
<span className="sr-only">Archive</span>
|
|
|
|
</Button>
|
|
|
|
</TooltipTrigger>
|
|
|
|
<TooltipContent>Archive</TooltipContent>
|
|
|
|
</Tooltip>
|
|
|
|
<Tooltip>
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
<Button variant="ghost" size="icon" disabled={!mail}>
|
|
|
|
<ArchiveX className="h-4 w-4" />
|
|
|
|
<span className="sr-only">Move to junk</span>
|
|
|
|
</Button>
|
|
|
|
</TooltipTrigger>
|
|
|
|
<TooltipContent>Move to junk</TooltipContent>
|
|
|
|
</Tooltip>
|
|
|
|
<Tooltip>
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
<Button variant="ghost" size="icon" disabled={!mail}>
|
|
|
|
<Trash2 className="h-4 w-4" />
|
|
|
|
<span className="sr-only">Move to trash</span>
|
|
|
|
</Button>
|
|
|
|
</TooltipTrigger>
|
|
|
|
<TooltipContent>Move to trash</TooltipContent>
|
|
|
|
</Tooltip>
|
|
|
|
<Separator orientation="vertical" className="mx-1 h-6" />
|
|
|
|
<Tooltip>
|
|
|
|
<Popover>
|
|
|
|
<PopoverTrigger asChild>
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
<Button variant="ghost" size="icon" disabled={!mail}>
|
|
|
|
<Clock className="h-4 w-4" />
|
|
|
|
<span className="sr-only">Snooze</span>
|
2025-06-21 19:45:21 -03:00
|
|
|
</Button>
|
2025-06-21 20:30:28 -03:00
|
|
|
</TooltipTrigger>
|
|
|
|
</PopoverTrigger>
|
|
|
|
<PopoverContent className="flex w-[535px] p-0">
|
|
|
|
<div className="flex flex-col gap-2 border-r px-2 py-4">
|
|
|
|
<div className="px-4 text-sm font-medium">Snooze until</div>
|
|
|
|
<div className="grid min-w-[250px] gap-1">
|
|
|
|
<Button variant="ghost" className="justify-start font-normal">
|
|
|
|
Later today <span className="ml-auto text-muted-foreground">
|
|
|
|
{format(addHours(today, 4), "E, h:m b")}
|
|
|
|
</span>
|
|
|
|
</Button>
|
|
|
|
<Button variant="ghost" className="justify-start font-normal">
|
|
|
|
Tomorrow<span className="ml-auto text-muted-foreground">
|
|
|
|
{format(addDays(today, 1), "E, h:m b")}
|
|
|
|
</span>
|
|
|
|
</Button>
|
|
|
|
<Button variant="ghost" className="justify-start font-normal">
|
|
|
|
This weekend<span className="ml-auto text-muted-foreground">
|
|
|
|
{format(nextSaturday(today), "E, h:m b")}
|
|
|
|
</span>
|
|
|
|
</Button>
|
|
|
|
<Button variant="ghost" className="justify-start font-normal">
|
|
|
|
Next week<span className="ml-auto text-muted-foreground">
|
|
|
|
{format(addDays(today, 7), "E, h:m b")}
|
|
|
|
</span>
|
|
|
|
</Button>
|
|
|
|
</div>
|
2025-06-21 19:45:21 -03:00
|
|
|
</div>
|
2025-06-21 20:30:28 -03:00
|
|
|
<div className="p-2">
|
|
|
|
<Calendar />
|
|
|
|
</div>
|
|
|
|
</PopoverContent>
|
|
|
|
</Popover>
|
|
|
|
<TooltipContent>Snooze</TooltipContent>
|
|
|
|
</Tooltip>
|
|
|
|
</div>
|
|
|
|
<div className="ml-auto flex items-center gap-2">
|
|
|
|
<Tooltip>
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
<Button variant="ghost" size="icon" disabled={!mail}>
|
|
|
|
<Reply className="h-4 w-4" />
|
|
|
|
<span className="sr-only">Reply</span>
|
|
|
|
</Button>
|
|
|
|
</TooltipTrigger>
|
|
|
|
<TooltipContent>Reply</TooltipContent>
|
|
|
|
</Tooltip>
|
|
|
|
<Tooltip>
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
<Button variant="ghost" size="icon" disabled={!mail}>
|
|
|
|
<ReplyAll className="h-4 w-4" />
|
|
|
|
<span className="sr-only">Reply all</span>
|
|
|
|
</Button>
|
|
|
|
</TooltipTrigger>
|
|
|
|
<TooltipContent>Reply all</TooltipContent>
|
|
|
|
</Tooltip>
|
|
|
|
<Tooltip>
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
<Button variant="ghost" size="icon" disabled={!mail}>
|
|
|
|
<Forward className="h-4 w-4" />
|
|
|
|
<span className="sr-only">Forward</span>
|
|
|
|
</Button>
|
|
|
|
</TooltipTrigger>
|
|
|
|
<TooltipContent>Forward</TooltipContent>
|
|
|
|
</Tooltip>
|
|
|
|
</div>
|
|
|
|
<Separator orientation="vertical" className="mx-2 h-6" />
|
|
|
|
<DropdownMenu>
|
|
|
|
<DropdownMenuTrigger asChild>
|
2025-06-21 19:45:21 -03:00
|
|
|
<Button variant="ghost" size="icon" disabled={!mail}>
|
2025-06-21 20:30:28 -03:00
|
|
|
<MoreVertical className="h-4 w-4" />
|
|
|
|
<span className="sr-only">More</span>
|
2025-06-21 19:45:21 -03:00
|
|
|
</Button>
|
2025-06-21 20:30:28 -03:00
|
|
|
</DropdownMenuTrigger>
|
|
|
|
<DropdownMenuContent align="end">
|
|
|
|
<DropdownMenuItem>Mark as unread</DropdownMenuItem>
|
|
|
|
<DropdownMenuItem>Star thread</DropdownMenuItem>
|
|
|
|
<DropdownMenuItem>Add label</DropdownMenuItem>
|
|
|
|
<DropdownMenuItem>Mute thread</DropdownMenuItem>
|
|
|
|
</DropdownMenuContent>
|
|
|
|
</DropdownMenu>
|
2025-06-21 19:45:21 -03:00
|
|
|
</div>
|
2025-06-21 20:30:28 -03:00
|
|
|
<Separator />
|
|
|
|
{mail ? (
|
|
|
|
<div className="flex flex-1 flex-col">
|
|
|
|
<div className="flex items-start p-4">
|
|
|
|
<div className="flex items-start gap-4 text-sm">
|
|
|
|
<Avatar>
|
|
|
|
<AvatarImage alt={mail.name} />
|
|
|
|
<AvatarFallback>
|
|
|
|
{mail.name.split(" ").map((chunk) => chunk[0]).join("")}
|
|
|
|
</AvatarFallback>
|
|
|
|
</Avatar>
|
|
|
|
<div className="grid gap-1">
|
|
|
|
<div className="font-semibold">{mail.name}</div>
|
|
|
|
<div className="line-clamp-1 text-xs">{mail.subject}</div>
|
|
|
|
<div className="line-clamp-1 text-xs">
|
|
|
|
<span className="font-medium">Reply-To:</span> {mail.email}
|
|
|
|
</div>
|
2025-06-21 19:45:21 -03:00
|
|
|
</div>
|
|
|
|
</div>
|
2025-06-21 20:30:28 -03:00
|
|
|
{mail.date && (
|
|
|
|
<div className="ml-auto text-xs text-muted-foreground">
|
|
|
|
{format(new Date(mail.date), "PPpp")}
|
|
|
|
</div>
|
|
|
|
)}
|
2025-06-21 19:45:21 -03:00
|
|
|
</div>
|
2025-06-21 20:30:28 -03:00
|
|
|
<Separator />
|
|
|
|
<div className="flex-1 whitespace-pre-wrap p-4 text-sm">
|
|
|
|
{mail.text}
|
|
|
|
</div>
|
|
|
|
<Separator className="mt-auto" />
|
|
|
|
<div className="p-4">
|
|
|
|
<form>
|
|
|
|
<div className="grid gap-4">
|
|
|
|
<Textarea className="p-4" placeholder={`Reply ${mail.name}...`} />
|
|
|
|
<div className="flex items-center">
|
|
|
|
<Label htmlFor="mute" className="flex items-center gap-2 text-xs font-normal">
|
|
|
|
<Switch id="mute" aria-label="Mute thread" /> Mute this thread
|
|
|
|
</Label>
|
|
|
|
<Button onClick={(e) => e.preventDefault()} size="sm" className="ml-auto">
|
|
|
|
Send
|
|
|
|
</Button>
|
|
|
|
</div>
|
2025-06-21 19:45:21 -03:00
|
|
|
</div>
|
2025-06-21 20:30:28 -03:00
|
|
|
</form>
|
|
|
|
</div>
|
2025-06-21 19:45:21 -03:00
|
|
|
</div>
|
2025-06-21 20:30:28 -03:00
|
|
|
) : (
|
|
|
|
<div className="p-8 text-center text-muted-foreground">
|
|
|
|
No message selected
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
2025-06-21 19:45:21 -03:00
|
|
|
|
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
function getBadgeVariantFromLabel(label: string) {
|
|
|
|
if (["work"].includes(label.toLowerCase())) return "default"
|
|
|
|
if (["personal"].includes(label.toLowerCase())) return "outline"
|
|
|
|
return "secondary"
|
|
|
|
}
|
2025-06-21 19:45:21 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
export default function MailPage() {
|
|
|
|
const layout = [20, 32, 48]; //cookies().get("react-resizable-panels:layout:mail")
|
|
|
|
const collapsed = false; //cookies().get("react-resizable-panels:collapsed")
|
2025-04-02 20:42:47 -03:00
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
const defaultLayout = layout// ? JSON.parse(layout.value) : undefined
|
|
|
|
const defaultCollapsed = collapsed //? JSON.parse(collapsed.value) : undefined
|
2025-04-02 20:42:47 -03:00
|
|
|
|
|
|
|
|
2025-06-28 19:30:35 -03:00
|
|
|
// Drive-specific keyboard shortcuts
|
|
|
|
// XTreeGold classic shortcut layout - two rows
|
|
|
|
// XTree/NC-style: only unique, non-browser, non-reserved keys for file ops
|
|
|
|
// No F1-F12, Ctrl+F, Ctrl+T, Ctrl+W, Ctrl+N, Ctrl+R, Ctrl+P, etc.
|
|
|
|
// Use Q, W, E, R, T, Y, U, I, O, P, A, S, D, G, H, J, K, L, Z, X, C, V, B, M with Ctrl/Shift if needed
|
|
|
|
const shortcuts = [
|
|
|
|
// File operations row (unique qkeys)
|
|
|
|
[
|
|
|
|
{ key: 'Q', label: 'Rename', action: () => console.log('Rename') },
|
|
|
|
{ key: 'W', label: 'View', action: () => console.log('View') },
|
|
|
|
{ key: 'E', label: 'Edit', action: () => console.log('Edit') },
|
|
|
|
{ key: 'R', label: 'Move', action: () => console.log('Move') },
|
|
|
|
{ key: 'T', label: 'MkDir', action: () => console.log('Make Directory') },
|
|
|
|
{ key: 'Y', label: 'Delete', action: () => console.log('Delete') },
|
|
|
|
{ key: 'U', label: 'Copy', action: () => console.log('Copy') },
|
|
|
|
{ key: 'I', label: 'Cut', action: () => console.log('Cut') },
|
|
|
|
{ key: 'O', label: 'Paste', action: () => console.log('Paste') },
|
|
|
|
{ key: 'P', label: 'Duplicate', action: () => console.log('Duplicate') },
|
|
|
|
],
|
|
|
|
// Navigation/info row (unique qkeys)
|
|
|
|
[
|
|
|
|
{ key: 'A', label: 'Select', action: () => console.log('Select') },
|
|
|
|
{ key: 'S', label: 'Select All', action: () => console.log('Select All') },
|
|
|
|
{ key: 'D', label: 'Deselect', action: () => console.log('Deselect') },
|
|
|
|
{ key: 'G', label: 'Details', action: () => console.log('Details') },
|
|
|
|
{ key: 'H', label: 'History', action: () => console.log('History') },
|
|
|
|
{ key: 'J', label: 'Share', action: () => console.log('Share') },
|
|
|
|
{ key: 'K', label: 'Star', action: () => console.log('Star') },
|
|
|
|
{ key: 'L', label: 'Download', action: () => console.log('Download') },
|
|
|
|
{ key: 'Z', label: 'Upload', action: () => console.log('Upload') },
|
|
|
|
{ key: 'X', label: 'Refresh', action: () => console.log('Refresh') },
|
|
|
|
]
|
|
|
|
];
|
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<div className="flex-col md:flex">
|
|
|
|
<MailComponent
|
|
|
|
accounts={accounts}
|
|
|
|
mails={mails}
|
|
|
|
defaultLayout={defaultLayout}
|
|
|
|
defaultCollapsed={defaultCollapsed}
|
|
|
|
navCollapsedSize={4}
|
|
|
|
/>
|
2025-06-28 19:30:35 -03:00
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
</div>
|
2025-06-28 19:30:35 -03:00
|
|
|
{/* Footer with Status Bar */}
|
|
|
|
<Footer shortcuts={shortcuts} />
|
|
|
|
|
2025-06-21 20:30:28 -03:00
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|