Skip to content
Merged
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
76 changes: 57 additions & 19 deletions apps/mail/components/context/thread-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ import { backgroundQueueAtom } from '@/store/backgroundQueue';
import { useThread, useThreads } from '@/hooks/use-threads';
import { useSearchValue } from '@/hooks/use-search-value';
import { useParams, useRouter } from 'next/navigation';
import { useLabels } from '@/hooks/use-labels';
import { modifyLabels } from '@/actions/mail';
import { LABELS, FOLDERS } from '@/lib/utils';
import { useStats } from '@/hooks/use-stats';
import { useTranslations } from 'next-intl';
import { useMail } from '../mail/use-mail';
import { Checkbox } from '../ui/checkbox';
import { type ReactNode } from 'react';
import { useQueryState } from 'nuqs';
import { useMemo } from 'react';
Expand Down Expand Up @@ -63,6 +65,49 @@ interface EmailContextMenuProps {
refreshCallback?: () => void;
}

const LabelsList = ({ threadId }: { threadId: string }) => {
const { labels } = useLabels();
const { data: thread, mutate } = useThread(threadId);
const t = useTranslations();

if (!labels || !thread) return null;

const handleToggleLabel = async (labelId: string) => {
if (!labelId) return;
const hasLabel = thread.labels?.map((label) => label.id).includes(labelId);
await modifyLabels({
threadId: [threadId],
addLabels: hasLabel ? [] : [labelId],
removeLabels: hasLabel ? [labelId] : [],
});
mutate();
};

return (
<>
{labels
.filter((label) => label.id)
.map((label) => (
<ContextMenuItem
key={label.id}
onClick={() => label.id && handleToggleLabel(label.id)}
className="font-normal"
>
<div className="flex items-center">
<Checkbox
checked={
label.id ? thread.labels?.map((label) => label.id).includes(label.id) : false
}
className="mr-2 h-4 w-4"
/>
{label.name}
</div>
</ContextMenuItem>
))}
</>
);
};

