PD auth and protected routes done

This commit is contained in:
ROSHAN GARG 2024-07-26 18:21:32 +05:30
parent 823fbd7d19
commit d5754487ad
18 changed files with 978 additions and 241 deletions

View File

@ -26,14 +26,20 @@
"@coreui/react": "^5.1.0",
"@coreui/react-chartjs": "^3.0.0",
"@coreui/utils": "^2.0.2",
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0",
"@fortawesome/fontawesome-svg-core": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"@mui/icons-material": "^5.16.4",
"@mui/material": "^5.16.4",
"@popperjs/core": "^2.11.8",
"@themesberg/react-bootstrap": "^1.4.1",
"axios": "^1.7.2",
"chart.js": "^4.4.3",
"classnames": "^2.5.1",
"core-js": "^3.37.1",
"date-fns": "^3.6.0",
"prop-types": "^15.8.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@ -41,7 +47,7 @@
"react-router-dom": "^6.23.1",
"redux": "5.0.1",
"simplebar-react": "^3.2.5",
"sweetalert2": "^11.12.2"
"sweetalert2": "^11.12.3"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.3.1",

View File

@ -4,6 +4,7 @@ import { useSelector } from 'react-redux'
import { CSpinner, useColorModes } from '@coreui/react'
import './scss/style.scss'
import ProtectedRoute from './protectedRoute'
// Containers
const DefaultLayout = React.lazy(() => import('./layout/DefaultLayout'))
@ -13,8 +14,7 @@ const Login = React.lazy(() => import('./views/pages/login/Login'))
const Register = React.lazy(() => import('./views/pages/register/Register'))
const Page404 = React.lazy(() => import('./views/pages/page404/Page404'))
const Page500 = React.lazy(() => import('./views/pages/page500/Page500'))
const MyProfile = React.lazy(() => import('./views/pages/profile/MyProfile'))
const ChangePassword = React.lazy(() => import('./views/pages/profile/ChangePassword'))
const ForgetPassword = React.lazy(() => import('./views/pages/forgetPassword'))
const App = () => {
const { isColorModeSet, setColorMode } = useColorModes('coreui-free-react-admin-template-theme')
@ -46,12 +46,18 @@ const App = () => {
}
>
<Routes>
<Route exact path="/my-profile" name="My profile" element={<MyProfile />} />
<Route exact path="/change-password" name="My profile" element={<ChangePassword />} />
{/* <Route exact path="/change-password" name="My profile" element={<ChangePassword />} /> */}
<Route exact path="/login" name="Login Page" element={<Login />} />
<Route exact path="/register" name="Register Page" element={<Register />} />
<Route exact path="/404" name="Page 404" element={<Page404 />} />
<Route exact path="/500" name="Page 500" element={<Page500 />} />
<Route
exact
path="/forget-password"
name="Forget password "
element={<ForgetPassword />}
/>
<Route path="/*" element={<ProtectedRoute element={DefaultLayout} />} />
<Route path="*" name="Home" element={<DefaultLayout />} />
</Routes>
</Suspense>

View File

@ -23,10 +23,6 @@ const _nav = [
name: 'Dashboard',
to: '/dashboard',
icon: <CIcon icon={cilSpeedometer} customClassName="nav-icon" />,
badge: {
color: 'info',
text: 'NEW',
},
},
{
component: CNavItem,
@ -34,12 +30,19 @@ const _nav = [
to: '/shop',
icon: <CIcon icon={cilShare} customClassName="nav-icon" />,
},
// {
// component: CNavItem,
// name: 'Orders',
// to: '/order',
// icon: <CIcon icon={cilBorderAll} customClassName="nav-icon" />,
// },
{
component: CNavItem,
name: 'Orders',
to: '/order',
icon: <CIcon icon={cilBorderAll} customClassName="nav-icon" />,
name: 'KYC',
to: '/kyc',
icon: <CIcon icon={cilDescription} customClassName="nav-icon" />,
},
// {
// component: CNavTitle,
// name: 'Theme',

View File

@ -0,0 +1,26 @@
[
{
"name": "Item 1",
"image": "https://images.pexels.com/photos/341523/pexels-photo-341523.jpeg?auto=compress&cs=tinysrgb&w=600",
"price": 10.99,
"categoryName": "phone"
},
{
"name": "Item 2",
"image": "https://images.pexels.com/photos/90946/pexels-photo-90946.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
"price": 20.99,
"categoryName": "phone"
},
{
"name": "Item 3",
"image": "https://images.pexels.com/photos/341523/pexels-photo-341523.jpeg?auto=compress&cs=tinysrgb&w=600",
"price": 30.99,
"categoryName": "camera"
},
{
"name": "Item 4",
"image": "https://images.pexels.com/photos/90946/pexels-photo-90946.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
"price": 40.99,
"categoryName": "camera"
}
]

10
src/auth.js Normal file
View File

@ -0,0 +1,10 @@
export const isAutheticated = () => {
if (typeof window == 'undefined') {
return true
}
if (localStorage.getItem('authToken')) {
return localStorage.getItem('authToken')
} else {
return false
}
}

View File

@ -1,10 +1,10 @@
import axios from 'axios'
const Axios = axios.create({
// baseURL: "http://localhost:5000/",
// baseURL: 'http://localhost:5000/',
// baseURL: 'https://leadesh-whatsapp.onrender.com',
// baseURL: "https://api.leadesh.com/",
baseURL: 'https://leadesh-api-github.onrender.com/', // latest is this one
baseURL: 'https://cheminova-api-2.onrender.com', // latest is this one
})
export default Axios

View File

@ -4,7 +4,7 @@ import { CFooter } from '@coreui/react'
const AppFooter = () => {
return (
<CFooter className="px-4">
<div>
{/* <div>
<a href="https://coreui.io" target="_blank" rel="noopener noreferrer">
CoreUI
</a>
@ -15,7 +15,7 @@ const AppFooter = () => {
<a href="https://coreui.io/react" target="_blank" rel="noopener noreferrer">
CoreUI React Admin &amp; Dashboard Template
</a>
</div>
</div> */}
</CFooter>
)
}

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React, { useEffect, useState } from 'react'
import {
CAvatar,
CBadge,
@ -26,55 +26,50 @@ import avatar8 from './../../assets/images/avatars/8.jpg'
import { useNavigate } from 'react-router-dom'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faUserCircle } from '@fortawesome/free-solid-svg-icons'
import Swal from 'sweetalert2'
import Axios from '../../axios'
import { isAutheticated } from '../../auth'
const AppHeaderDropdown = () => {
const navigate = useNavigate()
const signout = async () => {
localStorage.removeItem('authToken')
Swal.fire('success!', 'Logged Out', 'success')
navigate('/login')
}
const [user, setUser] = useState(null)
const token = isAutheticated()
const getData = async () => {
let res = await Axios.get(`/api/v1/user/details`, {
headers: {
Authorization: `Bearer ${token}`,
},
})
if (res.data.success) {
setUser({ ...res.data.user })
}
}
useEffect(() => {
getData()
}, [])
return (
<CDropdown variant="nav-item">
<CDropdownToggle placement="bottom-end" className="py-0 pe-0" caret={false}>
<div className="media d-flex align-items-center">
{/* <Image src={"Profile3"} className="user-avatar md-avatar rounded-circle" /> */}
<FontAwesomeIcon
style={{ fontSize: '2rem' }}
className="user-avatar md-avatar rounded-circle "
icon={faUserCircle}
/>
<div className="media-body ms-2 text-dark align-items-center d-none d-lg-block">
<span className="mb-0 font-small fw-bold"> Garg</span>
<span className="mb-0 font-small fw-bold"> {user?.name}</span>
</div>
</div>
</CDropdownToggle>
<CDropdownMenu className="pt-0" placement="bottom-end">
<CDropdownHeader className="bg-body-secondary fw-semibold mb-2">Account</CDropdownHeader>
{/* <CDropdownItem href="#">
<CIcon icon={cilBell} className="me-2" />
Update
<CBadge color="info" className="ms-2">
42
</CBadge>
</CDropdownItem>
<CDropdownItem href="#">
<CIcon icon={cilEnvelopeOpen} className="me-2" />
Messages
<CBadge color="success" className="ms-2">
42
</CBadge>
</CDropdownItem>
<CDropdownItem href="#">
<CIcon icon={cilTask} className="me-2" />
Tasks
<CBadge color="danger" className="ms-2">
42
</CBadge>
</CDropdownItem>
<CDropdownItem href="#">
<CIcon icon={cilCommentSquare} className="me-2" />
Comments
<CBadge color="warning" className="ms-2">
42
</CBadge>
</CDropdownItem>
<CDropdownHeader className="bg-body-secondary fw-semibold my-2">Settings</CDropdownHeader> */}
<CDropdownItem onClick={() => navigate('/my-profile')}>
<CIcon icon={cilUser} className="me-2" />
Profile
@ -83,22 +78,9 @@ const AppHeaderDropdown = () => {
<CIcon icon={cilSettings} className="me-2" />
Change Password
</CDropdownItem>
{/* <CDropdownItem href="#">
<CIcon icon={cilCreditCard} className="me-2" />
Payments
<CBadge color="secondary" className="ms-2">
42
</CBadge>
</CDropdownItem>
<CDropdownItem href="#">
<CIcon icon={cilFile} className="me-2" />
Projects
<CBadge color="primary" className="ms-2">
42
</CBadge>
</CDropdownItem> */}
<CDropdownDivider />
<CDropdownItem href="#">
<CDropdownItem onClick={signout}>
<CIcon icon={cilLockLocked} className="me-2" />
Logout
</CDropdownItem>

19
src/protectedRoute.js Normal file
View File

@ -0,0 +1,19 @@
/* eslint-disable react/react-in-jsx-scope */
/* eslint-disable react/prop-types */
import { useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
const ProtectedRoute = ({ element: Element }) => {
const navigate = useNavigate()
console.log('req came here ')
useEffect(() => {
if (!localStorage.getItem('authToken')) {
navigate('/login')
}
}, [navigate])
return <Element />
}
export default ProtectedRoute

View File

@ -3,12 +3,22 @@ import React from 'react'
const Dashboard = React.lazy(() => import('./views/dashboard/Dashboard'))
const Shop = React.lazy(() => import('./views/shops/Shop'))
const Order = React.lazy(() => import('./views/orders/Order'))
const MyProfile = React.lazy(() => import('./views/pages/profile/MyProfile'))
const ChangePassword = React.lazy(() => import('./views/pages/profile/ChangePassword'))
const Kyc = React.lazy(() => import('./views/pages/Kyc/kyc'))
const KycDetails = React.lazy(() => import('./views/pages/Kyc/kycDetails'))
const routes = [
{ path: '/', exact: true, name: 'Home' },
{ path: '/dashboard', name: 'Dashboard', element: Dashboard },
{ path: '/shop', name: 'Shop', element: Shop },
{ path: '/order', name: 'Order', element: Order },
// KYC
{ path: '/kyc', name: 'Kyc', element: Kyc },
{ path: '/kyc/details/:id', name: 'Kyc details', element: KycDetails },
{ path: '/my-profile', name: 'Profile', element: MyProfile },
{ path: '/change-password', name: 'Change password', element: ChangePassword },
]
export default routes

126
src/views/pages/Kyc/kyc.js Normal file
View File

@ -0,0 +1,126 @@
import React, { useState } from 'react'
import {
Box,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
TablePagination,
Button,
IconButton,
Tooltip,
} from '@mui/material'
import { Visibility, ThumbUp, ThumbDown } from '@mui/icons-material'
import { format } from 'date-fns'
import { useNavigate } from 'react-router-dom'
const generateRandomData = (numRows) => {
const statuses = ['New', 'Pending', 'Rejected', 'Approved']
const data = []
for (let i = 0; i < numRows; i++) {
data.push({
id: i + 1,
tradeName: `Trade ${i + 1}`,
createdOn: new Date(),
status: statuses[Math.floor(Math.random() * statuses.length)],
})
}
return data
}
const Kyc = () => {
const [rows, setRows] = useState(generateRandomData(50))
const [page, setPage] = useState(0)
const [rowsPerPage, setRowsPerPage] = useState(5)
const navigate = useNavigate()
const handleChangePage = (event, newPage) => {
setPage(newPage)
}
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(parseInt(event.target.value, 10))
setPage(0)
}
const handleViewClick = (id) => {
navigate(`/kyc/details/${id}`)
}
return (
<Box sx={{ width: '100%' }}>
<Paper sx={{ width: '100%', mb: 2 }}>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>ID</TableCell>
<TableCell>Trade Name</TableCell>
<TableCell>Created On</TableCell>
<TableCell>Status</TableCell>
<TableCell>Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((row) => (
<TableRow key={row.id}>
<TableCell>{row.id}</TableCell>
<TableCell>{row.tradeName}</TableCell>
<TableCell>{format(row.createdOn, 'yyyy-MM-dd HH:mm:ss')}</TableCell>
<TableCell>{row.status}</TableCell>
<TableCell>
<Tooltip title="View">
{/* <IconButton color="primary">
<Visibility />
</IconButton> */}
<Button
sx={{ mr: '1rem' }}
color="primary"
variant="contained"
onClick={() => handleViewClick(row.id)}
>
View
</Button>
</Tooltip>
<Tooltip title="Approve">
{/* <IconButton color="success">
<ThumbUp />
</IconButton> */}
<Button sx={{ mr: '1rem' }} color="success" variant="contained">
Approve
</Button>
</Tooltip>
<Tooltip title="Reject">
{/* <IconButton color="error">
<ThumbDown />
</IconButton> */}
<Button sx={{ mr: '1rem' }} color="error" variant="contained">
Reject
</Button>
</Tooltip>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={rows.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</Paper>
</Box>
)
}
export default Kyc

View File

@ -0,0 +1,190 @@
import React from 'react'
import { Box, Typography, Grid, Paper, Avatar } from '@mui/material'
import { useParams } from 'react-router-dom'
import { format } from 'date-fns'
const generateRandomRetailerDetails = (id) => ({
id,
tradeName: `Trade ${id}`,
name: `Retailer ${id}`,
address: '123 Main St',
townCity: 'Townsville',
district: 'District A',
state: 'State B',
pincode: '123456',
mobileNumber: '123-456-7890',
mappedDistributor: `Distributor ${id}`,
documents: {
panNumber: 'ABCDE1234F',
panCard:
'https://www.shutterstock.com/shutterstock/photos/2329691987/display_1500/stock-vector-blank-pan-card-vector-image-income-tax-card-personal-account-number-image-translation-income-2329691987.jpg',
aadharNumber: '1234-5678-9101',
aadharCard: 'https://via.placeholder.com/100',
gstNumber: '22AAAAA0000A1Z5',
gstRegistration: 'https://via.placeholder.com/100',
pesticideLicense: 'https://via.placeholder.com/100',
fertilizerLicense: 'https://via.placeholder.com/100',
entranceBoardSelfie: 'https://via.placeholder.com/100',
},
salesCoordinator: {
designation: 'Sales Coordinator',
name: `Coordinator ${id}`,
employeeId: `EMP${id}`,
uploadedOn: new Date(),
resubmittedOn: new Date(),
},
notes: ['Note 1', 'Note 2', 'Note 3'],
})
const KycDetails = () => {
const { id } = useParams()
console.log(id)
const retailerDetails = generateRandomRetailerDetails(id)
return (
<Box sx={{ p: 3 }}>
<Typography variant="h4" gutterBottom>
Retailer Details
</Typography>
<Paper sx={{ p: 2, mb: 3 }}>
<Typography variant="h5" gutterBottom>
Retailer Details
</Typography>
<Grid container spacing={2}>
<Grid item xs={6}>
<Typography>
<strong>Trade Name:</strong> {retailerDetails.tradeName}
</Typography>
<Typography>
<strong>Name:</strong> {retailerDetails.name}
</Typography>
<Typography>
<strong>Address:</strong> {retailerDetails.address}
</Typography>
<Typography>
<strong>Town/City:</strong> {retailerDetails.townCity}
</Typography>
</Grid>
<Grid item xs={6}>
<Typography>
<strong>District:</strong> {retailerDetails.district}
</Typography>
<Typography>
<strong>State:</strong> {retailerDetails.state}
</Typography>
<Typography>
<strong>Pincode:</strong> {retailerDetails.pincode}
</Typography>
<Typography>
<strong>Mobile Number:</strong> {retailerDetails.mobileNumber}
</Typography>
<Typography>
<strong>Mapped Principal Distributor:</strong> {retailerDetails.mappedDistributor}
</Typography>
</Grid>
</Grid>
</Paper>
<Paper sx={{ p: 2, mb: 3 }}>
<Typography variant="h6" gutterBottom>
Documents
</Typography>
<Grid container spacing={2}>
<Grid item xs={6}>
<Typography>
<strong>PAN Number:</strong> {retailerDetails.documents.panNumber}
</Typography>
<Avatar
variant="square"
src={retailerDetails.documents.panCard}
sx={{ width: 100, height: 100, mb: 2 }}
/>
<Typography>
<strong>Aadhar Number:</strong> {retailerDetails.documents.aadharNumber}
</Typography>
<Avatar
variant="square"
src={retailerDetails.documents.aadharCard}
sx={{ width: 100, height: 100, mb: 2 }}
/>
<Typography>
<strong>GST Number:</strong> {retailerDetails.documents.gstNumber}
</Typography>
<Avatar
variant="square"
src={retailerDetails.documents.gstRegistration}
sx={{ width: 100, height: 100, mb: 2 }}
/>
</Grid>
<Grid item xs={6}>
<Typography>
<strong>Pesticide License:</strong>
</Typography>
<Avatar
variant="square"
src={retailerDetails.documents.pesticideLicense}
sx={{ width: 100, height: 100, mb: 2 }}
/>
<Typography>
<strong>Fertilizer License (optional):</strong>
</Typography>
<Avatar
variant="square"
src={retailerDetails.documents.fertilizerLicense}
sx={{ width: 100, height: 100, mb: 2 }}
/>
<Typography>
<strong>Selfie of Entrance Board:</strong>
</Typography>
<Avatar
variant="square"
src={retailerDetails.documents.entranceBoardSelfie}
sx={{ width: 100, height: 100, mb: 2 }}
/>
</Grid>
</Grid>
</Paper>
<Paper sx={{ p: 2, mb: 3 }}>
<Typography variant="h6" gutterBottom>
Block 3: Sales Coordinators/Territory Manager Details
</Typography>
<Grid container spacing={2}>
<Grid item xs={6}>
<Typography>
<strong>Designation:</strong> {retailerDetails.salesCoordinator.designation}
</Typography>
<Typography>
<strong>Name:</strong> {retailerDetails.salesCoordinator.name}
</Typography>
<Typography>
<strong>Employee ID:</strong> {retailerDetails.salesCoordinator.employeeId}
</Typography>
</Grid>
<Grid item xs={6}>
<Typography>
<strong>Uploaded on:</strong>{' '}
{format(retailerDetails.salesCoordinator.uploadedOn, 'yyyy-MM-dd HH:mm:ss')}
</Typography>
<Typography>
<strong>Resubmitted on:</strong>{' '}
{format(retailerDetails.salesCoordinator.resubmittedOn, 'yyyy-MM-dd HH:mm:ss')}
</Typography>
</Grid>
</Grid>
</Paper>
<Paper sx={{ p: 2, mb: 3 }}>
<Typography variant="h6" gutterBottom>
Block 4: Notes
</Typography>
{retailerDetails.notes.map((note, index) => (
<Typography key={index}>{note}</Typography>
))}
</Paper>
</Box>
)
}
export default KycDetails

View File

@ -0,0 +1,106 @@
import { cilUser } from '@coreui/icons'
import CIcon from '@coreui/icons-react'
import {
CButton,
CCard,
CCardBody,
CCardGroup,
CCol,
CContainer,
CForm,
CFormInput,
CInputGroup,
CInputGroupText,
CRow,
CSpinner,
} from '@coreui/react'
import React, { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import Swal from 'sweetalert2'
import Axios from '../../axios'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faEnvelope } from '@fortawesome/free-solid-svg-icons'
const ForgetPassword = () => {
const [email, setEmail] = useState('')
const [loading, setLoading] = useState(false)
const navigate = useNavigate()
const handleSubmit = async (e) => {
e.preventDefault()
try {
const res = await Axios.post('/api/v1/user/password/forgot', { email })
console.log(res)
if (res?.status === 200) {
Swal.fire('success', res?.data?.message, 'success')
navigate('/login')
} else if (res?.status === 305) {
Swal.fire('error', res?.data?.message, 'error')
}
} catch (err) {
Swal.fire('error', 'User Not found with this email ', 'error')
}
}
return (
<>
<div className="bg-body-tertiary min-vh-100 d-flex flex-row align-items-center">
<CContainer>
<CRow className="justify-content-center">
<CCol md={8}>
<CCardGroup>
<CCard className="p-4">
<CCardBody>
<CForm onSubmit={handleSubmit}>
<h1>Forget Password</h1>
<p className="text-body-secondary">
Enter your email we will send you the password
</p>
<CInputGroup className="mb-3">
<CInputGroupText>
<FontAwesomeIcon icon={faEnvelope} />
</CInputGroupText>
<CFormInput
type="email"
required
placeholder="abc@gmail.com"
onChange={(e) => setEmail(e.target.value)}
value={email}
name="email"
autoComplete="email"
/>
</CInputGroup>
<CRow>
<CCol xs={12}>
<CButton
color="primary"
type="submit"
style={{ width: '100%' }}
className="px-4"
disabled={loading}
>
{loading ? <CSpinner variant="grow" /> : 'Generate password '}
</CButton>
</CCol>
<CCol xs={6} className="text-right">
<span className="text-body-secondary">
If you know you password? Continue to
</span>
<CButton onClick={() => navigate('/login')} color="link" className="px-2">
Sign in?
</CButton>
</CCol>
</CRow>
</CForm>
</CCardBody>
</CCard>
</CCardGroup>
</CCol>
</CRow>
</CContainer>
</div>
</>
)
}
export default ForgetPassword

View File

@ -1,5 +1,5 @@
import React from 'react'
import { Link } from 'react-router-dom'
import React, { useEffect, useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import {
CButton,
CCard,
@ -12,11 +12,110 @@ import {
CInputGroup,
CInputGroupText,
CRow,
CSpinner,
} from '@coreui/react'
import CIcon from '@coreui/icons-react'
import { cilLockLocked, cilUser } from '@coreui/icons'
// import axios from 'axios'
import Axios from '../../../axios'
import Swal from 'sweetalert2'
const Login = () => {
const [loading, setLoading] = useState(false)
const [validForm, setValidForm] = useState(false)
const navigate = useNavigate()
const [auth, setAuth] = useState({
email: '',
password: '',
})
const [errors, setErrors] = useState({
emailError: '',
passwordError: '',
})
const validEmailRegex = RegExp(
/^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i,
)
const validPasswordRegex = RegExp(/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[^\w\s]).{7,}$/)
// const history = useNavigate()
// const handleChange = (e) => (event) => {
// setAuth({ ...auth, [e]: event.target.value });
// };
const validateForm = () => {
let valid = true
Object.values(errors).forEach((val) => {
if (val.length > 0) {
valid = false
return false
}
})
Object.values(auth).forEach((val) => {
if (val.length <= 0) {
valid = false
return false
}
})
return valid
}
//cheking email and password
useEffect(() => {
if (validateForm()) {
setValidForm(true)
} else {
setValidForm(false)
}
}, [errors])
const handleChange = (e) => {
const { name, value } = e.target
switch (name) {
case 'email':
setErrors({
...errors,
emailError: validEmailRegex.test(value) ? '' : 'Email is not valid!',
})
break
case 'password':
setErrors((errors) => ({
...errors,
passwordError: validPasswordRegex.test(value)
? ''
: 'Password Shoud Be 8 Characters Long, Atleast One Uppercase, Atleast One Lowercase,Atleast One Digit, Atleast One Special Character',
}))
break
default:
break
}
setAuth({ ...auth, [name]: value })
}
const handleSubmit = async (e) => {
if (!(auth.email && auth.password)) {
return Swal.fire('Error!', 'All fields are required', 'error')
}
setLoading({ loading: true })
try {
const res = await Axios.post('/api/v1/user/login/', auth)
console.log(res)
if (res.data.success == true && res.data.user.role === 'principal-Distributor') {
localStorage.setItem('authToken', res.data.token)
navigate('/dashboard')
setLoading(false)
Swal.fire('success', 'logged in successfuly ', 'success')
// console.log(response.data)
} else if (res.data.success == true && res.data.user.role !== 'principal-Distributor') {
setLoading(false)
Swal.fire('error', 'Please login through the PD Credentials ', 'error')
}
} catch (error) {
setLoading(false)
Swal.fire('Error!', 'Invalid Credentials', 'error')
}
}
return (
<div className="bg-body-tertiary min-vh-100 d-flex flex-row align-items-center">
<CContainer>
@ -32,26 +131,53 @@ const Login = () => {
<CInputGroupText>
<CIcon icon={cilUser} />
</CInputGroupText>
<CFormInput placeholder="Username" autoComplete="username" />
<CFormInput
type="email"
placeholder="Email"
onChange={handleChange}
value={auth.email}
name="email"
autoComplete="email"
/>
</CInputGroup>
{errors.emailError && (
<p className="text-center py-2 text-danger">{errors.emailError}</p>
)}
<CInputGroup className="mb-4">
<CInputGroupText>
<CIcon icon={cilLockLocked} />
</CInputGroupText>
<CFormInput
type="password"
name="password"
value={auth.password}
onChange={handleChange}
placeholder="Password"
autoComplete="current-password"
/>
</CInputGroup>
{errors.passwordError && (
<p className="text-center py-2 text-danger">{errors.passwordError}</p>
)}
<CRow>
<CCol xs={6}>
<CButton color="primary" className="px-4">
Login
<CButton
color="primary"
type="submit"
className="px-4"
onClick={handleSubmit}
disabled={loading}
>
{loading ? <CSpinner variant="grow" /> : 'Login'}
</CButton>
</CCol>
<CCol xs={6} className="text-right">
<CButton color="link" className="px-0">
<CButton
onClick={() => navigate('/forget-password')}
color="link"
className="px-0"
>
Forgot password?
</CButton>
</CCol>
@ -59,22 +185,6 @@ const Login = () => {
</CForm>
</CCardBody>
</CCard>
<CCard className="text-white bg-primary py-5" style={{ width: '44%' }}>
<CCardBody className="text-center">
<div>
<h2>Sign up</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.
</p>
<Link to="/register">
<CButton color="primary" className="mt-3" active tabIndex={-1}>
Register Now!
</CButton>
</Link>
</div>
</CCardBody>
</CCard>
</CCardGroup>
</CCol>
</CRow>

View File

@ -2,80 +2,118 @@ import React, { useState } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faAngleLeft, faEnvelope, faLockOpen, faUnlockAlt } from '@fortawesome/free-solid-svg-icons'
import { Col, Row, Form, Card, Button, Container, InputGroup } from '@themesberg/react-bootstrap'
import { Link } from 'react-router-dom'
import { Link, useNavigate } from 'react-router-dom'
// import axios from 'axios'
// import { Routes } from '../../routes'
import Swal from 'sweetalert2'
import { isAutheticated } from '../../../auth'
import Axios from '../../../axios'
// import Axios from '../../axios'
const ChangePassword = () => {
const [Loading, setLoading] = useState(false)
const [formData, setFormData] = useState({
const [loading, setLoading] = useState(false)
const navigate = useNavigate()
const token = isAutheticated()
const [user, setUser] = useState({
oldPassword: '',
newPassword: '',
confirmPassword: '',
})
const [errors, setErrors] = useState({
confirmPasswordError: '',
newPasswordError: '',
oldPasswordError: '',
passwordMismatchError: '',
})
const validEmailRegex = RegExp(
/^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i,
)
const validPasswordRegex = RegExp(/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[^\w\s]).{7,}$/)
const handleChange = (e) => {
const { name, value } = e.target
switch (name) {
case 'oldPassword':
setErrors({
...errors,
oldPasswordError: validPasswordRegex.test(value)
? ''
: 'Password Shoud Be 8 Characters Long, Atleast One Uppercase, Atleast One Lowercase,Atleast One Digit, Atleast One Special Character',
})
// console.log(formData);
const handleInputChange = (e) => {
const { name, value } = e.target
setFormData((prevData) => ({ ...prevData, [name]: value }))
break
case 'newPassword':
setErrors({
...errors,
newPasswordError: validPasswordRegex.test(value)
? ''
: 'Password Shoud Be 8 Characters Long, Atleast One Uppercase, Atleast One Lowercase,Atleast One Digit, Atleast One Special Character',
})
break
case 'confirmPassword':
setErrors({
...errors,
confirmPasswordError: validPasswordRegex.test(value)
? ''
: 'Password should be 8 characters long, contain at least one uppercase letter, one lowercase letter, one digit, and one special character',
passwordMismatchError: value !== user.newPassword ? 'Passwords do not match' : '',
})
break
default:
break
}
const formHandler = async (e) => {
setUser({ ...user, [name]: value })
}
const handleSubmit = async (e) => {
e.preventDefault()
if (!(user.oldPassword && user.newPassword && user.confirmPassword)) {
return Swal.fire('Error!', 'All fields are required', 'error')
}
if (!(user.newPassword.length >= 8)) {
return Swal.fire('Error!', 'New password must be 8 characters long', 'error')
}
if (user.newPassword !== user.confirmPassword) {
return Swal.fire('Error!', 'New Password and Confirm Password do not match!', 'error')
}
setLoading(true)
// const jwt = localStorage.getItem('jwt')
// console.log(jwt);
// try {
// const response = await Axios.post(
// '/api/password',
// {
// oldPassword: formData.oldPassword,
// newPassword: formData.newPassword,
// },
// {
// headers: {
// jwt: `${jwt}`,
// 'Content-Type': 'application/json',
// },
// },
// )
// // console.log("response ", response);
// if (response.status === 200) {
// // Display success alert
// Swal.fire({
// icon: 'success',
// title: 'Password Changed Successfully',
// text: 'Your password has been changed successfully!',
// }).then((result) => {
// if (result.isConfirmed) {
// history.push(Routes.Presentation.path)
// // history.push(Routes.Dashboard.path);
// }
// })
// // Redirect to the dashboard
// } else {
// // Handle other scenarios if needed
// Swal.fire({
// icon: 'error',
// title: 'Password Change Failed',
// text: 'There was an issue changing your password. Please try again.',
// })
// }
// } catch (err) {
// console.log('error is ', err)
// Swal.fire({
// icon: 'error',
// title: 'Password Change Failed',
// text: 'There was an error changing your password. Please try again.',
// })
// }
try {
const res = await Axios.put(
'/api/v1/user/password/update',
{ ...user },
{
headers: {
Authorization: `Bearer ${token}`,
},
},
)
console.log(res)
if (res?.data.success) {
Swal.fire({
title: 'Done',
text: 'Password Changed',
icon: 'success',
confirmButtonText: 'ok',
confirmButtonColor: '#303c54',
iconColor: '#303c54',
}).then(() => {
navigate('/dashboard')
})
}
} catch (error) {
Swal.fire('Error!', error.response?.data.message || 'Something went wrong', 'error')
} finally {
setLoading(false)
}
}
return (
<main>
<section className="bg-soft d-flex align-items-center my-5 mt-lg-6 mb-lg-5">
@ -93,7 +131,7 @@ const ChangePassword = () => {
>
<div className="bg-white shadow-soft border rounded border-light p-4 p-lg-5 w-100 fmxw-500">
<h3 className="mb-4">Change Password</h3>
<Form onSubmit={formHandler}>
<Form onSubmit={handleSubmit}>
<Form.Group id="password" className="mb-4">
<Form.Label>Old Password</Form.Label>
<InputGroup>
@ -105,13 +143,16 @@ const ChangePassword = () => {
type="password"
placeholder="old password"
name="oldPassword"
value={formData.oldPassword}
onChange={handleInputChange}
value={user.oldPassword}
onChange={handleChange}
pattern="^(?=.*[a-z](){}[])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$"
title="The password should have 1 upper-case letter, 1 lower-case letter, 1 number, 1 special character and at least 8 characters."
/>
</InputGroup>
</Form.Group>
{errors.oldPasswordError && (
<p className="text-center py-2 text-danger">{errors.oldPasswordError}</p>
)}
<Form.Group id="password" className="mb-4">
<Form.Label>New Password</Form.Label>
<InputGroup>
@ -123,16 +164,40 @@ const ChangePassword = () => {
type="password"
placeholder="new password"
name="newPassword"
value={formData.newPassword}
onChange={handleInputChange}
value={user.newPassword}
onChange={handleChange}
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$"
title="The password should have 1 upper-case letter, 1 lower-case letter, 1 number, 1 special character and at least 8 characters."
/>
</InputGroup>
</Form.Group>
{errors.newPasswordError && (
<p className="text-center py-2 text-danger">{errors.newPasswordError}</p>
)}
<Form.Group id="password" className="mb-4">
<Form.Label>Confirm Password</Form.Label>
<InputGroup>
<InputGroup.Text>
<FontAwesomeIcon icon={faLockOpen} />
</InputGroup.Text>
<Form.Control
required
type="password"
placeholder="Confirm password"
name="confirmPassword"
value={user.confirmPassword}
onChange={handleChange}
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$"
title="The password should have 1 upper-case letter, 1 lower-case letter, 1 number, 1 special character and at least 8 characters."
/>
</InputGroup>
</Form.Group>
{errors.confirmPasswordError && (
<p className="text-center py-2 text-danger">{errors.confirmPasswordError}</p>
)}
<Button variant="primary" type="submit" className="w-100">
{Loading ? <>Loading...</> : <> Change Password </>}
{loading ? <>Loading...</> : <> Change Password </>}
</Button>
</Form>
</div>

View File

@ -10,6 +10,9 @@ import {
} from '@fortawesome/free-solid-svg-icons'
import { Col, Row, Form, Card, Button, Container, InputGroup } from '@themesberg/react-bootstrap'
import { Link, useNavigate } from 'react-router-dom'
import { isAutheticated } from '../../../auth'
import Axios from '../../../axios'
import Swal from 'sweetalert2'
// import { Axios } from 'axios'
// import Axios from '../../axios'
// import { Routes } from '../../routes'
@ -17,98 +20,91 @@ import { Link, useNavigate } from 'react-router-dom'
// import Swal from 'sweetalert2'
const MyProfile = () => {
const navigate = useNavigate()
const [Loading, setLoading] = useState(false)
const [image, setImage] = useState('')
const [loading, setLoading] = useState(false)
const [imagesPreview, setImagesPreview] = useState()
const token = isAutheticated()
const [formData, setFormData] = useState({
mobile: '',
const [ownerDetails, setOwnerDetails] = useState({
name: '',
email: '',
phone: '',
})
const history = useNavigate()
console.log(formData)
const getData = async () => {
let res = await Axios.get(`/api/v1/user/details`, {
headers: {
Authorization: `Bearer ${token}`,
},
})
if (res.data.success) {
setOwnerDetails({ ...res.data.user })
// console.log(formData);
const handleInputChange = (e) => {
const { name, value } = e.target
setFormData((prevData) => ({ ...prevData, [name]: value }))
if (res.data.user.avatar) {
setImagesPreview(res.data.user.avatar.url)
}
const formHandler = async (e) => {
}
}
const handleChange = (event) => {
const { name, value } = event.target
setOwnerDetails({ ...ownerDetails, [name]: value })
}
async function handleSubmit(e) {
e.preventDefault()
// const jwt = localStorage.getItem('jwt')
// console.log(jwt);
// try {
// const response = await Axios.post(
// '/api/user',
// {
// username: formData.name,
// newNumber: formData.mobile,
// email: formData.email,
// },
// {
// headers: {
// jwt: `${jwt}`,
// 'Content-Type': 'application/json',
// },
// },
// )
// // console.log("response ", response);
// if (response.status === 200) {
// // Display success alert
// Swal.fire({
// icon: 'success',
// title: 'Profile Updated Successfully',
// text: 'Your Profile has been Updated successfully!',
// }).then((result) => {
// if (result.isConfirmed) {
// history.push(Routes.Presentation.path)
// // history.push(Routes.Dashboard.path);
// }
// })
// // Redirect to the dashboard
// } else {
// // Handle other scenarios if needed
// Swal.fire({
// icon: 'error',
// title: 'Profile Update Failed',
// text: 'There was an issue updating your profile. Please try again.',
// })
// }
// } catch (err) {
// console.log('error is ', err)
// Swal.fire({
// icon: 'error',
// title: 'Profile Update Failed',
// text: 'There was an issue updating your profile. Please try again.',
// })
// }
if (ownerDetails.name === '' || ownerDetails.email === '' || ownerDetails.phone === '') {
Swal.fire({
title: 'Warning',
text: 'Fill all mandatory fields',
icon: 'error',
button: 'Close',
dangerMode: true,
})
return
}
// const getMe = async () => {
// const jwt = localStorage.getItem('jwt')
const formData = new FormData()
formData.append('name', ownerDetails.name)
formData.append('email', ownerDetails.email)
formData.append('phone', ownerDetails.phone)
// try {
// const respose = await Axios.get('/api/getMe', {
// headers: {
// jwt: `${jwt}`,
// 'Content-Type': 'application/json',
// },
// })
setLoading(true)
try {
const res = await Axios.put(`/api/v1/user/update/profile`, formData, {
headers: {
'Access-Control-Allow-Origin': '*',
Authorization: `Bearer ${token}`,
'Content-Type': 'multipart/formdata',
},
})
if (res.data.success === true) {
setLoading(false)
Swal.fire({
title: 'Edited',
text: 'Profile Edited Successfully!',
icon: 'success',
button: 'Return',
})
}
} catch (error) {
const message = error?.response?.data?.message || 'Something went wrong!'
setLoading(false)
Swal.fire({
title: 'Warning',
text: message,
icon: 'error',
button: 'Retry',
dangerMode: true,
})
}
}
console.log(ownerDetails)
const handleCancle = () => {
Navigate('/dashboard')
}
useEffect(() => {
getData()
}, [])
// // console.log(respose.data);
// setFormData({
// mobile: respose?.data?.number,
// name: respose?.data?.name,
// email: respose?.data?.email,
// })
// } catch (err) {
// console.log('error in get user', err)
// }
// }
// useEffect(() => {
// getMe()
// }, [])
return (
<main>
<section className="bg-soft d-flex align-items-center my-5 mt-lg-6 mb-lg-5">
@ -122,7 +118,7 @@ const MyProfile = () => {
<Col xs={12} className="d-flex align-items-center justify-content-center">
<div className="bg-white shadow-soft border rounded border-light p-4 p-lg-5 w-100 fmxw-500">
<h3 className="mb-4">Profile</h3>
<Form onSubmit={formHandler}>
<Form onSubmit={handleSubmit}>
<Form.Group id="name" className="mb-4">
<Form.Label>Name</Form.Label>
<InputGroup>
@ -134,8 +130,8 @@ const MyProfile = () => {
type="text"
placeholder="Your Name"
name="name"
value={formData.name}
onChange={handleInputChange}
value={ownerDetails.name}
onChange={handleChange}
/>
</InputGroup>
</Form.Group>
@ -152,9 +148,9 @@ const MyProfile = () => {
type="email"
placeholder="example@gmail.com"
name="email"
value={formData.email}
onChange={handleInputChange}
pattern="[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
value={ownerDetails.email}
onChange={handleChange}
// pattern="[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
title="Please enter a valid email address"
/>
</InputGroup>
@ -171,8 +167,8 @@ const MyProfile = () => {
type="tel"
placeholder="Your Mobile Number"
name="mobile"
value={formData.mobile}
onChange={handleInputChange}
value={ownerDetails.phone}
onChange={handleChange}
// pattern="[0-9]{10}" // Assuming a 10-digit mobile number, adjust as needed
title="Please enter a valid 10-digit mobile number"
/>
@ -180,7 +176,7 @@ const MyProfile = () => {
</Form.Group>
<Button variant="primary" type="submit" className="w-100">
{Loading ? <>Loading...</> : <>Update</>}
{loading ? <>Loading...</> : <>Update</>}
</Button>
</Form>
</div>

View File

@ -1,9 +1,58 @@
import React from 'react'
import {
Box,
Card,
CircularProgress,
Container,
FormControl,
Grid,
MenuItem,
Select,
Typography,
} from '@mui/material'
import cardData from '../../assets/data/cardjson.json'
import React, { useState } from 'react'
import ShopCard from './shopCard'
const Shop = () => {
const [option, setOption] = useState('all')
const handleChange = (event) => {
setOption(event.target.value)
}
const filteredData =
option === 'all' ? cardData : cardData.filter((item) => item.categoryName === option)
return (
<>
<div>Shop</div>
<Container>
<Typography sx={{ fontWeight: 'bold' }} variant="h4">
Categories
</Typography>
<Select
sx={{ width: '400px', mt: '1rem' }}
labelId="demo-simple-select-label"
id="demo-simple-select"
value={option}
label=""
onChange={handleChange}
>
<MenuItem value="all">All</MenuItem>
<MenuItem value="camera">Camera</MenuItem>
<MenuItem value="phone">Phone</MenuItem>
</Select>
<Box mt={3}>
{/* Cards */}
<Grid container spacing={2}>
{filteredData.map((item, i) => (
<Grid key={i} item xs={12} sm={6} md={4} lg={4}>
<ShopCard item={item} />
</Grid>
))}
</Grid>
</Box>
</Container>
</>
)
}

View File

@ -0,0 +1,33 @@
/* eslint-disable react/prop-types */
import { Button, Card, CardContent, CardMedia, Typography } from '@mui/material'
import React from 'react'
// eslint-disable-next-line react/prop-types
const ShopCard = ({ item }) => {
return (
<div>
<Card>
<CardMedia component="img" height="150" image={item?.image} alt={item?.name} />
<CardContent>
<Typography gutterBottom variant="h5" component="div">
{item?.name}
</Typography>
<Typography variant="body2" color="text.secondary">
${item?.price}
</Typography>
<Button
variant="contained"
color="primary"
fullWidth
// onClick={handleAddToCart}
sx={{ marginTop: '10px' }}
>
Add to Cart
</Button>
</CardContent>
</Card>
</div>
)
}
export default ShopCard