initial commit

This commit is contained in:
2026-03-12 06:37:16 +08:00
commit c31ebd35e7
41 changed files with 6272 additions and 0 deletions

86
src/components/Navbar.tsx Normal file
View File

@@ -0,0 +1,86 @@
import { useState } from "react"
import { useNavigate } from "react-router-dom"
import { useTranslation } from "../hooks/useTranslation"
import { Button } from "./ui/button"
import { SettingsMenu } from "./SettingsMenu"
import { LogOut } from "lucide-react"
import { logoutUser } from "../api/users"
import { toast } from "sonner"
export function Navbar() {
const { t } = useTranslation()
const navigate = useNavigate()
const [showLogoutModal, setShowLogoutModal] = useState(false)
const handleLogout = async () => {
try {
const refreshToken = localStorage.getItem("refreshToken")
if (refreshToken) {
await logoutUser(refreshToken)
}
} catch (error) {
console.error("Logout API failed:", error)
} finally {
localStorage.removeItem("accessToken")
localStorage.removeItem("refreshToken")
setShowLogoutModal(false)
toast.success(t.logoutToast || "Successfully logged out!")
navigate("/login")
}
}
return (
<>
<header className="border-b border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 px-6 py-4 flex items-center justify-between transition-colors">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded bg-blue-600 flex items-center justify-center text-white font-bold">
Q
</div>
<span className="font-bold text-xl tracking-tight text-slate-900 dark:text-slate-50">Qlockify</span>
</div>
<div className="flex items-center gap-4">
<SettingsMenu />
<Button
variant="ghost"
size="icon"
onClick={() => setShowLogoutModal(true)}
className="text-red-500 dark:text-red-500 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/50"
title={t.logout || "Logout"}
>
<LogOut className="h-5 w-5" />
</Button>
</div>
</header>
{showLogoutModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 px-4" onClick={() => setShowLogoutModal(false)}>
<div className="w-full max-w-sm rounded-lg bg-white p-6 shadow-lg dark:bg-slate-900 border dark:border-slate-800" onClick={(e) => e.stopPropagation()}>
<h2 className="mb-2 text-lg font-bold text-slate-900 dark:text-white">
{t.confirmLogoutTitle || "Confirm Logout"}
</h2>
<p className="mb-6 text-slate-600 dark:text-slate-400">
{t.confirmLogoutMessage || "Are you sure you want to log out of your account?"}
</p>
<div className="flex justify-end gap-3">
<Button
variant="outline"
onClick={() => setShowLogoutModal(false)}
className="dark:text-white"
>
{t.cancel || "Cancel"}
</Button>
<Button
variant="destructive"
onClick={handleLogout}
className="bg-red-500 text-white hover:bg-red-600 dark:bg-red-600 dark:hover:bg-red-700"
>
{t.logout || "Logout"}
</Button>
</div>
</div>
</div>
)}
</>
)
}