diff --git a/react-ui/scripts/modify-api.sh b/react-ui/scripts/modify-api.sh deleted file mode 100755 index d5b9fdf5b2dddc2d40e9526954e021ebd992408d..0000000000000000000000000000000000000000 --- a/react-ui/scripts/modify-api.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env sh - -# Verzeichnis mit den generierten Dateien -GENERATED_DIR="../src/utils/api/" - -# Template-Datei -TEMPLATE_FILE="./template.js" - -# Stelle sicher, dass das Template existiert -if [[ ! -f "$TEMPLATE_FILE" ]]; then - echo "Template file not found: $TEMPLATE_FILE" - exit 1 -fi - -# Füge den Inhalt des Templates in jede Query-Funktion ein -for file in "$GENERATED_DIR"/*.ts; do - if grep -q 'builder.query' "$file"; then - echo "Processing $file..." - - # Überprüfen, ob onCacheEntryAdded bereits vorhanden ist - if ! grep -q 'onCacheEntryAdded' "$file"; then - # Füge das Template in die Query-Funktion ein - sed -i.bak '/builder.query.*{/r '"$TEMPLATE_FILE" "$file" - - echo "Extended $file with onCacheEntryAdded." - else - echo "$file already contains onCacheEntryAdded, skipping." - fi - fi -done - -echo "All applicable files processed." \ No newline at end of file diff --git a/react-ui/scripts/template.js b/react-ui/scripts/template.js deleted file mode 100644 index 2fa7b2993e16f40f82d744c7ec36f22abdf3961d..0000000000000000000000000000000000000000 --- a/react-ui/scripts/template.js +++ /dev/null @@ -1,18 +0,0 @@ -onCacheEntryAdded: async (arg, { updateCachedData, cacheDataLoaded, cacheEntryRemoved }) => { - try { - // Warte, bis der Cache geladen ist - await cacheDataLoaded; - - // Beobachte kontinuierlich Änderungen am Cache - const unsubscribe = updateCachedData((draft) => { - console.log('Updated data:', draft); - // Hier kannst du auf die Daten zugreifen und z.B. weitere Aktionen auslösen - }); - - // Aufräumen, wenn der Cache entfernt wird - await cacheEntryRemoved; - unsubscribe(); - } catch (err) { - console.error('Error in onCacheEntryAdded:', err); - } - }, \ No newline at end of file diff --git a/react-ui/src/components/view/landingpage/landingpage.scss b/react-ui/src/components/view/device/device.scss similarity index 100% rename from react-ui/src/components/view/landingpage/landingpage.scss rename to react-ui/src/components/view/device/device.scss diff --git a/react-ui/src/components/view/device/device.view.tsx b/react-ui/src/components/view/device/device.view.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8a664c4d83ec9044710745022c625a5c8ca677d6 --- /dev/null +++ b/react-ui/src/components/view/device/device.view.tsx @@ -0,0 +1,50 @@ +import { useAppSelector } from '@hooks'; +import { Col, Container, Row, Table } from 'react-bootstrap'; +import './device.scss'; +import { useDeviceViewModel } from '@viewmodel/device.viewmodel'; + +function DeviceView() { + const { devices } = useAppSelector(state => state.device); + useDeviceViewModel(); + + + const getDeviceTable = () => { + return devices.map((device, index) => ( + <tr key={index}> + <td>{device.name}</td> + <td>{device.id}</td> + <td>{device.pid}</td> + </tr> + )) + } + + return ( + <div className='m-4 pt-4'> + <Container className="bg-white rounded c-box"> + <Row > + <Col><h3>Device list</h3></Col> + </Row> + + <Row className='mt-2'> + <Col> + <Table striped bordered hover className='table-primary'> + <thead> + <tr> + <th>Name</th> + <th>UUID</th> + <th>User</th> + <th>Last updated</th> + </tr> + </thead> + <tbody> + {getDeviceTable()} + </tbody> + </Table> + </Col> + </Row> + </Container> + </div> + ) +} + +export default DeviceView diff --git a/react-ui/src/components/view/landingpage/landingpage.tsx b/react-ui/src/components/view/landingpage/landingpage.tsx deleted file mode 100644 index 62c35863911b4ec2fbf70e03612e2b66cfcaec4d..0000000000000000000000000000000000000000 --- a/react-ui/src/components/view/landingpage/landingpage.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import './landingpage.scss' - -function Landingpage() { - return ( - <div className="App"> - - </div> - ) -} - -export default Landingpage diff --git a/react-ui/src/components/view/login/login.view.tsx b/react-ui/src/components/view/login/login.view.tsx index ba8da826ec61d73e9d28a85253784f38624580e6..405a42f84fed458e4f93863dda334f9d0c8d04f1 100644 --- a/react-ui/src/components/view/login/login.view.tsx +++ b/react-ui/src/components/view/login/login.view.tsx @@ -6,7 +6,7 @@ import logo from '@assets/logo.svg' import useLoginViewModel from '@viewmodel/login.viewmodel' import React, { useRef } from 'react' -const LoginPage = ({ children }) => { +const LoginView = ({ children }) => { const { t } = useTranslation('common') const { login, handleErrorMessageRendering, displayFormFieldChecks, loginLoading } = useLoginViewModel(); @@ -82,4 +82,4 @@ const LoginPage = ({ children }) => { ) } -export default LoginPage +export default LoginView diff --git a/react-ui/src/components/view/splash/splash.view.ts b/react-ui/src/components/view/splash/splash.view.ts deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/react-ui/src/components/view_model/device.viewmodel.ts b/react-ui/src/components/view_model/device.viewmodel.ts new file mode 100644 index 0000000000000000000000000000000000000000..7ceb788091a2fe5f14167e00536d9eadd87d69fe --- /dev/null +++ b/react-ui/src/components/view_model/device.viewmodel.ts @@ -0,0 +1,48 @@ +import { api, NetworkelementFlattenedManagedNetworkElement, NetworkelementGetAllFlattenedResponse, NetworkElementServiceGetAllFlattenedApiArg } from "@api/api"; +import { useAppSelector } from "@hooks"; +import { setDevices } from "@reducer/device.reducer"; +import { QueryActionCreatorResult } from "@reduxjs/toolkit/query"; +import { useEffect } from "react"; +import { useDispatch } from "react-redux"; +import { AppDispatch } from "src/stores"; + +const FETCH_DEVICES_INTERVAL = 15000; // in ms + + +export const useDeviceViewModel = () => { + const { user } = useAppSelector(state => state.user); + const [triggerFetchDevices] = api.endpoints.networkElementServiceGetAllFlattened.useLazyQuerySubscription({ + pollingInterval: FETCH_DEVICES_INTERVAL, + skipPollingIfUnfocused: true + }); + const dispatch = useDispatch<AppDispatch>(); + + + // TODO figure out how we get the proper response type here + let fetchDevicesSubscription: QueryActionCreatorResult<any> | undefined; + + + useEffect(() => { + fetchDevices(); + + return () => { + fetchDevicesSubscription?.unsubscribe(); + } + }, []) + + const fetchDevices = () => { + const payload: NetworkElementServiceGetAllFlattenedApiArg = { + pid: Object.keys(user?.roles)[0], + timestamp: new Date().getTime().toString(), + } + + fetchDevicesSubscription = triggerFetchDevices(payload); + fetchDevicesSubscription.then((response) => { + const { mne } = response.data as NetworkelementGetAllFlattenedResponse; + dispatch(setDevices(mne)); + }); + } + + return { + } +} \ No newline at end of file diff --git a/react-ui/src/components/view_model/login.viewmodel.ts b/react-ui/src/components/view_model/login.viewmodel.ts index 93f4c33f2bce4c03f7c324873c1b365d0b5b8cce..370834a0c38ecded0531cc468a3ab790114578cf 100644 --- a/react-ui/src/components/view_model/login.viewmodel.ts +++ b/react-ui/src/components/view_model/login.viewmodel.ts @@ -9,7 +9,6 @@ export interface PageLoginState { export default function useLoginViewModel() { const {login, loginProperties} = useAuth(); const {isLoading: loginLoading, error: loginError, reset: resetLogin} = loginProperties!; - const [localFormState, updateLocalFormState] = useState({ submitted: false, diff --git a/react-ui/src/index.tsx b/react-ui/src/index.tsx index 50f9c350991cbb122df01bdc309ef449fa32d7b9..3eb8a604f746fc0f6f677e7b16f0debe39942559 100644 --- a/react-ui/src/index.tsx +++ b/react-ui/src/index.tsx @@ -1,48 +1,27 @@ import React from 'react' import ReactDOM, { Container } from 'react-dom/client' import { - Route, - RouterProvider, - createBrowserRouter, - createRoutesFromElements + RouterProvider } from 'react-router-dom' -import Landingpage from './components/view/landingpage/landingpage' import './index.scss' -import { BasicLayout } from '@layout/auth.layout' -import { LoginLayout } from '@layout/login.layout' -import { ProtectedLayout } from '@layout/protected.layout/protected.layout' import i18next from 'i18next' import { I18nextProvider } from 'react-i18next' import { Provider } from 'react-redux' import { ToastContainer } from 'react-toastify' import { PersistGate } from 'redux-persist/integration/react' import './i18n/config' +import { router } from './routes' import { persistor, store } from './stores' import './utils/icons/icons' - -const root = ReactDOM.createRoot(document.getElementById('root') as Container) - -// create a proper routing -const router = createBrowserRouter( - createRoutesFromElements( - <Route element={<BasicLayout />}> - <Route path="/login" element={<LoginLayout />} /> - <Route element={<ProtectedLayout />}> - <Route path="/" element={<Landingpage />} /> - </Route> - </Route> - ) -) - const installToastify = () => { return ( <ToastContainer /> ) } -root.render( +ReactDOM.createRoot(document.getElementById("root")).render( <React.StrictMode> <Provider store={store}> <PersistGate loading={null} persistor={persistor}> @@ -53,4 +32,4 @@ root.render( </PersistGate> </Provider> </React.StrictMode> -) +); \ No newline at end of file diff --git a/react-ui/src/routes.tsx b/react-ui/src/routes.tsx new file mode 100644 index 0000000000000000000000000000000000000000..28a06cbfbfd99232aece84be5837b96df9c417da --- /dev/null +++ b/react-ui/src/routes.tsx @@ -0,0 +1,16 @@ +import { BasicLayout } from "@layout/basic.layout" +import { LoginLayout } from "@layout/login.layout" +import { ProtectedLayout } from "@layout/protected.layout/protected.layout" +import DeviceView from "@view/device/device.view" +import { createBrowserRouter, createRoutesFromElements, Route } from "react-router-dom" + +export const router = createBrowserRouter( + createRoutesFromElements( + <Route element={<BasicLayout />}> + <Route path="/login" element={<LoginLayout />} /> + <Route element={<ProtectedLayout />}> + <Route path="/" element={<DeviceView />} /> + </Route> + </Route> + ) +) \ No newline at end of file diff --git a/react-ui/src/stores/api.store.ts b/react-ui/src/stores/api.store.ts index bd36b822737ffddfaf17b59718198042e968b3e1..fd894e97b98b689bb43f973fd1bfea6c56f598be 100644 --- a/react-ui/src/stores/api.store.ts +++ b/react-ui/src/stores/api.store.ts @@ -1,11 +1,11 @@ -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' -import { RootState } from '.' +import { getCookieValue } from '@helper/coookie'; +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; // initialize an empty api service that we'll inject endpoints into later as needed export const emptySplitApi = createApi({ baseQuery: fetchBaseQuery({ - baseUrl: '/api', prepareHeaders: (headers, { getState }) => { - const token = (getState() as RootState).user.token + baseUrl: '/api', prepareHeaders: (headers) => { + const token = getCookieValue('token'); if (token) { headers.set('authorize', `${token}`) diff --git a/react-ui/src/stores/persist.store.ts b/react-ui/src/stores/persist.store.ts index 806b6e2e12710c9b7eb9ebe6aadb72375ca61a83..feb5241a879f80dfeafa7de5b5e7276c5813b07e 100644 --- a/react-ui/src/stores/persist.store.ts +++ b/react-ui/src/stores/persist.store.ts @@ -4,6 +4,7 @@ import { persistReducer } from "redux-persist"; import storage from "redux-persist/es/storage"; import { emptySplitApi } from "./api.store"; import scheduleReducer from "@reducer/schedule.reducer"; +import deviceReducer from "@reducer/device.reducer"; /** local storage config */ @@ -16,6 +17,7 @@ const rootPersistConfig = { const rootReducer = combineReducers({ user: userReducer, + device: deviceReducer, scheduler: scheduleReducer, [emptySplitApi.reducerPath]: emptySplitApi.reducer, }) diff --git a/react-ui/src/stores/reducer/device.reducer.ts b/react-ui/src/stores/reducer/device.reducer.ts new file mode 100644 index 0000000000000000000000000000000000000000..ec47ff1f852ecc1c8acfa3ea2236c6a68559f5e5 --- /dev/null +++ b/react-ui/src/stores/reducer/device.reducer.ts @@ -0,0 +1,74 @@ +import { api, NetworkelementFlattenedManagedNetworkElement, NetworkElementServiceGetAllFlattenedApiArg } from '@api/api'; +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { QueryActionCreatorResult } from '@reduxjs/toolkit/query'; +import { RootState } from '..'; +import { startListening } from '../middleware/listener.middleware'; +import { setUser } from './user.reducer'; + +type Device = NetworkelementFlattenedManagedNetworkElement; + + +export interface DeviceSliceState { + devices: Device[], +} + +const initialState: DeviceSliceState = { + devices: [], +} + + + + +const deviceSlice = createSlice({ + name: 'device', + initialState, + reducers: { + setDevices: (state, action: PayloadAction<Device[]>) => { state.devices = action.payload }, + }, +}) + +export const { setDevices } = deviceSlice.actions + +export default deviceSlice.reducer +export const deviceReducerPath = deviceSlice.reducerPath; + + + +let fetchSubscription: QueryActionCreatorResult<any>[] = []; +export const abortFetching = () => { + fetchSubscription.forEach((subscription) => { + subscription.unsubscribe(); + }); + fetchSubscription = []; +} + +// continously fetch devices +const FETCH_DEVICES_INTERVAL = 5000; // in ms +startListening({ + actionCreator: setUser, + effect: async (_, listenerApi) => { + const { user } = listenerApi.getState() as RootState; + + const payload: NetworkElementServiceGetAllFlattenedApiArg = { + pid: Object.keys(user?.user.roles)[0], + timestamp: new Date().getTime().toString(), + } + + const subscription = listenerApi.dispatch(api.endpoints.networkElementServiceGetAllFlattened.initiate(payload, { + subscriptionOptions: { + pollingInterval: FETCH_DEVICES_INTERVAL, + skipPollingIfUnfocused: true, + } + })); + + fetchSubscription = [...fetchSubscription, subscription]; + }, +}) + +// save fetched devices +startListening({ + predicate: (action) => api.endpoints.networkElementServiceGetAllFlattened.matchFulfilled(action), + effect: async (action, listenerApi) => { + listenerApi.dispatch(setDevices(action.payload.mne)); + }, +}) \ No newline at end of file diff --git a/react-ui/src/stores/reducer/schedule.reducer.ts b/react-ui/src/stores/reducer/schedule.reducer.ts index 345b26bf27aa25e6d58ba71fe81f0f6db82bd02d..2e2bbe36ba76278a7c16533a1839081ca370a6a1 100644 --- a/react-ui/src/stores/reducer/schedule.reducer.ts +++ b/react-ui/src/stores/reducer/schedule.reducer.ts @@ -3,18 +3,21 @@ import { startListening } from '../middleware/listener.middleware'; export enum ScheduleState { INIT, RUNNING, STOPPED } -type Task<T> = (options: T) => void; - -interface Schedule<T> { - f: Task<T>, +export type Task = { + job: (options: object) => void, interval: number, + type: string +}; + +interface Schedule { + task: Task, id: number, state: ScheduleState, intervalId: NodeJS.Timeout | undefined } export interface ScheduleReducerState { - schedules: Schedule<any>[] + schedules: Schedule[] } const initialState: ScheduleReducerState = { @@ -25,10 +28,9 @@ const ScheduleSlice = createSlice({ name: 'schedule', initialState, reducers: { - addSchedule: (state, action: PayloadAction<{ task: Task<any>, interval: number }>) => { + registerTask: (state, action: PayloadAction<Task>) => { const newSchedule = { - f: action.payload.task, - interval: action.payload.interval, + task: action.payload.task, id: state.schedules.length, state: ScheduleState.INIT, intervalId: undefined @@ -36,9 +38,9 @@ const ScheduleSlice = createSlice({ state.schedules = [...state.schedules, newSchedule] }, - startSchedule: (state, action: PayloadAction<Schedule<any>>) => { + startSchedule: (state, action: PayloadAction<Schedule>) => { const schedule = action.payload; - schedule.intervalId = setInterval(schedule.f, schedule.interval); + schedule.intervalId = setInterval(schedule.task.job, schedule.task.interval); schedule.state = ScheduleState.RUNNING; state.schedules[schedule.id] = schedule; @@ -46,25 +48,25 @@ const ScheduleSlice = createSlice({ }, }) -export const { addSchedule } = ScheduleSlice.actions +export const { registerTask } = ScheduleSlice.actions export const { startSchedule } = ScheduleSlice.actions export default ScheduleSlice.reducer -startListening({ - actionCreator: addSchedule, +// startListening({ +// actionCreator: addSchedule, - effect: (action, listenerApi) => { - const newState = listenerApi.getState() as ScheduleReducerState; - const originalState = listenerApi.getOriginalState() as ScheduleReducerState; +// effect: (action, listenerApi) => { +// const newState = listenerApi.getState() as ScheduleReducerState; +// const originalState = listenerApi.getOriginalState() as ScheduleReducerState; - // get the added schedule - const schedule = newState.schedules.filter(s => !originalState.schedules.includes(s)).at(0); - if (!schedule) { - throw new Error("Added schedule not found in store"); - } +// // get the added schedule +// const schedule = newState.schedules.filter(s => !originalState.schedules.includes(s)).at(0); +// if (!schedule) { +// throw new Error("Added schedule not found in store"); +// } - listenerApi.dispatch(startSchedule(schedule)) - }, -}) \ No newline at end of file +// listenerApi.dispatch(startSchedule(schedule)) +// }, +// }) \ No newline at end of file diff --git a/react-ui/src/stores/reducer/user.reducer.ts b/react-ui/src/stores/reducer/user.reducer.ts index b1d5f99cde77f55fda73e69b5c37ef25f2262f94..797626db0dcbea6c15a29c4060240dc7bf34fd26 100644 --- a/react-ui/src/stores/reducer/user.reducer.ts +++ b/react-ui/src/stores/reducer/user.reducer.ts @@ -1,10 +1,10 @@ import { api, RbacUser, UserServiceGetUsersApiArg } from '@api/api'; -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { startListening } from '../middleware/listener.middleware'; +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; import { RootState } from '..'; +import { startListening } from '../middleware/listener.middleware'; +import { getCookieValue, setCookieValue } from '@helper/coookie'; export interface UserSliceState { - token: string, // defined by the frontend user input. This value is getting compared with the backend response username: string, user: RbacUser | null, @@ -12,7 +12,6 @@ export interface UserSliceState { const initialState: UserSliceState = { - token: '', username: '', user: null, } @@ -22,8 +21,10 @@ const userSlice = createSlice({ initialState, reducers: { setToken: (state, action: PayloadAction<{ token: string, username: string }>) => { - state.token = action.payload.token; - state.username = action.payload.username + const token = action.payload?.token || ''; + setCookieValue('token', token); + + state.username = action.payload?.username || '' }, setUser: (state, action: PayloadAction<RbacUser>) => { state.user = action.payload }, }, @@ -36,33 +37,27 @@ export default userSlice.reducer export const userReducerPath = userSlice.reducerPath; +export const fetchUser = createAsyncThunk( + 'user/fetchUser', + (_, thunkAPI) => { + const payload: UserServiceGetUsersApiArg = {}; -startListening({ - predicate: (action, { user }: any) => { - return setToken.match(action) && !!user.token; - }, - - effect: async (_, listenerApi) => { - const payload: UserServiceGetUsersApiArg = {}; + thunkAPI.dispatch(api.endpoints.userServiceGetUsers.initiate(payload)).then((response) => { + if (response.error || !response.data?.user?.length) { + // TODO proper error handling + throw new Error('Fetching the pnd list after successful login failed'); + } - listenerApi.dispatch(api.endpoints.userServiceGetUsers.initiate(payload)).then((response) => { - if (response.error || !response.data?.user?.length) { - // TODO proper error handling - throw new Error('Fetching the pnd list after successful login failed'); - } + const { user } = thunkAPI.getState() as RootState; - const {user} = listenerApi.getState() as RootState; + // TODO ask if this is the correct approach + const matchedUser = response.data.user.find((_user) => _user.name === user.username); - // TODO ask if this is the correct approach - const matchedUser = response.data.user.find((_user) => _user.name === user.username); + if (!matchedUser) { + // TODO proper error handling + throw new Error('No user found with the provided username'); + } - if (!matchedUser) { - // TODO proper error handling - throw new Error('No user found with the provided username'); - } - - - listenerApi.dispatch(setUser(matchedUser)); - }); - }, -}) \ No newline at end of file + thunkAPI.dispatch(setUser(matchedUser)); + }); +}); \ No newline at end of file diff --git a/react-ui/src/utils/helper/coookie.ts b/react-ui/src/utils/helper/coookie.ts new file mode 100644 index 0000000000000000000000000000000000000000..75cba2dc8be12a5408cffd739d0f2d4a005e5d67 --- /dev/null +++ b/react-ui/src/utils/helper/coookie.ts @@ -0,0 +1,14 @@ +export const getCookieValue = (name: string): string => { + const regex = new RegExp(`(^| )${name}=([^;]+)`) + const match = document.cookie.match(regex) + + if (match) { + return match[2]; + } + + return ''; +} + +export const setCookieValue = (key: string, value: string): void => { + document.cookie = `${key}=${value}; Secure; SameSite=Lax`; +} \ No newline at end of file diff --git a/react-ui/src/utils/layouts/auth.layout.tsx b/react-ui/src/utils/layouts/basic.layout.tsx similarity index 52% rename from react-ui/src/utils/layouts/auth.layout.tsx rename to react-ui/src/utils/layouts/basic.layout.tsx index a38da03a943a5b967c246e9c97004f31269b0af6..7b7f2b11c1780101c70f71317a3e5ed8204cf5b1 100644 --- a/react-ui/src/utils/layouts/auth.layout.tsx +++ b/react-ui/src/utils/layouts/basic.layout.tsx @@ -1,19 +1,14 @@ import { AuthProvider } from "@provider/auth.provider"; -import { useEffect } from "react"; import { useOutlet } from "react-router-dom"; -import { ScheduleProvider } from "../scheduler"; export const BasicLayout = () => { const outlet = useOutlet(); - const { initSchedules } = ScheduleProvider(); - useEffect(() => { - initSchedules(); - }, []) + return ( <AuthProvider> - {outlet} + {outlet} </AuthProvider> ) } \ No newline at end of file diff --git a/react-ui/src/utils/layouts/login.layout.tsx b/react-ui/src/utils/layouts/login.layout.tsx index 08a875f7577e251b56fbfaf6fbc834c4dcd104cb..43e40aa302e96a61695c95cabbadb6b6c5a60b76 100644 --- a/react-ui/src/utils/layouts/login.layout.tsx +++ b/react-ui/src/utils/layouts/login.layout.tsx @@ -1,5 +1,5 @@ import { useAuth } from "@provider/auth.provider"; -import LoginPage from "@view/login/login.view"; +import LoginView from "@view/login/login.view"; import { useEffect } from "react"; import { useNavigate, useOutlet } from "react-router-dom"; @@ -18,6 +18,6 @@ export const LoginLayout = ({ children }) => { }, []); return ( - <LoginPage>{outlet}</LoginPage> + <LoginView>{outlet}</LoginView> ) } \ No newline at end of file diff --git a/react-ui/src/utils/layouts/protected.layout/protected.layout.scss b/react-ui/src/utils/layouts/protected.layout/protected.layout.scss index eb713fa7a0d3e6e23a0a04dfcae1cf4d91e76f27..47561615d69056a83140f4c6aae1be06abb5237e 100644 --- a/react-ui/src/utils/layouts/protected.layout/protected.layout.scss +++ b/react-ui/src/utils/layouts/protected.layout/protected.layout.scss @@ -1,5 +1,7 @@ @import "/src/style/colors.scss"; +$sidebar-width: 4.5em; + .head-links { text-decoration: none; color: map-get($theme-colors, dark); @@ -18,6 +20,10 @@ } .sidebar { - width: 4.5em; + width: $sidebar-width; height: 100vh; } + +.main-content { + margin-left: $sidebar-width; +} \ No newline at end of file diff --git a/react-ui/src/utils/layouts/protected.layout/protected.layout.tsx b/react-ui/src/utils/layouts/protected.layout/protected.layout.tsx index 3993a03833e711f65ac9df89a5c629976c9079ac..834b2ac5f52ee4a6d4d966560facf6e4ec624ff1 100644 --- a/react-ui/src/utils/layouts/protected.layout/protected.layout.tsx +++ b/react-ui/src/utils/layouts/protected.layout/protected.layout.tsx @@ -7,7 +7,8 @@ import { Dropdown } from "react-bootstrap"; import { useTranslation } from "react-i18next"; import { Link, Outlet, useNavigate } from "react-router-dom"; import "./protected.layout.scss"; -import { useAppSelector } from '@hooks'; +import { useAppDispatch, useAppSelector } from '@hooks'; +import { fetchUser } from '@reducer/user.reducer'; export const ProtectedLayout = () => { @@ -15,11 +16,15 @@ export const ProtectedLayout = () => { const { user } = useAppSelector(state => state.user); const navigate = useNavigate(); const { t } = useTranslation('common') + const dispatch = useAppDispatch(); useEffect(() => { if (!isAuthenticated()) { navigate('/login') + return; } + + dispatch(fetchUser()); }, []); /** @@ -27,9 +32,6 @@ export const ProtectedLayout = () => { */ const handleActiveLink = (targetPath: string): string => { const href = window.location.href; - console.log(href); - - return href.includes(targetPath) ? ' active' : ''; } @@ -101,7 +103,9 @@ export const ProtectedLayout = () => { <div> {HorizontalNavbar()} {VerticalSidebar()} - <Outlet /> + <div className='main-content'> + <Outlet /> + </div> </div> ) }; \ No newline at end of file diff --git a/react-ui/src/utils/provider/auth.provider.tsx b/react-ui/src/utils/provider/auth.provider.tsx index d583d411611f02818a1354ea634dcb2d98b3cc30..638d9b4088169519351b5ced03cac627860969fe 100644 --- a/react-ui/src/utils/provider/auth.provider.tsx +++ b/react-ui/src/utils/provider/auth.provider.tsx @@ -1,5 +1,7 @@ import { AuthServiceLoginApiArg, AuthServiceLoginApiResponse, useAuthServiceLoginMutation } from "@api/api"; +import { getCookieValue } from "@helper/coookie"; import { useAppDispatch, useAppSelector } from "@hooks"; +import { abortFetching } from "@reducer/device.reducer"; import { setToken } from "@reducer/user.reducer"; import { jwtDecode } from "jwt-decode"; import { createContext, useContext, useEffect, useMemo } from "react"; @@ -30,17 +32,17 @@ const AuthContext = createContext<AuthProviderType>({ export const AuthProvider = ({ children }) => { const dispatch = useAppDispatch();; const navigate = useNavigate(); - const { token } = useAppSelector(state => state.user); + const { username } = useAppSelector(state => state.user); useEffect(() => { - console.log('auth provider init'); - - if (isAuthenticated()) { + const token = getCookieValue('token'); + + if (token) { navigate('/') } else { navigate('/login') } - }, []); + }, [username]); const [ sendLogin, @@ -80,6 +82,7 @@ export const AuthProvider = ({ children }) => { } const isAuthenticated = () => { + const token = getCookieValue('token'); if (!token) { return false; } @@ -96,7 +99,8 @@ export const AuthProvider = ({ children }) => { } const logout = () => { - dispatch(setToken("")); + abortFetching(); + dispatch(setToken(null)); // TODO: purge other information } diff --git a/react-ui/src/utils/provider/fetch.provider.tsx b/react-ui/src/utils/provider/fetch.provider.tsx deleted file mode 100644 index 9db4e121c79b1dc947246c1605245aba207f1731..0000000000000000000000000000000000000000 --- a/react-ui/src/utils/provider/fetch.provider.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { useAppSelector } from "@hooks"; -import { useAuth } from "./auth.provider" - - -// seconds before token expire to trigger a refetch -const REFETCH_TOKEN_TIME = 43200; - -const FetchProvider = () => { - const { isAuthenticated: isAuthed } = useAuth(); - const { token } = useAppSelector((root) => root.user); - - - const checkFetch = (): boolean => { - const { exp } = jwtDecode(token); - const currentTime = new Date().getTime() / 1000; - - return exp - } - - return -} \ No newline at end of file diff --git a/react-ui/src/utils/scheduler.tsx b/react-ui/src/utils/scheduler.tsx deleted file mode 100644 index 51bf94de3871b3b6d5abd1a0a12ef42a533ea485..0000000000000000000000000000000000000000 --- a/react-ui/src/utils/scheduler.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { useAppSelector } from "@hooks"; -import { startSchedule } from "@reducer/schedule.reducer"; -import { useDispatch } from "react-redux"; -import { AppDispatch } from "src/stores"; - -export const ScheduleProvider = () => { - const dispatch = useDispatch<AppDispatch>(); - const { schedules } = useAppSelector(state => state.scheduler); - - - const initSchedules = () => { - schedules.forEach(schedule => { - dispatch(startSchedule(schedule)); - }) - } - - return { - initSchedules - } -} diff --git a/react-ui/tsconfig.json b/react-ui/tsconfig.json index b8c8c062584c4b502c414e4036d18e579d53e35f..cc5d30818ecf42b1e33916305f847fe94ea62eb4 100644 --- a/react-ui/tsconfig.json +++ b/react-ui/tsconfig.json @@ -15,7 +15,7 @@ "jsx": "react-jsx", /* Linting */ - "strict": true, + "strict": false, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, @@ -30,6 +30,8 @@ "@provider/*": ["src/utils/provider/*"], "@layout/*": ["src/utils/layouts/*"], "@hooks": ["src/hooks"], + "@task/*": ["src/utils/tasks/*"], + "@helper/*": ["src/utils/helper/*"], } }, "include": [ diff --git a/react-ui/tsconfig.node.json b/react-ui/tsconfig.node.json index 9357c44f34dccfa514fb7eed37c70d57d691bc70..3a29b0309c672024ea554454c07efee0c080c7e0 100644 --- a/react-ui/tsconfig.node.json +++ b/react-ui/tsconfig.node.json @@ -5,7 +5,7 @@ "module": "ES2020", "moduleResolution": "bundler", "allowSyntheticDefaultImports": true, - "strict": true + "strict": false }, "include": [ "vite.config.ts" diff --git a/react-ui/vite.config.mjs b/react-ui/vite.config.mjs index c52ee47caa32007655b72c71a31b4bfca45c7f48..dee436d2a13d20ea06d8b46747a7473c9b127a01 100644 --- a/react-ui/vite.config.mjs +++ b/react-ui/vite.config.mjs @@ -36,6 +36,8 @@ export default defineConfig({ "@provider": "/src/utils/provider", "@layout": "/src/utils/layouts", "@hooks": "/src/hooks.ts", + "@task": "/src/utils/tasks", + "@helper": "/src/utils/helper", }, },