Compare commits

...

1 Commits

Author SHA1 Message Date
dc12c47491 frontend Added 2025-10-11 21:51:06 +05:30
31 changed files with 19388 additions and 1 deletions

1
.gitignore vendored
View File

@ -1,3 +1,2 @@
.env
.venv
frontend

22
frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

0
frontend/5.2.0 Normal file
View File

70
frontend/README.md Normal file
View File

@ -0,0 +1,70 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
The page will reload when you make changes.\
You may also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

17777
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

47
frontend/package.json Normal file
View File

@ -0,0 +1,47 @@
{
"name": "my-frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.3.8",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-router-dom": "^6.30.1",
"react-scripts": "5.0.1",
"styled-components": "^6.1.19",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"autoprefixer": "^10.4.21",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.14"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 KiB

BIN
frontend/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
frontend/public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
frontend/public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

54
frontend/src/App.css Normal file
View File

@ -0,0 +1,54 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.toast-alert {
position: fixed;
top: 20px;
right: -400px; /* Start off-screen */
max-width: 350px;
z-index: 1050;
transition: right 0.3s ease-out;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.toast-alert.show {
right: 20px; /* Slide in */
}

34
frontend/src/App.js Normal file
View File

@ -0,0 +1,34 @@
// src/App.jsx
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Login from './components/Login';
import LandingPage from './components/LandingPage';
import VerifyEmail from './components/VerifyEmail';
import Navbar from './components/Navbar';
import Dashboard from './pages/Dashboard';
import ResetPassword from './components/ResetPassword';
function App() {
return (
<Router>
<div className="App">
<Routes>
<Route path="/login" element={<Login />} />
<Route path='/reset-password' element={<ResetPassword />} />
<Route path="/verify-email" element={<VerifyEmail />} />
<Route path="/dashboard" element={<Dashboard/>} />
<Route
path="/"
element={
<>
{/* <Navbar />
<LandingPage /> */}
< Login />
</>
}
/>
</Routes>
</div>
</Router>
);
}
export default App;

8
frontend/src/App.test.js Normal file
View File

@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View File

@ -0,0 +1,44 @@
import React from 'react';
import { Link } from 'react-router-dom';
export default function LandingPage() {
return (
<div className="vh-100 bg-light d-flex flex-column">
<main className="flex-grow-1 d-flex align-items-center justify-content-center p-4">
<div className="text-center" style={{ maxWidth: '600px' }}>
<h1 className="fw-bold display-5 mb-3" style={{ color: '#333' }}>
Your Personal <br />
<span className="text-primary" style={{color:'#00b4d8'}}>AI Doc/PDF</span> <br />
Generator
</h1>
<p className="text-muted mb-4" style={{ fontSize: '0.9rem' }}>
Generate professional documents quickly and efficiently with AI.
</p>
<div className="d-flex flex-wrap gap-3 justify-content-center">
<Link
to="/login"
className="btn btn-lg px-4 rounded-pill fw-bold"
style={{
backgroundColor: '#00b4d8',
borderColor: '#00b4d8',
color: 'white',
textDecoration: 'none', // removes underline
display: 'inline-block', // ensures button-like behavior
}}
onMouseEnter={(e) => {
e.target.style.backgroundColor = '#009ecf';
}}
onMouseLeave={(e) => {
e.target.style.backgroundColor = '#00b4d8';
}}
>
Log in
</Link>
</div>
</div>
</main>
</div>
);
}

View File

@ -0,0 +1,304 @@
import React, { useState, useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import '../App.css';
export default function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [rememberMe, setRememberMe] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
// Toast state: array of toasts
const [toasts, setToasts] = useState([]);
const navigate = useNavigate();
// Load attempt count from localStorage on mount
useEffect(() => {
const saved = localStorage.getItem('loginAttempts');
if (saved) {
const attempts = JSON.parse(saved);
const now = Date.now();
const validAttempts = {};
for (const [key, value] of Object.entries(attempts)) {
if (now - value.timestamp < 3600000) {
validAttempts[key] = value;
}
}
localStorage.setItem('loginAttempts', JSON.stringify(validAttempts));
}
}, []);
const getAttemptCount = (email) => {
const attempts = JSON.parse(localStorage.getItem('loginAttempts') || '{}');
return attempts[email]?.count || 0;
};
const incrementAttempt = (email) => {
const attempts = JSON.parse(localStorage.getItem('loginAttempts') || '{}');
const now = Date.now();
attempts[email] = {
count: (attempts[email]?.count || 0) + 1,
timestamp: now,
};
localStorage.setItem('loginAttempts', JSON.stringify(attempts));
};
const clearAttempts = (email) => {
const attempts = JSON.parse(localStorage.getItem('loginAttempts') || '{}');
delete attempts[email];
localStorage.setItem('loginAttempts', JSON.stringify(attempts));
};
// Add toast
// Add toast with 5-second timer
const addToast = (message, type = 'danger') => {
const id = Date.now();
const duration = 5000; // 5 seconds
// Add toast
setToasts(prev => [
...prev,
{ id, message, type, createdAt: Date.now(), duration, progress: 100 }
]);
// Start interval to update progress every 50ms
const interval = setInterval(() => {
setToasts(prev =>
prev.map(toast =>
toast.id === id
? {
...toast,
progress: Math.max(0, toast.progress - (30 / duration) * 100)
}
: toast
)
);
}, 30);
// Auto-dismiss after duration
setTimeout(() => {
clearInterval(interval);
setToasts(prev => prev.filter(t => t.id !== id));
}, duration);
};
// Remove toast by ID
const removeToast = (id) => {
setToasts(prev => prev.filter(t => t.id !== id));
};
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
setLoading(true);
if (!email || !password) {
setError('Please fill in all fields');
setLoading(false);
return;
}
const attemptCount = getAttemptCount(email);
if (attemptCount >= 6) {
addToast(`You've exceeded login attempts for ${email}. Please reset your password.`, 'danger');
setLoading(false);
return;
}
try {
// 🔌 REPLACE WITH YOUR LOGIN API
/*
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password, rememberMe }),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || 'Invalid credentials');
}
// Success: clear attempts, save token, redirect
clearAttempts(email);
localStorage.setItem('token', data.token);
navigate('/dashboard');
return;
*/
// 🧪 Simulate: let's say "test@example.com" / "123456" works
if (email === 'test@example.com' && password === '123456') {
clearAttempts(email);
alert('✅ Login successful!');
navigate('/dashboard');
} else {
throw new Error('Invalid email or password');
}
} catch (err) {
incrementAttempt(email);
const newCount = getAttemptCount(email);
if (newCount >= 6) {
addToast(`You've exceeded login attempts for ${email}. Please reset your password.`, 'danger');
setError('');
} else {
setError(`${err.message} (${6 - newCount} attempts left)`);
}
} finally {
setLoading(false);
}
};
return (
<>
{/* Background & Overlay */}
<div className="position-absolute top-0 start-0 w-100 h-100" style={{ backgroundImage: 'url(/assets/images/3.png)', backgroundSize: 'cover', backgroundPosition: 'center' }}></div>
{/* <div className="position-absolute top-0 start-0 w-100 h-100 bg-white opacity-75"></div> */}
{/* Toast Stack (Right Side) */}
{/* Toast Stack (Right Side) */}
<div className="position-fixed top-4 end-4 d-flex flex-column gap-2" style={{ zIndex: 1050 }}>
{toasts.map(toast => {
// Calculate progress percentage (0% to 100%)
const elapsed = Date.now() - toast.createdAt;
const progress = Math.max(0, 100 - (elapsed / toast.duration) * 100);
return (
<div
key={toast.id}
className={`alert alert-${toast.type} p-3 rounded shadow-sm position-relative`}
style={{
width: '300px',
borderLeft: `4px solid ${toast.type === 'danger' ? '#dc3545' : '#ffc107'}`,
animation: 'slideInRight 0.3s ease-out',
}}
>
<div className="d-flex justify-content-between align-items-start">
<div className="d-flex align-items-center gap-2">
<span style={{ fontSize: '1.1rem' }}>
{toast.type === 'danger' ? '⚠️' : ''}
</span>
<p className="mb-0 small">{toast.message}</p>
</div>
<button
type="button"
className="btn-close"
onClick={() => removeToast(toast.id)}
style={{ fontSize: '0.8rem' }}
></button>
</div>
{/* Timer Progress Bar */}
<div className="mt-2" style={{ height: '4px', backgroundColor: '#e9ecef', borderRadius: '2px', overflow: 'hidden' }}>
<div
className="h-100"
style={{
width: `${progress}%`,
backgroundColor: toast.type === 'danger' ? '#dc3545' : '#ffc107',
transition: 'width 0.1s linear',
}}
></div>
</div>
</div>
);
})}
</div>
{/* Login Card */}
<div className="position-relative d-flex align-items-center justify-content-center min-vh-100 p-3">
<div className="card shadow-sm border-0 rounded-4 p-4 w-100" style={{ maxWidth: '420px' }}>
<div className="text-center mb-3">
<h4 className="text-info fw-bold">Lolita</h4>
</div>
<form onSubmit={handleSubmit} className="text-start">
<h5 className="mb-2 text-center">Sign in to account</h5>
<p className="text-muted mb-4 small text-center">Enter your email & password to login</p>
<div className="mb-3">
<label className="form-label small">Email Address</label>
<input
type="email"
className="form-control"
style={{ borderColor: '#e0e0e0', backgroundColor: '#f0f8fa' }}
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter email"
required
disabled={loading}
/>
</div>
<div className="mb-4">
<label className="form-label small">Password</label>
<div className="input-group">
<input
type={showPassword ? 'text' : 'password'}
className="form-control"
style={{ borderColor: '#e0e0e0', backgroundColor: '#f0f8fa' }}
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
disabled={loading}
/>
<button
type="button"
className="btn btn-outline-secondary btn-sm"
onClick={() => setShowPassword(!showPassword)}
disabled={loading}
>
{showPassword ? 'hide' : 'show'}
</button>
</div>
</div>
{error && <div className="alert alert-danger small p-2 mb-3">{error}</div>}
<div className="d-flex justify-content-between align-items-center mb-4">
<div className="form-check">
<input
type="checkbox"
className="form-check-input"
id="rememberMe"
checked={rememberMe}
onChange={(e) => setRememberMe(e.target.checked)}
disabled={loading}
/>
<label className="form-check-label small" htmlFor="rememberMe">
Remember me
</label>
</div>
<Link to="/reset-password" className="text-decoration-none small" style={{ color: '#ff5757' }}>
Forgot password?
</Link>
</div>
<button
type="submit"
className="btn w-100 py-2 fw-medium"
style={{
backgroundColor: '#00b4d8',
borderColor: '#00b4d8',
color: 'white',
}}
disabled={loading}
>
{loading ? 'Signing in...' : 'Log in'}
</button>
</form>
</div>
</div>
{/* Slide-in Animation */}
<style jsx>{`
@keyframes slideInRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
`}</style>
</>
);
}

View File

@ -0,0 +1,55 @@
import { Link } from 'react-router-dom';
const Navbar = () => {
return (
<nav className="navbar navbar-expand-lg px-4 py-3 bg-white shadow-sm">
<div className="container-fluid">
{/* Brand - teal color */}
<Link
className="navbar-brand fw-bold fs-4"
to="/"
style={{ color: '#00b4d8' }} // 👈 brand color
>
Lolita
</Link>
<button
className="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarNav">
<ul className="navbar-nav ms-auto align-items-lg-center">
<li className="nav-item">
<Link
className="nav-link small"
to="/support"
style={{ color: '#00b4d8' }} // 👈 link color
>
Support
</Link>
</li>
<li className="nav-item">
<Link
className="nav-link small"
to="/about"
style={{ color: '#00b4d8' }}
>
About us
</Link>
</li>
</ul>
</div>
</div>
</nav>
);
};
export default Navbar;

View File

@ -0,0 +1,423 @@
import React, { useState, useRef, useEffect } from 'react';
import { Link } from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import '../App.css';
// 🔑 Configurable OTP expiry time (in seconds)
const OTP_EXPIRY_SECONDS = 300; // 5 minutes
export default function ResetPassword() {
const [email, setEmail] = useState('');
const [otp, setOtp] = useState(['', '', '', '', '', '']);
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [rememberMe, setRememberMe] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [success, setSuccess] = useState(false);
const [isOtpExpired, setIsOtpExpired] = useState(false);
const [countdown, setCountdown] = useState(0);
const countdownInterval = useRef(null);
const otpInputRefs = useRef([]);
// 🔑 Password rules
const passwordRules = [
{ id: 'length', regex: /.{8,}/, label: 'At least 8 characters' },
{ id: 'uppercase', regex: /[A-Z]/, label: 'One uppercase letter' },
{ id: 'lowercase', regex: /[a-z]/, label: 'One lowercase letter' },
{ id: 'number', regex: /[0-9]/, label: 'One number' },
{ id: 'special', regex: /[^A-Za-z0-9]/, label: 'One special character (e.g., !@#$%)' },
];
const isPasswordValid = (password) => {
return passwordRules.every(rule => rule.regex.test(password));
};
// 🔑 Start countdown timer
const startCountdown = (seconds) => {
if (countdownInterval.current) {
clearInterval(countdownInterval.current);
}
setCountdown(seconds);
countdownInterval.current = setInterval(() => {
setCountdown(prev => {
if (prev <= 1) {
clearInterval(countdownInterval.current);
setIsOtpExpired(true);
localStorage.removeItem('otpData');
return 0;
}
return prev - 1;
});
}, 1000);
};
// 🔑 On mount: check for existing OTP and resume countdown
useEffect(() => {
const stored = localStorage.getItem('otpData');
if (stored) {
const { timestamp } = JSON.parse(stored);
const now = Date.now();
const elapsedMs = now - timestamp;
const remainingMs = OTP_EXPIRY_SECONDS * 1000 - elapsedMs;
if (remainingMs > 0) {
const remainingSeconds = Math.ceil(remainingMs / 1000);
startCountdown(remainingSeconds);
} else {
localStorage.removeItem('otpData');
setIsOtpExpired(true);
}
}
// Cleanup on unmount
return () => {
if (countdownInterval.current) {
clearInterval(countdownInterval.current);
}
};
}, []);
// 🔑 Save OTP with timestamp
const saveOtpWithTimestamp = (email, otpCode) => {
localStorage.setItem('otpData', JSON.stringify({
email,
otp: otpCode,
timestamp: Date.now(),
}));
};
// 🔑 Get saved OTP data
const getSavedOtpData = () => {
const stored = localStorage.getItem('otpData');
if (stored) {
const data = JSON.parse(stored);
const now = Date.now();
if (now - data.timestamp <= OTP_EXPIRY_SECONDS * 1000) {
return data;
} else {
localStorage.removeItem('otpData');
setIsOtpExpired(true);
}
}
return null;
};
// Handle OTP input
const handleOtpChange = (index, value) => {
if (/^\d?$/.test(value)) {
const newOtp = [...otp];
newOtp[index] = value;
setOtp(newOtp);
if (value !== '' && index < otp.length - 1) {
otpInputRefs.current[index + 1]?.focus();
}
}
};
const handleOtpKeyDown = (index, e) => {
if (e.key === 'Backspace' && !otp[index] && index > 0) {
otpInputRefs.current[index - 1]?.focus();
}
};
const handleOtpPaste = (e) => {
e.preventDefault();
const paste = e.clipboardData.getData('text').replace(/\D/g, '').slice(0, 6);
if (paste.length === 6) {
const newOtp = paste.split('');
setOtp(newOtp);
}
};
// 🔑 Handle "Send OTP" click
const handleSendOtp = () => {
if (!email || !/\S+@\S+\.\S+/.test(email)) return;
console.log('Send OTP clicked for:', email);
alert('✅ Simulated "Send OTP" API call!');
setTimeout(() => {
const demoOtp = ['1','2','3','4','5','6'];
setOtp(demoOtp);
saveOtpWithTimestamp(email, demoOtp.join(''));
startCountdown(OTP_EXPIRY_SECONDS); // Start fresh countdown
}, 1000);
};
// 🔑 Handle "Resend OTP" click
const handleResendOtp = () => {
if (!email || !/\S+@\S+\.\S+/.test(email)) {
setError('Please enter a valid email first');
return;
}
console.log('Resend OTP clicked for:', email);
alert('✅ OTP resent!');
setOtp(['', '', '', '', '', '']);
localStorage.removeItem('otpData');
setIsOtpExpired(false);
setTimeout(() => {
const demoOtp = ['6','5','4','3','2','1'];
setOtp(demoOtp);
saveOtpWithTimestamp(email, demoOtp.join(''));
startCountdown(OTP_EXPIRY_SECONDS); // Start fresh countdown
}, 1000);
};
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
setSuccess(false);
if (!isPasswordValid(newPassword)) {
setError('Password does not meet security requirements');
return;
}
if (newPassword !== confirmPassword) {
setError('Passwords do not match');
return;
}
const otpCode = otp.join('');
if (otpCode.length !== 6) {
setError('Please enter a 6-digit OTP');
return;
}
const savedOtpData = getSavedOtpData();
if (!savedOtpData) {
setError('OTP has expired or was not requested. Please request a new OTP.');
return;
}
if (savedOtpData.otp !== otpCode) {
setError('Invalid OTP. Please check your code.');
return;
}
if (savedOtpData.email !== email) {
setError('OTP does not match the email address.');
return;
}
setLoading(true);
try {
console.log('API Payload:', { email, otp: otpCode, newPassword, rememberMe });
alert('✅ Password reset successful!');
setSuccess(true);
localStorage.removeItem('otpData');
if (countdownInterval.current) {
clearInterval(countdownInterval.current);
}
} catch (err) {
setError(err.message || 'Something went wrong');
} finally {
setLoading(false);
}
};
return (
<>
<div
className="position-absolute top-0 start-0 w-100 h-100"
style={{
backgroundImage: 'url(/assets/images/3.png)',
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
}}
></div>
<div className="position-relative d-flex align-items-center justify-content-center min-vh-100 p-3">
<div className="card shadow-sm border-0 rounded-4 p-4 w-100" style={{ maxWidth: '400px' }}>
<div className="text-center mb-3">
<h4 className="text-info fw-bold">Lolita</h4>
</div>
<form onSubmit={handleSubmit} className="text-start">
<h5 className="mb-2 text-center">Reset Your Password</h5>
<p className="text-muted mb-3 small mt-4">Enter your registered Email</p>
<div className="mb-3">
<input
type="email"
className="form-control"
style={{ borderColor: '#e0e0e0', backgroundColor: '#f0f8fa' }}
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter email"
required
disabled={loading}
/>
</div>
<div className="d-flex justify-content-end mb-3">
<button
type="button"
className="btn btn-sm"
style={{
backgroundColor: email && /\S+@\S+\.\S+/.test(email) ? '#00b4d8' : '#cccccc',
color: 'white',
borderColor: email && /\S+@\S+\.\S+/.test(email) ? '#00b4d8' : '#cccccc',
}}
disabled={!email || !/\S+@\S+\.\S+/.test(email) || loading}
onClick={handleSendOtp}
>
Send
</button>
</div>
{/* 🔑 LIVE COUNTDOWN - Only shows when active */}
{countdown > 0 && (
<div className="text-muted small mb-2 text-center">
OTP expires in <strong>{countdown}s</strong>
</div>
)}
{isOtpExpired && countdown === 0 && (
<div className="alert alert-warning small p-2 mb-3">
Your OTP has expired. Please request a new one.
</div>
)}
<div className="text-muted small mb-3">
Didnt receive OTP?{' '}
<button
type="button"
className="btn btn-link p-0"
style={{ color: '#ff5757', textDecoration: 'none' }}
disabled={loading}
onClick={handleResendOtp}
>
Resend
</button>
</div>
<div className="mb-3">
<label className="form-label small">Enter OTP</label>
<div className="d-flex gap-2 flex-wrap mt-2">
{otp.map((digit, index) => (
<input
key={index}
ref={(el) => (otpInputRefs.current[index] = el)}
type="text"
inputMode="numeric"
className="form-control text-center"
style={{
width: '50px',
height: '50px',
fontSize: '1.2rem',
borderColor: '#e0e0e0',
backgroundColor: '#f0f8fa',
}}
value={digit}
onChange={(e) => handleOtpChange(index, e.target.value)}
onKeyDown={(e) => handleOtpKeyDown(index, e)}
onPaste={handleOtpPaste}
maxLength={1}
required
disabled={loading}
/>
))}
</div>
</div>
{/* Password Section */}
<div className="mb-3">
<h6 className="fw-medium mb-2">Create Your Password</h6>
<div className="mb-3">
<label className="form-label small">New Password</label>
<input
type="password"
className="form-control"
style={{ borderColor: '#e0e0e0', backgroundColor: '#f0f8fa' }}
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
placeholder="••••••••"
required
disabled={loading}
/>
</div>
{/* Password Rules - Only show when user types */}
{newPassword && (
<div className="small mb-3">
{passwordRules.map((rule) => {
const isValid = rule.regex.test(newPassword);
return (
<div key={rule.id} className="d-flex align-items-center mb-1">
<span className="me-2" style={{ fontSize: '0.9rem' }}>
{isValid ? '✅' : '❌'}
</span>
<span className={isValid ? 'text-success' : 'text-muted'}>
{rule.label}
</span>
</div>
);
})}
</div>
)}
<div className="mb-3">
<label className="form-label small">Retype Password</label>
<input
type="password"
className="form-control"
style={{ borderColor: '#e0e0e0', backgroundColor: '#f0f8fa' }}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="••••••••"
required
disabled={loading}
/>
</div>
</div>
<div className="form-check mb-3">
<input
type="checkbox"
className="form-check-input"
id="rememberMe"
checked={rememberMe}
onChange={(e) => setRememberMe(e.target.checked)}
disabled={loading}
/>
<label className="form-check-label small" htmlFor="rememberMe">
Remember password
</label>
</div>
{error && <div className="alert alert-danger small p-2 mb-3">{error}</div>}
{success && <div className="alert alert-success small p-2 mb-3">Password updated successfully!</div>}
<button
type="submit"
className="btn w-100 py-2 fw-medium"
style={{
backgroundColor: '#00b4d8',
borderColor: '#00b4d8',
color: 'white',
}}
disabled={loading || !isPasswordValid(newPassword) || !confirmPassword}
>
{loading ? 'Processing...' : 'Done'}
</button>
<div className="text-center mt-3">
<Link to="/login" className="text-decoration-none small" style={{ color: '#00b4d8' }}>
Back to Login
</Link>
</div>
</form>
</div>
</div>
</>
);
}

