diff --git a/.cursor/rules/tailwind-css-v4.mdc b/.cursor/rules/tailwind-css-v4.mdc new file mode 100644 index 0000000000..7931034bde --- /dev/null +++ b/.cursor/rules/tailwind-css-v4.mdc @@ -0,0 +1,217 @@ +--- +name: tailwind_v4 +description: Guide for using Tailwind CSS v4 instead of v3.x +globs: ["**/*.{js,ts,jsx,tsx,mdx,css}"] +tags: + - tailwind + - css +--- + +# Tailwind CSS v4 + +## Core Changes + +- **CSS-first configuration**: Configuration is now done in CSS instead of JavaScript + - Use `@theme` directive in CSS instead of `tailwind.config.js` + - Example: + ```css + @import "tailwindcss"; + + @theme { + --font-display: "Satoshi", "sans-serif"; + --breakpoint-3xl: 1920px; + --color-avocado-500: oklch(0.84 0.18 117.33); + --ease-fluid: cubic-bezier(0.3, 0, 0, 1); + } + ``` +- Legacy `tailwind.config.js` files can still be imported using the `@config` directive: + ```css + @import "tailwindcss"; + @config "../../tailwind.config.js"; + ``` +- **CSS import syntax**: Use `@import "tailwindcss"` instead of `@tailwind` directives + - Old: `@tailwind base; @tailwind components; @tailwind utilities;` + - New: `@import "tailwindcss";` + +- **Package changes**: + - PostCSS plugin is now `@tailwindcss/postcss` (not `tailwindcss`) + - CLI is now `@tailwindcss/cli` + - Vite plugin is `@tailwindcss/vite` + - No need for `postcss-import` or `autoprefixer` anymore + +- **Native CSS cascade layers**: Uses real CSS `@layer` instead of Tailwind's custom implementation + +## Theme Configuration + +- **CSS theme variables**: All design tokens are available as CSS variables + - Namespace format: `--category-name` (e.g., `--color-blue-500`, `--font-sans`) + - Access in CSS: `var(--color-blue-500)` + - Available namespaces: + - `--color-*` : Color utilities like `bg-red-500` and `text-sky-300` + - `--font-*` : Font family utilities like `font-sans` + - `--text-*` : Font size utilities like `text-xl` + - `--font-weight-*` : Font weight utilities like `font-bold` + - `--tracking-*` : Letter spacing utilities like `tracking-wide` + - `--leading-*` : Line height utilities like `leading-tight` + - `--breakpoint-*` : Responsive breakpoint variants like `sm:*` + - `--container-*` : Container query variants like `@sm:*` and size utilities like `max-w-md` + - `--spacing-*` : Spacing and sizing utilities like `px-4` and `max-h-16` + - `--radius-*` : Border radius utilities like `rounded-sm` + - `--shadow-*` : Box shadow utilities like `shadow-md` + - `--inset-shadow-*` : Inset box shadow utilities like `inset-shadow-xs` + - `--drop-shadow-*` : Drop shadow filter utilities like `drop-shadow-md` + - `--blur-*` : Blur filter utilities like `blur-md` + - `--perspective-*` : Perspective utilities like `perspective-near` + - `--aspect-*` : Aspect ratio utilities like `aspect-video` + - `--ease-*` : Transition timing function utilities like `ease-out` + - `--animate-*` : Animation utilities like `animate-spin` + + +- **Simplified theme configuration**: Many utilities no longer need theme configuration + - Utilities like `grid-cols-12`, `z-40`, and `opacity-70` work without configuration + - Data attributes like `data-selected:opacity-100` don't need configuration + +- **Dynamic spacing scale**: Derived from a single spacing value + - Default: `--spacing: 0.25rem` + - Every multiple of the base value is available (e.g., `mt-21` works automatically) + +- **Overriding theme namespaces**: + - Override entire namespace: `--font-*: initial;` + - Override entire theme: `--*: initial;` + + +## New Features + +- **Container query support**: Built-in now, no plugin needed + - `@container` for container context + - `@sm:`, `@md:`, etc. for container-based breakpoints + - `@max-md:` for max-width container queries + - Combine with `@min-md:@max-xl:hidden` for ranges + +- **3D transforms**: + - `transform-3d` enables 3D transforms + - `rotate-x-*`, `rotate-y-*`, `rotate-z-*` for 3D rotation + - `scale-z-*` for z-axis scaling + - `translate-z-*` for z-axis translation + - `perspective-*` utilities (`perspective-near`, `perspective-distant`, etc.) + - `perspective-origin-*` utilities + - `backface-visible` and `backface-hidden` + +- **Gradient enhancements**: + - Linear gradient angles: `bg-linear-45` (renamed from `bg-gradient-*`) + - Gradient interpolation: `bg-linear-to-r/oklch`, `bg-linear-to-r/srgb` + - Conic and radial gradients: `bg-conic`, `bg-radial-[at_25%_25%]` + +- **Shadow enhancements**: + - `inset-shadow-*` and `inset-ring-*` utilities + - Can be composed with regular `shadow-*` and `ring-*` + +- **New CSS property utilities**: + - `field-sizing-content` for auto-resizing textareas + - `scheme-light`, `scheme-dark` for `color-scheme` property + - `font-stretch-*` utilities for variable fonts + +## New Variants + +- **Composable variants**: Chain variants together + - Example: `group-has-data-potato:opacity-100` + +- **New variants**: + - `starting` variant for `@starting-style` transitions + - `not-*` variant for `:not()` pseudo-class + - `inert` variant for `inert` attribute + - `nth-*` variants (`nth-3:`, `nth-last-5:`, `nth-of-type-4:`, `nth-last-of-type-6:`) + - `in-*` variant (like `group-*` but without adding `group` class) + - `open` variant now supports `:popover-open` + - `**` variant for targeting all descendants + +## Custom Extensions + +- **Custom utilities**: Use `@utility` directive + ```css + @utility tab-4 { + tab-size: 4; + } + ``` + +- **Custom variants**: Use `@variant` directive + ```css + @variant pointer-coarse (@media (pointer: coarse)); + @variant theme-midnight (&:where([data-theme="midnight"] *)); + ``` + +- **Plugins**: Use `@plugin` directive + ```css + @plugin "@tailwindcss/typography"; + ``` + +## Breaking Changes + +- **Removed deprecated utilities**: + - `bg-opacity-*` → Use `bg-black/50` instead + - `text-opacity-*` → Use `text-black/50` instead + - And others: `border-opacity-*`, `divide-opacity-*`, etc. + +- **Renamed utilities**: + - `shadow-sm` → `shadow-xs` (and `shadow` → `shadow-sm`) + - `drop-shadow-sm` → `drop-shadow-xs` (and `drop-shadow` → `drop-shadow-sm`) + - `blur-sm` → `blur-xs` (and `blur` → `blur-sm`) + - `rounded-sm` → `rounded-xs` (and `rounded` → `rounded-sm`) + - `outline-none` → `outline-hidden` (for the old behavior) + +- **Default style changes**: + - Default border color is now `currentColor` (was `gray-200`) + - Default `ring` width is now 1px (was 3px) + - Placeholder text now uses current color at 50% opacity (was `gray-400`) + - Hover styles only apply on devices that support hover (`@media (hover: hover)`) + +- **Syntax changes**: + - CSS variables in arbitrary values: `bg-(--brand-color)` instead of `bg-[--brand-color]` + - Stacked variants now apply left-to-right (not right-to-left) + - Use CSS variables instead of `theme()` function + +## Advanced Configuration + +- **Using a prefix**: + ```css + @import "tailwindcss" prefix(tw); + ``` + - Results in classes like `tw:flex`, `tw:bg-red-500`, `tw:hover:bg-red-600` + +- **Source detection**: + - Automatic by default (ignores `.gitignore` files and binary files) + - Add sources: `@source "../node_modules/@my-company/ui-lib";` + - Disable automatic detection: `@import "tailwindcss" source(none);` + +- **Legacy config files**: + ```css + @import "tailwindcss"; + @config "../../tailwind.config.js"; + ``` + +- **Dark mode configuration**: + ```css + @import "tailwindcss"; + @variant dark (&:where(.dark, .dark *)); + ``` + +- **Container customization**: Extend with `@utility` + ```css + @utility container { + margin-inline: auto; + padding-inline: 2rem; + } + ``` + +- **Using `@apply` in Vue/Svelte**: + ```html + + ``` \ No newline at end of file diff --git a/.env.example b/.env.example index 8c4f2052ab..a787a645c6 100644 --- a/.env.example +++ b/.env.example @@ -37,4 +37,9 @@ AUTUMN_SECRET_KEY= TWILIO_ACCOUNT_SID= TWILIO_AUTH_TOKEN= -TWILIO_PHONE_NUMBER= \ No newline at end of file +TWILIO_PHONE_NUMBER= + +# FOR PLAYWRIGHT E2E TESTING +PLAYWRIGHT_SESSION_TOKEN = +PLAYWRIGHT_SESSION_DATA = +EMAIL = \ No newline at end of file diff --git a/AGENT.md b/AGENT.md index 86fa16eaf3..68e20f816a 100644 --- a/AGENT.md +++ b/AGENT.md @@ -11,7 +11,6 @@ This is a pnpm workspace monorepo with the following structure: - `packages/cli/` - CLI tools (`nizzy` command) - `packages/db/` - Database schemas and utilities - `packages/eslint-config/` - Shared ESLint configuration -- `packages/tailwind-config/` - Shared Tailwind configuration - `packages/tsconfig/` - Shared TypeScript configuration ## Frequently Used Commands diff --git a/apps/mail/app/(auth)/login/login-client.tsx b/apps/mail/app/(auth)/login/login-client.tsx index eadf4f07ea..fb770b5e0d 100644 --- a/apps/mail/app/(auth)/login/login-client.tsx +++ b/apps/mail/app/(auth)/login/login-client.tsx @@ -134,7 +134,7 @@ function LoginClientContent({ providers, isProd }: LoginClientProps) { return (
-
+

