diff --git a/app/admin/analytics/page.tsx b/app/admin/analytics/page.tsx index 7d156666..2cba5001 100644 --- a/app/admin/analytics/page.tsx +++ b/app/admin/analytics/page.tsx @@ -425,74 +425,78 @@ export default function AnalyticsPage() {
- - ({ - label: line.label, - value: line.amount, - }))} - format={usd} - /> -
- - - - - - - - - - - {costs.last30.lines.map((line) => ( - - - -
Item - Quantity - - Unit price - Amount
- - {line.label} - - {line.note && ( - - {line.note} - - )} - - {quantity(line)} - - ${line.unitPrice} +
+ + ({ + label: line.label, + value: line.amount, + }))} + format={usd} + /> +
+ + + + + + + + + + + {costs.last30.lines.map((line) => ( + + + + + + + ))} + + + + - - ))} - - - - - - - -
Item + Quantity + + Unit price + + Amount +
+ + {line.label} + + {line.note && ( + + {line.note} + + )} + + {quantity(line)} + + ${line.unitPrice} + + {usd(line.amount)} +
+ Total - {usd(line.amount)} + + {usd(costs.last30.total)}
- Total - - {usd(costs.last30.total)} -
-
-
+ +
+
+
+
event.target === event.currentTarget && settle(null) } diff --git a/app/admin/documents/page.tsx b/app/admin/documents/page.tsx index d92ba16c..236025c1 100644 --- a/app/admin/documents/page.tsx +++ b/app/admin/documents/page.tsx @@ -507,15 +507,17 @@ export default function DocumentsPage() { key={doc.$key} > {doc.currentVersionId && ( - - toggleChecked(doc.$key, e.target.checked) - } - type="checkbox" - /> + )} -
+
{event.image?.url ? ( , whose own + // fade-in animation creates a CSS stacking context that would otherwise + // trap this fixed overlay below the mobile tab bar. + return createPortal(
-
+

{title}

-
+
, + document.body, ); } diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx index 054e13bb..862e13b2 100644 --- a/app/admin/layout.tsx +++ b/app/admin/layout.tsx @@ -104,7 +104,7 @@ function AdminShell({ children }: { children: React.ReactNode }) { return (
-
+
{onMenu ? ( ( -
+
{label}
diff --git a/app/admin/mail/attachment-preview.tsx b/app/admin/mail/attachment-preview.tsx new file mode 100644 index 00000000..b03793ad --- /dev/null +++ b/app/admin/mail/attachment-preview.tsx @@ -0,0 +1,247 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { createPortal } from "react-dom"; +import { ChevronLeft, ChevronRight, Download, X } from "lucide-react"; +import type { BodyPart } from "@/lib/mail/jmap-mail"; +import { withAs } from "./inbox-picker"; + +export type PreviewKind = "image" | "pdf" | "text" | "audio" | "video"; + +const TEXT_PREVIEW = /^text\/(plain|csv|markdown)$|^application\/json$/i; +const TEXT_PREVIEW_MAX_BYTES = 512 * 1024; + +/** What kind of inline preview a MIME type supports, or null to fall back + * to a plain download. Nothing here needs the blob route to change: Content- + * Disposition doesn't stop /