Skip to content

Commit 9d573b2

Browse files
Deploy to production (Automated) (Mail-0#1175)
This is an automated pull request to deploy the staging branch to production. Please review the pull request and comment `/deploy` to merge this PR and deploy to production.
1 parent c363f7d commit 9d573b2

30 files changed

Lines changed: 997 additions & 559 deletions

apps/mail/app/(routes)/layout.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { HotkeyProviderWrapper } from '@/components/providers/hotkey-provider-wrapper';
2+
import { CommandPaletteProvider } from '@/components/context/command-palette-context';
23
import { Outlet } from 'react-router';
34

45
export default function Layout() {
56
return (
6-
<HotkeyProviderWrapper>
7-
<div className="relative flex max-h-screen w-full overflow-hidden">
8-
<Outlet />
9-
</div>
10-
</HotkeyProviderWrapper>
7+
<CommandPaletteProvider>
8+
<HotkeyProviderWrapper>
9+
<div className="relative flex max-h-screen w-full overflow-hidden">
10+
<Outlet />
11+
</div>
12+
</HotkeyProviderWrapper>
13+
</CommandPaletteProvider>
1114
);
1215
}

apps/mail/app/(routes)/settings/general/page.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ import { Textarea } from '@/components/ui/textarea';
2828
import { useSettings } from '@/hooks/use-settings';
2929
import { Globe, Clock, XIcon } from 'lucide-react';
3030
import { availableLocales } from '@/i18n/config';
31-
import { Button } from '@/components/ui/button';
3231
import { Switch } from '@/components/ui/switch';
32+
import { Button } from '@/components/ui/button';
3333
import { useRevalidator } from 'react-router';
3434
import { cn } from '@/lib/utils';
3535
import { toast } from 'sonner';
@@ -133,6 +133,7 @@ export default function GeneralPage() {
133133
timezone: getBrowserTimezone(),
134134
dynamicContent: false,
135135
customPrompt: '',
136+
zeroSignature: true,
136137
},
137138
});
138139

@@ -242,6 +243,23 @@ export default function GeneralPage() {
242243
</FormItem>
243244
)}
244245
/>
246+
<FormField
247+
control={form.control}
248+
name="zeroSignature"
249+
render={({ field }) => (
250+
<FormItem className="flex max-w-xl flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
251+
<div className="space-y-0.5">
252+
<FormLabel>{t('pages.settings.general.zeroSignature')}</FormLabel>
253+
<FormDescription>
254+
{t('pages.settings.general.zeroSignatureDescription')}
255+
</FormDescription>
256+
</div>
257+
<FormControl>
258+
<Switch checked={field.value} onCheckedChange={field.onChange} />
259+
</FormControl>
260+
</FormItem>
261+
)}
262+
/>
245263
</form>
246264
</Form>
247265
</SettingsCard>

apps/mail/components/context/command-palette-context.tsx