export function ThreadContextMenu({
children,
emailId,
Expand Down Expand Up @@ -383,30 +428,23 @@ export function ThreadContextMenu({

<ContextMenuSeparator />

<ContextMenuSub>
<ContextMenuSubTrigger className="font-normal">
<Tag className="mr-2.5 h-4 w-4" />
{t('common.mail.labels')}
</ContextMenuSubTrigger>
<ContextMenuSubContent className="w-48">
<LabelsList threadId={threadId} />
</ContextMenuSubContent>
</ContextMenuSub>

<ContextMenuSeparator />

{getActions().map(renderAction as any)}

<ContextMenuSeparator />

{otherActions.map(renderAction)}

{/* <ContextMenuSeparator />

<ContextMenuSub>
<ContextMenuSubTrigger className="font-normal">
<Tag className="mr-2.5 h-4 w-4" />
{t('common.mail.labels')}
</ContextMenuSubTrigger>
<ContextMenuSubContent className="w-48">
<ContextMenuItem className="font-normal">
<MailPlus className="mr-2.5 h-4 w-4" />
{t('common.mail.createNewLabel')}
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem disabled className="text-muted-foreground italic">
{t('common.mail.noLabelsAvailable')}
</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub> */}
</ContextMenuContent>
</ContextMenu>
);
Expand Down
21 changes: 15 additions & 6 deletions apps/mail/components/mail/mail-display.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ import { handleUnsubscribe } from '@/lib/email-utils.client';
import { getListUnsubscribeAction } from '@/lib/email-utils';
import AttachmentsAccordion from './attachments-accordion';
import { cn, getEmailLogo, formatDate } from '@/lib/utils';
import { useThreadLabels } from '@/hooks/use-labels';
import { Sender, type ParsedMessage } from '@/types';
import AttachmentDialog from './attachment-dialog';
import { useSummary } from '@/hooks/use-summary';
import { TextShimmer } from '../ui/text-shimmer';
import { useSession } from '@/lib/auth-client';
import { RenderLabels } from './render-labels';
import ReplyCompose from './reply-composer';
import { Separator } from '../ui/separator';
import { useParams } from 'next/navigation';
Expand Down Expand Up @@ -287,6 +289,10 @@ const MailDisplay = ({ emailData, index, totalEmails, demo }: Props) => {
const t = useTranslations();
const [activeReplyId, setActiveReplyId] = useQueryState('activeReplyId');
const { data: session } = useSession();
const { labels: threadLabels } = useThreadLabels(
// @ts-expect-error shutup
emailData.tags ? emailData.tags.map((l) => l.id) : [],
);

useEffect(() => {
if (!demo) {
Expand Down Expand Up @@ -422,16 +428,19 @@ const MailDisplay = ({ emailData, index, totalEmails, demo }: Props) => {
<div className={cn('px-4', index === 0 && 'border-b py-4')}>
{index === 0 && (
<>
<p className="font-medium text-black dark:text-white">
{emailData.subject}{' '}
<span className="text-[#6D6D6D] dark:text-[#8C8C8C]">
{totalEmails && `[${totalEmails}]`}
<p className="inline-flex items-center gap-2 font-medium text-black dark:text-white">
<span>
{emailData.subject}{' '}
<span className="text-[#6D6D6D] dark:text-[#8C8C8C]">
{totalEmails && `[${totalEmails}]`}
</span>
</span>
</p>
<div className="mt-2 flex items-center gap-4">
{emailData?.tags ? (
<MailDisplayLabels labels={emailData?.tags.map((t) => t.name) || []} />
) : null}
</p>
<div className="mt-2 flex items-center gap-4">
<RenderLabels labels={threadLabels} />
<div className="bg-iconLight dark:bg-iconDark/20 relative h-3 w-0.5 rounded-full" />
<div className="flex items-center gap-2 text-sm text-[#6D6D6D] dark:text-[#8C8C8C]">
{(() => {
Expand Down
2 changes: 1 addition & 1 deletion apps/mail/components/mail/mail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ export function MailLayout() {
<div
className={cn(
`${category[0] === 'Important' ? 'bg-[#F59E0D]' : category[0] === 'All Mail' ? 'bg-[#006FFE]' : category[0] === 'Personal' ? 'bg-[#39ae4a]' : category[0] === 'Updates' ? 'bg-[#8B5CF6]' : category[0] === 'Promotions' ? 'bg-[#F43F5E]' : 'bg-[#F59E0D]'}`,
'relative bottom-0.5 z-[5] h-0.5 w-full transition-opacity',
'relative bottom-0.5 z-0 z-[5] h-0.5 w-full transition-opacity',
isValidating ? 'opacity-100' : 'opacity-0',
)}
/>
Expand Down
50 changes: 33 additions & 17 deletions apps/mail/components/mail/render-labels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ import { Popover } from '../ui/popover';
import { cn } from '@/lib/utils';
import * as React from 'react';

export const RenderLabels = ({ labels }: { labels: Label[] }) => {
export const RenderLabels = ({ count = 1, labels }: { count?: number; labels: Label[] }) => {
const [searchValue, setSearchValue] = useSearchValue();
const label = React.useMemo(() => labels[0], [labels]);

const handleFilterByLabel = (label: Label) => (event: any) => {
event.stopPropagation();
const existingValue = searchValue.value;
Expand All @@ -31,37 +29,55 @@ export const RenderLabels = ({ labels }: { labels: Label[] }) => {
});
};

if (!label) return null;
if (!labels.length) return null;

const visibleLabels = labels.slice(0, count);
const hiddenLabels = labels.slice(count);

return (
<span
key={label.id}
className="dark:bg-subtleBlack bg-subtleWhite text-primary inline-block truncate rounded border px-1.5 py-0.5 text-xs font-medium"
style={{ backgroundColor: label.color?.backgroundColor, color: label.color?.textColor }}
>
{label.name}
{labels.length > 1 && (
<div className="flex gap-1">
{visibleLabels.map((label) => (
<button
key={label.id}
onClick={handleFilterByLabel(label)}
className={cn(
'dark:bg-subtleBlack bg-subtleWhite text-primary inline-block truncate rounded border px-1.5 py-0.5 text-xs font-medium',
searchValue.value.includes(`label:${label.name}`) &&
'border-neutral-800 dark:border-white',
)}
style={{ backgroundColor: label.color?.backgroundColor, color: label.color?.textColor }}
>
{label.name}
</button>
))}
{hiddenLabels.length > 0 && (
<Tooltip>
<TooltipTrigger asChild>
<span className="text-muted-foreground text-[10px]"> (+{labels.length - 1})</span>
<button className="text-muted-foreground dark:bg-subtleBlack bg-subtleWhite inline-block truncate rounded border px-1.5 py-0.5 text-xs font-medium">
+{hiddenLabels.length}
</button>
</TooltipTrigger>
<TooltipContent className="flex gap-1">
{labels.map((label) => (
<TooltipContent className="z-[99] flex gap-1 px-1 py-1">
{hiddenLabels.map((label) => (
<button
key={label.id}
onClick={handleFilterByLabel(label)}
className={cn(
'dark:bg-subtleBlack bg-subtleWhite inline-block truncate rounded border px-1.5 py-0.5 text-xs font-medium',
searchValue.value.includes(`label:${label.name}`) &&
'border-purple-800 bg-purple-800/30',
'border-neutral-800 dark:border-white',
)}
key={label.id}
style={{
backgroundColor: label.color?.backgroundColor,
color: label.color?.textColor,
}}
>
{label.name}
</button>
))}
</TooltipContent>
</Tooltip>
)}
</span>
</div>
);
};
30 changes: 30 additions & 0 deletions apps/mail/components/ui/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"use client"

import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"

import { cn } from "@/lib/utils"

const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName

export { Checkbox }
1 change: 1 addition & 0 deletions apps/mail/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@microsoft/microsoft-graph-types": "^2.40.0",
"@radix-ui/react-accordion": "1.2.3",
"@radix-ui/react-avatar": "1.1.3",
"@radix-ui/react-checkbox": "^1.2.3",
"@radix-ui/react-collapsible": "1.1.3",
"@radix-ui/react-context-menu": "2.2.6",
"@radix-ui/react-dialog": "1.1.6",
Expand Down
Loading