From df745bbc41d9701d2987108e4a7c05baee13aeaf Mon Sep 17 00:00:00 2001 From: mengstabketemaw Date: Tue, 2 Jun 2026 12:11:29 +0300 Subject: [PATCH] login and sign up configuration --- .../features/auth/components/LoginForm.tsx | 15 +- .../features/auth/pages/SetPasswordPage.tsx | 137 ++++++++++++++++ .../app/features/auth/pages/SignupPage.tsx | 153 ++++++++++++++++++ .../features/auth/store/set-password.slice.ts | 42 +++++ .../app/features/auth/store/signup.slice.ts | 41 +++++ apps/backoffice/src/app/router/index.tsx | 8 +- apps/backoffice/src/app/store/index.ts | 4 + .../src/app/features/auth/pages/LoginPage.tsx | 10 +- .../features/auth/pages/SetPasswordPage.tsx | 138 ++++++++++++++++ .../app/features/auth/pages/SignupPage.tsx | 151 +++++++++++++++++ .../features/auth/store/set-password.slice.ts | 42 +++++ .../app/features/auth/store/signup.slice.ts | 41 +++++ apps/portal/src/app/router.tsx | 4 + apps/portal/src/app/store/index.ts | 4 + libs/api/src/lib/base-api/index.ts | 2 +- 15 files changed, 785 insertions(+), 7 deletions(-) create mode 100644 apps/backoffice/src/app/features/auth/pages/SetPasswordPage.tsx create mode 100644 apps/backoffice/src/app/features/auth/pages/SignupPage.tsx create mode 100644 apps/backoffice/src/app/features/auth/store/set-password.slice.ts create mode 100644 apps/backoffice/src/app/features/auth/store/signup.slice.ts create mode 100644 apps/portal/src/app/features/auth/pages/SetPasswordPage.tsx create mode 100644 apps/portal/src/app/features/auth/pages/SignupPage.tsx create mode 100644 apps/portal/src/app/features/auth/store/set-password.slice.ts create mode 100644 apps/portal/src/app/features/auth/store/signup.slice.ts diff --git a/apps/backoffice/src/app/features/auth/components/LoginForm.tsx b/apps/backoffice/src/app/features/auth/components/LoginForm.tsx index ebf09bd45..57e463101 100644 --- a/apps/backoffice/src/app/features/auth/components/LoginForm.tsx +++ b/apps/backoffice/src/app/features/auth/components/LoginForm.tsx @@ -6,11 +6,12 @@ import { Title, Paper, Text, + Anchor, } from '@mantine/core'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, Link } from 'react-router-dom'; import { useLoginMutation } from '../api/auth-api'; import { useAuth } from '../hooks/useAuth'; import { notify } from '@ema-platform/ui'; @@ -72,8 +73,14 @@ export function LoginForm() { Sign in - - - + + + Don't have an account?{' '} + + Sign up + + + + ); } diff --git a/apps/backoffice/src/app/features/auth/pages/SetPasswordPage.tsx b/apps/backoffice/src/app/features/auth/pages/SetPasswordPage.tsx new file mode 100644 index 000000000..41e311be9 --- /dev/null +++ b/apps/backoffice/src/app/features/auth/pages/SetPasswordPage.tsx @@ -0,0 +1,137 @@ +import { + Paper, + TextInput, + PasswordInput, + Button, + Stack, + Title, + Text, +} from '@mantine/core'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; +import { useNavigate, useLocation } from 'react-router-dom'; +import { useAppDispatch, useAppSelector } from '../../../store/hooks'; +import { + setPasswordStart, + setPasswordSuccess, + setPasswordFailure, +} from '../store/set-password.slice'; +import { notify } from '@ema-platform/ui'; + +const BASE_API_URL = + (import.meta as { env?: Record }).env?.['VITE_BASE_API_URL'] ?? + 'http://localhost:3001/api'; + +const schema = z + .object({ + userId: z.string().min(1, 'User ID is required'), + email: z.string().email('Enter a valid email'), + verificationCode: z.string().min(1, 'Verification code is required'), + newPassword: z.string().min(6, 'Password must be at least 6 characters'), + confirmPassword: z.string().min(6, 'Confirm your password'), + }) + .refine((data) => data.newPassword === data.confirmPassword, { + message: 'Passwords do not match', + path: ['confirmPassword'], + }); + +type FormValues = z.infer; + +export function SetPasswordPage() { + const navigate = useNavigate(); + const dispatch = useAppDispatch(); + const location = useLocation(); + const { loading } = useAppSelector((s) => s.setPassword); + const signupEmail = (location.state as { email?: string } | null)?.email ?? ''; + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(schema), + defaultValues: { email: signupEmail }, + }); + + const onSubmit = async (values: FormValues) => { + dispatch(setPasswordStart()); + try { + const res = await fetch(`${BASE_API_URL}/auth/set-password`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + userId: values.userId, + email: values.email, + verificationCode: values.verificationCode, + newPassword: values.newPassword, + confirmPassword: values.confirmPassword, + }), + }); + + if (!res.ok) { + const errorData = await res.json().catch(() => null); + throw new Error(errorData?.message ?? 'Failed to set password'); + } + + dispatch(setPasswordSuccess()); + notify.success('Password set successfully'); + navigate('/dashboard'); + } catch (err) { + const msg = err instanceof Error ? err.message : 'Something went wrong'; + dispatch(setPasswordFailure(msg)); + notify.error(msg); + } + }; + + return ( + + + + Set your password + + A verification code has been sent to your email. Enter it below along + with your new password to complete registration. + + +
+ + + + + + + + +
+
+
+ ); +} diff --git a/apps/backoffice/src/app/features/auth/pages/SignupPage.tsx b/apps/backoffice/src/app/features/auth/pages/SignupPage.tsx new file mode 100644 index 000000000..05ce9eae0 --- /dev/null +++ b/apps/backoffice/src/app/features/auth/pages/SignupPage.tsx @@ -0,0 +1,153 @@ +import { + Paper, + TextInput, + Button, + Stack, + Title, + Text, + Anchor, +} from '@mantine/core'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; +import { useNavigate, Link } from 'react-router-dom'; +import { useAppDispatch, useAppSelector } from '../../../store/hooks'; +import { signupStart, signupSuccess, signupFailure } from '../store/signup.slice'; +import { hydrateAuth } from '../store/auth.slice'; +import { authStorage } from '../utils/auth-storage'; +import { notify } from '@ema-platform/ui'; + +const BASE_API_URL = + (import.meta as { env?: Record }).env?.['VITE_BASE_API_URL'] ?? + 'http://localhost:3001/api'; + +const schema = z.object({ + email: z.string().email('Enter a valid email'), + username: z.string().min(3, 'Username must be at least 3 characters'), + phoneNumber: z.string().min(1, 'Phone number is required'), + userType: z.string().min(1, 'User type is required'), + name: z.string().min(1, 'Name is required'), +}); + +type FormValues = z.infer; + +interface SignupPayload { + email: string; + username: string; + phoneNumber: string; + userType: string; + name: { + am: string; + en: string; + }; +} + +export function SignupPage() { + const navigate = useNavigate(); + const dispatch = useAppDispatch(); + const { loading } = useAppSelector((s) => s.signup); + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(schema), + }); + + const onSubmit = async (values: FormValues) => { + dispatch(signupStart()); + try { + const payload: SignupPayload = { + email: values.email, + username: values.username, + phoneNumber: values.phoneNumber, + userType: values.userType, + name: { + am: '', + en: values.name, + }, + }; + + const res = await fetch(`${BASE_API_URL}/auth/signup`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + + if (!res.ok) { + const errorData = await res.json().catch(() => null); + throw new Error(errorData?.message ?? 'Signup failed'); + } + + const data = (await res.json()) as { token: string; refreshToken: string }; + + authStorage.setToken(data.token); + authStorage.setRefreshToken(data.refreshToken); + dispatch(hydrateAuth()); + dispatch(signupSuccess()); + + navigate('/set-password', { state: { email: values.email } }); + } catch (err) { + const msg = err instanceof Error ? err.message : 'Something went wrong'; + dispatch(signupFailure(msg)); + notify.error(msg); + } + }; + + return ( + + + + Create an account + + Fill in your details to get started + + +
+ + + + + + + + +
+ + Already have an account?{' '} + + Sign in + + +
+
+ ); +} diff --git a/apps/backoffice/src/app/features/auth/store/set-password.slice.ts b/apps/backoffice/src/app/features/auth/store/set-password.slice.ts new file mode 100644 index 000000000..bdcf8d745 --- /dev/null +++ b/apps/backoffice/src/app/features/auth/store/set-password.slice.ts @@ -0,0 +1,42 @@ +import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; + +interface SetPasswordState { + loading: boolean; + success: boolean; + error: string | null; +} + +const initialState: SetPasswordState = { + loading: false, + success: false, + error: null, +}; + +const setPasswordSlice = createSlice({ + name: 'setPassword', + initialState, + reducers: { + setPasswordStart(state) { + state.loading = true; + state.error = null; + state.success = false; + }, + setPasswordSuccess(state) { + state.loading = false; + state.success = true; + }, + setPasswordFailure(state, action: PayloadAction) { + state.loading = false; + state.error = action.payload; + }, + resetSetPassword(state) { + state.loading = false; + state.success = false; + state.error = null; + }, + }, +}); + +export const { setPasswordStart, setPasswordSuccess, setPasswordFailure, resetSetPassword } = + setPasswordSlice.actions; +export const setPasswordReducer = setPasswordSlice.reducer; diff --git a/apps/backoffice/src/app/features/auth/store/signup.slice.ts b/apps/backoffice/src/app/features/auth/store/signup.slice.ts new file mode 100644 index 000000000..92a5f4294 --- /dev/null +++ b/apps/backoffice/src/app/features/auth/store/signup.slice.ts @@ -0,0 +1,41 @@ +import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; + +interface SignupState { + loading: boolean; + success: boolean; + error: string | null; +} + +const initialState: SignupState = { + loading: false, + success: false, + error: null, +}; + +const signupSlice = createSlice({ + name: 'signup', + initialState, + reducers: { + signupStart(state) { + state.loading = true; + state.error = null; + state.success = false; + }, + signupSuccess(state) { + state.loading = false; + state.success = true; + }, + signupFailure(state, action: PayloadAction) { + state.loading = false; + state.error = action.payload; + }, + resetSignup(state) { + state.loading = false; + state.success = false; + state.error = null; + }, + }, +}); + +export const { signupStart, signupSuccess, signupFailure, resetSignup } = signupSlice.actions; +export const signupReducer = signupSlice.reducer; diff --git a/apps/backoffice/src/app/router/index.tsx b/apps/backoffice/src/app/router/index.tsx index 797c57e57..44a27ae6e 100644 --- a/apps/backoffice/src/app/router/index.tsx +++ b/apps/backoffice/src/app/router/index.tsx @@ -3,6 +3,8 @@ import { ProtectedRoute } from './ProtectedRoute'; import { BackofficeLayout } from '../layouts/BackofficeLayout'; import { AuthLayout } from '../layouts/AuthLayout'; import { LoginPage } from '../features/auth/pages/LoginPage'; +import { SignupPage } from '../features/auth/pages/SignupPage'; +import { SetPasswordPage } from '../features/auth/pages/SetPasswordPage'; import { DashboardPage } from '../features/dashboard/pages/DashboardPage'; import { ItemPage } from '../features/item/pages/ItemPage'; @@ -22,7 +24,11 @@ const router = createBrowserRouter([ }, { element: , - children: [{ path: '/login', element: }], + children: [ + { path: '/login', element: }, + { path: '/signup', element: }, + { path: '/set-password', element: }, + ], }, { path: '/404', element:
Page not found
}, { path: '*', element: }, diff --git a/apps/backoffice/src/app/store/index.ts b/apps/backoffice/src/app/store/index.ts index bc34aa245..bed66a931 100644 --- a/apps/backoffice/src/app/store/index.ts +++ b/apps/backoffice/src/app/store/index.ts @@ -1,10 +1,14 @@ import { configureStore } from '@reduxjs/toolkit'; import { baseApi } from '@ema-platform/api'; import { authReducer } from '../features/auth/store/auth.slice'; +import { signupReducer } from '../features/auth/store/signup.slice'; +import { setPasswordReducer } from '../features/auth/store/set-password.slice'; export const store = configureStore({ reducer: { auth: authReducer, + signup: signupReducer, + setPassword: setPasswordReducer, [baseApi.reducerPath]: baseApi.reducer, }, middleware: (getDefaultMiddleware) => diff --git a/apps/portal/src/app/features/auth/pages/LoginPage.tsx b/apps/portal/src/app/features/auth/pages/LoginPage.tsx index 898639783..f0153f2ef 100644 --- a/apps/portal/src/app/features/auth/pages/LoginPage.tsx +++ b/apps/portal/src/app/features/auth/pages/LoginPage.tsx @@ -7,11 +7,13 @@ import { Stack, Title, Center, + Text, + Anchor, } from '@mantine/core'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, Link } from 'react-router-dom'; import { useAuth } from '../hooks/useAuth'; import { notify } from '@ema-platform/ui'; @@ -82,6 +84,12 @@ export function LoginPage() { + + Don't have an account?{' '} + + Sign up + + diff --git a/apps/portal/src/app/features/auth/pages/SetPasswordPage.tsx b/apps/portal/src/app/features/auth/pages/SetPasswordPage.tsx new file mode 100644 index 000000000..eb2837420 --- /dev/null +++ b/apps/portal/src/app/features/auth/pages/SetPasswordPage.tsx @@ -0,0 +1,138 @@ +import { + Paper, + TextInput, + PasswordInput, + Button, + Stack, + Title, + Center, + Text, +} from '@mantine/core'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; +import { useNavigate, useLocation } from 'react-router-dom'; +import { useAppDispatch, useAppSelector } from '../../../store/hooks'; +import { + setPasswordStart, + setPasswordSuccess, + setPasswordFailure, +} from '../store/set-password.slice'; +import { notify } from '@ema-platform/ui'; + +const BASE_API_URL = + (import.meta as { env?: Record }).env?.['VITE_BASE_API_URL'] ?? + 'http://localhost:3001/api'; + +const schema = z + .object({ + userId: z.string().min(1, 'User ID is required'), + email: z.string().email('Enter a valid email'), + verificationCode: z.string().min(1, 'Verification code is required'), + newPassword: z.string().min(6, 'Password must be at least 6 characters'), + confirmPassword: z.string().min(6, 'Confirm your password'), + }) + .refine((data) => data.newPassword === data.confirmPassword, { + message: 'Passwords do not match', + path: ['confirmPassword'], + }); + +type FormValues = z.infer; + +export function SetPasswordPage() { + const navigate = useNavigate(); + const dispatch = useAppDispatch(); + const location = useLocation(); + const { loading } = useAppSelector((s) => s.setPassword); + const signupEmail = (location.state as { email?: string } | null)?.email ?? ''; + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(schema), + defaultValues: { email: signupEmail }, + }); + + const onSubmit = async (values: FormValues) => { + dispatch(setPasswordStart()); + try { + const res = await fetch(`${BASE_API_URL}/auth/set-password`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + userId: values.userId, + email: values.email, + verificationCode: values.verificationCode, + newPassword: values.newPassword, + confirmPassword: values.confirmPassword, + }), + }); + + if (!res.ok) { + const errorData = await res.json().catch(() => null); + throw new Error(errorData?.message ?? 'Failed to set password'); + } + + dispatch(setPasswordSuccess()); + notify.success('Password set successfully'); + navigate('/dashboard'); + } catch (err) { + const msg = err instanceof Error ? err.message : 'Something went wrong'; + dispatch(setPasswordFailure(msg)); + notify.error(msg); + } + }; + + return ( +
+ + + Set your password + + A verification code has been sent to your email. Enter it below along + with your new password to complete registration. + +
+ + + + + + + + +
+
+
+
+ ); +} diff --git a/apps/portal/src/app/features/auth/pages/SignupPage.tsx b/apps/portal/src/app/features/auth/pages/SignupPage.tsx new file mode 100644 index 000000000..2a277e457 --- /dev/null +++ b/apps/portal/src/app/features/auth/pages/SignupPage.tsx @@ -0,0 +1,151 @@ +import { + Paper, + TextInput, + Button, + Stack, + Title, + Center, + Text, + Anchor, +} from '@mantine/core'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; +import { useNavigate, Link } from 'react-router-dom'; +import { useAppDispatch, useAppSelector } from '../../../store/hooks'; +import { signupStart, signupSuccess, signupFailure } from '../store/signup.slice'; +import { hydrateAuth } from '../store/auth.slice'; +import { authStorage } from '../utils/auth-storage'; +import { notify } from '@ema-platform/ui'; + +const BASE_API_URL = + (import.meta as { env?: Record }).env?.['VITE_BASE_API_URL'] ?? + 'http://localhost:3001/api'; + +const schema = z.object({ + email: z.string().email('Enter a valid email'), + username: z.string().min(3, 'Username must be at least 3 characters'), + phoneNumber: z.string().min(1, 'Phone number is required'), + userType: z.string().min(1, 'User type is required'), + name: z.string().min(1, 'Name is required'), +}); + +type FormValues = z.infer; + +interface SignupPayload { + email: string; + username: string; + phoneNumber: string; + userType: string; + name: { + am: string; + en: string; + }; +} + +export function SignupPage() { + const navigate = useNavigate(); + const dispatch = useAppDispatch(); + const { loading } = useAppSelector((s) => s.signup); + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(schema), + }); + + const onSubmit = async (values: FormValues) => { + dispatch(signupStart()); + try { + const payload: SignupPayload = { + email: values.email, + username: values.username, + phoneNumber: values.phoneNumber, + userType: values.userType, + name: { + am: '', + en: values.name, + }, + }; + + const res = await fetch(`${BASE_API_URL}/auth/signup`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + + if (!res.ok) { + const errorData = await res.json().catch(() => null); + throw new Error(errorData?.message ?? 'Signup failed'); + } + + const data = (await res.json()) as { token: string; refreshToken: string }; + + authStorage.setToken(data.token); + authStorage.setRefreshToken(data.refreshToken); + dispatch(hydrateAuth()); + dispatch(signupSuccess()); + + navigate('/set-password', { state: { email: values.email } }); + } catch (err) { + const msg = err instanceof Error ? err.message : 'Something went wrong'; + dispatch(signupFailure(msg)); + notify.error(msg); + } + }; + + return ( +
+ + + Create an account +
+ + + + + + + + +
+ + Already have an account?{' '} + + Sign in + + +
+
+
+ ); +} diff --git a/apps/portal/src/app/features/auth/store/set-password.slice.ts b/apps/portal/src/app/features/auth/store/set-password.slice.ts new file mode 100644 index 000000000..bdcf8d745 --- /dev/null +++ b/apps/portal/src/app/features/auth/store/set-password.slice.ts @@ -0,0 +1,42 @@ +import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; + +interface SetPasswordState { + loading: boolean; + success: boolean; + error: string | null; +} + +const initialState: SetPasswordState = { + loading: false, + success: false, + error: null, +}; + +const setPasswordSlice = createSlice({ + name: 'setPassword', + initialState, + reducers: { + setPasswordStart(state) { + state.loading = true; + state.error = null; + state.success = false; + }, + setPasswordSuccess(state) { + state.loading = false; + state.success = true; + }, + setPasswordFailure(state, action: PayloadAction) { + state.loading = false; + state.error = action.payload; + }, + resetSetPassword(state) { + state.loading = false; + state.success = false; + state.error = null; + }, + }, +}); + +export const { setPasswordStart, setPasswordSuccess, setPasswordFailure, resetSetPassword } = + setPasswordSlice.actions; +export const setPasswordReducer = setPasswordSlice.reducer; diff --git a/apps/portal/src/app/features/auth/store/signup.slice.ts b/apps/portal/src/app/features/auth/store/signup.slice.ts new file mode 100644 index 000000000..92a5f4294 --- /dev/null +++ b/apps/portal/src/app/features/auth/store/signup.slice.ts @@ -0,0 +1,41 @@ +import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; + +interface SignupState { + loading: boolean; + success: boolean; + error: string | null; +} + +const initialState: SignupState = { + loading: false, + success: false, + error: null, +}; + +const signupSlice = createSlice({ + name: 'signup', + initialState, + reducers: { + signupStart(state) { + state.loading = true; + state.error = null; + state.success = false; + }, + signupSuccess(state) { + state.loading = false; + state.success = true; + }, + signupFailure(state, action: PayloadAction) { + state.loading = false; + state.error = action.payload; + }, + resetSignup(state) { + state.loading = false; + state.success = false; + state.error = null; + }, + }, +}); + +export const { signupStart, signupSuccess, signupFailure, resetSignup } = signupSlice.actions; +export const signupReducer = signupSlice.reducer; diff --git a/apps/portal/src/app/router.tsx b/apps/portal/src/app/router.tsx index 2e6e41493..52dd0f96e 100644 --- a/apps/portal/src/app/router.tsx +++ b/apps/portal/src/app/router.tsx @@ -2,6 +2,8 @@ import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom' import type { ReactNode } from 'react'; import { PortalLayout } from './layouts/PortalLayout'; import { LoginPage } from './features/auth/pages/LoginPage'; +import { SignupPage } from './features/auth/pages/SignupPage'; +import { SetPasswordPage } from './features/auth/pages/SetPasswordPage'; import { DashboardPage } from './features/dashboard/pages/DashboardPage'; function getToken(): string | null { @@ -15,6 +17,8 @@ function ProtectedRoute({ children }: { children: ReactNode }) { const router = createBrowserRouter([ { path: '/login', element: }, + { path: '/signup', element: }, + { path: '/set-password', element: }, { element: , children: [ diff --git a/apps/portal/src/app/store/index.ts b/apps/portal/src/app/store/index.ts index bc34aa245..bed66a931 100644 --- a/apps/portal/src/app/store/index.ts +++ b/apps/portal/src/app/store/index.ts @@ -1,10 +1,14 @@ import { configureStore } from '@reduxjs/toolkit'; import { baseApi } from '@ema-platform/api'; import { authReducer } from '../features/auth/store/auth.slice'; +import { signupReducer } from '../features/auth/store/signup.slice'; +import { setPasswordReducer } from '../features/auth/store/set-password.slice'; export const store = configureStore({ reducer: { auth: authReducer, + signup: signupReducer, + setPassword: setPasswordReducer, [baseApi.reducerPath]: baseApi.reducer, }, middleware: (getDefaultMiddleware) => diff --git a/libs/api/src/lib/base-api/index.ts b/libs/api/src/lib/base-api/index.ts index d22ba6101..6070cff60 100644 --- a/libs/api/src/lib/base-api/index.ts +++ b/libs/api/src/lib/base-api/index.ts @@ -3,7 +3,7 @@ import { resolveSessionContext } from '../session'; const BASE_API_URL = (import.meta as { env?: Record }).env?.['VITE_BASE_API_URL'] ?? - 'http://localhost:3001'; + 'http://localhost:3001/api'; export const baseApi = createApi({ reducerPath: 'baseApi',