feat: inline search filters, folder icon picker, richer demo data & UI polish
- Replace advanced search panel with inline filter chips and collapsible fields (from/to/subject/body inputs, folder dropdown, attachment/flagged/read toggles) - Add debounced auto-search on filter field changes - Folder settings: add icon picker for custom per-folder icons, toast feedback, reorder sections (folder list first, roles second), empty-state illustration - Avatar: show sender favicon for company domains, randomuser.me portraits for personal emails, custom avatars for demo senders, fallback to initials - Add /api/favicon proxy route for fetching domain favicons - Settings store: add senderFavicons toggle and folderIcons persistence - Richer mock inbox with 11 realistic emails (GitHub, Slack, Stripe, Vercel, etc.) - Navigation rail & sidebar cleanup, permanent-delete warning i18n (all 8 locales) - Rename search toggle label to 'More', add body/folder filter translations
This commit is contained in:
+102
-24
@@ -1253,11 +1253,11 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
keywords: { $seen: false },
|
||||
size: 1024,
|
||||
receivedAt: new Date().toISOString(),
|
||||
from: [{ name: "Alice Johnson", email: "alice@example.com" }],
|
||||
from: [{ name: "GitHub", email: "notifications@github.com" }],
|
||||
to: [{ email: "you@example.com" }],
|
||||
subject: "Q4 Budget Review Meeting",
|
||||
preview: "Hi team, I wanted to schedule a meeting to review our Q4 budget projections. Are you available this Thursday at 2 PM? We need to discuss...",
|
||||
hasAttachment: true,
|
||||
subject: "[jmap-webmail] New pull request #42: Add OAuth2 module",
|
||||
preview: "dependabot[bot] opened a pull request in root-fr/jmap-webmail. This PR adds a comprehensive authentication module with OAuth2 PKCE support...",
|
||||
hasAttachment: false,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
@@ -1266,11 +1266,11 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
keywords: { $seen: true, $flagged: true },
|
||||
size: 512,
|
||||
receivedAt: new Date(Date.now() - 3600000).toISOString(),
|
||||
from: [{ name: "Bob Smith", email: "bob@company.com" }],
|
||||
from: [{ name: "Emily Chen", email: "emily.chen@gmail.com" }],
|
||||
to: [{ email: "you@example.com" }],
|
||||
subject: "Re: Project Timeline Update",
|
||||
preview: "Thanks for the update. The new timeline looks good to me. I've reviewed the milestones and everything seems achievable...",
|
||||
hasAttachment: false,
|
||||
subject: "Re: Dashboard Redesign v2 — feedback",
|
||||
preview: "Hey! I just pushed the updated mockups to Figma. I incorporated all the feedback from last week's meeting. Let me know what you think about the new nav...",
|
||||
hasAttachment: true,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
@@ -1279,11 +1279,11 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
keywords: { $seen: false },
|
||||
size: 2048,
|
||||
receivedAt: new Date(Date.now() - 7200000).toISOString(),
|
||||
from: [{ name: "Carol White", email: "carol@design.co" }],
|
||||
from: [{ name: "Slack", email: "notifications@slack.com" }],
|
||||
to: [{ email: "you@example.com" }],
|
||||
subject: "New Design Mockups Ready",
|
||||
preview: "Hey! The new mockups for the landing page are ready for review. I've incorporated all the feedback from last week's meeting...",
|
||||
hasAttachment: true,
|
||||
subject: "3 new messages in #engineering",
|
||||
preview: "Marcus: Hey team, the CI pipeline is green again. Sarah: Great, merging the feature branch now. Alex: Let's do a quick sync at 3 PM...",
|
||||
hasAttachment: false,
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
@@ -1291,11 +1291,11 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
mailboxIds: { inbox: true },
|
||||
keywords: { $seen: true },
|
||||
size: 768,
|
||||
receivedAt: new Date(Date.now() - 86400000).toISOString(),
|
||||
from: [{ name: "GitHub", email: "notifications@github.com" }],
|
||||
receivedAt: new Date(Date.now() - 14400000).toISOString(),
|
||||
from: [{ name: "Marcus Rivera", email: "marcus.rivera@outlook.com" }],
|
||||
to: [{ email: "you@example.com" }],
|
||||
subject: "[PR] Feature: Add authentication module",
|
||||
preview: "A new pull request has been opened in your repository. This PR adds a comprehensive authentication module with OAuth support...",
|
||||
subject: "Quick question about the API rate limits",
|
||||
preview: "Hey, I was looking at the JMAP spec and I'm not sure how we should handle rate limiting on the server side. Do you have any thoughts on...",
|
||||
hasAttachment: false,
|
||||
},
|
||||
{
|
||||
@@ -1304,13 +1304,91 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
mailboxIds: { inbox: true },
|
||||
keywords: { $seen: true },
|
||||
size: 1536,
|
||||
receivedAt: new Date(Date.now() - 172800000).toISOString(),
|
||||
from: [{ name: "David Lee", email: "david@startup.io" }],
|
||||
receivedAt: new Date(Date.now() - 86400000).toISOString(),
|
||||
from: [{ name: "Stripe", email: "receipts@stripe.com" }],
|
||||
to: [{ email: "you@example.com" }],
|
||||
subject: "Investment Proposal Discussion",
|
||||
preview: "Following up on our call yesterday, I'm sending over the investment proposal we discussed. The terms are quite favorable...",
|
||||
subject: "Your invoice from Acme Corp is ready",
|
||||
preview: "Invoice #INV-2026-0312 for $49.00 has been paid. Thank you for your payment. View your receipt and download your invoice...",
|
||||
hasAttachment: true,
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
threadId: "thread-6",
|
||||
mailboxIds: { inbox: true },
|
||||
keywords: { $seen: false },
|
||||
size: 3072,
|
||||
receivedAt: new Date(Date.now() - 108000000).toISOString(),
|
||||
from: [{ name: "Sarah Kim", email: "sarah.kim@proton.me" }],
|
||||
to: [{ email: "you@example.com" }],
|
||||
subject: "Conference talk proposal — need your review",
|
||||
preview: "I'm submitting a talk to ReactConf about our email client architecture. Could you take a look at my abstract before the deadline on Friday?...",
|
||||
hasAttachment: true,
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
threadId: "thread-7",
|
||||
mailboxIds: { inbox: true },
|
||||
keywords: { $seen: true, $flagged: true },
|
||||
size: 4096,
|
||||
receivedAt: new Date(Date.now() - 172800000).toISOString(),
|
||||
from: [{ name: "Vercel", email: "notifications@vercel.com" }],
|
||||
to: [{ email: "you@example.com" }],
|
||||
subject: "Deployment successful: jmap-webmail \u2192 Production",
|
||||
preview: "Your project jmap-webmail has been deployed to production. Build completed in 47s. All checks passed. Preview: https://jmap-webmail.vercel.app...",
|
||||
hasAttachment: false,
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
threadId: "thread-8",
|
||||
mailboxIds: { inbox: true },
|
||||
keywords: { $seen: true },
|
||||
size: 2560,
|
||||
receivedAt: new Date(Date.now() - 259200000).toISOString(),
|
||||
from: [{ name: "Alex Petrov", email: "alex.petrov@fastmail.com" }],
|
||||
to: [{ email: "you@example.com" }],
|
||||
subject: "Meeting notes from yesterday's standup",
|
||||
preview: "Here are the action items from yesterday. 1) Finish the drag-and-drop implementation by Wednesday. 2) Review the accessibility audit results...",
|
||||
hasAttachment: false,
|
||||
},
|
||||
{
|
||||
id: "9",
|
||||
threadId: "thread-9",
|
||||
mailboxIds: { inbox: true },
|
||||
keywords: { $seen: false },
|
||||
size: 1280,
|
||||
receivedAt: new Date(Date.now() - 345600000).toISOString(),
|
||||
from: [{ name: "Linear", email: "notifications@linear.app" }],
|
||||
to: [{ email: "you@example.com" }],
|
||||
subject: "ENG-384: Implement email threading view \u2014 moved to In Progress",
|
||||
preview: "Alice Johnson moved ENG-384 to In Progress. This issue covers implementing the conversation thread view for the email client...",
|
||||
hasAttachment: false,
|
||||
},
|
||||
{
|
||||
id: "10",
|
||||
threadId: "thread-10",
|
||||
mailboxIds: { inbox: true },
|
||||
keywords: { $seen: true },
|
||||
size: 896,
|
||||
receivedAt: new Date(Date.now() - 432000000).toISOString(),
|
||||
from: [{ name: "Priya Sharma", email: "priya.sharma@icloud.com" }],
|
||||
to: [{ email: "you@example.com" }],
|
||||
subject: "Re: Onboarding docs for new contributors",
|
||||
preview: "Thanks for putting this together! I added a section on setting up the dev environment. Also linked the architecture diagram from our wiki...",
|
||||
hasAttachment: false,
|
||||
},
|
||||
{
|
||||
id: "11",
|
||||
threadId: "thread-11",
|
||||
mailboxIds: { inbox: true },
|
||||
keywords: { $seen: true },
|
||||
size: 5120,
|
||||
receivedAt: new Date(Date.now() - 518400000).toISOString(),
|
||||
from: [{ name: "LaunchWeekly", email: "newsletter@launchweekly.com" }],
|
||||
to: [{ email: "you@example.com" }],
|
||||
subject: "\uD83D\uDE80 This week in tech: AI agents, new frameworks, and more",
|
||||
preview: "Happy Monday! Here's your weekly roundup of the most interesting launches, open-source projects, and developer tools you might have missed...",
|
||||
hasAttachment: false,
|
||||
},
|
||||
];
|
||||
|
||||
const mockMailboxes: Mailbox[] = [
|
||||
@@ -1319,10 +1397,10 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
name: "Inbox",
|
||||
role: "inbox",
|
||||
sortOrder: 1,
|
||||
totalEmails: 5,
|
||||
unreadEmails: 2,
|
||||
totalThreads: 5,
|
||||
unreadThreads: 2,
|
||||
totalEmails: 11,
|
||||
unreadEmails: 4,
|
||||
totalThreads: 11,
|
||||
unreadThreads: 4,
|
||||
myRights: {
|
||||
mayReadItems: true,
|
||||
mayAddItems: true,
|
||||
|
||||
@@ -41,6 +41,12 @@ interface SettingsState {
|
||||
calendarNotificationsEnabled: boolean;
|
||||
calendarNotificationSound: boolean;
|
||||
|
||||
// Experimental
|
||||
senderFavicons: boolean;
|
||||
|
||||
// Folders
|
||||
folderIcons: Record<string, string>; // mailboxId -> icon name
|
||||
|
||||
// Advanced
|
||||
debugMode: boolean;
|
||||
|
||||
@@ -53,6 +59,10 @@ interface SettingsState {
|
||||
exportSettings: () => string;
|
||||
importSettings: (json: string) => boolean;
|
||||
|
||||
// Folder icons
|
||||
setFolderIcon: (mailboxId: string, icon: string) => void;
|
||||
removeFolderIcon: (mailboxId: string) => void;
|
||||
|
||||
// Trusted senders
|
||||
addTrustedSender: (email: string) => void;
|
||||
removeTrustedSender: (email: string) => void;
|
||||
@@ -90,6 +100,12 @@ const DEFAULT_SETTINGS = {
|
||||
calendarNotificationsEnabled: true,
|
||||
calendarNotificationSound: true,
|
||||
|
||||
// Experimental
|
||||
senderFavicons: true,
|
||||
|
||||
// Folders
|
||||
folderIcons: {} as Record<string, string>,
|
||||
|
||||
// Advanced
|
||||
debugMode: false,
|
||||
};
|
||||
@@ -146,6 +162,8 @@ export const useSettingsStore = create<SettingsState>()(
|
||||
sessionTimeout: state.sessionTimeout,
|
||||
calendarNotificationsEnabled: state.calendarNotificationsEnabled,
|
||||
calendarNotificationSound: state.calendarNotificationSound,
|
||||
senderFavicons: state.senderFavicons,
|
||||
folderIcons: state.folderIcons,
|
||||
debugMode: state.debugMode,
|
||||
};
|
||||
return JSON.stringify(settings, null, 2);
|
||||
@@ -179,6 +197,16 @@ export const useSettingsStore = create<SettingsState>()(
|
||||
}
|
||||
},
|
||||
|
||||
// Folder icon methods
|
||||
setFolderIcon: (mailboxId: string, icon: string) => {
|
||||
set({ folderIcons: { ...get().folderIcons, [mailboxId]: icon } });
|
||||
},
|
||||
|
||||
removeFolderIcon: (mailboxId: string) => {
|
||||
const { [mailboxId]: _, ...rest } = get().folderIcons;
|
||||
set({ folderIcons: rest });
|
||||
},
|
||||
|
||||
// Trusted senders methods
|
||||
addTrustedSender: (email: string) => {
|
||||
const normalizedEmail = email.toLowerCase().trim();
|
||||
|
||||
Reference in New Issue
Block a user