Navbar.jsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import React, { useState } from 'react';
  2. import { RefreshCw, User, LogOut, CheckCircle2 } from 'lucide-react';
  3. import { useAuth } from '../context/AuthContext';
  4. import { syncAPI } from '../api';
  5. export const Navbar = ({ activeTab, setActiveTab, onOpenAuthModal }) => {
  6. const { user, logout } = useAuth();
  7. const [syncing, setSyncing] = useState(false);
  8. const [syncToast, setSyncToast] = useState(false);
  9. // Format current date matching iOS app format
  10. const currentDateFormatted = (() => {
  11. const d = new Date();
  12. const year = d.getFullYear();
  13. const month = d.getMonth() + 1;
  14. const date = d.getDate();
  15. const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
  16. const weekDay = weekDays[d.getDay()];
  17. return `${year}年${month}月${date}日 ${weekDay}`;
  18. })();
  19. const handleSyncNow = async () => {
  20. if (!user) {
  21. if (onOpenAuthModal) onOpenAuthModal();
  22. return;
  23. }
  24. setSyncing(true);
  25. try {
  26. await syncAPI.triggerManualSync();
  27. setSyncToast(true);
  28. setTimeout(() => setSyncToast(false), 3000);
  29. } catch (e) {
  30. console.warn("Manual sync failed:", e);
  31. } finally {
  32. setSyncing(false);
  33. }
  34. };
  35. return (
  36. <header className="sticky top-0 z-40 w-full bg-[#F8F9FA]/90 backdrop-blur-md border-b border-black/5 px-4 sm:px-8 py-3.5 flex items-center justify-between">
  37. {/* Left Title Section matching iOS TopBar screenshot */}
  38. <div
  39. className="flex items-center space-x-3.5 cursor-pointer select-none"
  40. onClick={() => setActiveTab('dashboard')}
  41. >
  42. <div>
  43. <div className="flex items-center space-x-2">
  44. <h1 className="text-xs font-bold font-mono tracking-widest-tech uppercase text-[#1D1D1F] leading-tight">
  45. 星痕 现场 记录
  46. </h1>
  47. </div>
  48. <p className="text-[13px] text-[#8E8E93] font-sans leading-tight mt-0.5">
  49. {currentDateFormatted}
  50. </p>
  51. </div>
  52. </div>
  53. {/* Center Status Indicator & Sync Actions */}
  54. <div className="flex items-center space-x-3">
  55. {/* iOS Ready Status Badge matching screenshot */}
  56. <div className="flex items-center space-x-1.5 px-2.5 py-1 rounded-md bg-white border border-black/15 text-[10px] font-mono font-semibold text-[#8E8E93] shadow-2xs">
  57. <span className="w-1.5 h-1.5 rounded-full bg-[#34C759]"></span>
  58. <span>就绪</span>
  59. </div>
  60. {syncToast && (
  61. <div className="hidden sm:flex items-center space-x-1.5 px-3 py-1 rounded-md bg-emerald-50 border border-emerald-200 text-emerald-700 text-xs animate-fade-in">
  62. <CheckCircle2 className="w-3.5 h-3.5" />
  63. <span>已同步至云端</span>
  64. </div>
  65. )}
  66. <button
  67. onClick={handleSyncNow}
  68. disabled={syncing}
  69. className="flex items-center space-x-2 px-3 py-1.5 text-xs font-medium text-slate-700 bg-white hover:bg-slate-50 border border-black/10 rounded-lg shadow-2xs transition-all"
  70. >
  71. <RefreshCw className={`w-3.5 h-3.5 text-slate-600 ${syncing ? 'animate-spin' : ''}`} />
  72. <span className="hidden sm:inline">{syncing ? '同步中...' : '手动同步'}</span>
  73. </button>
  74. </div>
  75. {/* Right Controls: User Card / Auth button */}
  76. <div className="flex items-center space-x-2">
  77. {user ? (
  78. <div className="flex items-center space-x-2 bg-white px-2.5 py-1.5 rounded-xl border border-black/10 shadow-2xs">
  79. <img
  80. src={user?.avatarURL || "/spark-logo.svg"}
  81. alt="Avatar"
  82. className="w-7 h-7 rounded-lg object-cover ring-1 ring-black/10"
  83. />
  84. <div className="hidden sm:block text-left">
  85. <div className="text-xs font-semibold text-[#1D1D1F] leading-tight">{user.username}</div>
  86. <div className="text-[10px] text-[#8E8E93] leading-tight font-mono">{user.phoneNumber || user.email || `ID: ${String(user.id || '').substring(0, 6)}`}</div>
  87. </div>
  88. <button
  89. onClick={() => setActiveTab('profile')}
  90. className="p-1 text-slate-500 hover:text-slate-900 transition-colors"
  91. title="个人主页"
  92. >
  93. <User className="w-4 h-4" />
  94. </button>
  95. <button
  96. onClick={() => logout()}
  97. className="p-1 text-slate-500 hover:text-rose-600 transition-colors"
  98. title="退出登录"
  99. >
  100. <LogOut className="w-4 h-4" />
  101. </button>
  102. </div>
  103. ) : (
  104. <button
  105. onClick={onOpenAuthModal}
  106. className="flex items-center space-x-2 bg-slate-900 text-white dark:bg-white dark:text-slate-950 px-3.5 py-1.5 rounded-xl text-xs font-semibold shadow-2xs hover:opacity-90 transition-all"
  107. >
  108. <User className="w-3.5 h-3.5" />
  109. <span>登录 / 注册</span>
  110. </button>
  111. )}
  112. </div>
  113. </header>
  114. );
  115. };