Login to Zero

@@ -203,7 +203,7 @@ function LoginClientContent({ providers, isProd }: LoginClientProps) {
-
+
{provider.envVarStatus.map((envVar) => (
-
+
@@ -134,6 +135,7 @@ export default function GeneralPage() { customPrompt: '', zeroSignature: true, defaultEmailAlias: '', + animations: false, }, }); @@ -178,6 +180,20 @@ export default function GeneralPage() { } } + const renderAnimationsField = useCallback(({ field }: { field: any }) => ( + +
+ {m['pages.settings.general.animations']()} + + {m['pages.settings.general.animationsDescription']()} + +
+ + + +
+ ), []); + return (
( - + {m['pages.settings.general.defaultEmailAlias']()}{' '} @@ -307,6 +323,11 @@ export default function GeneralPage() { )} /> + diff --git a/apps/mail/app/(routes)/settings/labels/page.tsx b/apps/mail/app/(routes)/settings/labels/page.tsx index e0e4a90676..189a162479 100644 --- a/apps/mail/app/(routes)/settings/labels/page.tsx +++ b/apps/mail/app/(routes)/settings/labels/page.tsx @@ -123,7 +123,7 @@ export default function LabelsPage() { {label.name}
-
+
diff --git a/apps/mail/components/context/loading-context.tsx b/apps/mail/components/context/loading-context.tsx index 9d26669c8d..e8cc674bfb 100644 --- a/apps/mail/components/context/loading-context.tsx +++ b/apps/mail/components/context/loading-context.tsx @@ -22,7 +22,7 @@ export function LoadingProvider({ children }: { children: ReactNode }) { {children} {isLoading && ( -
+
diff --git a/apps/mail/components/context/sidebar-context.tsx b/apps/mail/components/context/sidebar-context.tsx index 967505ac08..c23ee1cd48 100644 --- a/apps/mail/components/context/sidebar-context.tsx +++ b/apps/mail/components/context/sidebar-context.tsx @@ -113,7 +113,7 @@ export const SidebarProvider = React.forwardRef< } as React.CSSProperties } className={cn( - 'group/sidebar-wrapper has-[[data-variant=inset]]:bg-sidebar flex min-h-svh w-full', + 'group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full', className, )} ref={ref} diff --git a/apps/mail/components/create/ai-chat.tsx b/apps/mail/components/create/ai-chat.tsx index e0c7a4e352..d19ad7a6b4 100644 --- a/apps/mail/components/create/ai-chat.tsx +++ b/apps/mail/components/create/ai-chat.tsx @@ -3,12 +3,14 @@ import { useAIFullScreen, useAISidebar } from '../ui/ai-sidebar'; import { VoiceProvider } from '@/providers/voice-provider'; import useComposeEditor from '@/hooks/use-compose-editor'; import { useRef, useCallback, useEffect } from 'react'; +import type { useAgentChat } from 'agents/ai-react'; import { Markdown } from '@react-email/components'; import { useBilling } from '@/hooks/use-billing'; import { TextShimmer } from '../ui/text-shimmer'; import { useThread } from '@/hooks/use-threads'; import { MailLabels } from '../mail/mail-list'; import { cn, getEmailLogo } from '@/lib/utils'; +import type { Message as AiMessage } from 'ai'; import { VoiceButton } from '../voice-button'; import { EditorContent } from '@tiptap/react'; import { CurvedArrow } from '../icons/icons'; @@ -69,7 +71,6 @@ const ThreadPreview = ({ threadId }: { threadId: string }) => { }; const ExampleQueries = ({ onQueryClick }: { onQueryClick: (query: string) => void }) => { - const firstRowQueries = [ 'Find invoice from Stripe', 'Show unpaid invoices', @@ -79,7 +80,7 @@ const ExampleQueries = ({ onQueryClick }: { onQueryClick: (query: string) => voi const secondRowQueries = ['Find all work meetings', 'What projects do i have coming up']; return ( -
+
{/* First row */}
@@ -87,7 +88,8 @@ const ExampleQueries = ({ onQueryClick }: { onQueryClick: (query: string) => voi ))} @@ -100,7 +102,7 @@ const ExampleQueries = ({ onQueryClick }: { onQueryClick: (query: string) => voi @@ -108,31 +110,31 @@ const ExampleQueries = ({ onQueryClick }: { onQueryClick: (query: string) => voi
{/* Left mask */} -
+
{/* Right mask */} -
+
); }; -interface Message { - id: string; - role: 'user' | 'assistant' | 'data' | 'system'; - parts: Array<{ - type: string; - text?: string; - toolInvocation?: { - toolName: string; - result?: { - threads?: Array<{ id: string; title: string; snippet: string }>; - }; - args?: any; - }; - }>; -} +// interface Message { +// id: string; +// role: 'user' | 'assistant' | 'data' | 'system'; +// parts: Array<{ +// type: string; +// text?: string; +// toolInvocation?: { +// toolName: string; +// result?: { +// threads?: Array<{ id: string; title: string; snippet: string }>; +// }; +// args?: any; +// }; +// }>; +// } export interface AIChatProps { - messages: Message[]; + messages: AiMessage[]; input: string; setInput: (input: string) => void; error?: Error; @@ -141,6 +143,7 @@ export interface AIChatProps { stop: () => void; className?: string; onModelChange?: (model: string) => void; + setMessages: (messages: AiMessage[]) => void; } // Subcomponents for ToolResponse @@ -198,7 +201,7 @@ export function AIChat({ error, handleSubmit, status, -}: AIChatProps): React.ReactElement { +}: ReturnType): React.ReactElement { const messagesEndRef = useRef(null); const messagesContainerRef = useRef(null); const { chatMessages } = useBilling(); @@ -233,7 +236,7 @@ export function AIChat({ }, }); - const onSubmit = (e: React.FormEvent) => { + const onSubmit = async (e: React.FormEvent) => { e.preventDefault(); handleSubmit(e); editor.commands.clearContent(true); @@ -260,13 +263,13 @@ export function AIChat({
{chatMessages && !chatMessages.enabled ? (
setPricingDialog('true')} - className="absolute inset-0 flex flex-col items-center justify-center" - > - - Upgrade to Zero Pro for unlimited AI chat - - + onClick={() => setPricingDialog('true')} + className="absolute inset-0 flex flex-col items-center justify-center" + > + + Upgrade to Zero Pro for unlimited AI chat + +
) : !messages.length ? (
@@ -365,7 +368,7 @@ export function AIChat({
{/* Fixed input at bottom */} -
+
@@ -402,7 +405,7 @@ export function AIChat({
- {/*
+ {/*
- + {aliases.map((alias) => (
@@ -1322,6 +1323,15 @@ export function EmailComposer({ Add + setValue('subject', value)} + to={toEmails} + cc={ccEmails ?? []} + bcc={bccEmails ?? []} + setRecipients={(field, val) => setValue(field, val)} + /> @@ -1387,7 +1397,7 @@ export function EmailComposer({ className="group flex items-center justify-between gap-3 rounded-md px-1.5 py-1.5 hover:bg-black/5 dark:hover:bg-white/10" >
-
+
{file.type.startsWith('image/') ? ( {truncatedName} {extension && ( - + .{extension} )} @@ -1438,7 +1448,7 @@ export function EmailComposer({ toast.error('Failed to remove attachment'); } }} - className="focus-visible:ring-ring ml-1 flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-transparent hover:bg-black/5 focus-visible:outline-none focus-visible:ring-2" + className="focus-visible:ring-ring ml-1 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-transparent hover:bg-black/5 focus-visible:outline-none focus-visible:ring-2" aria-label={`Remove ${file.name}`} > @@ -1556,7 +1566,7 @@ export function EmailComposer({
- + Discard message? @@ -1576,7 +1586,7 @@ export function EmailComposer({ - + Attachment Warning diff --git a/apps/mail/components/create/prosemirror.css b/apps/mail/components/create/prosemirror.css index 3b7702a294..ec5065f85a 100644 --- a/apps/mail/components/create/prosemirror.css +++ b/apps/mail/components/create/prosemirror.css @@ -69,23 +69,23 @@ ul[data-type='taskList'] li > label { ul[data-type='taskList'] li > label input[type='checkbox'] { -webkit-appearance: none; appearance: none; - background-color: hsl(var(--background)); + background-color: var(--background); cursor: pointer; width: 1.2em; height: 1.2em; position: relative; top: 5px; - border: 2px solid hsl(var(--border)); + border: 2px solid var(--border); margin-right: 0.3rem; display: grid; place-content: center; &:hover { - background-color: hsl(var(--accent)); + background-color: var(--accent); } &:active { - background-color: hsl(var(--accent)); + background-color: var(--accent); } &::before { diff --git a/apps/mail/components/create/template-button.tsx b/apps/mail/components/create/template-button.tsx new file mode 100644 index 0000000000..55bb5f861c --- /dev/null +++ b/apps/mail/components/create/template-button.tsx @@ -0,0 +1,260 @@ +import { useTemplates } from '@/hooks/use-templates'; +import { useTRPC } from '@/providers/query-provider'; +import { Editor } from '@tiptap/react'; +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { toast } from 'sonner'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { FileText, Save, Trash2 } from 'lucide-react'; +import React, { useState, useMemo, useDeferredValue, useCallback, startTransition } from 'react'; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { TRPCClientError } from '@trpc/client'; + +type RecipientField = 'to' | 'cc' | 'bcc'; + +type Template = { + id: string; + name: string; + subject?: string | null; + body?: string | null; + to?: string[] | null; + cc?: string[] | null; + bcc?: string[] | null; +}; + +type TemplatesQueryData = { + templates: Template[]; +} | undefined; + +interface TemplateButtonProps { + editor: Editor | null; + subject: string; + setSubject: (value: string) => void; + to: string[]; + cc: string[]; + bcc: string[]; + setRecipients: (field: RecipientField, value: string[]) => void; +} + +const TemplateButtonComponent: React.FC = ({ + editor, + subject, + setSubject, + to, + cc, + bcc, + setRecipients, +}) => { + const trpc = useTRPC(); + const queryClient = useQueryClient(); + const { data } = useTemplates(); + + const templates: Template[] = data?.templates ?? []; + + const [menuOpen, setMenuOpen] = useState(false); + const [isSaving, setIsSaving] = useState(false); + const [saveDialogOpen, setSaveDialogOpen] = useState(false); + const [templateName, setTemplateName] = useState(''); + const [search, setSearch] = useState(''); + + const deferredSearch = useDeferredValue(search); + + const filteredTemplates = useMemo(() => { + if (!deferredSearch.trim()) return templates; + return templates.filter((t) => + t.name.toLowerCase().includes(deferredSearch.toLowerCase()), + ); + }, [deferredSearch, templates]); + + const { mutateAsync: createTemplate } = useMutation(trpc.templates.create.mutationOptions()); + const { mutateAsync: deleteTemplateMutation } = useMutation( + trpc.templates.delete.mutationOptions(), + ); + + const handleSaveTemplate = async () => { + if (!editor) return; + if (!templateName.trim()) { + toast.error('Please provide a name'); + return; + } + + setIsSaving(true); + try { + const newTemplate = await createTemplate({ + name: templateName.trim(), + subject: subject || '', + body: editor.getHTML(), + to: to.length ? to : undefined, + cc: cc.length ? cc : undefined, + bcc: bcc.length ? bcc : undefined, + }); + queryClient.setQueryData(trpc.templates.list.queryKey(), (old: TemplatesQueryData) => { + if (!old?.templates) return old; + return { + templates: [newTemplate.template, ...old.templates], + }; + }); + toast.success('Template saved'); + setTemplateName(''); + setSaveDialogOpen(false); + } catch (error) { + if (error instanceof TRPCClientError) { + toast.error(error.message); + } else { + toast.error('Failed to save template'); + } + } finally { + setIsSaving(false); + } + }; + + const handleApplyTemplate = useCallback((template: Template) => { + if (!editor) return; + startTransition(() => { + if (template.subject) setSubject(template.subject); + if (template.body) editor.commands.setContent(template.body, false); + if (template.to) setRecipients('to', template.to); + if (template.cc) setRecipients('cc', template.cc); + if (template.bcc) setRecipients('bcc', template.bcc); + }); + }, [editor, setSubject, setRecipients]); + + const handleDeleteTemplate = useCallback( + async (templateId: string) => { + try { + await deleteTemplateMutation({ id: templateId }); + await queryClient.invalidateQueries({ + queryKey: trpc.templates.list.queryKey(), + }); + toast.success('Template deleted'); + } catch (err) { + if (err instanceof TRPCClientError) { + toast.error(err.message); + } else { + toast.error('Failed to delete template'); + } + } + }, + [deleteTemplateMutation, queryClient, trpc.templates.list], + ); + + return ( + <> + + + + + + { + setMenuOpen(false); + setSaveDialogOpen(true); + }} + disabled={isSaving} + > + Save current as template + + {templates.length > 0 ? ( + + + Use template + + +
+ setSearch(e.target.value)} + className="h-8 text-sm" + autoFocus + /> +
+
+ {filteredTemplates.map((t: Template) => ( + handleApplyTemplate(t)} + > + {t.name} + + + ))} + {filteredTemplates.length === 0 && ( +
No templates
+ )} +
+
+
+ ) : null} +
+
+ + + + + Save as Template + +
+ setTemplateName(e.target.value)} + autoFocus + /> +
+ + + + +
+
+ + ); +}; + +export const TemplateButton = React.memo(TemplateButtonComponent); \ No newline at end of file diff --git a/apps/mail/components/home/HomeContent.tsx b/apps/mail/components/home/HomeContent.tsx index 49741635b3..501f70d641 100644 --- a/apps/mail/components/home/HomeContent.tsx +++ b/apps/mail/components/home/HomeContent.tsx @@ -123,7 +123,7 @@ export default function HomeContent() { initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, delay: 0.6 }} - className="mb-6 md:hidden" + className="mb-6 lg:hidden" >
-
+
now
-
+
@@ -374,7 +374,7 @@ export default function HomeContent() {
-
+
- +
March 25 - March 29
@@ -721,7 +721,7 @@ export default function HomeContent() {
-
+
@@ -1179,7 +1179,7 @@ export default function HomeContent() {
-
+
@@ -1220,7 +1220,7 @@ export default function HomeContent() { {firstRowQueries.map((query) => (
@@ -1230,8 +1230,8 @@ export default function HomeContent() {
))}
-
-
+
+
{/* Second row */} @@ -1240,7 +1240,7 @@ export default function HomeContent() { {secondRowQueries.map((query) => (
@@ -1250,8 +1250,8 @@ export default function HomeContent() {
))}
-
-
+
+
@@ -1261,7 +1261,7 @@ export default function HomeContent() { Ask Zero to do anything...
- +
diff --git a/apps/mail/components/home/footer.tsx b/apps/mail/components/home/footer.tsx index 1b056250b4..68745baa77 100644 --- a/apps/mail/components/home/footer.tsx +++ b/apps/mail/components/home/footer.tsx @@ -28,7 +28,7 @@ export default function Footer() { return (
- {/*
*/} + {/*
*/}
Experience the Future of
Email Today diff --git a/apps/mail/components/magicui/file-tree.tsx b/apps/mail/components/magicui/file-tree.tsx index 4d061e41c1..98606f0a4c 100644 --- a/apps/mail/components/magicui/file-tree.tsx +++ b/apps/mail/components/magicui/file-tree.tsx @@ -247,7 +247,7 @@ const Folder = ({ > {canExpand ? ( { e.stopPropagation(); diff --git a/apps/mail/components/mail/attachment-dialog.tsx b/apps/mail/components/mail/attachment-dialog.tsx index 0ae6f9174b..7828251320 100644 --- a/apps/mail/components/mail/attachment-dialog.tsx +++ b/apps/mail/components/mail/attachment-dialog.tsx @@ -25,7 +25,7 @@ const AttachmentDialog = ({ selectedAttachment, setSelectedAttachment }: Props) open={!!selectedAttachment} onOpenChange={(open) => !open && setSelectedAttachment(null)} > - + {selectedAttachment?.name} diff --git a/apps/mail/components/mail/attachments-accordion.tsx b/apps/mail/components/mail/attachments-accordion.tsx index eb24de966c..60517b22fd 100644 --- a/apps/mail/components/mail/attachments-accordion.tsx +++ b/apps/mail/components/mail/attachments-accordion.tsx @@ -33,7 +33,7 @@ const AttachmentsAccordion = ({ attachments, setSelectedAttachment }: Props) => return (
diff --git a/apps/mail/components/mail/mail-display.tsx b/apps/mail/components/mail/mail-display.tsx index 77c735dee6..7f82fd9686 100644 --- a/apps/mail/components/mail/mail-display.tsx +++ b/apps/mail/components/mail/mail-display.tsx @@ -38,7 +38,6 @@ import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; import type { Sender, ParsedMessage, Attachment } from '@/types'; import { useActiveConnection } from '@/hooks/use-connections'; import { useAttachments } from '@/hooks/use-attachments'; -import { useBrainState } from '../../hooks/use-summary'; import { useTRPC } from '@/providers/query-provider'; import { useThreadLabels } from '@/hooks/use-labels'; import { useMutation } from '@tanstack/react-query'; @@ -134,7 +133,7 @@ const StreamingText = ({ text }: { text: string }) => {
@@ -682,7 +681,6 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }: const { labels: threadLabels } = useThreadLabels( emailData.tags ? emailData.tags.map((l) => l.id) : [], ); - const { data: brainState } = useBrainState(); const { data: activeConnection } = useActiveConnection(); const [researchSender, setResearchSender] = useState(null); const [searchQuery, setSearchQuery] = useState(null); @@ -1315,7 +1313,7 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }: })()}
- {brainState?.enabled && } + {threadAttachments && threadAttachments.length > 0 && ( )} @@ -1483,7 +1481,7 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }:
-
+
@@ -1523,7 +1521,7 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }: disabled={!messageAttachments?.length} className={ !messageAttachments?.length - ? 'data-[disabled]:pointer-events-auto' + ? 'data-disabled:pointer-events-auto' : '' } onClick={(e) => { @@ -1647,7 +1645,7 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }:
@@ -1695,7 +1693,7 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }: {index < (messageAttachments?.length || 0) - 1 && ( -
+
)}
))} diff --git a/apps/mail/components/mail/mail-list.tsx b/apps/mail/components/mail/mail-list.tsx index e19a41c719..ced2721e96 100644 --- a/apps/mail/components/mail/mail-list.tsx +++ b/apps/mail/components/mail/mail-list.tsx @@ -239,7 +239,7 @@ const Thread = memo( >
diff --git a/apps/mail/components/mail/mail-skeleton.tsx b/apps/mail/components/mail/mail-skeleton.tsx index 85fbc9a3b9..62d4140ce4 100644 --- a/apps/mail/components/mail/mail-skeleton.tsx +++ b/apps/mail/components/mail/mail-skeleton.tsx @@ -31,7 +31,7 @@ export const MailDisplaySkeleton = ({ isFullscreen }: { isFullscreen?: boolean }
- +
@@ -72,7 +72,7 @@ export const MailDisplaySkeleton = ({ isFullscreen }: { isFullscreen?: boolean }
- +
@@ -113,7 +113,7 @@ export const MailDisplaySkeleton = ({ isFullscreen }: { isFullscreen?: boolean }
- +
diff --git a/apps/mail/components/mail/mail.tsx b/apps/mail/components/mail/mail.tsx index 15edc1b1b0..7b563c4179 100644 --- a/apps/mail/components/mail/mail.tsx +++ b/apps/mail/components/mail/mail.tsx @@ -461,7 +461,7 @@ export function MailLayout() { return ( -
+
@@ -528,16 +528,16 @@ export function MailLayout() { Clear )} - + {isMac ? '⌘' : 'Ctrl'}{' '} - K + K @@ -582,11 +582,11 @@ export function MailLayout() {
-
+
diff --git a/apps/mail/components/mail/navbar.tsx b/apps/mail/components/mail/navbar.tsx index acb13f3b55..4c86995e8b 100644 --- a/apps/mail/components/mail/navbar.tsx +++ b/apps/mail/components/mail/navbar.tsx @@ -19,7 +19,7 @@ export function Nav({ links, isCollapsed }: NavProps) { data-collapsed={isCollapsed} className="group flex flex-col gap-4 py-2 data-[collapsed=true]:py-2" > -