Compare commits

..

No commits in common. "66c5adf6c2a07ec0d7ecf26cd8a754b167ff68a2" and "8b405e015c88865250b4a1bf7a498994a2fb5e34" have entirely different histories.

7 changed files with 11 additions and 76 deletions

1
.env
View File

@ -1,3 +1,2 @@
VITE_API_BASE_URL=http://localhost:8080/api/v1 VITE_API_BASE_URL=http://localhost:8080/api/v1
VITE_GOOGLE_CLIENT_ID=google_client_id VITE_GOOGLE_CLIENT_ID=google_client_id
VERSION=1.0.0

View File

@ -1,20 +1,7 @@
import { useEffect } from 'react'
import { Toaster } from 'sonner' import { Toaster } from 'sonner'
import { AppRoutes } from './app/AppRoutes' import { AppRoutes } from './app/AppRoutes'
const SESSION_KEY = 'yimaru_session_active'
export default function App() { export default function App() {
useEffect(() => {
if (!sessionStorage.getItem(SESSION_KEY)) {
localStorage.removeItem('access_token')
localStorage.removeItem('refresh_token')
localStorage.removeItem('member_id')
localStorage.removeItem('role')
sessionStorage.setItem(SESSION_KEY, '1')
}
}, [])
return ( return (
<> <>
<AppRoutes /> <AppRoutes />

View File

@ -109,12 +109,6 @@ http.interceptors.response.use(
} }
} }
// Backend is down (network error, timeout, connection refused)
if (!error.response) {
clearAuthAndRedirect();
return Promise.reject(error);
}
return Promise.reject(error); return Promise.reject(error);
} }
); );

2
src/globals.d.ts vendored
View File

@ -1,2 +0,0 @@
declare const __BUILD_HASH__: string
declare const __BUILD_TIME__: string

View File

@ -1,16 +1,11 @@
import { useState, useCallback } from "react" import { useState, useCallback } from "react"
import { Navigate, Outlet } from "react-router-dom" import { Outlet } from "react-router-dom"
import { Sidebar } from "../components/sidebar/Sidebar" import { Sidebar } from "../components/sidebar/Sidebar"
import { Topbar } from "../components/topbar/Topbar" import { Topbar } from "../components/topbar/Topbar"
export function AppLayout() { export function AppLayout() {
const [sidebarOpen, setSidebarOpen] = useState(false) const [sidebarOpen, setSidebarOpen] = useState(false)
const token = localStorage.getItem("access_token")
if (!token) {
return <Navigate to="/login" replace />
}
const handleMenuClick = useCallback(() => { const handleMenuClick = useCallback(() => {
setSidebarOpen(true) setSidebarOpen(true)
}, []) }, [])

View File

@ -71,7 +71,6 @@ export function LoginPage() {
const [googleLoading, setGoogleLoading] = useState(false); const [googleLoading, setGoogleLoading] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [googleReady, setGoogleReady] = useState(false); const [googleReady, setGoogleReady] = useState(false);
const [fieldErrors, setFieldErrors] = useState<{ email?: string; password?: string }>({});
const googleBtnRef = useRef<HTMLDivElement>(null); const googleBtnRef = useRef<HTMLDivElement>(null);
@ -157,16 +156,6 @@ export function LoginPage() {
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
const errors: { email?: string; password?: string } = {};
if (!email.trim()) errors.email = "Please enter your email address";
if (!password) errors.password = "Please enter your password";
if (Object.keys(errors).length > 0) {
setFieldErrors(errors);
return;
}
setFieldErrors({});
setError(null); setError(null);
setLoading(true); setLoading(true);
@ -320,7 +309,7 @@ export function LoginPage() {
</> </>
)} )}
<form onSubmit={handleSubmit} className="space-y-5" autoComplete="on" method="post" noValidate> <form onSubmit={handleSubmit} className="space-y-5" autoComplete="on" method="post">
{/* Email */} {/* Email */}
<div> <div>
<label <label
@ -336,15 +325,10 @@ export function LoginPage() {
placeholder="you@example.com" placeholder="you@example.com"
autoComplete="email" autoComplete="email"
value={email} value={email}
onChange={(e) => { onChange={(e) => setEmail(e.target.value)}
setEmail(e.target.value); required
if (fieldErrors.email) setFieldErrors((prev) => ({ ...prev, email: undefined })); className="h-11 rounded-xl"
}}
className={`h-11 rounded-xl ${fieldErrors.email ? "border-red-400 focus-visible:ring-red-400/40" : ""}`}
/> />
{fieldErrors.email && (
<p className="mt-1.5 text-xs text-red-500">{fieldErrors.email}</p>
)}
</div> </div>
{/* Password */} {/* Password */}
@ -371,11 +355,9 @@ export function LoginPage() {
placeholder="••••••••" placeholder="••••••••"
autoComplete="current-password" autoComplete="current-password"
value={password} value={password}
onChange={(e) => { onChange={(e) => setPassword(e.target.value)}
setPassword(e.target.value); required
if (fieldErrors.password) setFieldErrors((prev) => ({ ...prev, password: undefined })); className="h-11 rounded-xl pr-10"
}}
className={`h-11 rounded-xl pr-10 ${fieldErrors.password ? "border-red-400 focus-visible:ring-red-400/40" : ""}`}
/> />
<button <button
type="button" type="button"
@ -386,9 +368,6 @@ export function LoginPage() {
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />} {showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button> </button>
</div> </div>
{fieldErrors.password && (
<p className="mt-1.5 text-xs text-red-500">{fieldErrors.password}</p>
)}
</div> </div>
<Button <Button
@ -401,15 +380,11 @@ export function LoginPage() {
</form> </form>
{/* Footer */} {/* Footer */}
<div className="mt-10 text-center text-xs text-grayScale-400"> <p className="mt-10 text-center text-xs text-grayScale-400">
<p>© {new Date().getFullYear()} Yimaru Academy · All rights reserved</p> © {new Date().getFullYear()} Yimaru Academy · All rights reserved
<p className="mt-1 font-mono text-[10px] text-grayScale-300">
v{__BUILD_HASH__} · {new Date(__BUILD_TIME__).toLocaleDateString()}
</p> </p>
</div> </div>
</div> </div>
</div> </div>
</div>
); );
} }

View File

@ -1,21 +1,8 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import { execSync } from 'child_process'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
define: {
__BUILD_HASH__: JSON.stringify(
(() => {
try {
return execSync('git rev-parse --short HEAD').toString().trim()
} catch {
return 'unknown'
}
})()
),
__BUILD_TIME__: JSON.stringify(new Date().toISOString()),
},
}) })