From 305bbebffb2b5e633aed42d56cf0ee43359d04c1 Mon Sep 17 00:00:00 2001 From: Mohit Kalshan Date: Tue, 21 Jan 2025 13:06:24 +0530 Subject: [PATCH] login setup --- .prettierrc | 10 +- package.json | 5 +- pnpm-lock.yaml | 41 +++- public/index.html | 54 ++--- src/App.tsx | 34 ++- src/components/OptionsMenu/index.tsx | 13 +- src/hooks/useAuth.js | 27 +-- src/index.tsx | 55 ++--- src/lib/https.ts | 38 +++- src/pages/NotFound/index.tsx | 9 + src/pages/{Vechiles => Vehicles}/index.tsx | 2 +- src/redux/reducers.ts | 11 + src/redux/slices/authSlice.ts | 238 ++++++++++++--------- src/redux/{store => }/store.ts | 13 +- src/router.tsx | 101 +++++---- tsconfig.json | 37 ++-- 16 files changed, 403 insertions(+), 285 deletions(-) create mode 100644 src/pages/NotFound/index.tsx rename src/pages/{Vechiles => Vehicles}/index.tsx (97%) create mode 100644 src/redux/reducers.ts rename src/redux/{store => }/store.ts (59%) diff --git a/.prettierrc b/.prettierrc index 87cf9d6..21feb34 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,8 @@ { - "trailingComma": "es5", "tabWidth": 4, - "semi": false, - "singleQuote": false -} + "useTabs": true, + "semi": true, + "singleQuote": false, + "bracketSpacing": true + } + \ No newline at end of file diff --git a/package.json b/package.json index 1e29da7..1d09a79 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "mui-tel-input": "^7.0.0", "prop-types": "^15.8.1", "react": "^19.0.0", + "react-cookie": "^7.2.2", "react-dom": "^19.0.0", "react-dropzone": "^14.3.5", "react-hook-form": "^7.54.2", @@ -68,8 +69,8 @@ }, "devDependencies": { "@types/node": "^22.10.5", - "@types/react": "^19.0.3", + "@types/react": "^19.0.4", "@types/react-dom": "^19.0.2", - "typescript": "^5.7.2" + "typescript": "^5.7.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2d7544..7a51ff0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -95,6 +95,9 @@ importers: react: specifier: ^19.0.0 version: 19.0.0 + react-cookie: + specifier: ^7.2.2 + version: 7.2.2(react@19.0.0) react-dom: specifier: ^19.0.0 version: 19.0.0(react@19.0.0) @@ -127,13 +130,13 @@ importers: specifier: ^22.10.5 version: 22.10.5 '@types/react': - specifier: ^19.0.3 + specifier: ^19.0.4 version: 19.0.4 '@types/react-dom': specifier: ^19.0.2 version: 19.0.2(@types/react@19.0.4) typescript: - specifier: ^5.7.2 + specifier: ^5.7.3 version: 5.7.3 packages: @@ -1598,6 +1601,9 @@ packages: '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + '@types/hoist-non-react-statics@3.3.6': + resolution: {integrity: sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==} + '@types/html-minifier-terser@6.1.0': resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} @@ -2339,6 +2345,10 @@ packages: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + cookie@1.0.2: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} @@ -4915,6 +4925,11 @@ packages: resolution: {integrity: sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w==} engines: {node: '>=14'} + react-cookie@7.2.2: + resolution: {integrity: sha512-e+hi6axHcw9VODoeVu8WyMWyoosa1pzpyjfvrLdF7CexfU+WSGZdDuRfHa4RJgTpfv3ZjdIpHE14HpYBieHFhg==} + peerDependencies: + react: '>= 16.3.0' + react-dev-utils@12.0.1: resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==} engines: {node: '>=14'} @@ -5742,6 +5757,9 @@ packages: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} + universal-cookie@7.2.2: + resolution: {integrity: sha512-fMiOcS3TmzP2x5QV26pIH3mvhexLIT0HmPa3V7Q7knRfT9HG6kTwq02HZGLPw0sAOXrAmotElGRvTLCMbJsvxQ==} + universalify@0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} @@ -7883,6 +7901,11 @@ snapshots: dependencies: '@types/node': 22.10.5 + '@types/hoist-non-react-statics@3.3.6': + dependencies: + '@types/react': 19.0.4 + hoist-non-react-statics: 3.3.2 + '@types/html-minifier-terser@6.1.0': {} '@types/http-errors@2.0.4': {} @@ -8744,6 +8767,8 @@ snapshots: cookie@0.7.1: {} + cookie@0.7.2: {} + cookie@1.0.2: {} core-js-compat@3.40.0: @@ -11735,6 +11760,13 @@ snapshots: regenerator-runtime: 0.13.11 whatwg-fetch: 3.6.20 + react-cookie@7.2.2(react@19.0.0): + dependencies: + '@types/hoist-non-react-statics': 3.3.6 + hoist-non-react-statics: 3.3.2 + react: 19.0.0 + universal-cookie: 7.2.2 + react-dev-utils@12.0.1(eslint@8.57.1)(typescript@5.7.3)(webpack@5.97.1): dependencies: '@babel/code-frame': 7.26.2 @@ -12745,6 +12777,11 @@ snapshots: dependencies: crypto-random-string: 2.0.0 + universal-cookie@7.2.2: + dependencies: + '@types/cookie': 0.6.0 + cookie: 0.7.2 + universalify@0.2.0: {} universalify@2.0.1: {} diff --git a/public/index.html b/public/index.html index aa069f2..720d1f0 100644 --- a/public/index.html +++ b/public/index.html @@ -1,43 +1,21 @@ - - - - - - - - - - - React App - - - -
- - + DigiEV - Eco-friendly Charge + + +
+ diff --git a/src/App.tsx b/src/App.tsx index 82968f1..bf0e11b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,28 @@ -import { BrowserRouter as Router} from 'react-router-dom'; -import AppRouter from './router'; +import AppRouter from "./router"; +import { useSelector } from "react-redux"; +import { useMatch, useNavigate, useSearchParams } from "react-router-dom"; +import { useEffect } from "react"; +import { RootState } from "./redux/store"; +import { withCookies, ReactCookieProps } from "react-cookie"; -function App() { - return ( - - - +const App: React.FC = ({ cookies }) => { + const navigate = useNavigate(); + const isPanel = useMatch("/auth/login"); + const isCookiePresent = !!cookies?.get("authToken"); + console.log("cookies present:", isCookiePresent); + const [searchParams] = useSearchParams(); + + const isAuthenticated = useSelector( + (state: RootState) => state.authReducer.isAuthenticated ); -} -export default App; + useEffect(() => { + if (isPanel && isAuthenticated) { + navigate("/panel/dashboard"); + } + }, [isPanel, isAuthenticated, searchParams]); + + return ; +}; + +export default withCookies(App); diff --git a/src/components/OptionsMenu/index.tsx b/src/components/OptionsMenu/index.tsx index 3dd788a..e52b41b 100644 --- a/src/components/OptionsMenu/index.tsx +++ b/src/components/OptionsMenu/index.tsx @@ -11,6 +11,9 @@ import LogoutRoundedIcon from '@mui/icons-material/LogoutRounded'; import MoreVertRoundedIcon from '@mui/icons-material/MoreVertRounded'; import MenuButton from '../MenuButton'; import { Avatar } from '@mui/material'; +import { useDispatch } from 'react-redux'; +import { logoutUser } from '../../redux/slices/authSlice'; +import { useCookies } from 'react-cookie'; const MenuItem = styled(MuiMenuItem)({ margin: '2px 0', @@ -19,12 +22,20 @@ const MenuItem = styled(MuiMenuItem)({ export default function OptionsMenu({ avatar }: { avatar?: boolean }) { const [anchorEl, setAnchorEl] = React.useState(null); const open = Boolean(anchorEl); + const dispatch = useDispatch(); + const [cookies, setCookie, removeCookie] = useCookies(['authToken']); const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; + + const handleLogout = () => { + dispatch(logoutUser({ removeCookie })); + console.log('click') + handleClose(); + }; return ( Settings { +const useAuth = () => { const dispatch = useDispatch(); const [isAuthorized, setIsAuthorized] = React.useState(false); - - const { isAuthenticated, userCapabilities } = useSelector((state) => ({ + const { isAuthenticated } = useSelector((state) => ({ isAuthenticated: state.authReducer.isAuthenticated, - userCapabilities: state.authReducer.userCapabilities, })); - const requiredCaps = React.useMemo( - () => uniq(Array.isArray(caps) ? caps : [caps]), - [caps] - ); - React.useEffect(() => { - const userMatchedCaps = intersection(userCapabilities, requiredCaps); - - let isUserAuthorized = matchAllCaps - ? userMatchedCaps?.length === requiredCaps?.length - : userMatchedCaps?.length > 0; - if (requiredCaps.length === 0) { - isUserAuthorized = true; - } - // if (isAuthenticated === null) { - if (isAuthenticated === null || false || undefined) { + if (isAuthenticated) { dispatch(checkUserAuth()); } else { - setIsAuthorized(isAuthenticated && isUserAuthorized); + setIsAuthorized(false); } - }, [dispatch, isAuthenticated, requiredCaps, userCapabilities]); + }, [dispatch, isAuthenticated]); return { isAuthenticated, isAuthorized }; }; diff --git a/src/index.tsx b/src/index.tsx index 8c25f92..383f20b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,29 +1,32 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import reportWebVitals from './reportWebVitals'; -import App from './App'; -import { Provider } from 'react-redux'; -import store from './redux/store/store.ts'; -import { Slide, ToastContainer } from 'react-toastify'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import App from "./App"; +import { Provider } from "react-redux"; +import store from "./redux/store"; +import { Slide, ToastContainer } from "react-toastify"; +import { BrowserRouter as Router } from "react-router-dom"; +import { CookiesProvider } from "react-cookie"; -const root = ReactDOM.createRoot(document.getElementById('root')); -root.render( - - - - - - +const root = ReactDOM.createRoot( + document.getElementById("root") as HTMLElement ); -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); +root.render( + + + + + + + + + + +); diff --git a/src/lib/https.ts b/src/lib/https.ts index e196d71..3b350b9 100644 --- a/src/lib/https.ts +++ b/src/lib/https.ts @@ -1,16 +1,36 @@ -import axios from 'axios'; +import axios from "axios"; +import { Cookies } from "react-cookie"; + +const cookies = new Cookies(); const http = axios.create({ - baseURL: process.env.REACT_APP_BACKEND_URL, + baseURL: process.env.REACT_APP_BACKEND_URL, }); -console.log(process.env.REACT_APP_BACKEND_URL); -http.interceptors.request.use((config) => { - const authToken = localStorage.getItem('authToken'); - if (authToken) { - config.headers.Authorization = authToken; - } - return config; +http.interceptors.request.use((config) => { + const authToken = cookies.get("authToken"); + console.log(authToken); + if (authToken) { + config.headers.Authorization = authToken; + } + return config; }); +http.interceptors.response.use( + (response) => response, + (error) => { + const isCookiePresent = cookies.get("authToken"); + console.log(isCookiePresent,"jkk") + if ( + error.response && + isCookiePresent && + (error.response.status === 403 || error.response.status === 401) + ) { + cookies.remove("authToken", { path: "/" }); + window.location.href = "/"; + } + return Promise.reject(error); + } +); + export default http; diff --git a/src/pages/NotFound/index.tsx b/src/pages/NotFound/index.tsx new file mode 100644 index 0000000..578bc48 --- /dev/null +++ b/src/pages/NotFound/index.tsx @@ -0,0 +1,9 @@ +import React from 'react' + +function NotFoundPage() { + return ( +
NotFoundPage
+ ) +} + +export default NotFoundPage; \ No newline at end of file diff --git a/src/pages/Vechiles/index.tsx b/src/pages/Vehicles/index.tsx similarity index 97% rename from src/pages/Vechiles/index.tsx rename to src/pages/Vehicles/index.tsx index e6168aa..db9710a 100644 --- a/src/pages/Vechiles/index.tsx +++ b/src/pages/Vehicles/index.tsx @@ -19,7 +19,7 @@ const categoryRows = [ ]; export default function Vehicles() { - const [modalOpen, setModalOpen] = useState(false); + const [modalOpen, setModalOpen] = useState(false); const [editRow, setEditRow] = useState(null); const { reset } = useForm(); diff --git a/src/redux/reducers.ts b/src/redux/reducers.ts new file mode 100644 index 0000000..0f22e9d --- /dev/null +++ b/src/redux/reducers.ts @@ -0,0 +1,11 @@ +import { combineReducers } from "@reduxjs/toolkit"; + +import authReducer from "./slices/authSlice"; + +const rootReducer = combineReducers({ + authReducer, +}); + +export type RootState = ReturnType; + +export default rootReducer; diff --git a/src/redux/slices/authSlice.ts b/src/redux/slices/authSlice.ts index 21b6a23..452b820 100644 --- a/src/redux/slices/authSlice.ts +++ b/src/redux/slices/authSlice.ts @@ -1,118 +1,160 @@ -import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit" -import axios from "axios" -import http from "../../lib/https" -import { toast } from "react-toastify" +import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; +import axios from "axios"; +import http from "../../lib/https"; +import { toast } from "react-toastify"; +import { Cookies } from "react-cookie"; +const cookies = new Cookies(); // Define types for state interface User { - id: string - email: string + id: string; + email: string; } interface AuthState { - user: User | null - isAuthenticated: boolean - isLoading: boolean - error: string | null + user: User | null; + isAuthenticated: boolean; + isLoading: boolean; + error: string | null; } +export const checkUserAuth = createAsyncThunk< + boolean, + void, + { rejectValue: any } +>("application/checkUserAuth", async (_, thunkAPI) => { + try { + const isCookiePresent = cookies.get("authToken"); + if (!isCookiePresent) return thunkAPI.rejectWithValue(null); + return thunkAPI.fulfillWithValue(true); + } catch (error) { + console.log(error); + return thunkAPI.rejectWithValue(error); + } +}); // Async thunk for login export const loginUser = createAsyncThunk< - User, - { email: string; password: string }, - { rejectValue: string } + User, + { email: string; password: string }, + { rejectValue: string } >("auth/login", async ({ email, password }, { rejectWithValue }) => { - try { - const response = await http.post("admin/login", { email, password }) - localStorage.setItem("authToken", response.data?.data?.token) // Save token - toast.success(response.data?.message) - return response.data - } catch (error: any) { - return rejectWithValue( - error.response?.data?.message || "An error occurred" - ) - } -}) + try { + const response = await http.post("admin/login", { email, password }); + cookies.set("authToken", response.data?.data?.token); + toast.success(response.data?.message); + return response.data; + } catch (error: any) { + return rejectWithValue( + error.response?.data?.message || "An error occurred" + ); + } +}); // Async thunk for register export const registerUser = createAsyncThunk< - User, - { email: string; password: string }, - { rejectValue: string } + User, + { email: string; password: string }, + { rejectValue: string } >("auth/register", async (data, { rejectWithValue }) => { - try { - const response = await axios.post( - "https://health-digi.dmlabs.in/auth/register", - data - ) - return response.data - } catch (error: any) { - return rejectWithValue( - error.response?.data?.message || "An error occurred" - ) - } -}) + try { + const response = await axios.post( + "https://health-digi.dmlabs.in/auth/register", + data + ); + return response.data; + } catch (error: any) { + return rejectWithValue( + error.response?.data?.message || "An error occurred" + ); + } +}); + +export const logoutUser = createAsyncThunk< + void, + { removeCookie: any }, + { rejectValue: string } +>("auth/logout", async ({ removeCookie }, { rejectWithValue }) => { + try { + removeCookie("authToken", { path: "/auth" }); + toast.success("You have been logged out successfully."); + } catch (error: any) { + return rejectWithValue( + error.response?.data?.message || "Failed to log out." + ); + } +}); const initialState: AuthState = { - user: null, - isAuthenticated: false, - isLoading: false, - error: null, -} + user: null, + isAuthenticated: false, + isLoading: false, + error: null, +}; const authSlice = createSlice({ - name: "auth", - initialState, - reducers: { - logout: (state) => { - state.user = null - state.isAuthenticated = false - }, - }, - extraReducers: (builder) => { - builder - // Login - .addCase(loginUser.pending, (state) => { - state.isLoading = true - state.error = null - }) - .addCase( - loginUser.fulfilled, - (state, action: PayloadAction) => { - state.isLoading = false - state.isAuthenticated = true - state.user = action.payload - } - ) - .addCase( - loginUser.rejected, - (state, action: PayloadAction) => { - state.isLoading = false - state.error = action.payload || "An error occurred" - } - ) - // Register - .addCase(registerUser.pending, (state) => { - state.isLoading = true - state.error = null - }) - .addCase( - registerUser.fulfilled, - (state, action: PayloadAction) => { - state.isLoading = false - state.isAuthenticated = true - state.user = action.payload - } - ) - .addCase( - registerUser.rejected, - (state, action: PayloadAction) => { - state.isLoading = false - state.error = action.payload || "An error occurred" - } - ) - }, -}) + name: "auth", + initialState, + reducers: { + logout: (state) => { + state.user = null; + state.isAuthenticated = false; + }, + }, + extraReducers: (builder) => { + builder + // Login + .addCase(loginUser.pending, (state) => { + state.isLoading = true; + state.error = null; + }) + .addCase( + loginUser.fulfilled, + (state, action: PayloadAction) => { + state.isLoading = false; + state.isAuthenticated = true; + state.user = action.payload; + } + ) + .addCase( + loginUser.rejected, + (state, action: PayloadAction) => { + state.isLoading = false; + state.error = action.payload || "An error occurred"; + } + ) + // Register + .addCase(registerUser.pending, (state) => { + state.isLoading = true; + state.error = null; + }) + .addCase( + registerUser.fulfilled, + (state, action: PayloadAction) => { + state.isLoading = false; + state.isAuthenticated = true; + state.user = action.payload; + } + ) + .addCase( + registerUser.rejected, + (state, action: PayloadAction) => { + state.isLoading = false; + state.error = action.payload || "An error occurred"; + } + ) + // Logout + .addCase(logoutUser.fulfilled, (state) => { + state.user = null; + state.isAuthenticated = false; + }) + .addCase( + logoutUser.rejected, + (state, action: PayloadAction) => { + state.error = + action.payload || "An error occurred during logout"; + } + ); + }, +}); -export const { logout } = authSlice.actions -export default authSlice.reducer +export default authSlice.reducer; diff --git a/src/redux/store/store.ts b/src/redux/store.ts similarity index 59% rename from src/redux/store/store.ts rename to src/redux/store.ts index 3f04936..8b5ec9b 100644 --- a/src/redux/store/store.ts +++ b/src/redux/store.ts @@ -1,13 +1,10 @@ import { configureStore } from '@reduxjs/toolkit'; -import authReducer from '../slices/authSlice.ts' -const store = configureStore({ - reducer: { - auth: authReducer, - }, -}); +import rootReducer from './reducers'; + +export const store = configureStore({ + reducer: rootReducer, +}) export type RootState = ReturnType; export type AppDispatch = typeof store.dispatch; export default store; - - diff --git a/src/router.tsx b/src/router.tsx index 8d62e48..5948121 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -1,54 +1,61 @@ -import { Routes as BaseRoutes, Navigate, Route } from 'react-router-dom'; -// import useAuth from "./hooks/useAuth"; -import React, { Suspense } from 'react'; -import LoadingComponent from './components/Loading'; -import DashboardLayout from './layouts/DashboardLayout'; -import Login from './pages/Auth/Login'; -import SignUp from './pages/Auth/SignUp'; -import Dashboard from './pages/Dashboard'; -import Vehicles from './pages/Vechiles'; +import { Routes as BaseRoutes, Navigate, Route } from "react-router-dom"; +import { Suspense } from "react"; +import LoadingComponent from "./components/Loading"; +import DashboardLayout from "./layouts/DashboardLayout"; +import Login from "./pages/Auth/Login"; +import SignUp from "./pages/Auth/SignUp"; +import Dashboard from "./pages/Dashboard"; +import Vehicles from "./pages/Vehicles"; +import { useSelector } from "react-redux"; +import { RootState } from "./redux/reducers"; +import { useCookies } from "react-cookie"; -function ProtectedRoute({ - caps, - component, -}: { - caps: string[]; - component: React.ReactNode; -}) { - if (!localStorage.getItem('authToken')) - return ; +interface ProtectedRouteProps { + component: JSX.Element; + cookies?: { get: (key: string) => string | null }; +} +function ProtectedRoute({ component }: ProtectedRouteProps): JSX.Element { + const [cookies] = useCookies(["authToken"]); + const isCookiePresent = !!cookies?.authToken; + const isAuthenticated = useSelector( + (state: RootState) => state.authReducer.isAuthenticated + ); - return component; + if (!isAuthenticated && !isCookiePresent) { + return ; + } + + return component; } export default function AppRouter() { - return ( - }> - - } index /> + return ( + }> + + } index /> - - } - index - /> - } /> - } /> - - }> - } />} - /> - } />} - /> - 404} /> - - 404} /> - - - ); + + } + index + /> + } /> + } /> + + }> + } />} + /> + } />} + /> + 404} /> + + 404} /> + + + ); } diff --git a/tsconfig.json b/tsconfig.json index a46b3fa..32a2d79 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,19 +1,20 @@ { - "files": [], - "references": [ - { - "path": "./tsconfig.app.json" - }, - { - "path": "./tsconfig.node.json" - } - ], - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@/*": [ - "./src/*" - ] - } - } -} \ No newline at end of file + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.node.json" + } + ], + "compilerOptions": { + "baseUrl": ".", + "jsx": "react-jsx", + "types": ["react", "react-dom"], + "lib": ["dom", "dom.iterable", "esnext"], + "paths": { + "@/*": ["./src/*"] + } + } +}