diff --git a/Modules/HospitalPortal/Http/Controllers/Api/AuthController.php b/Modules/HospitalPortal/Http/Controllers/Api/AuthController.php index bf221968..44beb6b9 100644 --- a/Modules/HospitalPortal/Http/Controllers/Api/AuthController.php +++ b/Modules/HospitalPortal/Http/Controllers/Api/AuthController.php @@ -14,6 +14,9 @@ use Modules\Internal\Emails\SendVerifyEmail; use Modules\Internal\Events\ForgetPassword; use Illuminate\Support\Facades\Validator; use Modules\HospitalPortal\Helpers\ApiResponse; +use Illuminate\Support\Facades\DB; +use App\Helpers\Helper; +use Illuminate\Support\Facades\View; class AuthController extends Controller { @@ -27,9 +30,9 @@ class AuthController extends Controller 'email' => 'required|email', 'password' => 'required' ], [ - 'email.required' => trans('validation.required',['attribute' => 'Email']), - 'email.email' => trans('validation.email'), - 'password.required' => trans('validation.required',['attribute' => 'Password']), + 'email.required' => trans('Validation.required',['attribute' => 'Email']), + 'email.email' => trans('Validation.email'), + 'password.required' => trans('Validation.required',['attribute' => 'Password']), ]); if ($validator->fails()) @@ -40,11 +43,11 @@ class AuthController extends Controller { $user = User::where('email', $request->email)->first(); if (!$user) { - return ApiResponse::apiResponse('Not Found', $data, trans('message.not_found'), 404); + return ApiResponse::apiResponse('Not Found', $data, trans('Message.not_found'), 404); } if (!Hash::check($request->password, $user->password)) { - return ApiResponse::apiResponse('Bad Request', $data, trans('message.password'), 400); + return ApiResponse::apiResponse('Bad Request', $data, trans('Message.password'), 400); } $res_data = [ @@ -52,16 +55,15 @@ class AuthController extends Controller 'token' => $user->createToken('app')->plainTextToken ]; - return ApiResponse::apiResponse("Success", $res_data, trans('message.success'), 200); + return ApiResponse::apiResponse("Success", $res_data, trans('Message.success'), 200); } } public function logout(Request $request) { - $token = $request->bearerToken(); - Auth::user()->tokens()->where('id', $token)->delete(); + $request->user()->tokens()->delete(); - return response(['message' => 'Berhasil Logout.']); + return ApiResponse::apiResponse('Success', [], trans('Message.logout'), 200); } public function resetPassword(Request $request) @@ -75,12 +77,12 @@ class AuthController extends Controller ]); if (!Hash::check($request['old_password'], $user->password)) { - return response(['message' => 'Password Salah'], 403); + return response(['Message' => 'Password Salah'], 403); } if ($request["new_password"] != $request["confirm_new_password"]) { return response([ - 'message' => "Password Tidak Sama" + 'Message' => "Password Tidak Sama" ]); } @@ -101,7 +103,7 @@ class AuthController extends Controller ->first(); if (!$user) { - return response(['message' => 'User Tidak Ditemukan'], 404); + return response(['Message' => 'User Tidak Ditemukan'], 404); } Event(new ForgetPassword($user)); @@ -111,33 +113,77 @@ class AuthController extends Controller return response()->json($user); } - public function forgetPassword(Request $request) + public function forgotPassword(Request $request) { - $request->validate([ - 'new_password' => 'required', - 'confirm_new_password' => 'required' + $data = [ + 'email' => $request->email, + ]; + + $validator = Validator::make($request->all(), [ + 'email' => 'required|email', + ], [ + 'email.required' => trans('Validation.required',['attribute' => 'Email']), + 'email.email' => trans('Validation.email'), ]); - $token = Crypt::decryptString($request->token); - $email = explode('|', $token)[0]; - - $user = User::query() - ->where('email', $email) - ->first(); - - if (!$user) { - return response(['message' => 'User Tidak Ditemukan'], 404); + if ($validator->fails()) + { + return ApiResponse::apiResponse('Bad Request', $data, $validator->errors(), 400); } + else + { + $user = User::where('email', $request->email)->first(); + if (!$user) { + return ApiResponse::apiResponse('Not Found', $data, trans('Message.not_found'), 404); + } - if ($request["new_password"] != $request["confirm_new_password"]) { - return response([ - 'message' => "Password Tidak Sama" - ], 404); + //send email + // Insert data notifications + $emailTo = $request->email; + $dataNotif = [ + 'user_id' => $user->id, + 'email' => $emailTo, + 'title' => 'Forgot Password', + 'description' => 'Request forgot password from App Doctor', + 'type' => 1, + 'isUnRead' => true, + 'created_by' => auth()->check() ? auth()->user()->id : null, + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + ]; + $sendNotif = Helper::insertNotification($dataNotif); + //Insert data password reset + $token = mt_rand(100000, 999999); // Menghasilkan angka acak antara 100000 dan 999999 + $p_resets = DB::table('password_resets') + ->insert([ + 'email' => $request->email, + 'token' => $token, + 'created_at' => date('Y-m-d H:i:s'), + ]); + // Send Email after insert notifications + if($sendNotif && $p_resets) + { + //send to alarm + $nameTo = 'User'; + $dataEmail = [ + 'email' => $emailTo, + 'name' => $nameTo, + 'subject' => 'Request Forgot Password from App Doctor Date '. date('Y-m-d H:i:s'), + 'body' => View::make('email/forgot_password', ['token' => $token])->render(), + ]; + Helper::sendEmail($dataEmail); + + $res = DB::table('password_resets') + ->where('email', '=', $request->email) + ->where('token', '=', $token) + ->get(); + + return ApiResponse::apiResponse("Success", $res, trans('Message.success'), 200); + } + else + { + return ApiResponse::apiResponse("Internal Server Error", $data, trans('Message.server_error'), 500); + } } - - $user->update([ - 'password' => Hash::make($request->confirm_new_password), - ]); - return response()->json($user); } } diff --git a/Modules/HospitalPortal/Routes/api.php b/Modules/HospitalPortal/Routes/api.php index 3b73f731..b1deb0c8 100644 --- a/Modules/HospitalPortal/Routes/api.php +++ b/Modules/HospitalPortal/Routes/api.php @@ -24,13 +24,15 @@ Route::prefix('v1')->group(function() { Route::prefix('hospitalportal')->group(function () { Route::middleware(Authentication::class)->group(function () { - Route::controller(AuthController::class)->group(function () { - Route::post('login', 'login'); + Route::middleware('switch.db')->group(function () { + Route::controller(AuthController::class)->group(function () { + Route::post('login', 'login'); + }); }); }); - - //Route::post('forget-password', [AuthController::class, 'forgetPassword'])->name('forget-password'); - //Route::post('verify-email', [AuthController::class, 'verifyEmail'])->name('verify-email'); + + Route::post('forgot-password', [AuthController::class, 'forgotPassword']); + // Route::post('verify-email', [AuthController::class, 'verifyEmail'])->name('verify-email'); Route::middleware('auth:sanctum')->group(function () { diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 456eb067..267e7d8e 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -206,7 +206,7 @@ class AppServiceProvider extends ServiceProvider $this->logAuditTrail($model, 'deleted'); }); - Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); + // Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); } private function logAuditTrail($model, $action) diff --git a/frontend/hospital-portal/public/lang/en-US.json b/frontend/hospital-portal/public/lang/en-US.json index 6adf54c1..19a07abf 100644 --- a/frontend/hospital-portal/public/lang/en-US.json +++ b/frontend/hospital-portal/public/lang/en-US.json @@ -14,7 +14,7 @@ "txtDialogMember3" : "Detail", "txtDialogMember4" : "Please select services", "txtDialogMember5" : "Admission Date", - "txtDialogMember6" : "Please select admission date", + "txtDialogMember6" : "Please select admission date", "txtWarningDischargeDate" : "Please select discharge date", "txtCreateAt" : "Create at", "txtDateBirth" : "Date of Birth", @@ -59,5 +59,6 @@ "txtApprove": "Approve", "txtDialogConfirmation": "Are you sure you want to proceed with this action?", "txtStartDate": "Start Date", - "txtEndDate": "End Date" + "txtEndDate": "End Date", + "txtHelp1" : "Has problem with your account?" } diff --git a/frontend/hospital-portal/public/lang/id-ID.json b/frontend/hospital-portal/public/lang/id-ID.json index 5bb34e8e..b2eb32f6 100644 --- a/frontend/hospital-portal/public/lang/id-ID.json +++ b/frontend/hospital-portal/public/lang/id-ID.json @@ -59,5 +59,6 @@ "txtApprove": "Terima", "txtDialogConfirmation": "Apakah Anda yakin ingin melanjutkan tindakan ini?", "txtStartDate": "Tanggal Mulai", - "txtEndDate": "Tanggal Akhir" + "txtEndDate": "Tanggal Akhir", + "txtHelp1" : "Punya masalah dengan akun Anda?" } diff --git a/frontend/hospital-portal/src/@types/auth.ts b/frontend/hospital-portal/src/@types/auth.ts index 3df73c76..9ccd1060 100644 --- a/frontend/hospital-portal/src/@types/auth.ts +++ b/frontend/hospital-portal/src/@types/auth.ts @@ -26,7 +26,7 @@ export type JWTContextType = { isInitialized: boolean; user: AuthUser; method: 'jwt'; - login: (email: string, password: string) => Promise; + login: (email: string, password: string, rememberMe: boolean) => Promise; register: (email: string, password: string, firstName: string, lastName: string) => Promise; logout: () => Promise; }; diff --git a/frontend/hospital-portal/src/components/Table.tsx b/frontend/hospital-portal/src/components/Table.tsx index 7334e85b..d1df63f1 100644 --- a/frontend/hospital-portal/src/components/Table.tsx +++ b/frontend/hospital-portal/src/components/Table.tsx @@ -93,7 +93,7 @@ export default function Table({ ]); params.setAppliedParams(parameters); }; - + const { localeData }: any = useContext(LanguageContext); /* -------------------------------------------------------------------------- */ @@ -106,7 +106,7 @@ export default function Table({ return ( - {selected.useSelected && selected.selectedRows.length > 0 ? ( + {selected.useSelected && selected.selectedRows.length > 0 ? ( <> @@ -169,10 +169,10 @@ export default function Table({ ))} - + )} - - + + ); @@ -294,7 +294,7 @@ export default function Table({ } - + ) : null } @@ -380,7 +380,7 @@ export default function Table({ - ) : null } + ) : null } {/* Export Report */} @@ -389,11 +389,11 @@ export default function Table({ - ) : null } + ) : null } @@ -428,7 +428,7 @@ export default function Table({ ):( - + ))} {headCells && @@ -443,7 +443,7 @@ export default function Table({ )) ) : ( - + {localeData.txtDataNotFound} diff --git a/frontend/hospital-portal/src/contexts/LaravelAuthContext.tsx b/frontend/hospital-portal/src/contexts/LaravelAuthContext.tsx index e5719f70..3371bf04 100644 --- a/frontend/hospital-portal/src/contexts/LaravelAuthContext.tsx +++ b/frontend/hospital-portal/src/contexts/LaravelAuthContext.tsx @@ -2,7 +2,7 @@ import { createContext, ReactNode, useEffect, useReducer } from 'react'; // utils import axios from '@/utils/axios'; // import { isValidToken, setSession } from '@/utils/jwt'; -import { setSession, getSession, setUser, getUser } from '@/utils/token'; +import { setSession, getSession, setUser, getUser, getCookie } from '@/utils/token'; // @types import { ActionMap, AuthState, AuthUser, JWTContextType } from '@/@types/auth'; // ---------------------------------------------------------------------- @@ -86,12 +86,16 @@ function AuthProvider({ children }: AuthProviderProps) { const initialize = async () => { try { const accessToken = getSession(); - if (accessToken) { - setSession(accessToken); + const rememberMe = getCookie('rememberMe') == 'OK' ? false : true; + + if (accessToken) { + const userString = getUser(); + const storedUser = userString ? JSON.parse(userString) : null; + setUser(storedUser, rememberMe); + setSession(accessToken, rememberMe); + const response = await axios.get('/user'); + const user = response.data; - const response = await axios.get('/user'); - const user = response.data; - dispatch({ type: Types.Initial, payload: { @@ -126,16 +130,16 @@ function AuthProvider({ children }: AuthProviderProps) { headers: { 'Accept': 'application/json', 'Content-Type' : 'application/json', - 'Accept-Language': (localStorage.getItem('currentLocale') ? localStorage.getItem('currentLocale') : 'id-ID'), + 'Accept-Language': localStorage.getItem('currentLocale') ?? 'id-ID', }, }; - const login = async (email: string, password: string) => axios + const login = async (email: string, password: string, rememberMe: boolean) => axios .post('/login', { email, password }, headers) .then((response) => { const { user, token } = response.data.data; - setSession(token); - setUser(user); + setSession(token, rememberMe); + setUser(user, rememberMe); dispatch({ type: Types.Login, @@ -168,8 +172,9 @@ function AuthProvider({ children }: AuthProviderProps) { }; const logout = async () => { - setSession(null); - setUser(null); + await axios.post('logout'); + setSession(null, false); + setUser(null, false); dispatch({ type: Types.Logout }); }; @@ -187,9 +192,9 @@ function AuthProvider({ children }: AuthProviderProps) { ); // if (state.isInitialized) { - // return (!state.isAuthenticated && location.pathname !== '/auth/login') ? + // return (!state.isAuthenticated && location.pathname !== '/auth/login') ? // () - // : false && location.pathname == '/auth/login' ? + // : false && location.pathname == '/auth/login' ? // () // : ( // - hospitaladmin@gmail.com + {storedUser?.email} diff --git a/frontend/hospital-portal/src/pages/auth/Login.tsx b/frontend/hospital-portal/src/pages/auth/Login.tsx index debeae42..d2ada8bf 100644 --- a/frontend/hospital-portal/src/pages/auth/Login.tsx +++ b/frontend/hospital-portal/src/pages/auth/Login.tsx @@ -78,29 +78,28 @@ export default function Login() { const smUp = useResponsive("up", "sm"); const mdUp = useResponsive("up", "md"); + const handleClick = () => { + window.location.href = 'https://wa.me/6285890008500'; + }; return ( - {/* + {smUp && ( - Has problem with your account? {""} + {localeData.txtHelp1} {""} { - window.location.href = - "mailto:admin@linksehat.com"; - e.preventDefault(); - }} + onClick={handleClick} > - Contact Us + {localeData.txtContactUs} - )}*/} + )} {/* {mdUp && ( @@ -116,7 +115,7 @@ export default function Login() { /> )} */} - + @@ -125,7 +124,7 @@ export default function Login() { alignItems="center" sx={{ mb: 5 }} > - + diff --git a/frontend/hospital-portal/src/routes/index.tsx b/frontend/hospital-portal/src/routes/index.tsx index a0165145..132c7b1d 100644 --- a/frontend/hospital-portal/src/routes/index.tsx +++ b/frontend/hospital-portal/src/routes/index.tsx @@ -52,7 +52,7 @@ export default function Router() { }, // { path: 'login-unprotected', element: }, // { path: 'register-unprotected', element: }, - { path: 'reset-password', element: }, + { path: 'forgot-password', element: }, { path: 'forget-password', element: }, // { path: 'verify', element: }, ], @@ -117,7 +117,7 @@ export default function Router() { } const Login = Loadable(lazy(() => import('@/pages/auth/Login'))); -const ResetPassword = Loadable(lazy(() => import('@/pages/auth/ResetPassword'))); +const ResetPassword = Loadable(lazy(() => import('@/pages/auth/VerifyCode'))); const ForgetPassword = Loadable(lazy(() => import('@/pages/auth/ForgetPassword'))); // Dashboard diff --git a/frontend/hospital-portal/src/routes/paths.ts b/frontend/hospital-portal/src/routes/paths.ts index 07f3aa11..e3735b97 100644 --- a/frontend/hospital-portal/src/routes/paths.ts +++ b/frontend/hospital-portal/src/routes/paths.ts @@ -12,5 +12,5 @@ export const PATH_AUTH = { loginUnprotected: path(ROOTS_AUTH, '/login-unprotected'), registerUnprotected: path(ROOTS_AUTH, '/register-unprotected'), verify: path(ROOTS_AUTH, '/verify'), - resetPassword: path(ROOTS_AUTH, '/reset-password'), + resetPassword: path(ROOTS_AUTH, '/forgot-password'), }; diff --git a/frontend/hospital-portal/src/sections/auth/login/LoginForm.tsx b/frontend/hospital-portal/src/sections/auth/login/LoginForm.tsx index a2383267..c0a0bfc3 100644 --- a/frontend/hospital-portal/src/sections/auth/login/LoginForm.tsx +++ b/frontend/hospital-portal/src/sections/auth/login/LoginForm.tsx @@ -60,7 +60,7 @@ export default function LoginForm() { const onSubmit = async (data: FormValuesProps) => { try { - const loginResult = await login(data.email, data.password); + const loginResult = await login(data.email, data.password, data.remember); navigate('/dashboard'); } catch (error) { @@ -100,10 +100,10 @@ export default function LoginForm() { - {/* + Forgot password? - */} + { +let expiredCookie = '12 * 60'; + +const setCookie = (name:any, value:any, days:any) => { + let expires = ""; + if (days) { + const date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + expires = "; expires=" + date.toUTCString(); + } + document.cookie = name + "=" + decodeURIComponent(value || "") + expires + "; path=/; SameSite=Strict"; + }; + +const setSession = (accessToken: string | null, rememberMe: boolean) => { if (accessToken) { - localStorage.setItem('accessToken', accessToken); + const userString = getUser(); + const storedUser = userString ? JSON.parse(userString) : null; + if(rememberMe) + { + localStorage.setItem('accessToken', accessToken); + } + else + { + setCookie('accessToken', accessToken, expiredCookie); + setCookie('rememberMe', 'OK', expiredCookie); + } + axios.defaults.headers.common.Authorization = `Bearer ${accessToken}`; - axios.defaults.headers.common['Accept-Language'] = (localStorage.getItem('currentLocale') ? localStorage.getItem('currentLocale') : 'id-ID'); + axios.defaults.headers.common['Accept-Language'] = localStorage.getItem('currentLocale') ?? 'id-ID'; axios.defaults.headers.common['Accept'] = 'application/json'; axios.defaults.headers.common['Content-Type'] = 'application/json'; + axios.defaults.headers.common['Organization-id'] = storedUser?.organization_id; // This function below will handle when token is expired // const { exp } = jwtDecode(accessToken); // handleTokenExpired(exp); } else { localStorage.removeItem('accessToken'); + removeCookie('accessToken'); + removeCookie('rememberMe'); delete axios.defaults.headers.common.Authorization; delete axios.defaults.headers.common['Accept-Language']; delete axios.defaults.headers.common['Accept']; - delete axios.defaults.headers.common['Content-Type']; + delete axios.defaults.headers.common['Content-Type']; } }; -const setUser = (user: any) => { +const setUser = (user: any, rememberMe: boolean) => { if (user) { - localStorage.setItem('user', user); + if(rememberMe) + { + localStorage.setItem('user', JSON.stringify(user)); + } + else + { + setCookie('user', JSON.stringify(user), expiredCookie); + setCookie('rememberMe', 'OK', expiredCookie); + } + } else { localStorage.removeItem('user'); + removeCookie('user'); + removeCookie('rememberMe'); } }; -const getSession = () => window.localStorage.getItem('accessToken') -const getUser = () => window.localStorage.getItem('user') +const getCookie = (name:any) => { + const cookies = document.cookie.split('; '); + for (let i = 0; i < cookies.length; i++) { + const cookiePair = cookies[i].split('='); + if (cookiePair[0] === name) { + return decodeURIComponent(cookiePair[1]); + } + } + return null; + }; -export { setSession, getSession, setUser, getUser }; +const getSession = () => { + const localToken = window.localStorage.getItem('accessToken'); + const cookieToken = getCookie('accessToken'); + // Prioritaskan token dari localStorage + return localToken || cookieToken; +}; +// const getUser = () => window.localStorage.getItem('user') || window.sessionStorage.getItem('user') +const getUser = () => { + const localUser = window.localStorage.getItem('user'); + const cookieUser = getCookie('user'); + + // Prioritaskan token dari localStorage + return localUser || cookieUser; +}; +const removeCookie = (name:any) => { + document.cookie = name + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; +}; + +export { setSession, getSession, setUser, getUser, getCookie };