View File

@ -0,0 +1,126 @@
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import Navbar from './Navbar';
export default function Signup() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [phone, setPhone] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (password !== confirmPassword) {
alert("Passwords don't match!");
return;
}
console.log({ name, email, phone, password });
};
return (
<>
<Navbar />
<div className="vh-100 bg-light d-flex flex-column">
{/* Main Content */}
<main className="flex-grow-1 d-flex align-items-center justify-content-center p-4">
<div className="w-100" style={{ maxWidth: '480px' }}>
<div className="card border-0 shadow-lg rounded-4">
<div className="card-body p-5">
<div className="text-center mb-4">
<p className="text-muted mb-1">Create your account</p>
<h1 className="fw-bold fs-2">Sign up</h1>
</div>
<form onSubmit={handleSubmit}>
{/* Name */}
<div className="mb-3">
<input
type="text"
className="form-control form-control-lg py-3"
placeholder="Full Name"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</div>
{/* Email */}
<div className="mb-3">
<input
type="email"
className="form-control form-control-lg py-3"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
{/* Phone */}
<div className="mb-3">
<input
type="tel"
className="form-control form-control-lg py-3"
placeholder="Phone Number (optional)"
value={phone}
onChange={(e) => setPhone(e.target.value)}
/>
</div>
{/* Password */}
<div className="mb-3">
<input
type="password"
className="form-control form-control-lg py-3"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
minLength={6}
/>
</div>
{/* Confirm Password */}
<div className="mb-4">
<input
type="password"
className="form-control form-control-lg py-3"
placeholder="Confirm Password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
/>
</div>
{/* Submit Button */}
<div className="d-grid mb-4">
<button type="submit" className="btn btn-primary btn-lg py-3 fw-bold">
Sign Up
</button>
</div>
{/* Login Link */}
<div className="text-center">
<p className="mb-0 small text-muted">
Already have an account?{' '}
<Link to="/login" className="text-decoration-none fw-medium text-danger">
Log in
</Link>
</p>
</div>
</form>
</div>
</div>
</div>
</main>
</div>
</>
);
}

