Compare commits
No commits in common. "66c5adf6c2a07ec0d7ecf26cd8a754b167ff68a2" and "8b405e015c88865250b4a1bf7a498994a2fb5e34" have entirely different histories.
66c5adf6c2
...
8b405e015c
1
.env
1
.env
|
|
@ -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
|
|
||||||
|
|
|
||||||
13
src/App.tsx
13
src/App.tsx
|
|
@ -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 />
|
||||||
|
|
|
||||||
|
|
@ -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
2
src/globals.d.ts
vendored
|
|
@ -1,2 +0,0 @@
|
||||||
declare const __BUILD_HASH__: string
|
|
||||||
declare const __BUILD_TIME__: string
|
|
||||||
|
|
@ -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)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user