Skip to content
Merged

bin #629

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
2 changes: 1 addition & 1 deletion apps/mail/app/(routes)/mail/[folder]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface MailPageProps {
}>;
}

const ALLOWED_FOLDERS = ['inbox', 'draft', 'sent', 'spam', 'trash', 'archive'];
const ALLOWED_FOLDERS = ['inbox', 'draft', 'sent', 'spam', 'bin', 'archive'];

export default async function MailPage({ params, searchParams }: MailPageProps) {
const headersList = await headers();
Expand Down
6 changes: 3 additions & 3 deletions apps/mail/app/api/driver/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ export const driver = async (config: IConfig): Promise<MailManager> => {
}
const normalizeSearch = (folder: string, q: string) => {
// Handle special folders
if (folder === 'trash') {
if (folder === 'bin') {
return { folder: undefined, q: `in:trash ${q}` };
}
if (folder === 'archive') {
Expand Down Expand Up @@ -630,8 +630,8 @@ export const driver = async (config: IConfig): Promise<MailManager> => {

// Sort drafts by date, newest first
const sortedDrafts = [...drafts].sort((a, b) => {
const dateA = new Date(a.receivedOn || new Date()).getTime();
const dateB = new Date(b.receivedOn || new Date()).getTime();
const dateA = new Date(a?.receivedOn || new Date()).getTime();
const dateB = new Date(b?.receivedOn || new Date()).getTime();
return dateB - dateA;
});

Expand Down
57 changes: 46 additions & 11 deletions apps/mail/components/context/thread-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ interface EmailContextMenuProps {
isInbox?: boolean;
isSpam?: boolean;
isSent?: boolean;
isBin?: boolean;
refreshCallback?: () => void;
}

Expand All @@ -66,6 +67,7 @@ export function ThreadContextMenu({
isInbox = true,
isSpam = false,
isSent = false,
isBin = false,
refreshCallback,
}: EmailContextMenuProps) {
const { folder } = useParams<{ folder: string }>();
Expand Down Expand Up @@ -113,6 +115,7 @@ export function ThreadContextMenu({
let destination: ThreadDestination = null;
if (to === LABELS.INBOX) destination = FOLDERS.INBOX;
else if (to === LABELS.SPAM) destination = FOLDERS.SPAM;
else if (to === LABELS.TRASH) destination = FOLDERS.BIN;
else if (from && !to) destination = FOLDERS.ARCHIVE;

const promise = moveThreadsTo({
Expand All @@ -136,6 +139,9 @@ export function ThreadContextMenu({
} else if (destination === FOLDERS.ARCHIVE) {
loadingMessage = t('common.actions.archiving');
successMessage = t('common.actions.archived');
} else if (destination === FOLDERS.BIN) {
loadingMessage = t('common.actions.movingToBin');
successMessage = t('common.actions.movedToBin');
}

toast.promise(promise, {
Expand Down Expand Up @@ -217,6 +223,25 @@ export function ThreadContextMenu({
action: handleMove(LABELS.SPAM, LABELS.INBOX),
disabled: false,
},
{
id: 'move-to-bin',
label: t('common.mail.moveToBin'),
icon: <Trash className="mr-2.5 h-4 w-4" />,
action: handleMove(LABELS.SPAM, LABELS.TRASH),
disabled: false,
},
];
}

if (isBin) {
return [
{
id: 'restore-from-bin',
label: t('common.mail.restoreFromBin' as any),
icon: <Inbox className="mr-2.5 h-4 w-4" />,
action: handleMove(LABELS.TRASH, LABELS.INBOX),
disabled: false,
},
];
}

Expand All @@ -229,6 +254,13 @@ export function ThreadContextMenu({
action: handleMove('', LABELS.INBOX),
disabled: false,
},
{
id: 'move-to-bin',
label: t('common.mail.moveToBin'),
icon: <Trash className="mr-2.5 h-4 w-4" />,
action: handleMove('', LABELS.TRASH),
disabled: false,
},
];
}

Expand All @@ -242,6 +274,13 @@ export function ThreadContextMenu({
action: handleMove(LABELS.SENT, ''),
disabled: false,
},
{
id: 'move-to-bin',
label: t('common.mail.moveToBin'),
icon: <Trash className="mr-2.5 h-4 w-4" />,
action: handleMove(LABELS.SENT, LABELS.TRASH),
disabled: false,
},
];
}

Expand All @@ -261,19 +300,16 @@ export function ThreadContextMenu({
action: handleMove(LABELS.INBOX, LABELS.SPAM),
disabled: !isInbox,
},
{
id: 'move-to-bin',
label: t('common.mail.moveToBin'),
icon: <Trash className="mr-2.5 h-4 w-4" />,
action: handleMove(LABELS.INBOX, LABELS.TRASH),
disabled: false,
},
];
};

const moveActions: EmailAction[] = [
{
id: 'move-to-trash',
label: t('common.mail.moveToTrash'),
icon: <Trash className="mr-2.5 h-4 w-4" />,
action: noopAction,
disabled: true, // TODO: Move to trash functionality to be implemented
},
];

const otherActions: EmailAction[] = [
{
id: 'toggle-read',
Expand Down Expand Up @@ -324,7 +360,6 @@ export function ThreadContextMenu({
<ContextMenuSeparator />

{getActions().map(renderAction as any)}
{moveActions.filter((action) => action.id !== 'move-to-spam').map(renderAction)}

<ContextMenuSeparator />

Expand Down
5 changes: 5 additions & 0 deletions apps/mail/components/mail/mail-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const ThreadWrapper = ({
isFolderInbox,
isFolderSpam,
isFolderSent,
isFolderBin,
refreshCallback,
}: {
children: React.ReactNode;
Expand All @@ -55,6 +56,7 @@ const ThreadWrapper = ({
isFolderInbox: boolean;
isFolderSpam: boolean;
isFolderSent: boolean;
isFolderBin: boolean;
refreshCallback: () => void;
}) => {
return (
Expand All @@ -64,6 +66,7 @@ const ThreadWrapper = ({
isInbox={isFolderInbox}
isSpam={isFolderSpam}
isSent={isFolderSent}
isBin={isFolderBin}
refreshCallback={refreshCallback}
>
{children}
Expand Down Expand Up @@ -104,6 +107,7 @@ const Thread = memo(
const isFolderInbox = folder === FOLDERS.INBOX || !folder;
const isFolderSpam = folder === FOLDERS.SPAM;
const isFolderSent = folder === FOLDERS.SENT;
const isFolderBin = folder === FOLDERS.BIN;

const handleMouseEnter = () => {
if (demo) return;
Expand Down Expand Up @@ -332,6 +336,7 @@ const Thread = memo(
isFolderInbox={isFolderInbox}
isFolderSpam={isFolderSpam}
isFolderSent={isFolderSent}
isFolderBin={isFolderBin}
refreshCallback={() => mutate()}
>
{content}
Expand Down
2 changes: 1 addition & 1 deletion apps/mail/components/mail/mail-quick-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export const MailQuickActions = memo(
const handleDelete = useCallback(
async (e?: React.MouseEvent) => {
// TODO: Implement delete
toast.info(t('common.mail.moveToTrash'));
toast.info(t('common.mail.moveToBin'));
},
[t],
);
Expand Down
5 changes: 5 additions & 0 deletions apps/mail/components/mail/mail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
RotateCw,
Mail,
MailOpen,
Trash,
} from 'lucide-react';
import {
Dialog,
Expand Down Expand Up @@ -516,6 +517,10 @@ function BulkSelectActions() {
icon: <Inbox />,
tooltip: t('common.mail.moveToInbox'),
},
bin: {
icon: <Trash />,
tooltip: t('common.mail.moveToBin'),
},
};

return (
Expand Down
15 changes: 12 additions & 3 deletions apps/mail/components/mail/thread-display.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
MailOpen,
Reply,
X,
Trash,
} from 'lucide-react';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { useSearchParams, useParams } from 'next/navigation';
Expand Down Expand Up @@ -166,7 +167,7 @@ export function ThreadDisplay({ threadParam, onClose, isMobile, id }: ThreadDisp

const isInArchive = folder === FOLDERS.ARCHIVE;
const isInSpam = folder === FOLDERS.SPAM;

const isInBin = folder === FOLDERS.BIN;
const handleClose = useCallback(() => {
// Reset reply composer state when closing thread display
setMail((prev) => ({
Expand Down Expand Up @@ -196,7 +197,9 @@ export function ThreadDisplay({ threadParam, onClose, isMobile, id }: ThreadDisp
? t('common.actions.movedToInbox')
: destination === 'spam'
? t('common.actions.movedToSpam')
: t('common.actions.archived'),
: destination === 'bin'
? t('common.actions.movedToBin')
: t('common.actions.archived'),
error: t('common.actions.failedToMove'),
});
},
Expand Down Expand Up @@ -320,7 +323,7 @@ export function ThreadDisplay({ threadParam, onClose, isMobile, id }: ThreadDisp
disabled={!emailData}
onClick={() => setIsFullscreen(!isFullscreen)}
/>
{isInSpam || isInArchive ? (
{isInSpam || isInArchive || isInBin ? (
<ThreadActionButton
icon={Inbox}
label={t('common.mail.moveToInbox')}
Expand All @@ -341,6 +344,12 @@ export function ThreadDisplay({ threadParam, onClose, isMobile, id }: ThreadDisp
disabled={!emailData}
onClick={() => moveThreadTo('spam')}
/>
<ThreadActionButton
icon={Trash}
label={t('common.mail.moveToBin')}
disabled={!emailData}
onClick={() => moveThreadTo('bin')}
/>
</>
)}
<ThreadActionButton
Expand Down
2 changes: 1 addition & 1 deletion apps/mail/config/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const navigationConfig: Record<string, NavConfig> = {
title: 'navigation.sidebar.bin',
url: '/mail/bin',
icon: DeleteIcon,
disabled: true,
disabled: false,
},
{
id: 'settings',
Expand Down
33 changes: 25 additions & 8 deletions apps/mail/lib/thread-actions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { modifyLabels } from '@/actions/mail';
import { LABELS, FOLDERS } from '@/lib/utils';

export type ThreadDestination = 'inbox' | 'archive' | 'spam' | null;
export type FolderLocation = 'inbox' | 'archive' | 'spam' | 'sent' | string;
export type ThreadDestination = 'inbox' | 'archive' | 'spam' | 'bin' | null;
export type FolderLocation = 'inbox' | 'archive' | 'spam' | 'sent' | 'bin' | string;

interface MoveThreadOptions {
threadIds: string[];
Expand All @@ -21,22 +21,28 @@ export function isActionAvailable(folder: FolderLocation, action: ThreadDestinat
return true;
case `${FOLDERS.INBOX}_to_archive`:
return true;
case `${FOLDERS.INBOX}_to_trash`:
return true;

// From archive rules
case `${FOLDERS.ARCHIVE}_to_inbox`:
return true;
case `${FOLDERS.ARCHIVE}_to_trash`:
return true;

// From spam rules
case `${FOLDERS.SPAM}_to_inbox`:
return true;
case `${FOLDERS.SPAM}_to_trash`:
return true;

default:
return false;
}
}

export function getAvailableActions(folder: FolderLocation): ThreadDestination[] {
const allPossibleActions: ThreadDestination[] = ['inbox', 'archive', 'spam'];
const allPossibleActions: ThreadDestination[] = ['inbox', 'archive', 'spam', 'bin'];
return allPossibleActions.filter(action => isActionAvailable(folder, action));
}

Expand All @@ -49,31 +55,42 @@ export async function moveThreadsTo({
if (!threadIds.length) return;
const isInInbox = currentFolder === FOLDERS.INBOX || !currentFolder;
const isInSpam = currentFolder === FOLDERS.SPAM;
const isInBin = currentFolder === FOLDERS.BIN;

let addLabel = '';
let removeLabel = '';

switch(destination) {
case 'inbox':
addLabel = LABELS.INBOX;
removeLabel = isInSpam ? LABELS.SPAM : '';
removeLabel = isInSpam ? LABELS.SPAM : (isInBin ? LABELS.TRASH : '');
break;
case 'archive':
removeLabel = isInInbox ? LABELS.INBOX : (isInSpam ? LABELS.SPAM : '');
addLabel = '';
removeLabel = isInInbox ? LABELS.INBOX : (isInSpam ? LABELS.SPAM : (isInBin ? LABELS.TRASH : ''));
break;
case 'spam':
addLabel = LABELS.SPAM;
removeLabel = isInInbox ? LABELS.INBOX : '';
removeLabel = isInInbox ? LABELS.INBOX : (isInBin ? LABELS.TRASH : '');
break;
default:
case 'bin':
addLabel = LABELS.TRASH;
removeLabel = isInInbox ? LABELS.INBOX : (isInSpam ? LABELS.SPAM : '');
break;
default:
return;
}

if (!addLabel && !removeLabel) {
console.warn('No labels to modify, skipping API call');
return;
}

return modifyLabels({
threadId: threadIds,
addLabels: addLabel ? [addLabel] : [],
removeLabels: removeLabel ? [removeLabel] : [],
})
});
} catch (error) {
console.error(`Error moving thread(s):`, error);
throw error;
Expand Down
6 changes: 4 additions & 2 deletions apps/mail/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const FOLDERS = {
SPAM: 'spam',
INBOX: 'inbox',
ARCHIVE: 'archive',
TRASH: 'trash',
BIN: 'bin',
DRAFT: 'draft',
SENT: 'sent',
} as const;
Expand All @@ -22,12 +22,13 @@ export const LABELS = {
UNREAD: 'UNREAD',
IMPORTANT: 'IMPORTANT',
SENT: 'SENT',
TRASH: 'TRASH',
} as const;

export const FOLDER_NAMES = [
'inbox',
'spam',
'trash',
'bin',
'unread',
'starred',
'important',
Expand All @@ -40,6 +41,7 @@ export const FOLDER_TAGS: Record<string, string[]> = {
[FOLDERS.INBOX]: [LABELS.INBOX],
[FOLDERS.ARCHIVE]: [],
[FOLDERS.SENT]: [LABELS.SENT],
[FOLDERS.BIN]: [LABELS.TRASH],
};

export const getFolderTags = (folder: string): string[] => {
Expand Down
Loading