View File

@ -0,0 +1,70 @@
import React from 'react';
import { Link } from 'react-router-dom';
import Navbar from './Navbar';
export default function VerifyEmail() {
const email = "example@eg.com";
return (
<>
<Navbar />
<div className="vh-100 bg-light d-flex flex-column">
{/* Main Content */}
<main className="flex-grow-1 d-flex align-items-center justify-content-center p-4" style={{ marginTop: "-100px" }}>
{/* <div className="w-100" style={{ maxWidth: '500px' }}> */}
<div className="card border-0 shadow-lg rounded-4">
<div className="card-body p-5">
<div className="text-center mb-4">
<h1 className="fw-bold fs-2">Verify your E-mail address</h1>
</div>
<div className="mb-4">
<p className="text-muted mb-3">
Weve sent a confirmation email to <strong>{email}</strong>. Please click the link in the email to continue.
</p>
<p className="mb-0">
Didnt get the email?{' '}
<a href="#" className="text-decoration-none text-primary fw-medium">
Resend Email
</a>.
{' '}Entered the wrong address?{' '}
<a href="#" className="text-decoration-none text-primary fw-medium">
Change Email
</a>.
{' '}Need help?{' '}
<a href="#" className="text-decoration-none text-primary fw-medium">
Contact Support
</a>.
</p>
</div>
{/* Success Alert at Bottom */}
<div
className="alert alert-success d-flex align-items-center justify-content-between mt-4"
role="alert"
style={{
borderRadius: '1rem',
padding: '0.75rem 1rem',
}}
>
<span> Confirmation email sent to {email}</span>
<button
type="button"
className="btn-close"
aria-label="Close"
style={{ fontSize: '0.75rem' }}
></button>
</div>
</div>
</div>
{/* </div> */}
</main>
</div>
</>
);
}

