| 1 | <?xml version="1.0" encoding="UTF-8"?>
|
|---|
| 2 | <project version="4">
|
|---|
| 3 | <component name="CopilotDiffPersistence">
|
|---|
| 4 | <option name="pendingDiffs">
|
|---|
| 5 | <map>
|
|---|
| 6 | <entry key="$PROJECT_DIR$/frontend/src/App.jsx">
|
|---|
| 7 | <value>
|
|---|
| 8 | <PendingDiffInfo>
|
|---|
| 9 | <option name="filePath" value="$PROJECT_DIR$/frontend/src/App.jsx" />
|
|---|
| 10 | <option name="originalContent" value="import { Navigate, Route, Routes } from "react-router-dom"; import LandingPage from "./pages/LandingPage/LandingPage.jsx"; import Login from "./pages/Login/Login.jsx"; import Register from "./pages/Register/Register.jsx"; import DashboardLayout from "./pages/Dashboard/DashboardLayout.jsx"; import ControlCenter from "./pages/Dashboard/pages/ControlCenter/ControlCenter.jsx"; import Training from "./pages/Dashboard/pages/Training/Training.jsx"; import TrainingTracking from "./pages/Dashboard/pages/Training/TrainingTracking.jsx"; import NewTrainingSession from "./pages/Dashboard/pages/Training/NewTrainingSession.jsx"; import Weight from "./pages/Dashboard/pages/Weight/Weight.jsx"; import WeightTracking from "./pages/Dashboard/pages/Weight/WeightTracking.jsx"; import NewWeightIntake from "./pages/Dashboard/pages/Weight/NewWeightIntake.jsx"; import Finance from "./pages/Dashboard/pages/Finance/Finance.jsx"; import FinanceTracking from "./pages/Dashboard/pages/Finance/FinanceTracking.jsx"; import NewIncome from "./pages/Dashboard/pages/Finance/NewIncome.jsx"; import Investing from "./pages/Dashboard/pages/Investing/Investing.jsx"; import InvestingTracking from "./pages/Dashboard/pages/Investing/InvestingTracking.jsx"; import NewInvestment from "./pages/Dashboard/pages/Investing/NewInvestment.jsx"; import Discipline from "./pages/Dashboard/pages/Discipline/Discipline.jsx"; import DisciplineTracking from "./pages/Dashboard/pages/Discipline/DisciplineTracking.jsx"; import CustomCategoryKanban from "./pages/Dashboard/pages/CustomCategory/CustomCategoryKanban.jsx"; function RequireAuth({ children }) { const token = localStorage.getItem("authToken"); if (!token) return <Navigate to="/login" replace />; return children; } function App() { return ( <div data-theme="forest" className="min-h-screen w-full"> <Routes> <Route path="/" element={<LandingPage />} /> <Route path="/login" element={<Login />} /> <Route path="/register" element={<Register />} /> <Route path="/dashboard" element={ <RequireAuth> <DashboardLayout /> </RequireAuth> } > <Route index element={<Navigate to="control-center" replace />} /> <Route path="control-center" element={<ControlCenter />} /> <Route path="training" element={<Training />} /> <Route path="training/tracking" element={<TrainingTracking />} /> <Route path="training/sessions/new" element={<NewTrainingSession />} /> <Route path="weight" element={<Weight />} /> <Route path="weight/tracking" element={<WeightTracking />} /> <Route path="weight/intakes/new" element={<NewWeightIntake />} /> <Route path="finance" element={<Finance />} /> <Route path="finance/tracking" element={<FinanceTracking />} /> <Route path="finance/incomes/new" element={<NewIncome />} /> <Route path="investing" element={<Investing />} /> <Route path="investing/tracking" element={<InvestingTracking />} /> <Route path="investing/assets/new" element={<NewInvestment />} /> <Route path="discipline" element={<Discipline />} /> <Route path="discipline/tracking" element={<DisciplineTracking />} /> <Route path="custom/:customTrackingId" element={<CustomCategoryKanban />} /> </Route> <Route path="*" element={<Navigate to="/" replace />} /> </Routes> </div> ); } export default App; " />
|
|---|
| 11 | <option name="updatedContent" value="import { Navigate, Route, Routes } from "react-router-dom"; import { clearAuthSession, getAuthToken, isTokenExpired } from "./utils/authSession"; import LandingPage from "./pages/LandingPage/LandingPage.jsx"; import Login from "./pages/Login/Login.jsx"; import Register from "./pages/Register/Register.jsx"; import DashboardLayout from "./pages/Dashboard/DashboardLayout.jsx"; import ControlCenter from "./pages/Dashboard/pages/ControlCenter/ControlCenter.jsx"; import Training from "./pages/Dashboard/pages/Training/Training.jsx"; import TrainingTracking from "./pages/Dashboard/pages/Training/TrainingTracking.jsx"; import NewTrainingSession from "./pages/Dashboard/pages/Training/NewTrainingSession.jsx"; import Weight from "./pages/Dashboard/pages/Weight/Weight.jsx"; import WeightTracking from "./pages/Dashboard/pages/Weight/WeightTracking.jsx"; import NewWeightIntake from "./pages/Dashboard/pages/Weight/NewWeightIntake.jsx"; import Finance from "./pages/Dashboard/pages/Finance/Finance.jsx"; import FinanceTracking from "./pages/Dashboard/pages/Finance/FinanceTracking.jsx"; import NewIncome from "./pages/Dashboard/pages/Finance/NewIncome.jsx"; import Investing from "./pages/Dashboard/pages/Investing/Investing.jsx"; import InvestingTracking from "./pages/Dashboard/pages/Investing/InvestingTracking.jsx"; import NewInvestment from "./pages/Dashboard/pages/Investing/NewInvestment.jsx"; import Discipline from "./pages/Dashboard/pages/Discipline/Discipline.jsx"; import DisciplineTracking from "./pages/Dashboard/pages/Discipline/DisciplineTracking.jsx"; import CustomCategoryKanban from "./pages/Dashboard/pages/CustomCategory/CustomCategoryKanban.jsx"; function RequireAuth({ children }) { const token = getAuthToken(); if (token && isTokenExpired(token)) { clearAuthSession(); return <Navigate to="/login" replace />; } if (!token) return <Navigate to="/login" replace />; return children; } function App() { return ( <div data-theme="forest" className="min-h-screen w-full"> <Routes> <Route path="/" element={<LandingPage />} /> <Route path="/login" element={<Login />} /> <Route path="/register" element={<Register />} /> <Route path="/dashboard" element={ <RequireAuth> <DashboardLayout /> </RequireAuth> } > <Route index element={<Navigate to="control-center" replace />} /> <Route path="control-center" element={<ControlCenter />} /> <Route path="training" element={<Training />} /> <Route path="training/tracking" element={<TrainingTracking />} /> <Route path="training/sessions/new" element={<NewTrainingSession />} /> <Route path="weight" element={<Weight />} /> <Route path="weight/tracking" element={<WeightTracking />} /> <Route path="weight/intakes/new" element={<NewWeightIntake />} /> <Route path="finance" element={<Finance />} /> <Route path="finance/tracking" element={<FinanceTracking />} /> <Route path="finance/incomes/new" element={<NewIncome />} /> <Route path="investing" element={<Investing />} /> <Route path="investing/tracking" element={<InvestingTracking />} /> <Route path="investing/assets/new" element={<NewInvestment />} /> <Route path="discipline" element={<Discipline />} /> <Route path="discipline/tracking" element={<DisciplineTracking />} /> <Route path="custom/:customTrackingId" element={<CustomCategoryKanban />} /> </Route> <Route path="*" element={<Navigate to="/" replace />} /> </Routes> </div> ); } export default App; " />
|
|---|
| 12 | </PendingDiffInfo>
|
|---|
| 13 | </value>
|
|---|
| 14 | </entry>
|
|---|
| 15 | <entry key="$PROJECT_DIR$/frontend/src/api/axios.js">
|
|---|
| 16 | <value>
|
|---|
| 17 | <PendingDiffInfo>
|
|---|
| 18 | <option name="filePath" value="$PROJECT_DIR$/frontend/src/api/axios.js" />
|
|---|
| 19 | <option name="originalContent" value="import axios from "axios"; const api = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8080/api", }); api.interceptors.request.use((config) => { const token = localStorage.getItem("authToken"); if (token) { config.headers = config.headers ?? {}; config.headers.Authorization = `Bearer ${token}`; } return config; }); export default api;" />
|
|---|
| 20 | <option name="updatedContent" value="import axios from "axios"; import { getAuthToken, isTokenExpired, logoutAndRedirect } from "../utils/authSession"; const api = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8080/api", }); api.interceptors.request.use((config) => { const token = getAuthToken(); if (token) { if (isTokenExpired(token)) { logoutAndRedirect("/dashboard"); return Promise.reject(new Error("Authentication session expired")); } config.headers = config.headers ?? {}; config.headers.Authorization = `Bearer ${token}`; } return config; }); api.interceptors.response.use( (response) => response, (error) => { const status = error?.response?.status; if (status === 401 || status === 403) { logoutAndRedirect("/dashboard"); } return Promise.reject(error); }, ); export default api;" />
|
|---|
| 21 | </PendingDiffInfo>
|
|---|
| 22 | </value>
|
|---|
| 23 | </entry>
|
|---|
| 24 | <entry key="$PROJECT_DIR$/frontend/src/pages/Dashboard/DashboardLayout.jsx">
|
|---|
| 25 | <value>
|
|---|
| 26 | <PendingDiffInfo>
|
|---|
| 27 | <option name="filePath" value="$PROJECT_DIR$/frontend/src/pages/Dashboard/DashboardLayout.jsx" />
|
|---|
| 28 | <option name="originalContent" value="import React, { useMemo } from "react"; import { Outlet, useNavigate } from "react-router-dom"; import { SidebarInset, SidebarProvider } from "@relume_io/relume-ui"; import DashboardSidebar from "./components/DashboardSidebar.jsx"; import DashboardTopbar from "./components/DashboardTopbar.jsx"; const DashboardLayout = () => { const navigate = useNavigate(); const user = useMemo(() => { try { const raw = localStorage.getItem("authUser"); return raw ? JSON.parse(raw) : null; } catch { return null; } }, []); const username = user?.username ?? ""; const onLogout = () => { localStorage.removeItem("authToken"); localStorage.removeItem("authUser"); navigate("/", { replace: true }); }; return ( <SidebarProvider> {/* App shell background */} <div className="pointer-events-none fixed inset-0 -z-10"> <div className="absolute inset-0 bg-base-300/10" /> <div className="absolute inset-0 bg-radial-[circle_at_20%_20%] from-green-400/10 via-transparent to-transparent" /> <div className="absolute inset-0 bg-radial-[circle_at_80%_30%] from-blue-400/10 via-transparent to-transparent" /> <div className="absolute inset-0 bg-linear-to-b from-black/10 via-transparent to-black/25" /> </div> <DashboardSidebar /> <SidebarInset className="pt-16 lg:pt-0"> <DashboardTopbar username={username} onLogout={onLogout} /> <div className="h-[calc(100vh-4rem)] overflow-auto"> <div className="mx-auto w-full max-w-7xl px-4 py-6 sm:px-6 sm:py-8 lg:px-8 lg:py-10"> <div className="rounded-3xl border border-white/10 bg-base-100/60 shadow-[0_0_0_1px_rgba(255,255,255,0.04),0_24px_90px_rgba(0,0,0,0.35)] backdrop-blur-xl"> <div className="p-4 sm:p-6 lg:p-8"> <Outlet /> </div> </div> </div> </div> </SidebarInset> </SidebarProvider> ); }; export default DashboardLayout; " />
|
|---|
| 29 | <option name="updatedContent" value="import React, { useMemo } from "react"; import { Outlet, useNavigate } from "react-router-dom"; import { SidebarInset, SidebarProvider } from "@relume_io/relume-ui"; import DashboardSidebar from "./components/DashboardSidebar.jsx"; import DashboardTopbar from "./components/DashboardTopbar.jsx"; import { clearAuthSession } from "../../utils/authSession"; const DashboardLayout = () => { const navigate = useNavigate(); const user = useMemo(() => { try { const raw = localStorage.getItem("authUser"); return raw ? JSON.parse(raw) : null; } catch { return null; } }, []); const username = user?.username ?? ""; const onLogout = () => { clearAuthSession(); navigate("/", { replace: true }); }; return ( <SidebarProvider> {/* App shell background */} <div className="pointer-events-none fixed inset-0 -z-10"> <div className="absolute inset-0 bg-base-300/10" /> <div className="absolute inset-0 bg-radial-[circle_at_20%_20%] from-green-400/10 via-transparent to-transparent" /> <div className="absolute inset-0 bg-radial-[circle_at_80%_30%] from-blue-400/10 via-transparent to-transparent" /> <div className="absolute inset-0 bg-linear-to-b from-black/10 via-transparent to-black/25" /> </div> <DashboardSidebar /> <SidebarInset className="pt-16 lg:pt-0"> <DashboardTopbar username={username} onLogout={onLogout} /> <div className="h-[calc(100vh-4rem)] overflow-auto"> <div className="mx-auto w-full max-w-7xl px-4 py-6 sm:px-6 sm:py-8 lg:px-8 lg:py-10"> <div className="rounded-3xl border border-white/10 bg-base-100/60 shadow-[0_0_0_1px_rgba(255,255,255,0.04),0_24px_90px_rgba(0,0,0,0.35)] backdrop-blur-xl"> <div className="p-4 sm:p-6 lg:p-8"> <Outlet /> </div> </div> </div> </div> </SidebarInset> </SidebarProvider> ); }; export default DashboardLayout; " />
|
|---|
| 30 | </PendingDiffInfo>
|
|---|
| 31 | </value>
|
|---|
| 32 | </entry>
|
|---|
| 33 | <entry key="$PROJECT_DIR$/frontend/src/utils/authSession.js">
|
|---|
| 34 | <value>
|
|---|
| 35 | <PendingDiffInfo>
|
|---|
| 36 | <option name="filePath" value="$PROJECT_DIR$/frontend/src/utils/authSession.js" />
|
|---|
| 37 | <option name="updatedContent" value="export const AUTH_TOKEN_KEY = "authToken"; export const AUTH_USER_KEY = "authUser"; function decodeJwtPayload(token) { try { const parts = token.split("."); if (parts.length < 2) return null; const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/"); const normalized = base64.padEnd(Math.ceil(base64.length / 4) * 4, "="); const json = atob(normalized); return JSON.parse(json); } catch { return null; } } export function isTokenExpired(token) { if (!token) return true; const payload = decodeJwtPayload(token); if (!payload || typeof payload.exp !== "number") return true; return payload.exp * 1000 <= Date.now(); } export function getAuthToken() { return localStorage.getItem(AUTH_TOKEN_KEY); } export function clearAuthSession() { localStorage.removeItem(AUTH_TOKEN_KEY); localStorage.removeItem(AUTH_USER_KEY); } export function logoutAndRedirect(path = "/dashboard") { clearAuthSession(); window.location.assign(path); } " />
|
|---|
| 38 | </PendingDiffInfo>
|
|---|
| 39 | </value>
|
|---|
| 40 | </entry>
|
|---|
| 41 | </map>
|
|---|
| 42 | </option>
|
|---|
| 43 | </component>
|
|---|
| 44 | </project> |
|---|