feat: command palette (Ctrl+K), dark mode, pinned clients
- Global Command Palette: Ctrl+K search across clients, pages, and actions - Arrow key navigation, grouped results (Pages/Clients/Actions) - Keyboard hints in footer - Dark mode: full theme toggle (light/dark/system) with localStorage - Theme toggle in header bar - Dark mode applied to Layout, Dashboard, Clients, ClientDetail, Login, Modal - Tailwind v4 @custom-variant for class-based dark mode - Pinned/Favorite clients: star clients for quick dashboard access - Pin button on client detail page and dashboard recent clients - Pinned clients grid on dashboard - Uses localStorage (no backend changes needed) - Search bar trigger in header with ⌘K shortcut hint
This commit is contained in:
@@ -71,13 +71,13 @@ export default function ClientsPage() {
|
||||
<div className="max-w-6xl mx-auto space-y-5 animate-fade-in">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900">Clients</h1>
|
||||
<p className="text-slate-500 text-sm mt-1">{clients.length} contacts in your network</p>
|
||||
<h1 className="text-2xl font-bold text-slate-900 dark:text-slate-100">Clients</h1>
|
||||
<p className="text-slate-500 dark:text-slate-400 text-sm mt-1">{clients.length} contacts in your network</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setShowImport(true)}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-white border border-slate-200 text-slate-700 rounded-lg text-sm font-medium hover:bg-slate-50 transition-colors"
|
||||
className="flex items-center gap-2 px-4 py-2 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-300 rounded-lg text-sm font-medium hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors"
|
||||
>
|
||||
<Upload className="w-4 h-4" />
|
||||
Import CSV
|
||||
@@ -101,7 +101,7 @@ export default function ClientsPage() {
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="Search clients..."
|
||||
className="w-full pl-10 pr-4 py-2.5 bg-white border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
className="w-full pl-10 pr-4 py-2.5 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg text-sm text-slate-900 dark:text-slate-100 placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button onClick={() => setSearchQuery('')} className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600">
|
||||
@@ -145,18 +145,18 @@ export default function ClientsPage() {
|
||||
<Link
|
||||
key={client.id}
|
||||
to={`/clients/${client.id}`}
|
||||
className="bg-white border border-slate-200 rounded-xl p-5 hover:shadow-md hover:border-slate-300 transition-all group"
|
||||
className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl p-5 hover:shadow-md dark:hover:shadow-slate-900/50 hover:border-slate-300 dark:hover:border-slate-600 transition-all group"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="w-11 h-11 bg-blue-100 text-blue-700 rounded-full flex items-center justify-center text-sm font-bold flex-shrink-0 group-hover:bg-blue-200 transition-colors">
|
||||
<div className="w-11 h-11 bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 rounded-full flex items-center justify-center text-sm font-bold flex-shrink-0 group-hover:bg-blue-200 dark:group-hover:bg-blue-900 transition-colors">
|
||||
{getInitials(client.firstName, client.lastName)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-semibold text-slate-900 truncate">
|
||||
<h3 className="font-semibold text-slate-900 dark:text-slate-100 truncate">
|
||||
{client.firstName} {client.lastName}
|
||||
</h3>
|
||||
{client.company && (
|
||||
<p className="text-sm text-slate-500 truncate">{client.role ? `${client.role} at ` : ''}{client.company}</p>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-400 truncate">{client.role ? `${client.role} at ` : ''}{client.company}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -164,7 +164,7 @@ export default function ClientsPage() {
|
||||
{client.tags && client.tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5 mt-3">
|
||||
{client.tags.slice(0, 3).map((tag) => (
|
||||
<span key={tag} className="px-2 py-0.5 bg-slate-100 text-slate-600 rounded-full text-xs">
|
||||
<span key={tag} className="px-2 py-0.5 bg-slate-100 dark:bg-slate-700 text-slate-600 dark:text-slate-300 rounded-full text-xs">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
@@ -174,7 +174,7 @@ export default function ClientsPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-3 pt-3 border-t border-slate-100 text-xs text-slate-400">
|
||||
<div className="mt-3 pt-3 border-t border-slate-100 dark:border-slate-700 text-xs text-slate-400">
|
||||
Last contacted: {getRelativeTime(client.lastContacted)}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
Reference in New Issue
Block a user