13
frontend/src/index.css Normal file
View File

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

21
frontend/src/index.js Normal file
View File

@ -0,0 +1,21 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import 'bootstrap/dist/css/bootstrap.min.css';
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// 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();

1
frontend/src/logo.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,132 @@
import React from 'react';
import Navbar from '../components/Navbar';
const Dashboard = () => {
return (
<>
<Navbar />
<div className="container-fluid p-4" style={{ backgroundColor: '#f8f9fa' }}>
<div className="card border-0 shadow-sm mb-4 mx-auto mt-4" style={{ width: '85%', borderRadius: '12px' , height: '300px' }}>
<div className="card-body p-4">
<input
type="text"
className="form-control mb-3"
placeholder="Write your research query here..."
style={{ borderColor: '#0d6efd', borderWidth: '2px', padding: '1rem' }}
/>
<div className="d-flex flex-wrap align-items-center gap-3">
<div className="dropdown">
<button
className="btn btn-outline-primary dropdown-toggle d-flex align-items-center gap-2"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
style={{ borderColor: '#0d6efd', borderWidth: '2px' }}
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-paperclip" viewBox="0 0 16 16">
<path d="M4.5 3a2.5 2.5 0 0 1 5 0v9a1.5 1.5 0 0 1-3 0V5.5a1.5 1.5 0 0 1-3 0zm5 0v10.5a1.5 1.5 0 0 1-3 0V5.5a1.5 1.5 0 0 1-3 0V3z"/>
</svg>
Attachments
</button>
<ul className="dropdown-menu">
<li><a className="dropdown-item" href="#">File 1</a></li>
<li><a className="dropdown-item" href="#">File 2</a></li>
</ul>
</div>
<div className="dropdown">
<button
className="btn btn-outline-primary dropdown-toggle d-flex align-items-center gap-2"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
style={{ borderColor: '#0d6efd', borderWidth: '2px' }}
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-book" viewBox="0 0 16 16">
<path d="M1 2.828c.764-.763 2.077-.763 2.84 0l6.421 6.421a.75.75 0 0 0 .577.217.75.75 0 0 0 .577-.217L15.67 2.828A6.964 6.964 0 0 0 12 1.5a6.964 6.964 0 0 0-4.878 2.103L5 5.5a6.964 6.964 0 0 0-4.878 2.103z"/>
</svg>
Choose discipline
</button>
<ul className="dropdown-menu">
<li><a className="dropdown-item" href="#">Computer Science</a></li>
<li><a className="dropdown-item" href="#">Medicine</a></li>
</ul>
</div>
<div className="dropdown">
<button
className="btn btn-outline-primary dropdown-toggle d-flex align-items-center gap-2"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
style={{ borderColor: '#0d6efd', borderWidth: '2px' }}
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-file-earmark-text" viewBox="0 0 16 16">
<path d="M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1zM5 9.5a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1z"/>
</svg>
Templates
</button>
<ul className="dropdown-menu">
<li><a className="dropdown-item" href="#">Template 1</a></li>
<li><a className="dropdown-item" href="#">Template 2</a></li>
</ul>
</div>
{/* Create Button (Blue, not orange) */}
<button
className="btn btn-primary ms-auto"
style={{ borderRadius: '20px', padding: '0.5rem 1.5rem' }}
>
CREATE
</button>
</div>
</div>
</div>
{/* Projects Section */}
<h5 className="mb-3 fw-bold">Projects</h5>
<div className="row g-4">
{/* Project Card 1 */}
<div className="col-md-4">
<div className="card border-0 shadow-sm h-100" style={{ borderRadius: '12px' }}>
<div className="card-body">
<h6 className="card-title fw-bold mb-2">AI-Powered Summarization for Academic Research</h6>
<p className="card-text text-muted">
This project proposes the design and development of an AI-driven text summarization tool to assist...
</p>
</div>
</div>
</div>
{/* Project Card 2 */}
<div className="col-md-4">
<div className="card border-0 shadow-sm h-100" style={{ borderRadius: '12px' }}>
<div className="card-body">
<h6 className="card-title fw-bold mb-2">AI-Powered Summarization for Academic Research</h6>
<p className="card-text text-muted">
This project proposes the design and development of an AI-driven text summarization tool to assist...
</p>
</div>
</div>
</div>
{/* Project Card 3 */}
<div className="col-md-4">
<div className="card border-0 shadow-sm h-100" style={{ borderRadius: '12px' }}>
<div className="card-body">
<h6 className="card-title fw-bold mb-2">AI-Powered Summarization for Academic Research</h6>
<p className="card-text text-muted">
This project proposes the design and development of an AI-driven text summarization tool to assist...
</p>
</div>
</div>
</div>
</div>
</div>
</>
);
};
export default Dashboard;

