Implemented Oauth
This commit is contained in:
150
frontend/package-lock.json
generated
150
frontend/package-lock.json
generated
@@ -10,7 +10,7 @@
|
||||
"dependencies": {
|
||||
"@react-pdf/renderer": "^4.1.6",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"axios": "^1.7.5",
|
||||
"axios": "^1.13.2",
|
||||
"bootstrap": "^5.3.3",
|
||||
"chart.js": "^4.4.7",
|
||||
"chartjs-plugin-datalabels": "^2.2.0",
|
||||
@@ -18,6 +18,7 @@
|
||||
"framer-motion": "^11.15.0",
|
||||
"frontend": "file:",
|
||||
"hamburger-react": "^2.5.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"pdfjs-dist": "^4.7.76",
|
||||
"postcss": "^8.4.40",
|
||||
"react": "^18.3.1",
|
||||
@@ -2007,13 +2008,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
|
||||
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
||||
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
@@ -2185,6 +2186,19 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -2641,6 +2655,20 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
@@ -2717,13 +2745,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@@ -2732,7 +2757,6 @@
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@@ -2763,10 +2787,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
|
||||
"integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
|
||||
"dev": true,
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
@@ -2775,14 +2799,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
|
||||
"integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
|
||||
"dev": true,
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.1"
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -3355,13 +3380,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3592,16 +3619,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"dev": true,
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -3610,6 +3642,19 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-symbol-description": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz",
|
||||
@@ -3684,12 +3729,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -3752,10 +3797,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"dev": true,
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@@ -3767,7 +3812,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
@@ -4336,6 +4380,15 @@
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/js-cookie": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
|
||||
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -4530,6 +4583,15 @@
|
||||
"url": "https://github.com/wojtekmaj/make-event-props?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/media-engine": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"dependencies": {
|
||||
"@react-pdf/renderer": "^4.1.6",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"axios": "^1.7.5",
|
||||
"axios": "^1.13.2",
|
||||
"bootstrap": "^5.3.3",
|
||||
"chart.js": "^4.4.7",
|
||||
"chartjs-plugin-datalabels": "^2.2.0",
|
||||
@@ -20,6 +20,7 @@
|
||||
"framer-motion": "^11.15.0",
|
||||
"frontend": "file:",
|
||||
"hamburger-react": "^2.5.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"pdfjs-dist": "^4.7.76",
|
||||
"postcss": "^8.4.40",
|
||||
"react": "^18.3.1",
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useSearchParams, useNavigate } from 'react-router-dom';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
const OAuthCallbackHandler = ({ children }) => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const token = searchParams.get('token');
|
||||
const loginSuccess = searchParams.get('login');
|
||||
|
||||
if (token && loginSuccess === 'success') {
|
||||
// Store token in cookie (matching backend cookie name)
|
||||
Cookies.set('access_token', token, {
|
||||
expires: 1, // 1 day
|
||||
path: '/',
|
||||
sameSite: 'Lax',
|
||||
secure: false, // set to true in production with HTTPS
|
||||
});
|
||||
|
||||
// Remove token from URL for security
|
||||
searchParams.delete('token');
|
||||
searchParams.delete('login');
|
||||
|
||||
// Update URL without the token parameter
|
||||
const newSearch = searchParams.toString();
|
||||
const currentPath = window.location.pathname;
|
||||
const newUrl = newSearch ? `${currentPath}?${newSearch}` : currentPath;
|
||||
|
||||
// Replace URL without reload
|
||||
window.history.replaceState({}, '', newUrl);
|
||||
|
||||
console.log('OAuth token stored successfully');
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
export default OAuthCallbackHandler;
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { useNavigate, useRouteLoaderData } from "react-router-dom";
|
||||
import OAuthCallbackHandler from "../../components/OAuthCallback/OAuthCallbackHandler";
|
||||
|
||||
function Dashboard() {
|
||||
const { role, user } =
|
||||
@@ -15,7 +16,8 @@ function Dashboard() {
|
||||
const greetingLine2 = `${designation} in ${department} Department, ${institute}`;
|
||||
|
||||
return (
|
||||
<div className="font-sans bg-white overflow-y-scroll scroll-smooth snap-y h-screen" >
|
||||
<OAuthCallbackHandler>
|
||||
<div className="font-sans bg-white overflow-y-scroll scroll-smooth snap-y h-screen" >
|
||||
{/* Hero Section */}
|
||||
<section
|
||||
className="relative w-full h-screen flex items-center justify-center text-white overflow-hidden bg-cover bg-center snap-start"
|
||||
@@ -161,7 +163,8 @@ function Dashboard() {
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
</OAuthCallbackHandler>
|
||||
);
|
||||
}
|
||||
|
||||
export default Dashboard;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
import './Login.css';
|
||||
import loginPageBg from '/images/campus_bg.jpeg';
|
||||
@@ -8,6 +9,26 @@ import ValidatorLogin from './components/ValidatorLogin';
|
||||
|
||||
const Login = () => {
|
||||
const [isApplicant, setIsApplicant] = useState(true);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const error = searchParams.get('error');
|
||||
if (error === 'auth_failed') {
|
||||
setErrorMessage('Google authentication failed. Please try again.');
|
||||
} else if (error === 'invalid_intent') {
|
||||
setErrorMessage('Invalid login type. Please try again.');
|
||||
}
|
||||
|
||||
// Clear error from URL after displaying
|
||||
if (error) {
|
||||
setTimeout(() => {
|
||||
searchParams.delete('error');
|
||||
setSearchParams(searchParams);
|
||||
setErrorMessage('');
|
||||
}, 5000);
|
||||
}
|
||||
}, [searchParams, setSearchParams]);
|
||||
|
||||
const toggleRole = () => {
|
||||
setIsApplicant(!isApplicant);
|
||||
@@ -17,6 +38,24 @@ const Login = () => {
|
||||
<div className="login-page">
|
||||
<img src={loginPageBg} className='loginPage_bg' />
|
||||
<div className='login'>
|
||||
{errorMessage && (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '20px',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
backgroundColor: '#f44336',
|
||||
color: 'white',
|
||||
padding: '12px 24px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.2)',
|
||||
zIndex: 1000,
|
||||
maxWidth: '90%',
|
||||
textAlign: 'center'
|
||||
}}>
|
||||
{errorMessage}
|
||||
</div>
|
||||
)}
|
||||
<div className={`login-container`}>
|
||||
{isApplicant ? (
|
||||
<>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import './LoginAnimation.css';
|
||||
import React, { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import "./LoginAnimation.css";
|
||||
|
||||
function ApplicantLogin({ changeRole }) {
|
||||
const [credentials, setCredentials] = useState({ email: 'faculty.computer.kjsce@example.com', password: 'securePassword123' });
|
||||
const [credentials, setCredentials] = useState({
|
||||
email: "faculty.computer.kjsce@example.com",
|
||||
password: "securePassword123",
|
||||
});
|
||||
const [animate, setAnimate] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [error, setError] = useState("");
|
||||
const [loading, setLoading] = useState(false); // Loading state
|
||||
|
||||
const handleChangeRole = () => {
|
||||
@@ -17,68 +20,91 @@ function ApplicantLogin({ changeRole }) {
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
|
||||
// Basic Validation
|
||||
if (!credentials.email || !credentials.password) {
|
||||
setError('Please enter both email and password.');
|
||||
setError("Please enter both email and password.");
|
||||
return;
|
||||
}
|
||||
if (!/\S+@\S+\.\S+/.test(credentials.email)) {
|
||||
setError('Please enter a valid email address.');
|
||||
setError("Please enter a valid email address.");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true); // Show loading state
|
||||
setError(''); // Reset previous errors
|
||||
setError(""); // Reset previous errors
|
||||
|
||||
try {
|
||||
const response = await fetch(`${import.meta.env.VITE_APP_API_URL}/applicant-login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
const response = await fetch(
|
||||
`${import.meta.env.VITE_APP_API_URL}/applicant-login`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify(credentials),
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(credentials),
|
||||
});
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
if (response.ok) {
|
||||
window.location.href = '/applicant/dashboard';
|
||||
window.location.href = "/applicant/dashboard";
|
||||
} else {
|
||||
setError(result.message || 'Invalid login credentials.');
|
||||
setError(result.message || "Invalid login credentials.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during login:', error);
|
||||
setError('An error occurred. Please try again later.');
|
||||
console.error("Error during login:", error);
|
||||
setError("An error occurred. Please try again later.");
|
||||
} finally {
|
||||
setLoading(false); // Hide loading state
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoogleLogin = () => {
|
||||
//I personally would transfer all the apis to the Auth.js in APIFactory dir, but I am following the project's code style
|
||||
// Google OAuth requires a full page redirect, not a fetch call
|
||||
const designation = "applicant";
|
||||
// Redirect to backend OAuth endpoint - it will handle the Google redirect
|
||||
window.location.href = `${import.meta.env.VITE_APP_API_URL}/auth/oauth/${designation}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row bg-red-700 shadow-lg rounded-lg overflow-hidden max-w-4xl mx-auto">
|
||||
<div className={`w-full md:w-3/4 bg-red-700 p-4 flex flex-col justify-center ${animate ? 'slide-out-right' : 'fade-in-fwd'}`}>
|
||||
<h2 className="text-white text-xl md:text-2xl lg:text-3xl font-bold mb-3 hidden md:block">Travel Policy</h2>
|
||||
<div
|
||||
className={`w-full md:w-3/4 bg-red-700 p-4 flex flex-col justify-center ${animate ? "slide-out-right" : "fade-in-fwd"}`}
|
||||
>
|
||||
<h2 className="text-white text-xl md:text-2xl lg:text-3xl font-bold mb-3 hidden md:block">
|
||||
Travel Policy
|
||||
</h2>
|
||||
<p className="text-white text-sm md:text-base mb-6 hidden md:block">
|
||||
Our web application simplifies the process of requesting, approving, and managing financial support for research students and associates.
|
||||
Our web application simplifies the process of requesting, approving,
|
||||
and managing financial support for research students and associates.
|
||||
</p>
|
||||
<h3 className="text-white text-lg md:text-xl font-bold">Validator?</h3>
|
||||
<p className="text-white mb-3">Go to Validator’s Sign in</p>
|
||||
<button
|
||||
type='button'
|
||||
className="bg-white text-red-700 text-sm md:text-base px-3 py-1.5 rounded-full font-semibold shadow-md hover:bg-gray-100 transition"
|
||||
<button
|
||||
type="button"
|
||||
className="bg-white text-red-700 text-sm md:text-base px-3 py-1.5 rounded-full font-semibold shadow-md hover:bg-gray-100 transition"
|
||||
onClick={handleChangeRole}
|
||||
>
|
||||
Click Here
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={`bg-white w-full md:w-3/4 p-8 flex flex-col justify-center ${animate ? 'text-blur-out' : 'fade-in-fwd'}`}>
|
||||
<h2 className="text-lg md:text-xl lg:text-2xl font-bold mb-3">Login for Applicants<span role="img" aria-label="wave">👋</span></h2>
|
||||
<button
|
||||
type='button'
|
||||
<div
|
||||
className={`bg-white w-full md:w-3/4 p-8 flex flex-col justify-center ${animate ? "text-blur-out" : "fade-in-fwd"}`}
|
||||
>
|
||||
<h2 className="text-lg md:text-xl lg:text-2xl font-bold mb-3">
|
||||
Login for Applicants
|
||||
<span role="img" aria-label="wave">
|
||||
👋
|
||||
</span>
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
className="bg-gray-100 text-gray-700 text-sm md:text-base px-4 py-2 rounded-full font-semibold mb-3 shadow-md flex items-center justify-center hover:bg-gray-200 transition-transform transform hover:scale-105"
|
||||
onClick={handleSubmit}
|
||||
onClick={handleGoogleLogin}
|
||||
>
|
||||
<svg
|
||||
className="w-6 h-6 mr-2"
|
||||
@@ -88,46 +114,72 @@ function ApplicantLogin({ changeRole }) {
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M29.074 13.3887H28V13.3333H16V18.6667H23.5354C22.436 21.7713 19.482 24 16 24C11.582 24 8.00002 20.418 8.00002 16C8.00002 11.582 11.582 8 16 8C18.0394 8 19.8947 8.76934 21.3074 10.026L25.0787 6.25467C22.6974 4.03534 19.512 2.66667 16 2.66667C8.63669 2.66667 2.66669 8.63667 2.66669 16C2.66669 23.3633 8.63669 29.3333 16 29.3333C23.3634 29.3333 29.3334 23.3633 29.3334 16C29.3334 15.106 29.2414 14.2333 29.074 13.3887Z" fill="#FFC107"/>
|
||||
<path d="M4.20398 9.794L8.58465 13.0067C9.76998 10.072 12.6406 8 16 8C18.0393 8 19.8946 8.76934 21.3073 10.026L25.0786 6.25467C22.6973 4.03534 19.512 2.66667 16 2.66667C10.8786 2.66667 6.43731 5.558 4.20398 9.794Z" fill="#FF3D00"/>
|
||||
<path d="M16 29.3333C19.444 29.3333 22.5733 28.0153 24.9393 25.872L20.8127 22.38C19.429 23.4323 17.7383 24.0014 16 24C12.532 24 9.58734 21.7887 8.478 18.7027L4.13 22.0527C6.33667 26.3707 10.818 29.3333 16 29.3333Z" fill="#4CAF50"/>
|
||||
<path d="M29.074 13.3887H28V13.3333H16V18.6667H23.5353C23.0095 20.1443 22.0622 21.4354 20.8107 22.3807L20.8127 22.3793L24.9393 25.8713C24.6473 26.1367 29.3333 22.6667 29.3333 16C29.3333 15.106 29.2413 14.2333 29.074 13.3887Z" fill="#1976D2"/>
|
||||
<path
|
||||
d="M29.074 13.3887H28V13.3333H16V18.6667H23.5354C22.436 21.7713 19.482 24 16 24C11.582 24 8.00002 20.418 8.00002 16C8.00002 11.582 11.582 8 16 8C18.0394 8 19.8947 8.76934 21.3074 10.026L25.0787 6.25467C22.6974 4.03534 19.512 2.66667 16 2.66667C8.63669 2.66667 2.66669 8.63667 2.66669 16C2.66669 23.3633 8.63669 29.3333 16 29.3333C23.3634 29.3333 29.3334 23.3633 29.3334 16C29.3334 15.106 29.2414 14.2333 29.074 13.3887Z"
|
||||
fill="#FFC107"
|
||||
/>
|
||||
<path
|
||||
d="M4.20398 9.794L8.58465 13.0067C9.76998 10.072 12.6406 8 16 8C18.0393 8 19.8946 8.76934 21.3073 10.026L25.0786 6.25467C22.6973 4.03534 19.512 2.66667 16 2.66667C10.8786 2.66667 6.43731 5.558 4.20398 9.794Z"
|
||||
fill="#FF3D00"
|
||||
/>
|
||||
<path
|
||||
d="M16 29.3333C19.444 29.3333 22.5733 28.0153 24.9393 25.872L20.8127 22.38C19.429 23.4323 17.7383 24.0014 16 24C12.532 24 9.58734 21.7887 8.478 18.7027L4.13 22.0527C6.33667 26.3707 10.818 29.3333 16 29.3333Z"
|
||||
fill="#4CAF50"
|
||||
/>
|
||||
<path
|
||||
d="M29.074 13.3887H28V13.3333H16V18.6667H23.5353C23.0095 20.1443 22.0622 21.4354 20.8107 22.3807L20.8127 22.3793L24.9393 25.8713C24.6473 26.1367 29.3333 22.6667 29.3333 16C29.3333 15.106 29.2413 14.2333 29.074 13.3887Z"
|
||||
fill="#1976D2"
|
||||
/>
|
||||
</svg>
|
||||
Login With Google
|
||||
</button>
|
||||
<p className="text-center text-gray-500 text-xs md:text-sm mb-3">or use email</p>
|
||||
<p className="text-center text-gray-500 text-xs md:text-sm mb-3">
|
||||
or use email
|
||||
</p>
|
||||
|
||||
{/* Display Error Message */}
|
||||
{error && <div className="text-red-600 text-sm mb-3">{error}</div>}
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
placeholder="Email"
|
||||
<input
|
||||
placeholder="Email"
|
||||
className="w-full mb-3 p-2 border border-gray-300 rounded-lg text-sm md:text-base focus:outline-none focus:ring-2 focus:ring-red-500"
|
||||
value={credentials.email}
|
||||
onChange={(event) => setCredentials(prev => ({ ...prev, email: event.target.value }))}
|
||||
value={credentials.email}
|
||||
onChange={(event) =>
|
||||
setCredentials((prev) => ({ ...prev, email: event.target.value }))
|
||||
}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
className="w-full mb-3 p-2 border border-gray-300 rounded-lg text-sm md:text-base focus:outline-none focus:ring-2 focus:ring-red-500"
|
||||
autoComplete='on'
|
||||
value={credentials.password}
|
||||
onChange={(event) => setCredentials(prev => ({ ...prev, password: event.target.value }))}
|
||||
autoComplete="on"
|
||||
value={credentials.password}
|
||||
onChange={(event) =>
|
||||
setCredentials((prev) => ({
|
||||
...prev,
|
||||
password: event.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<div className="flex flex-col md:flex-row items-center justify-between mb-3">
|
||||
<label className="flex items-center mb-2 md:mb-0 text-sm md:text-base">
|
||||
<input type="checkbox" className="mr-2" />
|
||||
<span>Remember me</span>
|
||||
</label>
|
||||
<a href="#" className="text-red-700 text-sm md:text-base hover:underline">Forgot Password?</a>
|
||||
<a
|
||||
href="#"
|
||||
className="text-red-700 text-sm md:text-base hover:underline"
|
||||
>
|
||||
Forgot Password?
|
||||
</a>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className={`bg-red-700 text-white text-sm md:text-base w-full py-2 rounded-lg font-semibold shadow-md hover:bg-red-800 transition ${loading ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||
<button
|
||||
type="submit"
|
||||
className={`bg-red-700 text-white text-sm md:text-base w-full py-2 rounded-lg font-semibold shadow-md hover:bg-red-800 transition ${loading ? "opacity-50 cursor-not-allowed" : ""}`}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Logging in...' : 'Log in'}
|
||||
{loading ? "Logging in..." : "Log in"}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import './LoginAnimation.css';
|
||||
import React, { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import "./LoginAnimation.css";
|
||||
|
||||
function ValidatorLogin({ changeRole }) {
|
||||
const [credentials, setCredentials] = useState({ email: 'hod.computer.kjsce@example.com', password: 'securePassword123' });
|
||||
const [credentials, setCredentials] = useState({
|
||||
email: "hod.computer.kjsce@example.com",
|
||||
password: "securePassword123",
|
||||
});
|
||||
const [animate, setAnimate] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [error, setError] = useState("");
|
||||
const [loading, setLoading] = useState(false); // Loading state
|
||||
|
||||
const handleChangeRole = () => {
|
||||
@@ -26,52 +29,69 @@ function ValidatorLogin({ changeRole }) {
|
||||
|
||||
// Basic validation
|
||||
if (!credentials.email || !credentials.password) {
|
||||
setError('Please enter both email and password.');
|
||||
setError("Please enter both email and password.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate email format
|
||||
if (!validateEmail(credentials.email)) {
|
||||
setError('Please enter a valid email address.');
|
||||
setError("Please enter a valid email address.");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true); // Show loading state
|
||||
setError(''); // Reset previous errors
|
||||
setError(""); // Reset previous errors
|
||||
|
||||
try {
|
||||
const response = await fetch(`${import.meta.env.VITE_APP_API_URL}/validator-login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
const response = await fetch(
|
||||
`${import.meta.env.VITE_APP_API_URL}/validator-login`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify(credentials),
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(credentials),
|
||||
});
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
if (response.ok) {
|
||||
// Handle successful login (navigate, store tokens, etc.)
|
||||
window.location.href = '/validator/dashboard';
|
||||
window.location.href = "/validator/dashboard";
|
||||
} else {
|
||||
setError(result.message || 'Invalid login credentials.');
|
||||
setError(result.message || "Invalid login credentials.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during login:', error);
|
||||
setError('An error occurred. Please try again later.');
|
||||
console.error("Error during login:", error);
|
||||
setError("An error occurred. Please try again later.");
|
||||
} finally {
|
||||
setLoading(false); // Hide loading state
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoogleLogin = () => {
|
||||
// Google OAuth requires a full page redirect, not a fetch call
|
||||
const designation = "validator";
|
||||
// Redirect to backend OAuth endpoint - it will handle the Google redirect
|
||||
window.location.href = `${import.meta.env.VITE_APP_API_URL}/auth/oauth/${designation}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row bg-red-700 shadow-lg rounded-lg overflow-hidden max-w-4xl mx-auto">
|
||||
<div className={`bg-white w-full md:w-3/4 p-8 flex flex-col justify-center ${animate ? 'text-blur-out' : 'fade-in-fwd'}`}>
|
||||
<h2 className="text-lg md:text-xl lg:text-2xl font-bold mb-3">Login for Validator<span role="img" aria-label="wave">👋</span></h2>
|
||||
<button
|
||||
type='button'
|
||||
<div
|
||||
className={`bg-white w-full md:w-3/4 p-8 flex flex-col justify-center ${animate ? "text-blur-out" : "fade-in-fwd"}`}
|
||||
>
|
||||
<h2 className="text-lg md:text-xl lg:text-2xl font-bold mb-3">
|
||||
Login for Validator
|
||||
<span role="img" aria-label="wave">
|
||||
👋
|
||||
</span>
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
className="bg-gray-100 text-gray-700 text-sm md:text-base px-4 py-2 rounded-full font-semibold mb-3 shadow-md flex items-center justify-center hover:bg-gray-200 transition-transform transform hover:scale-105"
|
||||
onClick={handleSubmit}
|
||||
onClick={handleGoogleLogin}
|
||||
>
|
||||
<svg
|
||||
className="w-6 h-6 mr-2" // Adjust the size of the icon if needed
|
||||
@@ -81,32 +101,53 @@ function ValidatorLogin({ changeRole }) {
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M29.074 13.3887H28V13.3333H16V18.6667H23.5354C22.436 21.7713 19.482 24 16 24C11.582 24 8.00002 20.418 8.00002 16C8.00002 11.582 11.582 8 16 8C18.0394 8 19.8947 8.76934 21.3074 10.026L25.0787 6.25467C22.6974 4.03534 19.512 2.66667 16 2.66667C8.63669 2.66667 2.66669 8.63667 2.66669 16C2.66669 23.3633 8.63669 29.3333 16 29.3333C23.3634 29.3333 29.3334 23.3633 29.3334 16C29.3334 15.106 29.2414 14.2333 29.074 13.3887Z" fill="#FFC107"/>
|
||||
<path d="M4.20398 9.794L8.58465 13.0067C9.76998 10.072 12.6406 8 16 8C18.0393 8 19.8946 8.76934 21.3073 10.026L25.0786 6.25467C22.6973 4.03534 19.512 2.66667 16 2.66667C10.8786 2.66667 6.43731 5.558 4.20398 9.794Z" fill="#FF3D00"/>
|
||||
<path d="M16 29.3333C19.444 29.3333 22.5733 28.0153 24.9393 25.872L20.8127 22.38C19.429 23.4323 17.7383 24.0014 16 24C12.532 24 9.58734 21.7887 8.478 18.7027L4.13 22.0527C6.33667 26.3707 10.818 29.3333 16 29.3333Z" fill="#4CAF50"/>
|
||||
<path d="M29.074 13.3887H28V13.3333H16V18.6667H23.5353C23.0095 20.1443 22.0622 21.4354 20.8107 22.3807L20.8127 22.3793L24.9393 25.8713C24.6473 26.1367 29.3333 22.6667 29.3333 16C29.3333 15.106 29.2413 14.2333 29.074 13.3887Z" fill="#1976D2"/>
|
||||
<path
|
||||
d="M29.074 13.3887H28V13.3333H16V18.6667H23.5354C22.436 21.7713 19.482 24 16 24C11.582 24 8.00002 20.418 8.00002 16C8.00002 11.582 11.582 8 16 8C18.0394 8 19.8947 8.76934 21.3074 10.026L25.0787 6.25467C22.6974 4.03534 19.512 2.66667 16 2.66667C8.63669 2.66667 2.66669 8.63667 2.66669 16C2.66669 23.3633 8.63669 29.3333 16 29.3333C23.3634 29.3333 29.3334 23.3633 29.3334 16C29.3334 15.106 29.2414 14.2333 29.074 13.3887Z"
|
||||
fill="#FFC107"
|
||||
/>
|
||||
<path
|
||||
d="M4.20398 9.794L8.58465 13.0067C9.76998 10.072 12.6406 8 16 8C18.0393 8 19.8946 8.76934 21.3073 10.026L25.0786 6.25467C22.6973 4.03534 19.512 2.66667 16 2.66667C10.8786 2.66667 6.43731 5.558 4.20398 9.794Z"
|
||||
fill="#FF3D00"
|
||||
/>
|
||||
<path
|
||||
d="M16 29.3333C19.444 29.3333 22.5733 28.0153 24.9393 25.872L20.8127 22.38C19.429 23.4323 17.7383 24.0014 16 24C12.532 24 9.58734 21.7887 8.478 18.7027L4.13 22.0527C6.33667 26.3707 10.818 29.3333 16 29.3333Z"
|
||||
fill="#4CAF50"
|
||||
/>
|
||||
<path
|
||||
d="M29.074 13.3887H28V13.3333H16V18.6667H23.5353C23.0095 20.1443 22.0622 21.4354 20.8107 22.3807L20.8127 22.3793L24.9393 25.8713C24.6473 26.1367 29.3333 22.6667 29.3333 16C29.3333 15.106 29.2413 14.2333 29.074 13.3887Z"
|
||||
fill="#1976D2"
|
||||
/>
|
||||
</svg>
|
||||
Login With Google
|
||||
</button>
|
||||
<p className="text-center text-gray-500 text-xs md:text-sm mb-3">or use email</p>
|
||||
<p className="text-center text-gray-500 text-xs md:text-sm mb-3">
|
||||
or use email
|
||||
</p>
|
||||
|
||||
{/* Display Error Message */}
|
||||
{error && <div className="text-red-600 text-sm mb-3">{error}</div>}
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
placeholder="Email"
|
||||
<input
|
||||
placeholder="Email"
|
||||
className="w-full mb-3 p-2 border border-gray-300 rounded-lg text-sm md:text-base focus:outline-none focus:ring-2 focus:ring-red-500"
|
||||
value={credentials.email}
|
||||
onChange={(event) => setCredentials(prev => ({ ...prev, email: event.target.value }))}
|
||||
value={credentials.email}
|
||||
onChange={(event) =>
|
||||
setCredentials((prev) => ({ ...prev, email: event.target.value }))
|
||||
}
|
||||
/>
|
||||
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
className="w-full mb-3 p-2 border border-gray-300 rounded-lg text-sm md:text-base focus:outline-none focus:ring-2 focus:ring-red-500"
|
||||
value={credentials.password}
|
||||
onChange={(event) => setCredentials(prev => ({ ...prev, password: event.target.value }))}
|
||||
value={credentials.password}
|
||||
onChange={(event) =>
|
||||
setCredentials((prev) => ({
|
||||
...prev,
|
||||
password: event.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col md:flex-row items-center justify-between mb-3">
|
||||
@@ -114,27 +155,43 @@ function ValidatorLogin({ changeRole }) {
|
||||
<input type="checkbox" className="mr-2" />
|
||||
<span>Remember me</span>
|
||||
</label>
|
||||
<a href="#" className="text-red-700 text-sm md:text-base hover:underline">Forgot Password?</a>
|
||||
<a
|
||||
href="#"
|
||||
className="text-red-700 text-sm md:text-base hover:underline"
|
||||
>
|
||||
Forgot Password?
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className={`bg-red-700 text-white text-sm md:text-base w-full py-2 rounded-lg font-semibold shadow-md hover:bg-red-800 transition ${loading ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||
<button
|
||||
type="submit"
|
||||
className={`bg-red-700 text-white text-sm md:text-base w-full py-2 rounded-lg font-semibold shadow-md hover:bg-red-800 transition ${loading ? "opacity-50 cursor-not-allowed" : ""}`}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Logging in...' : 'Log in'}
|
||||
{loading ? "Logging in..." : "Log in"}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className={`w-full md:w-3/4 bg-red-700 p-4 flex flex-col justify-center ${animate ? 'slide-out-left' : 'fade-in-fwd'}`}>
|
||||
<h2 className="text-white text-xl md:text-2xl lg:text-3xl font-bold mb-3 hidden md:block">Travel Policy</h2>
|
||||
<div
|
||||
className={`w-full md:w-3/4 bg-red-700 p-4 flex flex-col justify-center ${animate ? "slide-out-left" : "fade-in-fwd"}`}
|
||||
>
|
||||
<h2 className="text-white text-xl md:text-2xl lg:text-3xl font-bold mb-3 hidden md:block">
|
||||
Travel Policy
|
||||
</h2>
|
||||
<p className="text-white text-sm md:text-base mb-6 hidden md:block">
|
||||
Our web application simplifies the process of requesting, approving, and managing financial support for research students and associates.
|
||||
Our web application simplifies the process of requesting, approving,
|
||||
and managing financial support for research students and associates.
|
||||
</p>
|
||||
<h3 className="text-white text-lg md:text-xl font-bold">Applicant?</h3>
|
||||
<p className="text-white mb-3">Go to Applicant’s Sign in</p>
|
||||
<button type='button' className="bg-white text-red-700 text-sm md:text-base px-3 py-1.5 rounded-full font-semibold shadow-md hover:bg-gray-100 transition" onClick={handleChangeRole}>Click Here</button>
|
||||
<button
|
||||
type="button"
|
||||
className="bg-white text-red-700 text-sm md:text-base px-3 py-1.5 rounded-full font-semibold shadow-md hover:bg-gray-100 transition"
|
||||
onClick={handleChangeRole}
|
||||
>
|
||||
Click Here
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user