mirror of
https://github.com/Mail-0/Zero.git
synced 2026-06-30 15:56:59 +00:00
ui privacy email composer open (#1265)
# READ CAREFULLY THEN REMOVE Remove bullet points that are not relevant. PLEASE REFRAIN FROM USING AI TO WRITE YOUR CODE AND PR DESCRIPTION. IF YOU DO USE AI TO WRITE YOUR CODE PLEASE PROVIDE A DESCRIPTION AND REVIEW IT CAREFULLY. MAKE SURE YOU UNDERSTAND THE CODE YOU ARE SUBMITTING USING AI. - Pull requests that do not follow these guidelines will be closed without review or comment. - If you use AI to write your PR description your pr will be close without review or comment. - If you are unsure about anything, feel free to ask for clarification. ## Description Please provide a clear description of your changes. --- ## Type of Change Please delete options that are not relevant. - [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [ ] 💥 Breaking change (fix or feature with breaking changes) - [ ] 📝 Documentation update - [ ] 🎨 UI/UX improvement - [ ] 🔒 Security enhancement - [ ] ⚡ Performance improvement ## Areas Affected Please check all that apply: - [ ] Email Integration (Gmail, IMAP, etc.) - [ ] User Interface/Experience - [ ] Authentication/Authorization - [ ] Data Storage/Management - [ ] API Endpoints - [ ] Documentation - [ ] Testing Infrastructure - [ ] Development Workflow - [ ] Deployment/Infrastructure ## Testing Done Describe the tests you've done: - [ ] Unit tests added/updated - [ ] Integration tests added/updated - [ ] Manual testing performed - [ ] Cross-browser testing (if UI changes) - [ ] Mobile responsiveness verified (if UI changes) ## Security Considerations For changes involving data or authentication: - [ ] No sensitive data is exposed - [ ] Authentication checks are in place - [ ] Input validation is implemented - [ ] Rate limiting is considered (if applicable) ## Checklist - [ ] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document - [ ] My code follows the project's style guidelines - [ ] I have performed a self-review of my code - [ ] I have commented my code, particularly in complex areas - [ ] I have updated the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix/feature works - [ ] All tests pass locally - [ ] Any dependent changes are merged and published ## Additional Notes Add any other context about the pull request here. ## Screenshots/Recordings Add screenshots or recordings here if applicable. --- _By submitting this pull request, I confirm that my contribution is made under the terms of the project's license._ <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added a "Pricing and Refund Policy" section to the privacy policy, outlining subscription terms, billing, refunds, and price change notifications. - Introduced a GitHub star count button in the navigation bar, displaying the current repository star count with animated updates. - **UI Improvements** - Updated button and panel border radius for a more consistent, rounded appearance across various components. - Improved color schemes and spacing for buttons, avatars, and panels for a cleaner look. - Adjusted scrollbar styling and removed some borders for a sleeker interface. - Refined layout and sizing of mail panels and navigation elements for better usability. - Enhanced visual indicators for unread threads and improved reply composer auto-opening behavior. - **Localization** - Updated the command palette string from "New Email" to "New email" for consistency. - **Customization** - Added the ability to pass custom CSS classes to the email composer for flexible editor styling. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -337,6 +337,66 @@ const sections = [
|
||||
</ul>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Pricing and Refund Policy',
|
||||
content: (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="mb-3 text-lg font-medium">Free Plan and Trial Period</h3>
|
||||
<ul className="ml-4 list-disc space-y-2">
|
||||
<li>Zero offers a free plan with basic features that requires no payment information</li>
|
||||
<li>For premium features, we offer a 7-day free trial period</li>
|
||||
<li>A valid credit card is required to start the premium free trial</li>
|
||||
<li>During the trial period, you have full access to all premium features</li>
|
||||
<li>You can cancel at any time during the trial period without any charges</li>
|
||||
<li>If you don't cancel before the trial ends, you'll be automatically charged for the premium subscription</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="mb-3 text-lg font-medium">Payment and Billing</h3>
|
||||
<ul className="ml-4 list-disc space-y-2">
|
||||
<li>After the 7-day free trial period ends, subscription charges will begin automatically</li>
|
||||
<li>Subscription fees are billed in advance on a monthly or annual basis</li>
|
||||
<li>Current pricing information is available on our pricing page</li>
|
||||
<li>All payments are processed securely through our trusted payment partners</li>
|
||||
<li>Subscription charges will appear on your billing statement as "Zero Email"</li>
|
||||
<li>We accept major credit cards and other payment methods as available in your region</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="mb-3 text-lg font-medium">Non-Refundable Policy</h3>
|
||||
<ul className="ml-4 list-disc space-y-2">
|
||||
<li className="font-semibold">
|
||||
Important: All subscription fees are non-refundable once the 7-day free trial period has ended
|
||||
</li>
|
||||
<li>This policy applies to all premium subscription plans (monthly, annual, and enterprise plans)</li>
|
||||
<li>Refunds are not provided for partial subscription periods</li>
|
||||
<li>Refunds are not available for unused portions of your subscription</li>
|
||||
<li>In exceptional circumstances, refunds may be considered on a case-by-case basis at our sole discretion</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="mb-3 text-lg font-medium">Subscription Management</h3>
|
||||
<ul className="ml-4 list-disc space-y-2">
|
||||
<li>You can cancel your subscription at any time through your account settings</li>
|
||||
<li>Cancellation takes effect at the end of your current billing period</li>
|
||||
<li>You will continue to have access to premium features until the end of your paid period</li>
|
||||
<li>No partial refunds are provided for early cancellation</li>
|
||||
<li>Reactivation of cancelled subscriptions may be subject to current pricing</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="mb-3 text-lg font-medium">Price Changes</h3>
|
||||
<ul className="ml-4 list-disc space-y-2">
|
||||
<li>We reserve the right to modify subscription pricing at any time</li>
|
||||
<li>Existing subscribers will be notified of price changes at least 30 days in advance</li>
|
||||
<li>Price changes will take effect at your next billing cycle</li>
|
||||
<li>You may cancel your subscription before the price change takes effect</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Contact',
|
||||
content: (
|
||||
|
||||
@@ -359,5 +359,5 @@
|
||||
|
||||
/* Add scrollbar styling */
|
||||
.style-scrollbar {
|
||||
@apply scrollbar scrollbar-w-1 scrollbar-thumb-accent/40 scrollbar-track-transparent hover:scrollbar-thumb-accent scrollbar-thumb-rounded-full;
|
||||
@apply scrollbar scrollbar-w-0 scrollbar-thumb-accent/40 scrollbar-track-transparent hover:scrollbar-thumb-accent scrollbar-thumb-rounded-full;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ const AIToggleButton = () => {
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="dark:bg-sidebar h-12 w-12 rounded-full"
|
||||
className="dark:bg-sidebar border h-12 w-12 rounded-lg"
|
||||
onClick={(e) => {
|
||||
if (!isSidebarOpen) {
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -59,6 +59,7 @@ interface EmailComposerProps {
|
||||
className?: string;
|
||||
autofocus?: boolean;
|
||||
settingsLoading?: boolean;
|
||||
editorClassName?: string;
|
||||
}
|
||||
|
||||
const isValidEmail = (email: string): boolean => {
|
||||
@@ -91,6 +92,7 @@ export function EmailComposer({
|
||||
className,
|
||||
autofocus = false,
|
||||
settingsLoading = false,
|
||||
editorClassName,
|
||||
}: EmailComposerProps) {
|
||||
const [showCc, setShowCc] = useState(initialCc.length > 0);
|
||||
const [showBcc, setShowBcc] = useState(initialBcc.length > 0);
|
||||
@@ -937,7 +939,7 @@ export function EmailComposer({
|
||||
editor.commands.focus();
|
||||
}}
|
||||
className={cn(
|
||||
'max-h-[300px] min-h-[200px] w-full',
|
||||
`max-h-[300px] min-h-[200px] w-full ${editorClassName}`,
|
||||
aiGeneratedMessage !== null ? 'blur-sm' : '',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -819,7 +819,7 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }:
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [demo, emailData.id, isLastEmail]);
|
||||
}, [demo, emailData.id, isLastEmail, activeReplyId]);
|
||||
|
||||
// const listUnsubscribeAction = useMemo(
|
||||
// () =>
|
||||
@@ -1754,7 +1754,7 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }:
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="mb-2 mt-2 flex gap-2 px-4">
|
||||
<div className="my-2.5 flex gap-2 px-4">
|
||||
<ActionButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -142,7 +142,7 @@ const Thread = memo(
|
||||
const nextThread = threads[focusedIndex];
|
||||
if (nextThread) {
|
||||
setThreadId(nextThread.id);
|
||||
setActiveReplyId(null);
|
||||
// Don't clear activeReplyId - let ThreadDisplay handle Reply All auto-opening
|
||||
setFocusedIndex(focusedIndex);
|
||||
}
|
||||
}
|
||||
@@ -364,14 +364,12 @@ const Thread = memo(
|
||||
<Avatar
|
||||
className={cn(
|
||||
'h-8 w-8 rounded-full',
|
||||
displayUnread && !isMailSelected && !isFolderSent
|
||||
? 'border border-[#006FFE]'
|
||||
: 'border',
|
||||
displayUnread && !isMailSelected && !isFolderSent ? '' : 'border',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-full w-full items-center justify-center rounded-full bg-blue-500 p-2 dark:bg-blue-500',
|
||||
'flex h-full w-full items-center justify-center rounded-full bg-[#006FFE] p-2 dark:bg-[#006FFE]',
|
||||
{
|
||||
hidden: !isMailBulkSelected,
|
||||
},
|
||||
@@ -412,12 +410,12 @@ const Thread = memo(
|
||||
</>
|
||||
)}
|
||||
</Avatar>
|
||||
{displayUnread && !isMailSelected && !isFolderSent ? (
|
||||
{/* {displayUnread && !isMailSelected && !isFolderSent ? (
|
||||
<>
|
||||
<span className="absolute left-2 top-2 size-1.5 rounded bg-[#006FFE]" />
|
||||
<span className="absolute left-[11px] top-4 size-1 rounded bg-[#006FFE]" />
|
||||
</>
|
||||
) : null}
|
||||
) : null} */}
|
||||
</div>
|
||||
|
||||
<div className="flex w-full justify-between">
|
||||
@@ -439,12 +437,23 @@ const Thread = memo(
|
||||
{highlightText(latestMessage.subject, searchValue.highlight)}
|
||||
</span>
|
||||
) : (
|
||||
<span className={cn('line-clamp-1 overflow-hidden text-sm')}>
|
||||
{highlightText(
|
||||
cleanNameDisplay(latestMessage.sender.name) || '',
|
||||
searchValue.highlight,
|
||||
)}
|
||||
</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<span
|
||||
className={cn(
|
||||
'line-clamp-1 overflow-hidden text-sm',
|
||||
)}
|
||||
>
|
||||
{highlightText(
|
||||
cleanNameDisplay(latestMessage.sender.name) || '',
|
||||
searchValue.highlight,
|
||||
)}
|
||||
</span>
|
||||
{displayUnread && !isMailSelected && !isFolderSent ? (
|
||||
<>
|
||||
<span className="ml-0.5 size-2 rounded-full bg-[#006FFE]" />
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
)}{' '}
|
||||
{/* {!isFolderSent ? (
|
||||
<span className="hidden items-center space-x-2 md:flex">
|
||||
@@ -768,7 +777,7 @@ export const MailList = memo(
|
||||
if (message.unread) optimisticMarkAsRead([messageThreadId], true);
|
||||
await setThreadId(messageThreadId);
|
||||
await setDraftId(null);
|
||||
await setActiveReplyId(null);
|
||||
// Don't clear activeReplyId - let ThreadDisplay handle Reply All auto-opening
|
||||
},
|
||||
[
|
||||
getSelectMode,
|
||||
@@ -886,7 +895,7 @@ export const MailList = memo(
|
||||
count={filteredItems.length}
|
||||
overscan={20}
|
||||
keepMounted={[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}
|
||||
className="style-scrollbar flex-1 overflow-x-hidden"
|
||||
className="scrollbar-none flex-1 overflow-x-hidden"
|
||||
children={vListRenderer}
|
||||
onScroll={() => {
|
||||
if (!vListRef.current) return;
|
||||
|
||||
@@ -226,19 +226,14 @@ const AutoLabelingSettings = () => {
|
||||
}}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{/* <div
|
||||
className={cn(
|
||||
'h-2 w-2 animate-pulse rounded-full',
|
||||
brainState?.enabled ? 'bg-green-400' : 'bg-red-400',
|
||||
)}
|
||||
/> */}
|
||||
|
||||
|
||||
<Switch
|
||||
disabled={isEnablingBrain || isDisablingBrain || isLoading}
|
||||
checked={brainState?.enabled ?? false}
|
||||
/>
|
||||
<span className="text-muted-foreground cursor-pointer text-xs">Auto label</span>
|
||||
<span className="text-muted-foreground cursor-pointer text-xs font-medium">Auto label</span>
|
||||
</div>
|
||||
</DialogTrigger>
|
||||
<DialogContent showOverlay className="max-w-2xl">
|
||||
@@ -468,18 +463,18 @@ export function MailLayout() {
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<PricingDialog />
|
||||
<div className="rounded-inherit relative z-[5] flex p-0 md:mt-1">
|
||||
<div className="rounded-inherit relative z-[5] flex p-0 md:mr-0.5 md:mt-1">
|
||||
<ResizablePanelGroup
|
||||
direction="horizontal"
|
||||
autoSaveId="mail-panel-layout"
|
||||
className="rounded-inherit overflow-hidden"
|
||||
>
|
||||
<ResizablePanel
|
||||
defaultSize={35}
|
||||
minSize={35}
|
||||
maxSize={40}
|
||||
defaultSize={30}
|
||||
minSize={30}
|
||||
maxSize={30}
|
||||
className={cn(
|
||||
`bg-panelLight dark:bg-panelDark mb-1 w-fit shadow-sm md:rounded-2xl md:border md:border-[#E7E7E7] lg:flex lg:shadow-sm dark:border-[#252525]`,
|
||||
`bg-panelLight dark:bg-panelDark mb-1 mr-[3px] w-fit shadow-sm md:rounded-2xl lg:flex lg:h-[calc(100dvh-8px)] lg:shadow-sm`,
|
||||
isDesktop && threadId && 'hidden lg:block',
|
||||
)}
|
||||
onMouseEnter={handleMailListMouseEnter}
|
||||
@@ -488,7 +483,7 @@ export function MailLayout() {
|
||||
<div className="w-full md:h-[calc(100dvh-10px)]">
|
||||
<div
|
||||
className={cn(
|
||||
'sticky top-0 z-[15] flex items-center justify-between gap-1.5 border-b border-[#E7E7E7] p-2 px-[20px] transition-colors md:min-h-14 dark:border-[#252525]',
|
||||
'sticky top-0 z-[15] flex items-center justify-between gap-1.5 p-2 px-[20px] transition-colors md:min-h-14',
|
||||
)}
|
||||
>
|
||||
<div className="flex w-full items-center justify-between gap-2">
|
||||
@@ -537,7 +532,7 @@ export function MailLayout() {
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn(
|
||||
'text-muted-foreground relative flex h-8 w-full select-none items-center justify-start overflow-hidden rounded-[0.5rem] rounded-lg border bg-white pl-2 text-left text-sm font-normal shadow-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0 dark:border-none dark:bg-[#141414]',
|
||||
'text-muted-foreground relative flex h-8 w-full select-none items-center justify-start overflow-hidden rounded-lg border bg-white pl-2 text-left text-sm font-normal shadow-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0 dark:border-none dark:bg-[#141414]',
|
||||
)}
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
@@ -605,19 +600,19 @@ export function MailLayout() {
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
|
||||
<ResizableHandle className="mr-0.5 hidden opacity-0 md:block" />
|
||||
{/* <ResizableHandle className="mr-0.5 hidden opacity-0 md:block" /> */}
|
||||
|
||||
{isDesktop && (
|
||||
<ResizablePanel
|
||||
className={cn(
|
||||
'bg-panelLight dark:bg-panelDark mb-1 mr-0.5 w-fit rounded-2xl border border-[#E7E7E7] shadow-sm dark:border-[#252525]',
|
||||
'bg-panelLight dark:bg-panelDark mb-1 mr-0.5 w-fit rounded-2xl shadow-sm lg:h-[calc(100dvh-8px)]',
|
||||
// Only show on md screens and larger when there is a threadId
|
||||
!threadId && 'hidden lg:block',
|
||||
)}
|
||||
defaultSize={30}
|
||||
minSize={30}
|
||||
>
|
||||
<div className="lg:h-[calc(100dvh-(10px)] relative h-[calc(100dvh-(10px))] flex-1">
|
||||
<div className="relative flex-1">
|
||||
<ThreadDisplay />
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
|
||||
@@ -514,7 +514,7 @@ export function NotesPanel({ threadId }: NotesPanelProps) {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={cn(
|
||||
'inline-flex h-7 w-7 items-center justify-center gap-1 overflow-hidden rounded-md bg-white dark:bg-[#313131]',
|
||||
'inline-flex h-7 w-7 items-center justify-center gap-1 overflow-hidden rounded-lg bg-white dark:bg-[#313131]',
|
||||
notes.length > 0 && 'text-amber-500',
|
||||
isOpen && 'bg-white/80 dark:bg-[#313131]/80',
|
||||
)}
|
||||
|
||||
@@ -215,6 +215,7 @@ export default function ReplyCompose({ messageId }: ReplyComposeProps) {
|
||||
return (
|
||||
<div className="w-full rounded-xl">
|
||||
<EmailComposer
|
||||
editorClassName="min-h-[140px]"
|
||||
className="w-full !max-w-none border pb-1"
|
||||
onSendEmail={handleSendEmail}
|
||||
onClose={async () => {
|
||||
|
||||
@@ -216,13 +216,11 @@ export function ThreadDisplay() {
|
||||
setActiveReplyId(null);
|
||||
if (nextThread) {
|
||||
setThreadId(nextThread.id);
|
||||
setFocusedIndex(focusedIndex);
|
||||
} else {
|
||||
setThreadId(null);
|
||||
setFocusedIndex(null);
|
||||
// Don't clear activeReplyId - let the auto-open effect handle it
|
||||
setFocusedIndex(focusedIndex + 1);
|
||||
}
|
||||
}
|
||||
}, [items, id, focusedIndex, setThreadId, setActiveReplyId, setFocusedIndex]);
|
||||
}, [items, id, focusedIndex, setThreadId, setFocusedIndex]);
|
||||
|
||||
const handleUnsubscribeProcess = () => {
|
||||
if (!emailData?.latest) return;
|
||||
@@ -674,14 +672,20 @@ export function ThreadDisplay() {
|
||||
}
|
||||
}, [optimisticState.optimisticStarred]);
|
||||
|
||||
// When mode changes, set the active reply to the latest message
|
||||
// Automatically open Reply All composer when email thread is loaded
|
||||
useEffect(() => {
|
||||
// Only clear the active reply when mode is cleared
|
||||
// This prevents overriding the specifically selected message
|
||||
if (!mode) {
|
||||
setActiveReplyId(null);
|
||||
if (emailData?.latest?.id) {
|
||||
// Small delay to ensure other effects have completed
|
||||
const timer = setTimeout(() => {
|
||||
setMode('replyAll');
|
||||
setActiveReplyId(emailData.latest!.id);
|
||||
}, 50);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [mode]);
|
||||
}, [emailData?.latest?.id, setMode, setActiveReplyId]);
|
||||
|
||||
// Removed conflicting useEffect that was clearing activeReplyId
|
||||
|
||||
// Scroll to the active reply composer when it's opened
|
||||
useEffect(() => {
|
||||
@@ -763,7 +767,7 @@ export function ThreadDisplay() {
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-shrink-0 items-center border-b border-[#E7E7E7] px-1 pb-1 md:px-3 md:pb-[11px] md:pt-[12px] dark:border-[#252525]',
|
||||
'flex flex-shrink-0 items-center px-1 pb-1 md:px-3 md:pb-[11px] md:pt-[12px] ',
|
||||
isMobile && 'bg-panelLight dark:bg-panelDark sticky top-0 z-10 mt-2',
|
||||
)}
|
||||
>
|
||||
@@ -843,12 +847,12 @@ export function ThreadDisplay() {
|
||||
setMode('replyAll');
|
||||
setActiveReplyId(emailData?.latest?.id ?? '');
|
||||
}}
|
||||
className="inline-flex h-7 items-center justify-center gap-1 overflow-hidden rounded-md border bg-white px-1.5 dark:border-none dark:bg-[#313131]"
|
||||
className="inline-flex h-7 items-center justify-center gap-1 overflow-hidden rounded-lg border bg-white px-1.5 dark:border-none dark:bg-[#313131]"
|
||||
>
|
||||
<Reply className="fill-muted-foreground dark:fill-[#9B9B9B]" />
|
||||
<div className="flex items-center justify-center gap-2.5 pl-0.5 pr-1">
|
||||
<div className="justify-start text-sm leading-none text-black dark:text-white">
|
||||
Reply
|
||||
Reply All
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
@@ -858,7 +862,7 @@ export function ThreadDisplay() {
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
onClick={handleToggleStar}
|
||||
className="inline-flex h-7 w-7 items-center justify-center gap-1 overflow-hidden rounded-md bg-white dark:bg-[#313131]"
|
||||
className="inline-flex h-7 w-7 items-center justify-center gap-1 overflow-hidden rounded-lg bg-white dark:bg-[#313131]"
|
||||
>
|
||||
<Star
|
||||
className={cn(
|
||||
@@ -883,7 +887,7 @@ export function ThreadDisplay() {
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
onClick={() => moveThreadTo('archive')}
|
||||
className="inline-flex h-7 w-7 items-center justify-center gap-1 overflow-hidden rounded-md bg-white dark:bg-[#313131]"
|
||||
className="inline-flex h-7 w-7 items-center justify-center gap-1 overflow-hidden rounded-lg bg-white dark:bg-[#313131]"
|
||||
>
|
||||
<Archive className="fill-iconLight dark:fill-iconDark" />
|
||||
</button>
|
||||
@@ -900,7 +904,7 @@ export function ThreadDisplay() {
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
onClick={() => moveThreadTo('bin')}
|
||||
className="inline-flex h-7 w-7 items-center justify-center gap-1 overflow-hidden rounded-md bg-white dark:bg-[#313131]"
|
||||
className="inline-flex h-7 w-7 items-center justify-center gap-1 overflow-hidden rounded-lg border border-[#FCCDD5] bg-[#FDE4E9] dark:border-[#6E2532] dark:bg-[#411D23]"
|
||||
>
|
||||
<Trash className="fill-iconLight dark:fill-iconDark" />
|
||||
</button>
|
||||
@@ -914,7 +918,7 @@ export function ThreadDisplay() {
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button className="inline-flex h-7 w-7 items-center justify-center gap-1 overflow-hidden rounded-md bg-white focus:outline-none focus:ring-0 dark:bg-[#313131]">
|
||||
<button className="inline-flex h-7 w-7 items-center justify-center gap-1 overflow-hidden rounded-lg bg-white focus:outline-none focus:ring-0 dark:bg-[#313131]">
|
||||
<ThreeDots className="fill-iconLight dark:fill-iconDark" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
@@ -8,14 +8,17 @@ import {
|
||||
ListItem,
|
||||
} from '@/components/ui/navigation-menu';
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet';
|
||||
import { GitHub, Twitter, Discord, LinkedIn } from './icons/icons';
|
||||
import { GitHub, Twitter, Discord, LinkedIn, Star } from './icons/icons';
|
||||
import { AnimatedNumber } from '@/components/ui/animated-number';
|
||||
import { signIn, useSession } from '@/lib/auth-client';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Link, useNavigate } from 'react-router';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Menu } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { toast } from 'sonner';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const resources = [
|
||||
{
|
||||
@@ -74,11 +77,37 @@ const IconComponent = {
|
||||
linkedin: LinkedIn,
|
||||
};
|
||||
|
||||
interface GitHubApiResponse {
|
||||
stargazers_count: number;
|
||||
}
|
||||
|
||||
export function Navigation() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [stars, setStars] = useState(0); // Default fallback value
|
||||
const { data: session } = useSession();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { data: githubData } = useQuery({
|
||||
queryKey: ['githubStars'],
|
||||
queryFn: async () => {
|
||||
const response = await fetch('https://api.github.com/repos/Mail-0/Zero', {
|
||||
headers: {
|
||||
'Accept': 'application/vnd.github.v3+json',
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch GitHub stars');
|
||||
}
|
||||
return response.json() as Promise<GitHubApiResponse>;
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (githubData) {
|
||||
setStars(githubData.stargazers_count || 0);
|
||||
}
|
||||
}, [githubData]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Desktop Navigation - Hidden on mobile */}
|
||||
@@ -133,6 +162,27 @@ export function Navigation() {
|
||||
</NavigationMenu>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<a
|
||||
href="https://github.com/Mail-0/Zero"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={cn(
|
||||
"h-8 inline-flex items-center gap-2 rounded-lg bg-black px-3 text-sm text-white hover:bg-black/90 transition-colors group"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center text-white">
|
||||
<GitHub className="size-4 fill-white mr-1" />
|
||||
<span className="ml-1 lg:hidden">Star</span>
|
||||
<span className="ml-1 hidden lg:inline">Stars on GitHub</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-sm">
|
||||
<Star className="size-4 fill-gray-400 transition-all duration-300 group-hover:fill-yellow-400 group-hover:drop-shadow-[0_0_8px_rgba(250,204,21,0.6)] relative top-[1px]" />
|
||||
<AnimatedNumber
|
||||
value={stars}
|
||||
className="font-medium text-white"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
<Button
|
||||
className="h-8 bg-white text-black hover:bg-white hover:text-black"
|
||||
onClick={() => {
|
||||
|
||||
@@ -47,7 +47,7 @@ function ChatHeader({
|
||||
const [, setPricingDialog] = useQueryState('pricingDialog');
|
||||
const { chatMessages } = useBilling();
|
||||
return (
|
||||
<div className="relative flex items-center justify-between border-b border-[#E7E7E7] px-2.5 pb-[10px] pt-[13px] dark:border-[#252525]">
|
||||
<div className="relative flex items-center justify-between px-2.5 pb-[10px] pt-[13px]">
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -452,7 +452,7 @@ function AISidebar({ className }: AISidebarProps) {
|
||||
defaultSize={24}
|
||||
minSize={24}
|
||||
maxSize={24}
|
||||
className="bg-panelLight dark:bg-panelDark mb-1 mr-1 hidden h-[calc(100dvh-8px)] border-[#E7E7E7] shadow-sm md:block md:rounded-2xl md:border md:shadow-sm dark:border-[#252525]"
|
||||
className="bg-panelLight dark:bg-panelDark mb-1 mr-1 hidden h-[calc(100dvh-8px)] shadow-sm md:block md:rounded-2xl md:shadow-sm "
|
||||
>
|
||||
<div className={cn('h-[calc(98vh)]', 'flex flex-col', '', className)}>
|
||||
<div className="flex h-full flex-col">
|
||||
|
||||
@@ -220,7 +220,7 @@ function ComposeButton() {
|
||||
<DialogDescription></DialogDescription>
|
||||
|
||||
<DialogTrigger asChild>
|
||||
<button className="inline-flex h-8 w-full items-center justify-center gap-1 self-stretch overflow-hidden rounded-md border border-gray-200 bg-transparent text-black dark:border-none dark:bg-gradient-to-b dark:from-white/20 dark:to-white/10 dark:text-white dark:outline dark:outline-1 dark:outline-offset-[-1px] dark:outline-white/5">
|
||||
<button className="inline-flex h-8 w-full items-center justify-center gap-1 self-stretch overflow-hidden rounded-lg border border-gray-200 bg-white text-black dark:border-none dark:bg-[#2C2C2C] dark:text-white relative mb-1.5">
|
||||
{state === 'collapsed' && !isMobile ? (
|
||||
<PencilCompose className="fill-iconLight dark:fill-iconDark mt-0.5 text-black" />
|
||||
) : (
|
||||
|
||||
@@ -25,7 +25,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from './popover';
|
||||
import { CallInboxDialog, SetupInboxDialog } from '../setup-phone';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { CircleCheck, Danger, ThreeDots } from '../icons/icons';
|
||||
import { CircleCheck, Danger, OldPhone, ThreeDots } from '../icons/icons';
|
||||
import { signOut, useSession } from '@/lib/auth-client';
|
||||
import { AddConnectionDialog } from '../connection/add';
|
||||
import { useTRPC } from '@/providers/query-provider';
|
||||
@@ -131,7 +131,7 @@ export function NavUser() {
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div className="flex cursor-pointer items-center">
|
||||
<div className="relative">
|
||||
<Avatar className="size-8 rounded-[5px]">
|
||||
<Avatar className="size-7 rounded-[5px] relative left-0.5">
|
||||
<AvatarImage
|
||||
className="rounded-[5px]"
|
||||
src={activeAccount?.picture || undefined}
|
||||
@@ -305,7 +305,7 @@ export function NavUser() {
|
||||
}`}
|
||||
>
|
||||
<div className="relative">
|
||||
<Avatar className="size-7 rounded-[5px]">
|
||||
<Avatar className="size-6 rounded-[5px]">
|
||||
<AvatarImage
|
||||
className="rounded-[5px]"
|
||||
src={activeAccount.picture || undefined}
|
||||
@@ -328,7 +328,7 @@ export function NavUser() {
|
||||
) : (
|
||||
<div className="flex cursor-pointer items-center">
|
||||
<div className="relative">
|
||||
<div className="bg-muted size-7 animate-pulse rounded-[5px]" />
|
||||
<div className="bg-muted size-6 animate-pulse rounded-[5px]" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -423,7 +423,7 @@ export function NavUser() {
|
||||
|
||||
{isPro ? (
|
||||
<AddConnectionDialog>
|
||||
<button className="flex h-7 w-7 cursor-pointer items-center justify-center rounded-[5px] border border-dashed dark:bg-[#262626] dark:text-[#929292]">
|
||||
<button className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-[5px] border border-dashed dark:bg-[#262626] dark:text-[#929292]">
|
||||
<Plus className="size-4" />
|
||||
</button>
|
||||
</AddConnectionDialog>
|
||||
@@ -439,7 +439,7 @@ export function NavUser() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-1">
|
||||
<div className="flex items-center justify-center gap-1">
|
||||
{isSessionPending ? null : false ? <SetupInboxDialog /> : <CallInboxDialog />}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -523,7 +523,7 @@ export function NavUser() {
|
||||
|
||||
{state !== 'collapsed' && (
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="my-2 flex flex-col items-start gap-1 space-y-1">
|
||||
<div className="mt-[2px] flex flex-col items-start gap-1 space-y-1">
|
||||
<div className="flex items-center gap-1 text-[13px] leading-none text-black dark:text-white">
|
||||
<p className={cn('truncate text-[13px]', isPro ? 'max-w-[14.5ch]' : 'max-w-[8.5ch]')}>
|
||||
{activeAccount?.name || session.user.name || 'User'}
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
"goToArchive": "Go to Archive",
|
||||
"goToBin": "Go to Bin",
|
||||
"goToSettings": "Go to Settings",
|
||||
"newEmail": "New Email",
|
||||
"newEmail": "New email",
|
||||
"composeMessage": "New Email",
|
||||
"searchEmails": "Search Emails",
|
||||
"toggleTheme": "Toggle Theme",
|
||||
|
||||
Reference in New Issue
Block a user