View File

@ -0,0 +1,98 @@
import 'bootstrap/dist/css/bootstrap.min.css';
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Yuri admin is super flexible, powerful, clean &amp; modern responsive bootstrap 5 admin template with unlimited possibilities.">
<meta name="keywords" content="admin template, Yuri admin template, dashboard template, flat admin template, responsive admin template, web app">
<meta name="author" content="pixelstrap">
<link rel="icon" href="../assets/images/favicon.png" type="image/x-icon">
<link rel="shortcut icon" href="../assets/images/favicon.png" type="image/x-icon">
<title>Yuri - Premium Admin Template</title>
<!-- Google font-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&amp;family=Nunito+Sans:ital,wght@0,300;0,400;0,700;0,800;0,900;1,700&amp;display=swap" rel="stylesheet">
<!-- Font Awesome-->
<link rel="stylesheet" type="text/css" href="../assets/css/font-awesome.css">
<!-- ico-font-->
<link rel="stylesheet" type="text/css" href="../assets/css/vendors/icofont.css">
<!-- Themify icon-->
<link rel="stylesheet" type="text/css" href="../assets/css/vendors/themify.css">
<!-- Flag icon-->
<link rel="stylesheet" type="text/css" href="../assets/css/vendors/flag-icon.css">
<!-- Feather icon-->
<link rel="stylesheet" type="text/css" href="../assets/css/vendors/feather-icon.css">
<!-- Plugins css start-->
<!-- Plugins css Ends-->
<!-- Bootstrap css-->
<link rel="stylesheet" type="text/css" href="../assets/css/vendors/bootstrap.css">
<!-- App css-->
<link rel="stylesheet" type="text/css" href="../assets/css/style.css">
<!-- Responsive css-->
<link rel="stylesheet" type="text/css" href="../assets/css/responsive.css">
</head>
<body>
<!-- login page start-->
<div class="container-fluid">
<div class="row">
<div class="col-xl-7"><img class="bg-img-cover bg-center" src="../assets/images/login/2.jpg" alt="looginpage"></div>
<div class="col-xl-5 p-0">
<div class="login-card login-dark">
<div>
<div><a class="logo" href="index.html"><img class="img-fluid for-light" src="../assets/images/logo/logo.png" alt="looginpage"><img class="img-fluid for-dark" src="../assets/images/logo/logo_dark.png" alt="looginpage"></a></div>
<div class="login-main">
<form class="theme-form">
<h2>Sign in to account</h2>
<p class="f-m-light mt-1">Enter your email & password to login</p>
<div class="form-group">
<label class="col-form-label">Email Address</label>
<input class="form-control" type="email" required="" placeholder="Test@gmail.com">
</div>
<div class="form-group">
<label class="col-form-label">Password</label>
<div class="form-input position-relative">
<input class="form-control" type="password" name="login[password]" required="" placeholder="*********">
<div class="show-hide"><span class="show"> </span></div>
</div>
</div>
<div class="form-group mb-0">
<div class="checkbox p-0">
<input id="checkbox1" type="checkbox">
<label class="text-muted" for="checkbox1">Remember password</label>
</div>
<button class="btn btn-primary btn-block w-100" type="submit">Sign in</button>
</div>
<h6 class="text-muted mt-4 or">Or Sign in with</h6>
<div class="social mt-4">
<div class="btn-showcase"><a class="btn btn-light" href="https://www.linkedin.com/login" target="_blank"><i class="txt-linkedin" data-feather="linkedin"></i> LinkedIn </a><a class="btn btn-light" href="https://twitter.com/login?lang=en" target="_blank"><i class="txt-twitter" data-feather="twitter"></i>twitter</a><a class="btn btn-light" href="https://www.facebook.com/" target="_blank"><i class="txt-fb" data-feather="facebook"></i>facebook</a></div>
</div>
<p class="mt-4 mb-0 text-center">Don't have account?<a class="ms-2" href="sign-up.html">Create Account</a></p>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- latest jquery-->
<script src="../assets/js/jquery.min.js"></script>
<!-- Bootstrap js-->
<script src="../assets/js/bootstrap/bootstrap.bundle.min.js"></script>
<!-- feather icon js-->
<script src="../assets/js/icons/feather-icon/feather.min.js"></script>
<script src="../assets/js/icons/feather-icon/feather-icon.js"></script>
<!-- scrollbar js-->
<!-- Sidebar jquery-->
<script src="../assets/js/config.js"></script>
<!-- Plugins JS start-->
<!-- Plugins JS Ends-->
<!-- Theme js-->
<script src="../assets/js/script.js"></script>
</div>
</body>
</html>

View File

@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View File

@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';