Lines changed: 62 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import {
99
Info,
1010
Mail,
1111
Paperclip,
12-
Plus,
13-
Save,
1412
Search,
1513
Star,
1614
Tag,
@@ -32,19 +30,15 @@ import {
3230
import {
3331
createContext,
3432
Fragment,
33+
Suspense,
3534
useCallback,
3635
useContext,
3736
useEffect,
3837
useMemo,
3938
useState,
4039
type ComponentType,
4140
} from 'react';
42-
import {
43-
cn,
44-
getMainSearchTerm,
45-
parseNaturalLanguageDate,
46-
parseNaturalLanguageSearch,
47-
} from '@/lib/utils';
41+
import { getMainSearchTerm, parseNaturalLanguageSearch } from '@/lib/utils';
4842
import { DialogDescription, DialogTitle } from '@/components/ui/dialog';
4943
import { navigationConfig, type MessageKey } from '@/config/navigation';
5044
import { useSearchValue } from '@/hooks/use-search-value';
@@ -56,11 +50,11 @@ import { Calendar } from '@/components/ui/calendar';
5650
import { useMutation } from '@tanstack/react-query';
5751
import { useThreads } from '@/hooks/use-threads';
5852
import { useLabels } from '@/hooks/use-labels';
59-
import { Badge } from '@/components/ui/badge';
60-
import { Input } from '@/components/ui/input';
6153
import { Label } from '@/components/ui/label';
62-
import { format, subDays } from 'date-fns';
54+
import { Input } from '@/components/ui/input';
55+
import { Badge } from '@/components/ui/badge';
6356
import { useTranslations } from 'use-intl';
57+
import { format, subDays } from 'date-fns';
6458
import { VisuallyHidden } from 'radix-ui';
6559
import { Pencil2 } from '../icons/icons';
6660
import { Button } from '../ui/button';
@@ -71,6 +65,8 @@ type CommandPaletteContext = {
7165
open: boolean;
7266
setOpen: (open: boolean) => void;
7367
openModal: () => void;
68+
activeFilters: ActiveFilter[];
69+
clearAllFilters: () => void;
7470
};
7571

7672
interface CommandItem {
@@ -180,7 +176,7 @@ const deleteSavedSearch = (id: string) => {
180176
}
181177
};
182178

183-
export function CommandPalette() {
179+
export function CommandPalette({ children }: { children: React.ReactNode }) {
184180
const [open, setOpen] = useState(false);
185181
const [, setIsComposeOpen] = useQueryState('isComposeOpen');
186182
const [currentView, setCurrentView] = useState<CommandView>('main');
@@ -433,6 +429,19 @@ export function CommandPalette() {
433429
finalQuery = semanticQuery || query;
434430
}
435431

432+
const isFilterSyntax = /^(from:|to:|subject:|has:|is:|after:|before:|label:)/.test(
433+
query.trim(),
434+
);
435+
if (query.trim() && !isFilterSyntax) {
436+
const searchFilter: ActiveFilter = {
437+
id: `search-${Date.now()}`,
438+
type: 'search',
439+
value: query,
440+
display: `Search: "${query}"`,
441+
};
442+
addFilter(searchFilter);
443+
}
444+
436445
const filterQuery = activeFilters.map((f) => f.value).join(' ');
437446
if (filterQuery) {
438447
finalQuery = `${finalQuery} ${filterQuery}`.trim();
@@ -449,7 +458,7 @@ export function CommandPalette() {
449458
description: finalQuery,
450459
});
451460
},
452-
[activeFilters, searchValue.folder, setSearchValue],
461+
[activeFilters, searchValue.folder, setSearchValue, addFilter],
453462
);
454463

455464
const quickFilterOptions = useMemo(
@@ -515,59 +524,6 @@ export function CommandPalette() {
515524
[addFilter, executeSearch],
516525
);
517526

518-
const processSearchQuery = useCallback((query: string): string => {
519-
let searchTerms = [];
520-
521-
try {
522-
if (query.trim()) {
523-
const searchTerm = query.trim();
524-
525-
const dateRange = parseNaturalLanguageDate(searchTerm);
526-
if (dateRange) {
527-
if (dateRange.from) {
528-
const fromDate = format(dateRange.from, 'yyyy/MM/dd');
529-
searchTerms.push(`after:${fromDate}`);
530-
}
531-
if (dateRange.to) {
532-
const toDate = format(dateRange.to, 'yyyy/MM/dd');
533-
searchTerms.push(`before:${toDate}`);
534-
}
535-
536-
const cleanedQuery = searchTerm
537-
.replace(/emails?\s+from\s+/i, '')
538-
.replace(/\b\d{4}\b/g, '')
539-
.replace(
540-
/\b(january|february|march|april|may|june|july|august|september|october|november|december)\b/gi,
541-
'',
542-
)
543-
.trim();
544-
545-
if (cleanedQuery) {
546-
searchTerms.push(cleanedQuery);
547-
}
548-
} else {
549-
const parsedTerm = parseNaturalLanguageSearch(searchTerm);
550-
if (parsedTerm !== searchTerm) {
551-
searchTerms.push(parsedTerm);
552-
} else {
553-
if (searchTerm.includes('@')) {
554-
searchTerms.push(`from:${searchTerm}`);
555-
} else {
556-
searchTerms.push(
557-
`(from:${searchTerm} OR from:"${searchTerm}" OR subject:"${searchTerm}" OR "${searchTerm}")`,
558-
);
559-
}
560-
}
561-
}
562-
}
563-
564-
return searchTerms.join(' ');
565-
} catch (error) {
566-
console.error('Search processing error:', error);
567-
return query;
568-
}
569-
}, []);
570-
571527
const handleSearch = useCallback(
572528
async (query: string, useNaturalLanguage = true) => {
573529
setIsProcessing(true);
@@ -581,6 +537,15 @@ export function CommandPalette() {
581537
toast.info('Search applied', {
582538
description: finalQuery,
583539
});
540+
541+
const searchFilter: ActiveFilter = {
542+
id: `ai-search-${Date.now()}`,
543+
type: 'search',
544+
value: finalQuery,
545+
display: `AI Search: "${query}"`,
546+
};
547+
addFilter(searchFilter);
548+
584549
return setSearchValue({
585550
value: finalQuery,
586551
highlight: getMainSearchTerm(query),
@@ -590,6 +555,19 @@ export function CommandPalette() {
590555
});
591556
}
592557

558+
const isFilterSyntax = /^(from:|to:|subject:|has:|is:|after:|before:|label:)/.test(
559+
query.trim(),
560+
);
561+
if (query.trim() && !isFilterSyntax) {
562+
const searchFilter: ActiveFilter = {
563+
id: `search-${Date.now()}`,
564+
type: 'search',
565+
value: query,
566+
display: `Search: "${query}"`,
567+
};
568+
addFilter(searchFilter);
569+
}
570+
593571
const filterQuery = activeFilters.map((f) => f.value).join(' ');
594572
if (filterQuery) {
595573
finalQuery = `${finalQuery} ${filterQuery}`.trim();
@@ -620,7 +598,7 @@ export function CommandPalette() {
620598
setIsProcessing(false);
621599
}
622600
},
623-
[activeFilters, processSearchQuery, searchValue.folder, setSearchValue, setOpen],
601+
[activeFilters, searchValue.folder, setSearchValue, setOpen, generateSearchQuery, addFilter],
624602
);
625603

626604
const quickSearchResults = useMemo(() => {
@@ -799,7 +777,7 @@ export function CommandPalette() {
799777
<Button
800778
variant="ghost"
801779
size="sm"
802-
className="h-6 px-2 text-xs"
780+
className="text-muted-foreground hover:text-foreground h-6 px-2 text-xs"
803781
onClick={clearAllFilters}
804782
>
805783
Clear All
@@ -1853,25 +1831,17 @@ export function CommandPalette() {
18531831
};
18541832

18551833
return (
1856-
<>
1857-
<Button
1858-
variant="outline"
1859-
className={cn(
1860-
'text-muted-foreground relative h-9 w-full select-none justify-start rounded-[0.5rem] border bg-white text-sm font-normal shadow-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0 dark:bg-[#141414]',
1861-
)}
1862-
onClick={() => setOpen(true)}
1863-
>
1864-
<span className="hidden lg:inline-flex">Search & Filters</span>
1865-
<span className="inline-flex lg:hidden">Search...</span>
1866-
{activeFilters.length > 0 && (
1867-
<Badge variant="secondary" className="ml-2 h-5 px-1">
1868-
{activeFilters.length}
1869-
</Badge>
1870-
)}
1871-
<kbd className="bg-muted pointer-events-none absolute right-[0.45rem] top-[0.45rem] hidden h-5 select-none items-center gap-1 rounded border px-1.5 font-mono text-[10px] font-medium opacity-100 sm:flex">
1872-
<span className="text-xs"></span>K
1873-
</kbd>
1874-
</Button>
1834+
<CommandPaletteContext.Provider
1835+
value={{
1836+
open,
1837+
setOpen,
1838+
openModal: () => {
1839+
setOpen(true);
1840+
},
1841+
activeFilters,
1842+
clearAllFilters,
1843+
}}
1844+
>
18751845
<CommandDialog
18761846
open={open}
18771847
onOpenChange={(isOpen) => {
@@ -1888,21 +1858,15 @@ export function CommandPalette() {
18881858
</VisuallyHidden.VisuallyHidden>
18891859
{renderView()}
18901860
</CommandDialog>
1891-
</>
1861+
{children}
1862+
</CommandPaletteContext.Provider>
18921863
);
18931864
}
18941865

18951866
export function CommandPaletteProvider({ children }: { children: React.ReactNode }) {
1896-
const [open, setOpen] = useState(false);
1897-
1898-
const openModal = useCallback(() => {
1899-
setOpen(true);
1900-
}, []);
1901-
19021867
return (
1903-
<CommandPaletteContext.Provider value={{ open, setOpen, openModal }}>
1904-
{children}
1905-
<CommandPalette />
1906-
</CommandPaletteContext.Provider>
1868+
<Suspense>
1869+
<CommandPalette>{children}</CommandPalette>
1870+
</Suspense>
19071871
);
19081872
}

0 commit comments

Comments
 (0)