first commit
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import React from 'react'
|
||||
import {KTSVG, toAbsoluteUrl} from '../../../_metronic/helpers'
|
||||
import {Link} from 'react-router-dom'
|
||||
import {Dropdown1} from '../../../_metronic/partials'
|
||||
import {useLocation} from 'react-router'
|
||||
|
||||
const AccountHeader: React.FC = () => {
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<div className='card mb-5 mb-xl-10'>
|
||||
<div className='card-body pt-9 pb-0'>
|
||||
<div className='d-flex flex-wrap flex-sm-nowrap mb-3'>
|
||||
<div className='me-7 mb-4'>
|
||||
<div className='symbol symbol-100px symbol-lg-160px symbol-fixed position-relative'>
|
||||
<img src={toAbsoluteUrl('/media/avatars/300-1.jpg')} alt='Metronic' />
|
||||
<div className='position-absolute translate-middle bottom-0 start-100 mb-6 bg-success rounded-circle border border-4 border-white h-20px w-20px'></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex-grow-1'>
|
||||
<div className='d-flex justify-content-between align-items-start flex-wrap mb-2'>
|
||||
<div className='d-flex flex-column'>
|
||||
<div className='d-flex align-items-center mb-2'>
|
||||
<a href='#' className='text-gray-800 text-hover-primary fs-2 fw-bolder me-1'>
|
||||
Max Smith
|
||||
</a>
|
||||
<a href='#'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/general/gen026.svg'
|
||||
className='svg-icon-1 svg-icon-primary'
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href='#'
|
||||
className='btn btn-sm btn-light-success fw-bolder ms-2 fs-8 py-1 px-3'
|
||||
data-bs-toggle='modal'
|
||||
data-bs-target='#kt_modal_upgrade_plan'
|
||||
>
|
||||
Upgrade to Pro
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-wrap fw-bold fs-6 mb-4 pe-2'>
|
||||
<a
|
||||
href='#'
|
||||
className='d-flex align-items-center text-gray-400 text-hover-primary me-5 mb-2'
|
||||
>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/communication/com006.svg'
|
||||
className='svg-icon-4 me-1'
|
||||
/>
|
||||
Developer
|
||||
</a>
|
||||
<a
|
||||
href='#'
|
||||
className='d-flex align-items-center text-gray-400 text-hover-primary me-5 mb-2'
|
||||
>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/general/gen018.svg'
|
||||
className='svg-icon-4 me-1'
|
||||
/>
|
||||
SF, Bay Area
|
||||
</a>
|
||||
<a
|
||||
href='#'
|
||||
className='d-flex align-items-center text-gray-400 text-hover-primary mb-2'
|
||||
>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/communication/com011.svg'
|
||||
className='svg-icon-4 me-1'
|
||||
/>
|
||||
max@kt.com
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex my-4'>
|
||||
<a href='#' className='btn btn-sm btn-light me-2' id='kt_user_follow_button'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/arrows/arr012.svg'
|
||||
className='svg-icon-3 d-none'
|
||||
/>
|
||||
|
||||
<span className='indicator-label'>Follow</span>
|
||||
<span className='indicator-progress'>
|
||||
Please wait...
|
||||
<span className='spinner-border spinner-border-sm align-middle ms-2'></span>
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
href='#'
|
||||
className='btn btn-sm btn-primary me-3'
|
||||
data-bs-toggle='modal'
|
||||
data-bs-target='#kt_modal_offer_a_deal'
|
||||
>
|
||||
Hire Me
|
||||
</a>
|
||||
<div className='me-0'>
|
||||
<button
|
||||
className='btn btn-sm btn-icon btn-bg-light btn-active-color-primary'
|
||||
data-kt-menu-trigger='click'
|
||||
data-kt-menu-placement='bottom-end'
|
||||
data-kt-menu-flip='top-end'
|
||||
>
|
||||
<i className='bi bi-three-dots fs-3'></i>
|
||||
</button>
|
||||
<Dropdown1 />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-wrap flex-stack'>
|
||||
<div className='d-flex flex-column flex-grow-1 pe-8'>
|
||||
<div className='d-flex flex-wrap'>
|
||||
<div className='border border-gray-300 border-dashed rounded min-w-125px py-3 px-4 me-6 mb-3'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/arrows/arr066.svg'
|
||||
className='svg-icon-3 svg-icon-success me-2'
|
||||
/>
|
||||
<div className='fs-2 fw-bolder'>4500$</div>
|
||||
</div>
|
||||
|
||||
<div className='fw-bold fs-6 text-gray-400'>Earnings</div>
|
||||
</div>
|
||||
|
||||
<div className='border border-gray-300 border-dashed rounded min-w-125px py-3 px-4 me-6 mb-3'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/arrows/arr065.svg'
|
||||
className='svg-icon-3 svg-icon-danger me-2'
|
||||
/>
|
||||
<div className='fs-2 fw-bolder'>75</div>
|
||||
</div>
|
||||
|
||||
<div className='fw-bold fs-6 text-gray-400'>Projects</div>
|
||||
</div>
|
||||
|
||||
<div className='border border-gray-300 border-dashed rounded min-w-125px py-3 px-4 me-6 mb-3'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/arrows/arr066.svg'
|
||||
className='svg-icon-3 svg-icon-success me-2'
|
||||
/>
|
||||
<div className='fs-2 fw-bolder'>60%</div>
|
||||
</div>
|
||||
|
||||
<div className='fw-bold fs-6 text-gray-400'>Success Rate</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex align-items-center w-200px w-sm-300px flex-column mt-3'>
|
||||
<div className='d-flex justify-content-between w-100 mt-auto mb-2'>
|
||||
<span className='fw-bold fs-6 text-gray-400'>Profile Compleation</span>
|
||||
<span className='fw-bolder fs-6'>50%</span>
|
||||
</div>
|
||||
<div className='h-5px mx-3 w-100 bg-light mb-3'>
|
||||
<div
|
||||
className='bg-success rounded h-5px'
|
||||
role='progressbar'
|
||||
style={{width: '50%'}}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex overflow-auto h-55px'>
|
||||
<ul className='nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bolder flex-nowrap'>
|
||||
<li className='nav-item'>
|
||||
<Link
|
||||
className={
|
||||
`nav-link text-active-primary me-6 ` +
|
||||
(location.pathname === '/crafted/account/overview' && 'active')
|
||||
}
|
||||
to='/crafted/account/overview'
|
||||
>
|
||||
Overview
|
||||
</Link>
|
||||
</li>
|
||||
<li className='nav-item'>
|
||||
<Link
|
||||
className={
|
||||
`nav-link text-active-primary me-6 ` +
|
||||
(location.pathname === '/crafted/account/settings' && 'active')
|
||||
}
|
||||
to='/crafted/account/settings'
|
||||
>
|
||||
Settings
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {AccountHeader}
|
||||
@@ -0,0 +1,58 @@
|
||||
import React from 'react'
|
||||
import {Navigate, Route, Routes, Outlet} from 'react-router-dom'
|
||||
import {PageLink, PageTitle} from '../../../_metronic/layout/core'
|
||||
import {Overview} from './components/Overview'
|
||||
import {Settings} from './components/settings/Settings'
|
||||
import {AccountHeader} from './AccountHeader'
|
||||
|
||||
const accountBreadCrumbs: Array<PageLink> = [
|
||||
{
|
||||
title: 'Account',
|
||||
path: '/crafted/account/overview',
|
||||
isSeparator: false,
|
||||
isActive: false,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
path: '',
|
||||
isSeparator: true,
|
||||
isActive: false,
|
||||
},
|
||||
]
|
||||
|
||||
const AccountPage: React.FC = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route
|
||||
element={
|
||||
<>
|
||||
<AccountHeader />
|
||||
<Outlet />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Route
|
||||
path='overview'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={accountBreadCrumbs}>Overview</PageTitle>
|
||||
<Overview />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='settings'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={accountBreadCrumbs}>Settings</PageTitle>
|
||||
<Settings />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route index element={<Navigate to='/crafted/account/overview' />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
@@ -0,0 +1,144 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import React from 'react'
|
||||
import {Link} from 'react-router-dom'
|
||||
import {KTSVG} from '../../../../_metronic/helpers'
|
||||
import {
|
||||
ChartsWidget1,
|
||||
TablesWidget1,
|
||||
ListsWidget5,
|
||||
TablesWidget5,
|
||||
} from '../../../../_metronic/partials/widgets'
|
||||
|
||||
export function Overview() {
|
||||
return (
|
||||
<>
|
||||
<div className='card mb-5 mb-xl-10' id='kt_profile_details_view'>
|
||||
<div className='card-header cursor-pointer'>
|
||||
<div className='card-title m-0'>
|
||||
<h3 className='fw-bolder m-0'>Profile Details</h3>
|
||||
</div>
|
||||
|
||||
<Link to='/crafted/account/settings' className='btn btn-primary align-self-center'>
|
||||
Edit Profile
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className='card-body p-9'>
|
||||
<div className='row mb-7'>
|
||||
<label className='col-lg-4 fw-bold text-muted'>Full Name</label>
|
||||
|
||||
<div className='col-lg-8'>
|
||||
<span className='fw-bolder fs-6 text-dark'>Max Smith</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row mb-7'>
|
||||
<label className='col-lg-4 fw-bold text-muted'>Company</label>
|
||||
|
||||
<div className='col-lg-8 fv-row'>
|
||||
<span className='fw-bold fs-6'>Keenthemes</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row mb-7'>
|
||||
<label className='col-lg-4 fw-bold text-muted'>
|
||||
Contact Phone
|
||||
<i
|
||||
className='fas fa-exclamation-circle ms-1 fs-7'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Phone number must be active'
|
||||
></i>
|
||||
</label>
|
||||
|
||||
<div className='col-lg-8 d-flex align-items-center'>
|
||||
<span className='fw-bolder fs-6 me-2'>044 3276 454 935</span>
|
||||
|
||||
<span className='badge badge-success'>Verified</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row mb-7'>
|
||||
<label className='col-lg-4 fw-bold text-muted'>Company Site</label>
|
||||
|
||||
<div className='col-lg-8'>
|
||||
<a href='#' className='fw-bold fs-6 text-dark text-hover-primary'>
|
||||
keenthemes.com
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row mb-7'>
|
||||
<label className='col-lg-4 fw-bold text-muted'>
|
||||
Country
|
||||
<i
|
||||
className='fas fa-exclamation-circle ms-1 fs-7'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Country of origination'
|
||||
></i>
|
||||
</label>
|
||||
|
||||
<div className='col-lg-8'>
|
||||
<span className='fw-bolder fs-6 text-dark'>Germany</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row mb-7'>
|
||||
<label className='col-lg-4 fw-bold text-muted'>Communication</label>
|
||||
|
||||
<div className='col-lg-8'>
|
||||
<span className='fw-bolder fs-6 text-dark'>Email, Phone</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row mb-10'>
|
||||
<label className='col-lg-4 fw-bold text-muted'>Allow Changes</label>
|
||||
|
||||
<div className='col-lg-8'>
|
||||
<span className='fw-bold fs-6'>Yes</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='notice d-flex bg-light-warning rounded border-warning border border-dashed p-6'>
|
||||
<KTSVG
|
||||
path='icons/duotune/general/gen044.svg'
|
||||
className='svg-icon-2tx svg-icon-warning me-4'
|
||||
/>
|
||||
<div className='d-flex flex-stack flex-grow-1'>
|
||||
<div className='fw-bold'>
|
||||
<h4 className='text-gray-800 fw-bolder'>We need your attention!</h4>
|
||||
<div className='fs-6 text-gray-600'>
|
||||
Your payment was declined. To start using tools, please
|
||||
<Link className='fw-bolder' to='/crafted/account/settings'>
|
||||
{' '}
|
||||
Add Payment Method
|
||||
</Link>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row gy-10 gx-xl-10'>
|
||||
<div className='col-xl-6'>
|
||||
<ChartsWidget1 className='card-xxl-stretch mb-5 mb-xl-10' />
|
||||
</div>
|
||||
|
||||
<div className='col-xl-6'>
|
||||
<TablesWidget1 className='card-xxl-stretch mb-5 mb-xl-10' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row gy-10 gx-xl-10'>
|
||||
<div className='col-xl-6'>
|
||||
<ListsWidget5 className='card-xxl-stretch mb-5 mb-xl-10' />
|
||||
</div>
|
||||
|
||||
<div className='col-xl-6'>
|
||||
<TablesWidget5 className='card-xxl-stretch mb-5 mb-xl-10' />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react'
|
||||
import {ProfileDetails} from './cards/ProfileDetails'
|
||||
import {SignInMethod} from './cards/SignInMethod'
|
||||
import {ConnectedAccounts} from './cards/ConnectedAccounts'
|
||||
import {EmailPreferences} from './cards/EmailPreferences'
|
||||
import {Notifications} from './cards/Notifications'
|
||||
import {DeactivateAccount} from './cards/DeactivateAccount'
|
||||
|
||||
export function Settings() {
|
||||
return (
|
||||
<>
|
||||
<ProfileDetails />
|
||||
<SignInMethod />
|
||||
<ConnectedAccounts />
|
||||
<EmailPreferences />
|
||||
<Notifications />
|
||||
<DeactivateAccount />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
export interface IProfileDetails {
|
||||
avatar: string
|
||||
fName: string
|
||||
lName: string
|
||||
company: string
|
||||
contactPhone: string
|
||||
companySite: string
|
||||
country: string
|
||||
language: string
|
||||
timeZone: string
|
||||
currency: string
|
||||
communications: {
|
||||
email: boolean
|
||||
phone: boolean
|
||||
}
|
||||
allowMarketing: boolean
|
||||
}
|
||||
|
||||
export interface IUpdateEmail {
|
||||
newEmail: string
|
||||
confirmPassword: string
|
||||
}
|
||||
|
||||
export interface IUpdatePassword {
|
||||
currentPassword: string
|
||||
newPassword: string
|
||||
passwordConfirmation: string
|
||||
}
|
||||
|
||||
export interface IConnectedAccounts {
|
||||
google: boolean
|
||||
github: boolean
|
||||
stack: boolean
|
||||
}
|
||||
|
||||
export interface IEmailPreferences {
|
||||
successfulPayments: boolean
|
||||
payouts: boolean
|
||||
freeCollections: boolean
|
||||
customerPaymentDispute: boolean
|
||||
refundAlert: boolean
|
||||
invoicePayments: boolean
|
||||
webhookAPIEndpoints: boolean
|
||||
}
|
||||
|
||||
export interface INotifications {
|
||||
notifications: {
|
||||
email: boolean
|
||||
phone: boolean
|
||||
}
|
||||
billingUpdates: {
|
||||
email: boolean
|
||||
phone: boolean
|
||||
}
|
||||
newTeamMembers: {
|
||||
email: boolean
|
||||
phone: boolean
|
||||
}
|
||||
completeProjects: {
|
||||
email: boolean
|
||||
phone: boolean
|
||||
}
|
||||
newsletters: {
|
||||
email: boolean
|
||||
phone: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface IDeactivateAccount {
|
||||
confirm: boolean
|
||||
}
|
||||
|
||||
export const profileDetailsInitValues: IProfileDetails = {
|
||||
avatar: '/media/avatars/300-1.jpg',
|
||||
fName: 'Max',
|
||||
lName: 'Smith',
|
||||
company: 'Keenthemes',
|
||||
contactPhone: '044 3276 454 935',
|
||||
companySite: 'keenthemes.com',
|
||||
country: '',
|
||||
language: '',
|
||||
timeZone: '',
|
||||
currency: '',
|
||||
communications: {
|
||||
email: false,
|
||||
phone: false,
|
||||
},
|
||||
allowMarketing: false,
|
||||
}
|
||||
|
||||
export const updateEmail: IUpdateEmail = {
|
||||
newEmail: 'support@keenthemes.com',
|
||||
confirmPassword: '',
|
||||
}
|
||||
|
||||
export const updatePassword: IUpdatePassword = {
|
||||
currentPassword: '',
|
||||
newPassword: '',
|
||||
passwordConfirmation: '',
|
||||
}
|
||||
|
||||
export const connectedAccounts: IConnectedAccounts = {
|
||||
google: true,
|
||||
github: true,
|
||||
stack: false,
|
||||
}
|
||||
|
||||
export const emailPreferences: IEmailPreferences = {
|
||||
successfulPayments: false,
|
||||
payouts: true,
|
||||
freeCollections: false,
|
||||
customerPaymentDispute: true,
|
||||
refundAlert: false,
|
||||
invoicePayments: true,
|
||||
webhookAPIEndpoints: false,
|
||||
}
|
||||
|
||||
export const notifications: INotifications = {
|
||||
notifications: {
|
||||
email: true,
|
||||
phone: true,
|
||||
},
|
||||
billingUpdates: {
|
||||
email: true,
|
||||
phone: true,
|
||||
},
|
||||
newTeamMembers: {
|
||||
email: true,
|
||||
phone: false,
|
||||
},
|
||||
completeProjects: {
|
||||
email: false,
|
||||
phone: true,
|
||||
},
|
||||
newsletters: {
|
||||
email: false,
|
||||
phone: false,
|
||||
},
|
||||
}
|
||||
|
||||
export const deactivateAccount: IDeactivateAccount = {
|
||||
confirm: false,
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import React, {useState} from 'react'
|
||||
import {KTSVG, toAbsoluteUrl} from '../../../../../../_metronic/helpers'
|
||||
import {IConnectedAccounts, connectedAccounts} from '../SettingsModel'
|
||||
|
||||
const ConnectedAccounts: React.FC = () => {
|
||||
const [data, setData] = useState<IConnectedAccounts>(connectedAccounts)
|
||||
|
||||
const updateData = (fieldsToUpdate: Partial<IConnectedAccounts>) => {
|
||||
const updatedData = {...data, ...fieldsToUpdate}
|
||||
setData(updatedData)
|
||||
}
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const click = () => {
|
||||
setLoading(true)
|
||||
setTimeout(() => {
|
||||
setLoading(false)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='card mb-5 mb-xl-10'>
|
||||
<div
|
||||
className='card-header border-0 cursor-pointer'
|
||||
role='button'
|
||||
data-bs-toggle='collapse'
|
||||
data-bs-target='#kt_account_connected_accounts'
|
||||
aria-expanded='true'
|
||||
aria-controls='kt_account_connected_accounts'
|
||||
>
|
||||
<div className='card-title m-0'>
|
||||
<h3 className='fw-bolder m-0'>Connected Accounts</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id='kt_account_connected_accounts' className='collapse show'>
|
||||
<div className='card-body border-top p-9'>
|
||||
<div className='notice d-flex bg-light-primary rounded border-primary border border-dashed mb-9 p-6'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/art/art006.svg'
|
||||
className='svg-icon-2tx svg-icon-primary me-4'
|
||||
/>
|
||||
<div className='d-flex flex-stack flex-grow-1'>
|
||||
<div className='fw-bold'>
|
||||
<div className='fs-6 text-gray-600'>
|
||||
Two-factor authentication adds an extra layer of security to your account. To log
|
||||
in, in you'll need to provide a 4 digit amazing code.
|
||||
<a href='#' className='fw-bolder'>
|
||||
Learn More
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='py-2'>
|
||||
<div className='d-flex flex-stack'>
|
||||
<div className='d-flex'>
|
||||
<img
|
||||
src={toAbsoluteUrl('/media/svg/brand-logos/google-icon.svg')}
|
||||
className='w-30px me-6'
|
||||
alt=''
|
||||
/>
|
||||
|
||||
<div className='d-flex flex-column'>
|
||||
<a href='#' className='fs-5 text-dark text-hover-primary fw-bolder'>
|
||||
Google
|
||||
</a>
|
||||
<div className='fs-6 fw-bold text-gray-400'>Plan properly your workflow</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='d-flex justify-content-end'>
|
||||
<div className='form-check form-check-solid form-switch'>
|
||||
<input
|
||||
className='form-check-input w-45px h-30px'
|
||||
type='checkbox'
|
||||
id='googleswitch'
|
||||
checked={data.google}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
google: !data.google,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label' htmlFor='googleswitch'></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed my-5'></div>
|
||||
|
||||
<div className='d-flex flex-stack'>
|
||||
<div className='d-flex'>
|
||||
<img
|
||||
src={toAbsoluteUrl('/media/svg/brand-logos/github.svg')}
|
||||
className='w-30px me-6'
|
||||
alt=''
|
||||
/>
|
||||
|
||||
<div className='d-flex flex-column'>
|
||||
<a href='#' className='fs-5 text-dark text-hover-primary fw-bolder'>
|
||||
Github
|
||||
</a>
|
||||
<div className='fs-6 fw-bold text-gray-400'>Keep eye on on your Repositories</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='d-flex justify-content-end'>
|
||||
<div className='form-check form-check-solid form-switch'>
|
||||
<input
|
||||
className='form-check-input w-45px h-30px'
|
||||
type='checkbox'
|
||||
id='githubswitch'
|
||||
checked={data.github}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
github: !data.github,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label' htmlFor='githubswitch'></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed my-5'></div>
|
||||
|
||||
<div className='d-flex flex-stack'>
|
||||
<div className='d-flex'>
|
||||
<img
|
||||
src={toAbsoluteUrl('/media/svg/brand-logos/slack-icon.svg')}
|
||||
className='w-30px me-6'
|
||||
alt=''
|
||||
/>
|
||||
|
||||
<div className='d-flex flex-column'>
|
||||
<a href='#' className='fs-5 text-dark text-hover-primary fw-bolder'>
|
||||
Slack
|
||||
</a>
|
||||
<div className='fs-6 fw-bold text-gray-400'>Integrate Projects Discussions</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='d-flex justify-content-end'>
|
||||
<div className='form-check form-check-solid form-switch'>
|
||||
<input
|
||||
className='form-check-input w-45px h-30px'
|
||||
type='checkbox'
|
||||
checked={data.stack}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
stack: !data.stack,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label' htmlFor='slackswitch'></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='card-footer d-flex justify-content-end py-6 px-9'>
|
||||
<button className='btn btn-light btn-active-light-primary me-2'>Discard</button>
|
||||
<button onClick={click} className='btn btn-primary'>
|
||||
{!loading && 'Save Changes'}
|
||||
{loading && (
|
||||
<span className='indicator-progress' style={{display: 'block'}}>
|
||||
Please wait...{' '}
|
||||
<span className='spinner-border spinner-border-sm align-middle ms-2'></span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {ConnectedAccounts}
|
||||
@@ -0,0 +1,105 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import React, {useState} from 'react'
|
||||
import {KTSVG} from '../../../../../../_metronic/helpers'
|
||||
import {IDeactivateAccount, deactivateAccount} from '../SettingsModel'
|
||||
import * as Yup from 'yup'
|
||||
import {useFormik} from 'formik'
|
||||
|
||||
const deactivateAccountSchema = Yup.object().shape({
|
||||
confirm: Yup.boolean().oneOf([true], 'Please check the box to deactivate your account'),
|
||||
})
|
||||
|
||||
const DeactivateAccount: React.FC = () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const formik = useFormik<IDeactivateAccount>({
|
||||
initialValues: {
|
||||
...deactivateAccount,
|
||||
},
|
||||
validationSchema: deactivateAccountSchema,
|
||||
onSubmit: () => {
|
||||
setLoading(true)
|
||||
setTimeout(() => {
|
||||
setLoading(false)
|
||||
}, 1000)
|
||||
alert('Account has been successfully deleted!')
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='card'>
|
||||
<div
|
||||
className='card-header border-0 cursor-pointer'
|
||||
role='button'
|
||||
data-bs-toggle='collapse'
|
||||
data-bs-target='#kt_account_deactivate'
|
||||
aria-expanded='true'
|
||||
aria-controls='kt_account_deactivate'
|
||||
>
|
||||
<div className='card-title m-0'>
|
||||
<h3 className='fw-bolder m-0'>Deactivate Account</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id='kt_account_deactivate' className='collapse show'>
|
||||
<form onSubmit={formik.handleSubmit} id='kt_account_deactivate_form' className='form'>
|
||||
<div className='card-body border-top p-9'>
|
||||
<div className='notice d-flex bg-light-warning rounded border-warning border border-dashed mb-9 p-6'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/general/gen044.svg'
|
||||
className='svg-icon-2tx svg-icon-warning me-4'
|
||||
/>
|
||||
|
||||
<div className='d-flex flex-stack flex-grow-1'>
|
||||
<div className='fw-bold'>
|
||||
<h4 className='text-gray-800 fw-bolder'>You Are Deactivating Your Account</h4>
|
||||
<div className='fs-6 text-gray-600'>
|
||||
For extra security, this requires you to confirm your email or phone number when
|
||||
you reset yousignr password.
|
||||
<br />
|
||||
<a className='fw-bolder' href='#'>
|
||||
Learn more
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='form-check form-check-solid fv-row'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
{...formik.getFieldProps('confirm')}
|
||||
/>
|
||||
<label className='form-check-label fw-bold ps-2 fs-6' htmlFor='deactivate'>
|
||||
I confirm my account deactivation
|
||||
</label>
|
||||
</div>
|
||||
{formik.touched.confirm && formik.errors.confirm && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>{formik.errors.confirm}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='card-footer d-flex justify-content-end py-6 px-9'>
|
||||
<button
|
||||
id='kt_account_deactivate_account_submit'
|
||||
type='submit'
|
||||
className='btn btn-danger fw-bold'
|
||||
>
|
||||
{!loading && 'Deactivate Account'}
|
||||
{loading && (
|
||||
<span className='indicator-progress' style={{display: 'block'}}>
|
||||
Please wait...{' '}
|
||||
<span className='spinner-border spinner-border-sm align-middle ms-2'></span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {DeactivateAccount}
|
||||
@@ -0,0 +1,219 @@
|
||||
import React, {useState} from 'react'
|
||||
import {IEmailPreferences, emailPreferences} from '../SettingsModel'
|
||||
|
||||
const EmailPreferences: React.FC = () => {
|
||||
const [data, setData] = useState<IEmailPreferences>(emailPreferences)
|
||||
|
||||
const updateData = (fieldsToUpdate: Partial<IEmailPreferences>) => {
|
||||
const updatedData = {...data, ...fieldsToUpdate}
|
||||
setData(updatedData)
|
||||
}
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const click = () => {
|
||||
setLoading(true)
|
||||
setTimeout(() => {
|
||||
setLoading(false)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='card mb-5 mb-xl-10'>
|
||||
<div
|
||||
className='card-header border-0 cursor-pointer'
|
||||
role='button'
|
||||
data-bs-toggle='collapse'
|
||||
data-bs-target='#kt_account_email_preferences'
|
||||
aria-expanded='true'
|
||||
aria-controls='kt_account_email_preferences'
|
||||
>
|
||||
<div className='card-title m-0'>
|
||||
<h3 className='fw-bolder m-0'>Email Preferences</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id='kt_account_email_preferences' className='collapse show'>
|
||||
<form className='form'>
|
||||
<div className='card-body border-top px-9 py-9'>
|
||||
<label className='form-check form-check-custom form-check-solid align-items-start'>
|
||||
<input
|
||||
className='form-check-input me-3'
|
||||
type='checkbox'
|
||||
name='email-preferences[]'
|
||||
defaultChecked={data.successfulPayments}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
successfulPayments: !data.successfulPayments,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<span className='form-check-label d-flex flex-column align-items-start'>
|
||||
<span className='fw-bolder fs-5 mb-0'>Successful Payments</span>
|
||||
<span className='text-muted fs-6'>
|
||||
Receive a notification for every successful payment.
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div className='separator separator-dashed my-6'></div>
|
||||
|
||||
<label className='form-check form-check-custom form-check-solid align-items-start'>
|
||||
<input
|
||||
className='form-check-input me-3'
|
||||
type='checkbox'
|
||||
name='email-preferences[]'
|
||||
defaultChecked={data.payouts}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
payouts: !data.payouts,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<span className='form-check-label d-flex flex-column align-items-start'>
|
||||
<span className='fw-bolder fs-5 mb-0'>Payouts</span>
|
||||
<span className='text-muted fs-6'>
|
||||
Receive a notification for every initiated payout.
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div className='separator separator-dashed my-6'></div>
|
||||
|
||||
<label className='form-check form-check-custom form-check-solid align-items-start'>
|
||||
<input
|
||||
className='form-check-input me-3'
|
||||
type='checkbox'
|
||||
name='email-preferences[]'
|
||||
defaultChecked={data.freeCollections}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
freeCollections: !data.freeCollections,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<span className='form-check-label d-flex flex-column align-items-start'>
|
||||
<span className='fw-bolder fs-5 mb-0'>Fee Collection</span>
|
||||
<span className='text-muted fs-6'>
|
||||
Receive a notification each time you collect a fee from sales
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div className='separator separator-dashed my-6'></div>
|
||||
|
||||
<label className='form-check form-check-custom form-check-solid align-items-start'>
|
||||
<input
|
||||
className='form-check-input me-3'
|
||||
type='checkbox'
|
||||
name='email-preferences[]'
|
||||
defaultChecked={data.customerPaymentDispute}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
customerPaymentDispute: !data.customerPaymentDispute,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<span className='form-check-label d-flex flex-column align-items-start'>
|
||||
<span className='fw-bolder fs-5 mb-0'>Customer Payment Dispute</span>
|
||||
<span className='text-muted fs-6'>
|
||||
Receive a notification if a payment is disputed by a customer and for dispute
|
||||
purposes.
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div className='separator separator-dashed my-6'></div>
|
||||
|
||||
<label className='form-check form-check-custom form-check-solid align-items-start'>
|
||||
<input
|
||||
className='form-check-input me-3'
|
||||
type='checkbox'
|
||||
name='email-preferences[]'
|
||||
defaultChecked={data.refundAlert}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
refundAlert: !data.refundAlert,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<span className='form-check-label d-flex flex-column align-items-start'>
|
||||
<span className='fw-bolder fs-5 mb-0'>Refund Alerts</span>
|
||||
<span className='text-muted fs-6'>
|
||||
Receive a notification if a payment is stated as risk by the Finance Department.
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div className='separator separator-dashed my-6'></div>
|
||||
|
||||
<label className='form-check form-check-custom form-check-solid align-items-start'>
|
||||
<input
|
||||
className='form-check-input me-3'
|
||||
type='checkbox'
|
||||
name='email-preferences[]'
|
||||
defaultChecked={data.invoicePayments}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
invoicePayments: !data.invoicePayments,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<span className='form-check-label d-flex flex-column align-items-start'>
|
||||
<span className='fw-bolder fs-5 mb-0'>Invoice Payments</span>
|
||||
<span className='text-muted fs-6'>
|
||||
Receive a notification if a customer sends an incorrect amount to pay their
|
||||
invoice.
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div className='separator separator-dashed my-6'></div>
|
||||
|
||||
<label className='form-check form-check-custom form-check-solid align-items-start'>
|
||||
<input
|
||||
className='form-check-input me-3'
|
||||
type='checkbox'
|
||||
name='email-preferences[]'
|
||||
defaultChecked={data.webhookAPIEndpoints}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
webhookAPIEndpoints: !data.webhookAPIEndpoints,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<span className='form-check-label d-flex flex-column align-items-start'>
|
||||
<span className='fw-bolder fs-5 mb-0'>Webhook API Endpoints</span>
|
||||
<span className='text-muted fs-6'>
|
||||
Receive notifications for consistently failing webhook API endpoints.
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className='card-footer d-flex justify-content-end py-6 px-9'>
|
||||
<button className='btn btn-lightbtn-active-light-primary me-2'>Discard</button>
|
||||
<button type='button' onClick={click} className='btn btn-primary'>
|
||||
{!loading && 'Save Changes'}
|
||||
{loading && (
|
||||
<span className='indicator-progress' style={{display: 'block'}}>
|
||||
Please wait...{' '}
|
||||
<span className='spinner-border spinner-border-sm align-middle ms-2'></span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {EmailPreferences}
|
||||
@@ -0,0 +1,294 @@
|
||||
import React, {useState} from 'react'
|
||||
import {INotifications, notifications} from '../SettingsModel'
|
||||
|
||||
const Notifications: React.FC = () => {
|
||||
const [data, setData] = useState<INotifications>(notifications)
|
||||
|
||||
const updateData = (fieldsToUpdate: Partial<INotifications>) => {
|
||||
const updatedData = {...data, ...fieldsToUpdate}
|
||||
setData(updatedData)
|
||||
}
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const click = () => {
|
||||
setLoading(true)
|
||||
setTimeout(() => {
|
||||
setLoading(false)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='card mb-5 mb-xl-10'>
|
||||
<div
|
||||
className='card-header border-0 cursor-pointer'
|
||||
role='button'
|
||||
data-bs-toggle='collapse'
|
||||
data-bs-target='#kt_account_notifications'
|
||||
aria-expanded='true'
|
||||
aria-controls='kt_account_notifications'
|
||||
>
|
||||
<div className='card-title m-0'>
|
||||
<h3 className='fw-bolder m-0'>Notifications</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id='kt_account_notifications' className='collapse show'>
|
||||
<form className='form'>
|
||||
<div className='card-body border-top px-9 pt-3 pb-4'>
|
||||
<div className='table-responsive'>
|
||||
<table className='table table-row-dashed border-gray-300 align-middle gy-6'>
|
||||
<tbody className='fs-6 fw-bold'>
|
||||
<tr>
|
||||
<td className='min-w-250px fs-4 fw-bolder'>Notifications</td>
|
||||
<td className='w-125px'>
|
||||
<div className='form-check form-check-solid'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
value=''
|
||||
id='kt_settings_notification_email'
|
||||
defaultChecked={data.notifications.email}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
notifications: {
|
||||
phone: data.notifications.phone,
|
||||
email: !data.notifications.email,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label
|
||||
className='form-check-label ps-2'
|
||||
htmlFor='kt_settings_notification_email'
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
<td className='w-125px'>
|
||||
<div className='form-check form-check-solid'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
value=''
|
||||
id='kt_settings_notification_phone'
|
||||
defaultChecked={data.notifications.phone}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
notifications: {
|
||||
phone: !data.notifications.phone,
|
||||
email: data.notifications.email,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label
|
||||
className='form-check-label ps-2'
|
||||
htmlFor='kt_settings_notification_phone'
|
||||
>
|
||||
Phone
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Billing Updates</td>
|
||||
<td>
|
||||
<div className='form-check form-check-solid'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
value='1'
|
||||
id='billing1'
|
||||
defaultChecked={data.billingUpdates.email}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
billingUpdates: {
|
||||
phone: data.billingUpdates.phone,
|
||||
email: !data.billingUpdates.email,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label ps-2' htmlFor='billing1'></label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className='form-check form-check-solid'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
value=''
|
||||
id='billing2'
|
||||
defaultChecked={data.billingUpdates.phone}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
billingUpdates: {
|
||||
phone: !data.billingUpdates.phone,
|
||||
email: data.billingUpdates.email,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label ps-2' htmlFor='billing2'></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>New Team Members</td>
|
||||
<td>
|
||||
<div className='form-check form-check-solid'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
value=''
|
||||
id='team1'
|
||||
defaultChecked={data.newTeamMembers.email}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
newTeamMembers: {
|
||||
phone: data.newTeamMembers.phone,
|
||||
email: !data.newTeamMembers.email,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label ps-2' htmlFor='team1'></label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className='form-check form-check-solid'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
value=''
|
||||
id='team2'
|
||||
defaultChecked={data.newTeamMembers.phone}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
newTeamMembers: {
|
||||
phone: !data.newTeamMembers.phone,
|
||||
email: data.newTeamMembers.email,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label ps-2' htmlFor='team2'></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Completed Projects</td>
|
||||
<td>
|
||||
<div className='form-check form-check-solid'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
value=''
|
||||
id='project1'
|
||||
defaultChecked={data.completeProjects.email}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
completeProjects: {
|
||||
phone: data.completeProjects.phone,
|
||||
email: !data.completeProjects.email,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label ps-2' htmlFor='project1'></label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className='form-check form-check-solid'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
value=''
|
||||
id='project2'
|
||||
defaultChecked={data.completeProjects.phone}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
completeProjects: {
|
||||
phone: !data.completeProjects.phone,
|
||||
email: data.completeProjects.email,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label ps-2' htmlFor='project2'></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td className='border-bottom-0'>Newsletters</td>
|
||||
<td className='border-bottom-0'>
|
||||
<div className='form-check form-check-solid'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
value=''
|
||||
id='newsletter1'
|
||||
defaultChecked={data.newsletters.email}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
newsletters: {
|
||||
phone: data.newsletters.phone,
|
||||
email: !data.newsletters.email,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label ps-2' htmlFor='newsletter1'></label>
|
||||
</div>
|
||||
</td>
|
||||
<td className='border-bottom-0'>
|
||||
<div className='form-check form-check-solid'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
value=''
|
||||
id='newsletter2'
|
||||
defaultChecked={data.newsletters.phone}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
newsletters: {
|
||||
phone: !data.newsletters.phone,
|
||||
email: data.newsletters.email,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label ps-2' htmlFor='newsletter2'></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='card-footer d-flex justify-content-end py-6 px-9'>
|
||||
<button className='btn btn-light btn-active-light-primary me-2'>Discard</button>
|
||||
<button type='button' onClick={click} className='btn btn-primary'>
|
||||
{!loading && 'Save Changes'}
|
||||
{loading && (
|
||||
<span className='indicator-progress' style={{display: 'block'}}>
|
||||
Please wait...{' '}
|
||||
<span className='spinner-border spinner-border-sm align-middle ms-2'></span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {Notifications}
|
||||
@@ -0,0 +1,782 @@
|
||||
import React, {useState} from 'react'
|
||||
import {toAbsoluteUrl} from '../../../../../../_metronic/helpers'
|
||||
import {IProfileDetails, profileDetailsInitValues as initialValues} from '../SettingsModel'
|
||||
import * as Yup from 'yup'
|
||||
import {useFormik} from 'formik'
|
||||
|
||||
const profileDetailsSchema = Yup.object().shape({
|
||||
fName: Yup.string().required('First name is required'),
|
||||
lName: Yup.string().required('Last name is required'),
|
||||
company: Yup.string().required('Company name is required'),
|
||||
contactPhone: Yup.string().required('Contact phone is required'),
|
||||
companySite: Yup.string().required('Company site is required'),
|
||||
country: Yup.string().required('Country is required'),
|
||||
language: Yup.string().required('Language is required'),
|
||||
timeZone: Yup.string().required('Time zone is required'),
|
||||
currency: Yup.string().required('Currency is required'),
|
||||
})
|
||||
|
||||
const ProfileDetails: React.FC = () => {
|
||||
const [data, setData] = useState<IProfileDetails>(initialValues)
|
||||
const updateData = (fieldsToUpdate: Partial<IProfileDetails>): void => {
|
||||
const updatedData = Object.assign(data, fieldsToUpdate)
|
||||
setData(updatedData)
|
||||
}
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const formik = useFormik<IProfileDetails>({
|
||||
initialValues,
|
||||
validationSchema: profileDetailsSchema,
|
||||
onSubmit: (values) => {
|
||||
setLoading(true)
|
||||
setTimeout(() => {
|
||||
values.communications.email = data.communications.email
|
||||
values.communications.phone = data.communications.phone
|
||||
values.allowMarketing = data.allowMarketing
|
||||
const updatedData = Object.assign(data, values)
|
||||
setData(updatedData)
|
||||
setLoading(false)
|
||||
}, 1000)
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='card mb-5 mb-xl-10'>
|
||||
<div
|
||||
className='card-header border-0 cursor-pointer'
|
||||
role='button'
|
||||
data-bs-toggle='collapse'
|
||||
data-bs-target='#kt_account_profile_details'
|
||||
aria-expanded='true'
|
||||
aria-controls='kt_account_profile_details'
|
||||
>
|
||||
<div className='card-title m-0'>
|
||||
<h3 className='fw-bolder m-0'>Profile Details</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id='kt_account_profile_details' className='collapse show'>
|
||||
<form onSubmit={formik.handleSubmit} noValidate className='form'>
|
||||
<div className='card-body border-top p-9'>
|
||||
<div className='row mb-6'>
|
||||
<label className='col-lg-4 col-form-label fw-bold fs-6'>Avatar</label>
|
||||
<div className='col-lg-8'>
|
||||
<div
|
||||
className='image-input image-input-outline'
|
||||
data-kt-image-input='true'
|
||||
style={{backgroundImage: `url(${toAbsoluteUrl('/media/avatars/blank.png')})`}}
|
||||
>
|
||||
<div
|
||||
className='image-input-wrapper w-125px h-125px'
|
||||
style={{backgroundImage: `url(${toAbsoluteUrl(data.avatar)})`}}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row mb-6'>
|
||||
<label className='col-lg-4 col-form-label required fw-bold fs-6'>Full Name</label>
|
||||
|
||||
<div className='col-lg-8'>
|
||||
<div className='row'>
|
||||
<div className='col-lg-6 fv-row'>
|
||||
<input
|
||||
type='text'
|
||||
className='form-control form-control-lg form-control-solid mb-3 mb-lg-0'
|
||||
placeholder='First name'
|
||||
{...formik.getFieldProps('fName')}
|
||||
/>
|
||||
{formik.touched.fName && formik.errors.fName && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>{formik.errors.fName}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='col-lg-6 fv-row'>
|
||||
<input
|
||||
type='text'
|
||||
className='form-control form-control-lg form-control-solid'
|
||||
placeholder='Last name'
|
||||
{...formik.getFieldProps('lName')}
|
||||
/>
|
||||
{formik.touched.lName && formik.errors.lName && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>{formik.errors.lName}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row mb-6'>
|
||||
<label className='col-lg-4 col-form-label required fw-bold fs-6'>Company</label>
|
||||
|
||||
<div className='col-lg-8 fv-row'>
|
||||
<input
|
||||
type='text'
|
||||
className='form-control form-control-lg form-control-solid'
|
||||
placeholder='Company name'
|
||||
{...formik.getFieldProps('company')}
|
||||
/>
|
||||
{formik.touched.company && formik.errors.company && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>{formik.errors.company}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row mb-6'>
|
||||
<label className='col-lg-4 col-form-label fw-bold fs-6'>
|
||||
<span className='required'>Contact Phone</span>
|
||||
</label>
|
||||
|
||||
<div className='col-lg-8 fv-row'>
|
||||
<input
|
||||
type='tel'
|
||||
className='form-control form-control-lg form-control-solid'
|
||||
placeholder='Phone number'
|
||||
{...formik.getFieldProps('contactPhone')}
|
||||
/>
|
||||
{formik.touched.contactPhone && formik.errors.contactPhone && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>{formik.errors.contactPhone}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row mb-6'>
|
||||
<label className='col-lg-4 col-form-label fw-bold fs-6'>
|
||||
<span className='required'>Company Site</span>
|
||||
</label>
|
||||
|
||||
<div className='col-lg-8 fv-row'>
|
||||
<input
|
||||
type='text'
|
||||
className='form-control form-control-lg form-control-solid'
|
||||
placeholder='Company website'
|
||||
{...formik.getFieldProps('companySite')}
|
||||
/>
|
||||
{formik.touched.companySite && formik.errors.companySite && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>{formik.errors.companySite}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row mb-6'>
|
||||
<label className='col-lg-4 col-form-label fw-bold fs-6'>
|
||||
<span className='required'>Country</span>
|
||||
</label>
|
||||
|
||||
<div className='col-lg-8 fv-row'>
|
||||
<select
|
||||
className='form-select form-select-solid form-select-lg fw-bold'
|
||||
{...formik.getFieldProps('country')}
|
||||
>
|
||||
<option value=''>Select a Country...</option>
|
||||
<option value='AF'>Afghanistan</option>
|
||||
<option value='AX'>Aland Islands</option>
|
||||
<option value='AL'>Albania</option>
|
||||
<option value='DZ'>Algeria</option>
|
||||
<option value='AS'>American Samoa</option>
|
||||
<option value='AD'>Andorra</option>
|
||||
<option value='AO'>Angola</option>
|
||||
<option value='AI'>Anguilla</option>
|
||||
<option value='AQ'>Antarctica</option>
|
||||
<option value='AG'>Antigua and Barbuda</option>
|
||||
<option value='AR'>Argentina</option>
|
||||
<option value='AM'>Armenia</option>
|
||||
<option value='AW'>Aruba</option>
|
||||
<option value='AU'>Australia</option>
|
||||
<option value='AT'>Austria</option>
|
||||
<option value='AZ'>Azerbaijan</option>
|
||||
<option value='BS'>Bahamas</option>
|
||||
<option value='BH'>Bahrain</option>
|
||||
<option value='BD'>Bangladesh</option>
|
||||
<option value='BB'>Barbados</option>
|
||||
<option value='BY'>Belarus</option>
|
||||
<option value='BE'>Belgium</option>
|
||||
<option value='BZ'>Belize</option>
|
||||
<option value='BJ'>Benin</option>
|
||||
<option value='BM'>Bermuda</option>
|
||||
<option value='BT'>Bhutan</option>
|
||||
<option value='BO'>Bolivia, Plurinational State of</option>
|
||||
<option value='BQ'>Bonaire, Sint Eustatius and Saba</option>
|
||||
<option value='BA'>Bosnia and Herzegovina</option>
|
||||
<option value='BW'>Botswana</option>
|
||||
<option value='BV'>Bouvet Island</option>
|
||||
<option value='BR'>Brazil</option>
|
||||
<option value='IO'>British Indian Ocean Territory</option>
|
||||
<option value='BN'>Brunei Darussalam</option>
|
||||
<option value='BG'>Bulgaria</option>
|
||||
<option value='BF'>Burkina Faso</option>
|
||||
<option value='BI'>Burundi</option>
|
||||
<option value='KH'>Cambodia</option>
|
||||
<option value='CM'>Cameroon</option>
|
||||
<option value='CA'>Canada</option>
|
||||
<option value='CV'>Cape Verde</option>
|
||||
<option value='KY'>Cayman Islands</option>
|
||||
<option value='CF'>Central African Republic</option>
|
||||
<option value='TD'>Chad</option>
|
||||
<option value='CL'>Chile</option>
|
||||
<option value='CN'>China</option>
|
||||
<option value='CX'>Christmas Island</option>
|
||||
<option value='CC'>Cocos (Keeling) Islands</option>
|
||||
<option value='CO'>Colombia</option>
|
||||
<option value='KM'>Comoros</option>
|
||||
<option value='CG'>Congo</option>
|
||||
<option value='CD'>Congo, the Democratic Republic of the</option>
|
||||
<option value='CK'>Cook Islands</option>
|
||||
<option value='CR'>Costa Rica</option>
|
||||
<option value='CI'>Côte d'Ivoire</option>
|
||||
<option value='HR'>Croatia</option>
|
||||
<option value='CU'>Cuba</option>
|
||||
<option value='CW'>Curaçao</option>
|
||||
<option value='CY'>Cyprus</option>
|
||||
<option value='CZ'>Czech Republic</option>
|
||||
<option value='DK'>Denmark</option>
|
||||
<option value='DJ'>Djibouti</option>
|
||||
<option value='DM'>Dominica</option>
|
||||
<option value='DO'>Dominican Republic</option>
|
||||
<option value='EC'>Ecuador</option>
|
||||
<option value='EG'>Egypt</option>
|
||||
<option value='SV'>El Salvador</option>
|
||||
<option value='GQ'>Equatorial Guinea</option>
|
||||
<option value='ER'>Eritrea</option>
|
||||
<option value='EE'>Estonia</option>
|
||||
<option value='ET'>Ethiopia</option>
|
||||
<option value='FK'>Falkland Islands (Malvinas)</option>
|
||||
<option value='FO'>Faroe Islands</option>
|
||||
<option value='FJ'>Fiji</option>
|
||||
<option value='FI'>Finland</option>
|
||||
<option value='FR'>France</option>
|
||||
<option value='GF'>French Guiana</option>
|
||||
<option value='PF'>French Polynesia</option>
|
||||
<option value='TF'>French Southern Territories</option>
|
||||
<option value='GA'>Gabon</option>
|
||||
<option value='GM'>Gambia</option>
|
||||
<option value='GE'>Georgia</option>
|
||||
<option value='DE'>Germany</option>
|
||||
<option value='GH'>Ghana</option>
|
||||
<option value='GI'>Gibraltar</option>
|
||||
<option value='GR'>Greece</option>
|
||||
<option value='GL'>Greenland</option>
|
||||
<option value='GD'>Grenada</option>
|
||||
<option value='GP'>Guadeloupe</option>
|
||||
<option value='GU'>Guam</option>
|
||||
<option value='GT'>Guatemala</option>
|
||||
<option value='GG'>Guernsey</option>
|
||||
<option value='GN'>Guinea</option>
|
||||
<option value='GW'>Guinea-Bissau</option>
|
||||
<option value='GY'>Guyana</option>
|
||||
<option value='HT'>Haiti</option>
|
||||
<option value='HM'>Heard Island and McDonald Islands</option>
|
||||
<option value='VA'>Holy See (Vatican City State)</option>
|
||||
<option value='HN'>Honduras</option>
|
||||
<option value='HK'>Hong Kong</option>
|
||||
<option value='HU'>Hungary</option>
|
||||
<option value='IS'>Iceland</option>
|
||||
<option value='IN'>India</option>
|
||||
<option value='ID'>Indonesia</option>
|
||||
<option value='IR'>Iran, Islamic Republic of</option>
|
||||
<option value='IQ'>Iraq</option>
|
||||
<option value='IE'>Ireland</option>
|
||||
<option value='IM'>Isle of Man</option>
|
||||
<option value='IL'>Israel</option>
|
||||
<option value='IT'>Italy</option>
|
||||
<option value='JM'>Jamaica</option>
|
||||
<option value='JP'>Japan</option>
|
||||
<option value='JE'>Jersey</option>
|
||||
<option value='JO'>Jordan</option>
|
||||
<option value='KZ'>Kazakhstan</option>
|
||||
<option value='KE'>Kenya</option>
|
||||
<option value='KI'>Kiribati</option>
|
||||
<option value='KP'>Korea, Democratic People's Republic of</option>
|
||||
<option value='KW'>Kuwait</option>
|
||||
<option value='KG'>Kyrgyzstan</option>
|
||||
<option value='LA'>Lao People's Democratic Republic</option>
|
||||
<option value='LV'>Latvia</option>
|
||||
<option value='LB'>Lebanon</option>
|
||||
<option value='LS'>Lesotho</option>
|
||||
<option value='LR'>Liberia</option>
|
||||
<option value='LY'>Libya</option>
|
||||
<option value='LI'>Liechtenstein</option>
|
||||
<option value='LT'>Lithuania</option>
|
||||
<option value='LU'>Luxembourg</option>
|
||||
<option value='MO'>Macao</option>
|
||||
<option value='MK'>Macedonia, the former Yugoslav Republic of</option>
|
||||
<option value='MG'>Madagascar</option>
|
||||
<option value='MW'>Malawi</option>
|
||||
<option value='MY'>Malaysia</option>
|
||||
<option value='MV'>Maldives</option>
|
||||
<option value='ML'>Mali</option>
|
||||
<option value='MT'>Malta</option>
|
||||
<option value='MH'>Marshall Islands</option>
|
||||
<option value='MQ'>Martinique</option>
|
||||
<option value='MR'>Mauritania</option>
|
||||
<option value='MU'>Mauritius</option>
|
||||
<option value='YT'>Mayotte</option>
|
||||
<option value='MX'>Mexico</option>
|
||||
<option value='FM'>Micronesia, Federated States of</option>
|
||||
<option value='MD'>Moldova, Republic of</option>
|
||||
<option value='MC'>Monaco</option>
|
||||
<option value='MN'>Mongolia</option>
|
||||
<option value='ME'>Montenegro</option>
|
||||
<option value='MS'>Montserrat</option>
|
||||
<option value='MA'>Morocco</option>
|
||||
<option value='MZ'>Mozambique</option>
|
||||
<option value='MM'>Myanmar</option>
|
||||
<option value='NA'>Namibia</option>
|
||||
<option value='NR'>Nauru</option>
|
||||
<option value='NP'>Nepal</option>
|
||||
<option value='NL'>Netherlands</option>
|
||||
<option value='NC'>New Caledonia</option>
|
||||
<option value='NZ'>New Zealand</option>
|
||||
<option value='NI'>Nicaragua</option>
|
||||
<option value='NE'>Niger</option>
|
||||
<option value='NG'>Nigeria</option>
|
||||
<option value='NU'>Niue</option>
|
||||
<option value='NF'>Norfolk Island</option>
|
||||
<option value='MP'>Northern Mariana Islands</option>
|
||||
<option value='NO'>Norway</option>
|
||||
<option value='OM'>Oman</option>
|
||||
<option value='PK'>Pakistan</option>
|
||||
<option value='PW'>Palau</option>
|
||||
<option value='PS'>Palestinian Territory, Occupied</option>
|
||||
<option value='PA'>Panama</option>
|
||||
<option value='PG'>Papua New Guinea</option>
|
||||
<option value='PY'>Paraguay</option>
|
||||
<option value='PE'>Peru</option>
|
||||
<option value='PH'>Philippines</option>
|
||||
<option value='PN'>Pitcairn</option>
|
||||
<option value='PL'>Poland</option>
|
||||
<option value='PT'>Portugal</option>
|
||||
<option value='PR'>Puerto Rico</option>
|
||||
<option value='QA'>Qatar</option>
|
||||
<option value='RE'>Réunion</option>
|
||||
<option value='RO'>Romania</option>
|
||||
<option value='RU'>Russian Federation</option>
|
||||
<option value='RW'>Rwanda</option>
|
||||
<option value='BL'>Saint Barthélemy</option>
|
||||
<option value='SH'>Saint Helena, Ascension and Tristan da Cunha</option>
|
||||
<option value='KN'>Saint Kitts and Nevis</option>
|
||||
<option value='LC'>Saint Lucia</option>
|
||||
<option value='MF'>Saint Martin (French part)</option>
|
||||
<option value='PM'>Saint Pierre and Miquelon</option>
|
||||
<option value='VC'>Saint Vincent and the Grenadines</option>
|
||||
<option value='WS'>Samoa</option>
|
||||
<option value='SM'>San Marino</option>
|
||||
<option value='ST'>Sao Tome and Principe</option>
|
||||
<option value='SA'>Saudi Arabia</option>
|
||||
<option value='SN'>Senegal</option>
|
||||
<option value='RS'>Serbia</option>
|
||||
<option value='SC'>Seychelles</option>
|
||||
<option value='SL'>Sierra Leone</option>
|
||||
<option value='SG'>Singapore</option>
|
||||
<option value='SX'>Sint Maarten (Dutch part)</option>
|
||||
<option value='SK'>Slovakia</option>
|
||||
<option value='SI'>Slovenia</option>
|
||||
<option value='SB'>Solomon Islands</option>
|
||||
<option value='SO'>Somalia</option>
|
||||
<option value='ZA'>South Africa</option>
|
||||
<option value='GS'>South Georgia and the South Sandwich Islands</option>
|
||||
<option value='KR'>South Korea</option>
|
||||
<option value='SS'>South Sudan</option>
|
||||
<option value='ES'>Spain</option>
|
||||
<option value='LK'>Sri Lanka</option>
|
||||
<option value='SD'>Sudan</option>
|
||||
<option value='SR'>Suriname</option>
|
||||
<option value='SJ'>Svalbard and Jan Mayen</option>
|
||||
<option value='SZ'>Swaziland</option>
|
||||
<option value='SE'>Sweden</option>
|
||||
<option value='CH'>Switzerland</option>
|
||||
<option value='SY'>Syrian Arab Republic</option>
|
||||
<option value='TW'>Taiwan, Province of China</option>
|
||||
<option value='TJ'>Tajikistan</option>
|
||||
<option value='TZ'>Tanzania, United Republic of</option>
|
||||
<option value='TH'>Thailand</option>
|
||||
<option value='TL'>Timor-Leste</option>
|
||||
<option value='TG'>Togo</option>
|
||||
<option value='TK'>Tokelau</option>
|
||||
<option value='TO'>Tonga</option>
|
||||
<option value='TT'>Trinidad and Tobago</option>
|
||||
<option value='TN'>Tunisia</option>
|
||||
<option value='TR'>Turkey</option>
|
||||
<option value='TM'>Turkmenistan</option>
|
||||
<option value='TC'>Turks and Caicos Islands</option>
|
||||
<option value='TV'>Tuvalu</option>
|
||||
<option value='UG'>Uganda</option>
|
||||
<option value='UA'>Ukraine</option>
|
||||
<option value='AE'>United Arab Emirates</option>
|
||||
<option value='GB'>United Kingdom</option>
|
||||
<option value='US'>United States</option>
|
||||
<option value='UY'>Uruguay</option>
|
||||
<option value='UZ'>Uzbekistan</option>
|
||||
<option value='VU'>Vanuatu</option>
|
||||
<option value='VE'>Venezuela, Bolivarian Republic of</option>
|
||||
<option value='VN'>Vietnam</option>
|
||||
<option value='VI'>Virgin Islands</option>
|
||||
<option value='WF'>Wallis and Futuna</option>
|
||||
<option value='EH'>Western Sahara</option>
|
||||
<option value='YE'>Yemen</option>
|
||||
<option value='ZM'>Zambia</option>
|
||||
<option value='ZW'>Zimbabwe</option>
|
||||
</select>
|
||||
{formik.touched.country && formik.errors.country && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>{formik.errors.country}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row mb-6'>
|
||||
<label className='col-lg-4 col-form-label required fw-bold fs-6'>Language</label>
|
||||
<div className='col-lg-8 fv-row'>
|
||||
<select
|
||||
className='form-select form-select-solid form-select-lg'
|
||||
{...formik.getFieldProps('language')}
|
||||
>
|
||||
<option value=''>Select a Language...</option>
|
||||
<option value='id'>Bahasa Indonesia - Indonesian</option>
|
||||
<option value='msa'>Bahasa Melayu - Malay</option>
|
||||
<option value='ca'>Català - Catalan</option>
|
||||
<option value='cs'>Čeština - Czech</option>
|
||||
<option value='da'>Dansk - Danish</option>
|
||||
<option value='de'>Deutsch - German</option>
|
||||
<option value='en'>English</option>
|
||||
<option value='en-gb'>English UK - British English</option>
|
||||
<option value='es'>Español - Spanish</option>
|
||||
<option value='fil'>Filipino</option>
|
||||
<option value='fr'>Français - French</option>
|
||||
<option value='ga'>Gaeilge - Irish (beta)</option>
|
||||
<option value='gl'>Galego - Galician (beta)</option>
|
||||
<option value='hr'>Hrvatski - Croatian</option>
|
||||
<option value='it'>Italiano - Italian</option>
|
||||
<option value='hu'>Magyar - Hungarian</option>
|
||||
<option value='nl'>Nederlands - Dutch</option>
|
||||
<option value='no'>Norsk - Norwegian</option>
|
||||
<option value='pl'>Polski - Polish</option>
|
||||
<option value='pt'>Português - Portuguese</option>
|
||||
<option value='ro'>Română - Romanian</option>
|
||||
<option value='sk'>Slovenčina - Slovak</option>
|
||||
<option value='fi'>Suomi - Finnish</option>
|
||||
<option value='sv'>Svenska - Swedish</option>
|
||||
<option value='vi'>Tiếng Việt - Vietnamese</option>
|
||||
<option value='tr'>Türkçe - Turkish</option>
|
||||
<option value='el'>Ελληνικά - Greek</option>
|
||||
<option value='bg'>Български език - Bulgarian</option>
|
||||
<option value='ru'>Русский - Russian</option>
|
||||
<option value='sr'>Српски - Serbian</option>
|
||||
<option value='uk'>Українська мова - Ukrainian</option>
|
||||
<option value='he'>עִבְרִית - Hebrew</option>
|
||||
<option value='ur'>اردو - Urdu (beta)</option>
|
||||
<option value='ar'>العربية - Arabic</option>
|
||||
<option value='fa'>فارسی - Persian</option>
|
||||
<option value='mr'>मराठी - Marathi</option>
|
||||
<option value='hi'>हिन्दी - Hindi</option>
|
||||
<option value='bn'>বাংলা - Bangla</option>
|
||||
<option value='gu'>ગુજરાતી - Gujarati</option>
|
||||
<option value='ta'>தமிழ் - Tamil</option>
|
||||
<option value='kn'>ಕನ್ನಡ - Kannada</option>
|
||||
<option value='th'>ภาษาไทย - Thai</option>
|
||||
<option value='ko'>한국어 - Korean</option>
|
||||
<option value='ja'>日本語 - Japanese</option>
|
||||
<option value='zh-cn'>简体中文 - Simplified Chinese</option>
|
||||
<option value='zh-tw'>繁體中文 - Traditional Chinese</option>
|
||||
</select>
|
||||
{formik.touched.language && formik.errors.language && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>{formik.errors.language}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='form-text'>
|
||||
Please select a preferred language, including date, time, and number formatting.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row mb-6'>
|
||||
<label className='col-lg-4 col-form-label required fw-bold fs-6'>Time Zone</label>
|
||||
|
||||
<div className='col-lg-8 fv-row'>
|
||||
<select
|
||||
className='form-select form-select-solid form-select-lg'
|
||||
{...formik.getFieldProps('timeZone')}
|
||||
>
|
||||
<option value=''>Select a Timezone..</option>
|
||||
<option value='International Date Line West'>
|
||||
(GMT-11:00) International Date Line West
|
||||
</option>
|
||||
<option value='Midway Island'>(GMT-11:00) Midway Island</option>
|
||||
<option value='Samoa'>(GMT-11:00) Samoa</option>
|
||||
<option value='Hawaii'>(GMT-10:00) Hawaii</option>
|
||||
<option value='Alaska'>(GMT-08:00) Alaska</option>
|
||||
<option value='Pacific Time (US & Canada)'>
|
||||
(GMT-07:00) Pacific Time (US & Canada)
|
||||
</option>
|
||||
<option value='Tijuana'>(GMT-07:00) Tijuana</option>
|
||||
<option value='Arizona'>(GMT-07:00) Arizona</option>
|
||||
<option value='Mountain Time (US & Canada)'>
|
||||
(GMT-06:00) Mountain Time (US & Canada)
|
||||
</option>
|
||||
<option value='Chihuahua'>(GMT-06:00) Chihuahua</option>
|
||||
<option value='Mazatlan'>(GMT-06:00) Mazatlan</option>
|
||||
<option value='Saskatchewan'>(GMT-06:00) Saskatchewan</option>
|
||||
<option value='Central America'>(GMT-06:00) Central America</option>
|
||||
<option value='Central Time (US & Canada)'>
|
||||
(GMT-05:00) Central Time (US & Canada)
|
||||
</option>
|
||||
<option value='Guadalajara'>(GMT-05:00) Guadalajara</option>
|
||||
<option value='Mexico City'>(GMT-05:00) Mexico City</option>
|
||||
<option value='Monterrey'>(GMT-05:00) Monterrey</option>
|
||||
<option value='Bogota'>(GMT-05:00) Bogota</option>
|
||||
<option value='Lima'>(GMT-05:00) Lima</option>
|
||||
<option value='Quito'>(GMT-05:00) Quito</option>
|
||||
<option value='Eastern Time (US & Canada)'>
|
||||
(GMT-04:00) Eastern Time (US & Canada)
|
||||
</option>
|
||||
<option value='Indiana (East)'>(GMT-04:00) Indiana (East)</option>
|
||||
<option value='Caracas'>(GMT-04:00) Caracas</option>
|
||||
<option value='La Paz'>(GMT-04:00) La Paz</option>
|
||||
<option value='Georgetown'>(GMT-04:00) Georgetown</option>
|
||||
<option value='Atlantic Time (Canada)'>(GMT-03:00) Atlantic Time (Canada)</option>
|
||||
<option value='Santiago'>(GMT-03:00) Santiago</option>
|
||||
<option value='Brasilia'>(GMT-03:00) Brasilia</option>
|
||||
<option value='Buenos Aires'>(GMT-03:00) Buenos Aires</option>
|
||||
<option value='Newfoundland'>(GMT-02:30) Newfoundland</option>
|
||||
<option value='Greenland'>(GMT-02:00) Greenland</option>
|
||||
<option value='Mid-Atlantic'>(GMT-02:00) Mid-Atlantic</option>
|
||||
<option value='Cape Verde Is.'>(GMT-01:00) Cape Verde Is.</option>
|
||||
<option value='Azores'>(GMT) Azores</option>
|
||||
<option value='Monrovia'>(GMT) Monrovia</option>
|
||||
<option value='UTC'>(GMT) UTC</option>
|
||||
<option value='Dublin'>(GMT+01:00) Dublin</option>
|
||||
<option value='Edinburgh'>(GMT+01:00) Edinburgh</option>
|
||||
<option value='Lisbon'>(GMT+01:00) Lisbon</option>
|
||||
<option value='London'>(GMT+01:00) London</option>
|
||||
<option value='Casablanca'>(GMT+01:00) Casablanca</option>
|
||||
<option value='West Central Africa'>(GMT+01:00) West Central Africa</option>
|
||||
<option value='Belgrade'>(GMT+02:00) Belgrade</option>
|
||||
<option value='Bratislava'>(GMT+02:00) Bratislava</option>
|
||||
<option value='Budapest'>(GMT+02:00) Budapest</option>
|
||||
<option value='Ljubljana'>(GMT+02:00) Ljubljana</option>
|
||||
<option value='Prague'>(GMT+02:00) Prague</option>
|
||||
<option value='Sarajevo'>(GMT+02:00) Sarajevo</option>
|
||||
<option value='Skopje'>(GMT+02:00) Skopje</option>
|
||||
<option value='Warsaw'>(GMT+02:00) Warsaw</option>
|
||||
<option value='Zagreb'>(GMT+02:00) Zagreb</option>
|
||||
<option value='Brussels'>(GMT+02:00) Brussels</option>
|
||||
<option value='Copenhagen'>(GMT+02:00) Copenhagen</option>
|
||||
<option value='Madrid'>(GMT+02:00) Madrid</option>
|
||||
<option value='Paris'>(GMT+02:00) Paris</option>
|
||||
<option value='Amsterdam'>(GMT+02:00) Amsterdam</option>
|
||||
<option value='Berlin'>(GMT+02:00) Berlin</option>
|
||||
<option value='Bern'>(GMT+02:00) Bern</option>
|
||||
<option value='Rome'>(GMT+02:00) Rome</option>
|
||||
<option value='Stockholm'>(GMT+02:00) Stockholm</option>
|
||||
<option value='Vienna'>(GMT+02:00) Vienna</option>
|
||||
<option value='Cairo'>(GMT+02:00) Cairo</option>
|
||||
<option value='Harare'>(GMT+02:00) Harare</option>
|
||||
<option value='Pretoria'>(GMT+02:00) Pretoria</option>
|
||||
<option value='Bucharest'>(GMT+03:00) Bucharest</option>
|
||||
<option value='Helsinki'>(GMT+03:00) Helsinki</option>
|
||||
<option value='Kiev'>(GMT+03:00) Kiev</option>
|
||||
<option value='Kyiv'>(GMT+03:00) Kyiv</option>
|
||||
<option value='Riga'>(GMT+03:00) Riga</option>
|
||||
<option value='Sofia'>(GMT+03:00) Sofia</option>
|
||||
<option value='Tallinn'>(GMT+03:00) Tallinn</option>
|
||||
<option value='Vilnius'>(GMT+03:00) Vilnius</option>
|
||||
<option value='Athens'>(GMT+03:00) Athens</option>
|
||||
<option value='Istanbul'>(GMT+03:00) Istanbul</option>
|
||||
<option value='Minsk'>(GMT+03:00) Minsk</option>
|
||||
<option value='Jerusalem'>(GMT+03:00) Jerusalem</option>
|
||||
<option value='Moscow'>(GMT+03:00) Moscow</option>
|
||||
<option value='St. Petersburg'>(GMT+03:00) St. Petersburg</option>
|
||||
<option value='Volgograd'>(GMT+03:00) Volgograd</option>
|
||||
<option value='Kuwait'>(GMT+03:00) Kuwait</option>
|
||||
<option value='Riyadh'>(GMT+03:00) Riyadh</option>
|
||||
<option value='Nairobi'>(GMT+03:00) Nairobi</option>
|
||||
<option value='Baghdad'>(GMT+03:00) Baghdad</option>
|
||||
<option value='Abu Dhabi'>(GMT+04:00) Abu Dhabi</option>
|
||||
<option value='Muscat'>(GMT+04:00) Muscat</option>
|
||||
<option value='Baku'>(GMT+04:00) Baku</option>
|
||||
<option value='Tbilisi'>(GMT+04:00) Tbilisi</option>
|
||||
<option value='Yerevan'>(GMT+04:00) Yerevan</option>
|
||||
<option value='Tehran'>(GMT+04:30) Tehran</option>
|
||||
<option value='Kabul'>(GMT+04:30) Kabul</option>
|
||||
<option value='Ekaterinburg'>(GMT+05:00) Ekaterinburg</option>
|
||||
<option value='Islamabad'>(GMT+05:00) Islamabad</option>
|
||||
<option value='Karachi'>(GMT+05:00) Karachi</option>
|
||||
<option value='Tashkent'>(GMT+05:00) Tashkent</option>
|
||||
<option value='Chennai'>(GMT+05:30) Chennai</option>
|
||||
<option value='Kolkata'>(GMT+05:30) Kolkata</option>
|
||||
<option value='Mumbai'>(GMT+05:30) Mumbai</option>
|
||||
<option value='New Delhi'>(GMT+05:30) New Delhi</option>
|
||||
<option value='Sri Jayawardenepura'>(GMT+05:30) Sri Jayawardenepura</option>
|
||||
<option value='Kathmandu'>(GMT+05:45) Kathmandu</option>
|
||||
<option value='Astana'>(GMT+06:00) Astana</option>
|
||||
<option value='Dhaka'>(GMT+06:00) Dhaka</option>
|
||||
<option value='Almaty'>(GMT+06:00) Almaty</option>
|
||||
<option value='Urumqi'>(GMT+06:00) Urumqi</option>
|
||||
<option value='Rangoon'>(GMT+06:30) Rangoon</option>
|
||||
<option value='Novosibirsk'>(GMT+07:00) Novosibirsk</option>
|
||||
<option value='Bangkok'>(GMT+07:00) Bangkok</option>
|
||||
<option value='Hanoi'>(GMT+07:00) Hanoi</option>
|
||||
<option value='Jakarta'>(GMT+07:00) Jakarta</option>
|
||||
<option value='Krasnoyarsk'>(GMT+07:00) Krasnoyarsk</option>
|
||||
<option value='Beijing'>(GMT+08:00) Beijing</option>
|
||||
<option value='Chongqing'>(GMT+08:00) Chongqing</option>
|
||||
<option value='Hong Kong'>(GMT+08:00) Hong Kong</option>
|
||||
<option value='Kuala Lumpur'>(GMT+08:00) Kuala Lumpur</option>
|
||||
<option value='Singapore'>(GMT+08:00) Singapore</option>
|
||||
<option value='Taipei'>(GMT+08:00) Taipei</option>
|
||||
<option value='Perth'>(GMT+08:00) Perth</option>
|
||||
<option value='Irkutsk'>(GMT+08:00) Irkutsk</option>
|
||||
<option value='Ulaan Bataar'>(GMT+08:00) Ulaan Bataar</option>
|
||||
<option value='Seoul'>(GMT+09:00) Seoul</option>
|
||||
<option value='Osaka'>(GMT+09:00) Osaka</option>
|
||||
<option value='Sapporo'>(GMT+09:00) Sapporo</option>
|
||||
<option value='Tokyo'>(GMT+09:00) Tokyo</option>
|
||||
<option value='Yakutsk'>(GMT+09:00) Yakutsk</option>
|
||||
<option value='Darwin'>(GMT+09:30) Darwin</option>
|
||||
<option value='Adelaide'>(GMT+09:30) Adelaide</option>
|
||||
<option value='Canberra'>(GMT+10:00) Canberra</option>
|
||||
<option value='Melbourne'>(GMT+10:00) Melbourne</option>
|
||||
<option value='Sydney'>(GMT+10:00) Sydney</option>
|
||||
<option value='Brisbane'>(GMT+10:00) Brisbane</option>
|
||||
<option value='Hobart'>(GMT+10:00) Hobart</option>
|
||||
<option value='Vladivostok'>(GMT+10:00) Vladivostok</option>
|
||||
<option value='Guam'>(GMT+10:00) Guam</option>
|
||||
<option value='Port Moresby'>(GMT+10:00) Port Moresby</option>
|
||||
<option value='Solomon Is.'>(GMT+10:00) Solomon Is.</option>
|
||||
<option value='Magadan'>(GMT+11:00) Magadan</option>
|
||||
<option value='New Caledonia'>(GMT+11:00) New Caledonia</option>
|
||||
<option value='Fiji'>(GMT+12:00) Fiji</option>
|
||||
<option value='Kamchatka'>(GMT+12:00) Kamchatka</option>
|
||||
<option value='Marshall Is.'>(GMT+12:00) Marshall Is.</option>
|
||||
<option value='Auckland'>(GMT+12:00) Auckland</option>
|
||||
<option value='Wellington'>(GMT+12:00) Wellington</option>
|
||||
<option value="Nuku'alofa">(GMT+13:00) Nuku'alofa</option>
|
||||
</select>
|
||||
{formik.touched.timeZone && formik.errors.timeZone && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>{formik.errors.timeZone}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row mb-6'>
|
||||
<label className='col-lg-4 col-form-label required fw-bold fs-6'>Currency</label>
|
||||
|
||||
<div className='col-lg-8 fv-row'>
|
||||
<select
|
||||
className='form-select form-select-solid form-select-lg'
|
||||
{...formik.getFieldProps('currency')}
|
||||
>
|
||||
<option value=''>Select a currency..</option>
|
||||
<option value='USD'>USD - USA dollar</option>
|
||||
<option value='GBP'>GBP - British pound</option>
|
||||
<option value='AUD'>AUD - Australian dollar</option>
|
||||
<option value='JPY'>JPY - Japanese yen</option>
|
||||
<option value='SEK'>SEK - Swedish krona</option>
|
||||
<option value='CAD'>CAD - Canadian dollar</option>
|
||||
<option value='CHF'>CHF - Swiss franc</option>
|
||||
</select>
|
||||
{formik.touched.currency && formik.errors.currency && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>{formik.errors.currency}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row mb-6'>
|
||||
<label className='col-lg-4 col-form-label fw-bold fs-6'>Communication</label>
|
||||
|
||||
<div className='col-lg-8 fv-row'>
|
||||
<div className='d-flex align-items-center mt-3'>
|
||||
<label className='form-check form-check-inline form-check-solid me-5'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
name='communication[]'
|
||||
type='checkbox'
|
||||
defaultChecked={data.communications?.email}
|
||||
onChange={() => {
|
||||
updateData({
|
||||
communications: {
|
||||
email: !data.communications?.email,
|
||||
phone: data.communications?.phone,
|
||||
},
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<span className='fw-bold ps-2 fs-6'>Email</span>
|
||||
</label>
|
||||
|
||||
<label className='form-check form-check-inline form-check-solid'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
name='communication[]'
|
||||
type='checkbox'
|
||||
defaultChecked={data.communications?.phone}
|
||||
onChange={() => {
|
||||
updateData({
|
||||
communications: {
|
||||
email: data.communications?.email,
|
||||
phone: !data.communications?.phone,
|
||||
},
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<span className='fw-bold ps-2 fs-6'>Phone</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row mb-0'>
|
||||
<label className='col-lg-4 col-form-label fw-bold fs-6'>Allow Marketing</label>
|
||||
|
||||
<div className='col-lg-8 d-flex align-items-center'>
|
||||
<div className='form-check form-check-solid form-switch fv-row'>
|
||||
<input
|
||||
className='form-check-input w-45px h-30px'
|
||||
type='checkbox'
|
||||
id='allowmarketing'
|
||||
defaultChecked={data.allowMarketing}
|
||||
onChange={() => {
|
||||
updateData({allowMarketing: !data.allowMarketing})
|
||||
}}
|
||||
/>
|
||||
<label className='form-check-label'></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='card-footer d-flex justify-content-end py-6 px-9'>
|
||||
<button type='submit' className='btn btn-primary' disabled={loading}>
|
||||
{!loading && 'Save Changes'}
|
||||
{loading && (
|
||||
<span className='indicator-progress' style={{display: 'block'}}>
|
||||
Please wait...{' '}
|
||||
<span className='spinner-border spinner-border-sm align-middle ms-2'></span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {ProfileDetails}
|
||||
@@ -0,0 +1,343 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import React, {useState} from 'react'
|
||||
import {KTSVG} from '../../../../../../_metronic/helpers'
|
||||
import * as Yup from 'yup'
|
||||
import {useFormik} from 'formik'
|
||||
import {IUpdatePassword, IUpdateEmail, updatePassword, updateEmail} from '../SettingsModel'
|
||||
|
||||
const emailFormValidationSchema = Yup.object().shape({
|
||||
newEmail: Yup.string()
|
||||
.email('Wrong email format')
|
||||
.min(3, 'Minimum 3 symbols')
|
||||
.max(50, 'Maximum 50 symbols')
|
||||
.required('Email is required'),
|
||||
confirmPassword: Yup.string()
|
||||
.min(3, 'Minimum 3 symbols')
|
||||
.max(50, 'Maximum 50 symbols')
|
||||
.required('Password is required'),
|
||||
})
|
||||
|
||||
const passwordFormValidationSchema = Yup.object().shape({
|
||||
currentPassword: Yup.string()
|
||||
.min(3, 'Minimum 3 symbols')
|
||||
.max(50, 'Maximum 50 symbols')
|
||||
.required('Password is required'),
|
||||
newPassword: Yup.string()
|
||||
.min(3, 'Minimum 3 symbols')
|
||||
.max(50, 'Maximum 50 symbols')
|
||||
.required('Password is required'),
|
||||
passwordConfirmation: Yup.string()
|
||||
.min(3, 'Minimum 3 symbols')
|
||||
.max(50, 'Maximum 50 symbols')
|
||||
.required('Password is required')
|
||||
.oneOf([Yup.ref('newPassword'), null], 'Passwords must match'),
|
||||
})
|
||||
|
||||
const SignInMethod: React.FC = () => {
|
||||
const [emailUpdateData, setEmailUpdateData] = useState<IUpdateEmail>(updateEmail)
|
||||
const [passwordUpdateData, setPasswordUpdateData] = useState<IUpdatePassword>(updatePassword)
|
||||
|
||||
const [showEmailForm, setShowEmailForm] = useState<boolean>(false)
|
||||
const [showPasswordForm, setPasswordForm] = useState<boolean>(false)
|
||||
|
||||
const [loading1, setLoading1] = useState(false)
|
||||
|
||||
const formik1 = useFormik<IUpdateEmail>({
|
||||
initialValues: {
|
||||
...emailUpdateData,
|
||||
},
|
||||
validationSchema: emailFormValidationSchema,
|
||||
onSubmit: (values) => {
|
||||
setLoading1(true)
|
||||
setTimeout((values) => {
|
||||
setEmailUpdateData(values)
|
||||
setLoading1(false)
|
||||
setShowEmailForm(false)
|
||||
}, 1000)
|
||||
},
|
||||
})
|
||||
|
||||
const [loading2, setLoading2] = useState(false)
|
||||
|
||||
const formik2 = useFormik<IUpdatePassword>({
|
||||
initialValues: {
|
||||
...passwordUpdateData,
|
||||
},
|
||||
validationSchema: passwordFormValidationSchema,
|
||||
onSubmit: (values) => {
|
||||
setLoading2(true)
|
||||
setTimeout((values) => {
|
||||
setPasswordUpdateData(values)
|
||||
setLoading2(false)
|
||||
setPasswordForm(false)
|
||||
}, 1000)
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='card mb-5 mb-xl-10'>
|
||||
<div
|
||||
className='card-header border-0 cursor-pointer'
|
||||
role='button'
|
||||
data-bs-toggle='collapse'
|
||||
data-bs-target='#kt_account_signin_method'
|
||||
>
|
||||
<div className='card-title m-0'>
|
||||
<h3 className='fw-bolder m-0'>Sign-in Method</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id='kt_account_signin_method' className='collapse show'>
|
||||
<div className='card-body border-top p-9'>
|
||||
<div className='d-flex flex-wrap align-items-center'>
|
||||
<div id='kt_signin_email' className={' ' + (showEmailForm && 'd-none')}>
|
||||
<div className='fs-6 fw-bolder mb-1'>Email Address</div>
|
||||
<div className='fw-bold text-gray-600'>support@keenthemes.com</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id='kt_signin_email_edit'
|
||||
className={'flex-row-fluid ' + (!showEmailForm && 'd-none')}
|
||||
>
|
||||
<form
|
||||
onSubmit={formik1.handleSubmit}
|
||||
id='kt_signin_change_email'
|
||||
className='form'
|
||||
noValidate
|
||||
>
|
||||
<div className='row mb-6'>
|
||||
<div className='col-lg-6 mb-4 mb-lg-0'>
|
||||
<div className='fv-row mb-0'>
|
||||
<label htmlFor='emailaddress' className='form-label fs-6 fw-bolder mb-3'>
|
||||
Enter New Email Address
|
||||
</label>
|
||||
<input
|
||||
type='email'
|
||||
className='form-control form-control-lg form-control-solid'
|
||||
id='emailaddress'
|
||||
placeholder='Email Address'
|
||||
{...formik1.getFieldProps('newEmail')}
|
||||
/>
|
||||
{formik1.touched.newEmail && formik1.errors.newEmail && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>{formik1.errors.newEmail}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='col-lg-6'>
|
||||
<div className='fv-row mb-0'>
|
||||
<label
|
||||
htmlFor='confirmemailpassword'
|
||||
className='form-label fs-6 fw-bolder mb-3'
|
||||
>
|
||||
Confirm Password
|
||||
</label>
|
||||
<input
|
||||
type='password'
|
||||
className='form-control form-control-lg form-control-solid'
|
||||
id='confirmemailpassword'
|
||||
{...formik1.getFieldProps('confirmPassword')}
|
||||
/>
|
||||
{formik1.touched.confirmPassword && formik1.errors.confirmPassword && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>{formik1.errors.confirmPassword}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='d-flex'>
|
||||
<button
|
||||
id='kt_signin_submit'
|
||||
type='submit'
|
||||
className='btn btn-primary me-2 px-6'
|
||||
>
|
||||
{!loading1 && 'Update Email'}
|
||||
{loading1 && (
|
||||
<span className='indicator-progress' style={{display: 'block'}}>
|
||||
Please wait...{' '}
|
||||
<span className='spinner-border spinner-border-sm align-middle ms-2'></span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
id='kt_signin_cancel'
|
||||
type='button'
|
||||
onClick={() => {
|
||||
setShowEmailForm(false)
|
||||
}}
|
||||
className='btn btn-color-gray-400 btn-active-light-primary px-6'
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id='kt_signin_email_button' className={'ms-auto ' + (showEmailForm && 'd-none')}>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowEmailForm(true)
|
||||
}}
|
||||
className='btn btn-light btn-active-light-primary'
|
||||
>
|
||||
Change Email
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed my-6'></div>
|
||||
|
||||
<div className='d-flex flex-wrap align-items-center mb-10'>
|
||||
<div id='kt_signin_password' className={' ' + (showPasswordForm && 'd-none')}>
|
||||
<div className='fs-6 fw-bolder mb-1'>Password</div>
|
||||
<div className='fw-bold text-gray-600'>************</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id='kt_signin_password_edit'
|
||||
className={'flex-row-fluid ' + (!showPasswordForm && 'd-none')}
|
||||
>
|
||||
<form
|
||||
onSubmit={formik2.handleSubmit}
|
||||
id='kt_signin_change_password'
|
||||
className='form'
|
||||
noValidate
|
||||
>
|
||||
<div className='row mb-1'>
|
||||
<div className='col-lg-4'>
|
||||
<div className='fv-row mb-0'>
|
||||
<label htmlFor='currentpassword' className='form-label fs-6 fw-bolder mb-3'>
|
||||
Current Password
|
||||
</label>
|
||||
<input
|
||||
type='password'
|
||||
className='form-control form-control-lg form-control-solid '
|
||||
id='currentpassword'
|
||||
{...formik2.getFieldProps('currentPassword')}
|
||||
/>
|
||||
{formik2.touched.currentPassword && formik2.errors.currentPassword && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>{formik2.errors.currentPassword}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='col-lg-4'>
|
||||
<div className='fv-row mb-0'>
|
||||
<label htmlFor='newpassword' className='form-label fs-6 fw-bolder mb-3'>
|
||||
New Password
|
||||
</label>
|
||||
<input
|
||||
type='password'
|
||||
className='form-control form-control-lg form-control-solid '
|
||||
id='newpassword'
|
||||
{...formik2.getFieldProps('newPassword')}
|
||||
/>
|
||||
{formik2.touched.newPassword && formik2.errors.newPassword && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>{formik2.errors.newPassword}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='col-lg-4'>
|
||||
<div className='fv-row mb-0'>
|
||||
<label htmlFor='confirmpassword' className='form-label fs-6 fw-bolder mb-3'>
|
||||
Confirm New Password
|
||||
</label>
|
||||
<input
|
||||
type='password'
|
||||
className='form-control form-control-lg form-control-solid '
|
||||
id='confirmpassword'
|
||||
{...formik2.getFieldProps('passwordConfirmation')}
|
||||
/>
|
||||
{formik2.touched.passwordConfirmation && formik2.errors.passwordConfirmation && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>{formik2.errors.passwordConfirmation}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='form-text mb-5'>
|
||||
Password must be at least 8 character and contain symbols
|
||||
</div>
|
||||
|
||||
<div className='d-flex'>
|
||||
<button
|
||||
id='kt_password_submit'
|
||||
type='submit'
|
||||
className='btn btn-primary me-2 px-6'
|
||||
>
|
||||
{!loading2 && 'Update Password'}
|
||||
{loading2 && (
|
||||
<span className='indicator-progress' style={{display: 'block'}}>
|
||||
Please wait...{' '}
|
||||
<span className='spinner-border spinner-border-sm align-middle ms-2'></span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setPasswordForm(false)
|
||||
}}
|
||||
id='kt_password_cancel'
|
||||
type='button'
|
||||
className='btn btn-color-gray-400 btn-active-light-primary px-6'
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id='kt_signin_password_button'
|
||||
className={'ms-auto ' + (showPasswordForm && 'd-none')}
|
||||
>
|
||||
<button
|
||||
onClick={() => {
|
||||
setPasswordForm(true)
|
||||
}}
|
||||
className='btn btn-light btn-active-light-primary'
|
||||
>
|
||||
Reset Password
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='notice d-flex bg-light-primary rounded border-primary border border-dashed p-6'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/general/gen048.svg'
|
||||
className='svg-icon-2tx svg-icon-primary me-4'
|
||||
/>
|
||||
<div className='d-flex flex-stack flex-grow-1 flex-wrap flex-md-nowrap'>
|
||||
<div className='mb-3 mb-md-0 fw-bold'>
|
||||
<h4 className='text-gray-800 fw-bolder'>Secure Your Account</h4>
|
||||
<div className='fs-6 text-gray-600 pe-7'>
|
||||
Two-factor authentication adds an extra layer of security to your account. To log
|
||||
in, in addition you'll need to provide a 6 digit code
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href='#'
|
||||
className='btn btn-primary px-6 align-self-center text-nowrap'
|
||||
data-bs-toggle='modal'
|
||||
data-bs-target='#kt_modal_two_factor_authentication'
|
||||
>
|
||||
Enable
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {SignInMethod}
|
||||
@@ -0,0 +1,59 @@
|
||||
import {Navigate, Route, Routes, Outlet} from 'react-router-dom'
|
||||
import {PageLink, PageTitle} from '../../../../_metronic/layout/core'
|
||||
import {Private} from './components/Private'
|
||||
import {Group} from './components/Group'
|
||||
import {Drawer} from './components/Drawer'
|
||||
|
||||
const chatBreadCrumbs: Array<PageLink> = [
|
||||
{
|
||||
title: 'Chat',
|
||||
path: '/apps/chat/private-chat',
|
||||
isSeparator: false,
|
||||
isActive: false,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
path: '',
|
||||
isSeparator: true,
|
||||
isActive: false,
|
||||
},
|
||||
]
|
||||
|
||||
const ChatPage = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route element={<Outlet />}>
|
||||
<Route
|
||||
path='private-chat'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={chatBreadCrumbs}>Private chat</PageTitle>
|
||||
<Private />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='group-chat'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={chatBreadCrumbs}>Group chat</PageTitle>
|
||||
<Group />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='drawer-chat'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={chatBreadCrumbs}>Drawer chat</PageTitle>
|
||||
<Drawer />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route index element={<Navigate to='/apps/chat/private-chat' />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChatPage
|
||||
@@ -0,0 +1,203 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import React, {FC} from 'react'
|
||||
import {Card1} from '../../../../../_metronic/partials/content/cards/Card1'
|
||||
|
||||
const Drawer: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<div className='d-flex flex-wrap flex-stack mb-6'>
|
||||
<h3 className='fw-bolder my-2'>
|
||||
My Contacts
|
||||
<span className='fs-6 text-gray-400 fw-bold ms-1'>(59)</span>
|
||||
</h3>
|
||||
|
||||
<div className='d-flex my-2'>
|
||||
<select
|
||||
name='status'
|
||||
data-control='select2'
|
||||
data-hide-search='true'
|
||||
className='form-select form-select-white form-select-sm w-125px'
|
||||
defaultValue='Online'
|
||||
>
|
||||
<option value='Online'>Online</option>
|
||||
<option value='Pending'>Pending</option>
|
||||
<option value='Declined'>Declined</option>
|
||||
<option value='Accepted'>Accepted</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row g-6 g-xl-9'>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card1
|
||||
avatar='/media/avatars/300-6.jpg'
|
||||
name='Emma Smith'
|
||||
job='Art Director'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card1
|
||||
color='danger'
|
||||
name='Melody Macy'
|
||||
job='Marketing Analytic'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
online={true}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card1
|
||||
avatar='/media/avatars/300-1.jpg'
|
||||
name='Max Smith'
|
||||
job='Software Enginer'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card1
|
||||
avatar='/media/avatars/300-5.jpg'
|
||||
name='Sean Bean'
|
||||
job='Web Developer'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card1
|
||||
avatar='/media/avatars/300-25.jpg'
|
||||
name='Brian Cox'
|
||||
job='UI/UX Designer'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card1
|
||||
color='warning'
|
||||
name='Mikaela Collins'
|
||||
job='Head Of Marketing'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
online={true}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card1
|
||||
avatar='/media/avatars/300-9.jpg'
|
||||
name='Francis Mitcham'
|
||||
job='Software Arcitect'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card1
|
||||
color='danger'
|
||||
name='Olivia Wild'
|
||||
job='System Admin'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
online={true}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card1
|
||||
color='primary'
|
||||
name='Neil Owen'
|
||||
job='Account Manager'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
online={true}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card1
|
||||
avatar='/media/avatars/300-23.jpg'
|
||||
name='Dan Wilson'
|
||||
job='Web Desinger'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card1
|
||||
color='danger'
|
||||
name='Emma Bold'
|
||||
job='Corporate Finance'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
online={true}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card1
|
||||
avatar='/media/avatars/300-12.jpg'
|
||||
name='Ana Crown'
|
||||
job='Customer Relationship'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-stack flex-wrap pt-10'>
|
||||
<div className='fs-6 fw-bold text-gray-700'>Showing 1 to 10 of 50 entries</div>
|
||||
|
||||
<ul className='pagination'>
|
||||
<li className='page-item previous'>
|
||||
<a href='#' className='page-link'>
|
||||
<i className='previous'></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item active'>
|
||||
<a href='#' className='page-link'>
|
||||
1
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
2
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
3
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
4
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
5
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
6
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item next'>
|
||||
<a href='#' className='page-link'>
|
||||
<i className='next'></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export {Drawer}
|
||||
@@ -0,0 +1,331 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import React, {FC} from 'react'
|
||||
import {KTSVG, toAbsoluteUrl} from '../../../../../_metronic/helpers'
|
||||
import {Dropdown1, ChatInner} from '../../../../../_metronic/partials'
|
||||
|
||||
const Group: FC = () => {
|
||||
return (
|
||||
<div className='d-flex flex-column flex-lg-row'>
|
||||
<div className='flex-column flex-lg-row-auto w-100 w-lg-300px w-xl-400px mb-10 mb-lg-0'>
|
||||
<div className='card card-flush'>
|
||||
<div className='card-header pt-7' id='kt_chat_contacts_header'>
|
||||
<form className='w-100 position-relative' autoComplete='off'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/general/gen021.svg'
|
||||
className='svg-icon-2 svg-icon-lg-1 svg-icon-gray-500 position-absolute top-50 ms-5 translate-middle-y'
|
||||
/>
|
||||
|
||||
<input
|
||||
type='text'
|
||||
className='form-control form-control-solid px-15'
|
||||
name='search'
|
||||
placeholder='Search by username or email...'
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className='card-body pt-5' id='kt_chat_contacts_body'>
|
||||
<div
|
||||
className='scroll-y me-n5 pe-5 h-200px h-lg-auto'
|
||||
data-kt-scroll='true'
|
||||
data-kt-scroll-activate='{default: false, lg: true}'
|
||||
data-kt-scroll-max-height='auto'
|
||||
data-kt-scroll-dependencies='#kt_header, #kt_toolbar, #kt_footer, #kt_chat_contacts_header'
|
||||
data-kt-scroll-wrappers='#kt_content, #kt_chat_contacts_body'
|
||||
data-kt-scroll-offset='0px'
|
||||
>
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<span className='symbol-label bg-light-danger text-danger fs-6 fw-bolder'>
|
||||
M
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Melody Macy
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>melody@altbox.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>5 hrs</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<img alt='Pic' src={toAbsoluteUrl('/media/avatars/300-1.jpg')} />
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Max Smith
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>max@kt.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>20 hrs</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<img alt='Pic' src={toAbsoluteUrl('/media/avatars/300-5.jpg')} />
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Sean Bean
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>sean@dellito.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>20 hrs</span>
|
||||
<span className='badge badge-sm badge-circle badge-light-success'>6</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<img alt='Pic' src={toAbsoluteUrl('/media/avatars/300-25.jpg')} />
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Brian Cox
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>brian@exchange.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>20 hrs</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<span className='symbol-label bg-light-warning text-warning fs-6 fw-bolder'>
|
||||
M
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Mikaela Collins
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>mikaela@pexcom.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>1 day</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<img alt='Pic' src={toAbsoluteUrl('/media/avatars/300-9.jpg')} />
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Francis Mitcham
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>f.mitcham@kpmg.com.au</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>5 hrs</span>
|
||||
<span className='badge badge-sm badge-circle badge-light-success'>6</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<span className='symbol-label bg-light-danger text-danger fs-6 fw-bolder'>
|
||||
O
|
||||
</span>
|
||||
<div className='symbol-badge bg-success start-100 top-100 border-4 h-15px w-15px ms-n2 mt-n2'></div>
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Olivia Wild
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>olivia@corpmail.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>1 week</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<span className='symbol-label bg-light-primary text-primary fs-6 fw-bolder'>
|
||||
N
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Neil Owen
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>owen.neil@gmail.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>20 hrs</span>
|
||||
<span className='badge badge-sm badge-circle badge-light-success'>6</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<img alt='Pic' src={toAbsoluteUrl('/media/avatars/300-23.jpg')} />
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Dan Wilson
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>dam@consilting.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>2 weeks</span>
|
||||
<span className='badge badge-sm badge-circle badge-light-warning'>9</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<span className='symbol-label bg-light-danger text-danger fs-6 fw-bolder'>
|
||||
E
|
||||
</span>
|
||||
<div className='symbol-badge bg-success start-100 top-100 border-4 h-15px w-15px ms-n2 mt-n2'></div>
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Emma Bold
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>emma@intenso.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>1 day</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex-lg-row-fluid ms-lg-7 ms-xl-10'>
|
||||
<div className='card' id='kt_chat_messenger'>
|
||||
<div className='card-header' id='kt_chat_messenger_header'>
|
||||
<div className='card-title'>
|
||||
<div className='symbol-group symbol-hover'>
|
||||
<div className='symbol symbol-35px symbol-circle'>
|
||||
<img alt='Pic' src={toAbsoluteUrl('/media/avatars/300-5.jpg')} />
|
||||
</div>
|
||||
<div className='symbol symbol-35px symbol-circle'>
|
||||
<img alt='Pic' src={toAbsoluteUrl('/media/avatars/300-25.jpg')} />
|
||||
</div>
|
||||
<div className='symbol symbol-35px symbol-circle'>
|
||||
<span className='symbol-label bg-light-warning text-warning 40px'>M</span>
|
||||
</div>
|
||||
<div className='symbol symbol-35px symbol-circle'>
|
||||
<img alt='Pic' src={toAbsoluteUrl('/media/avatars/300-9.jpg')} />
|
||||
</div>
|
||||
<div className='symbol symbol-35px symbol-circle'>
|
||||
<span className='symbol-label bg-light-danger text-danger 40px'>O</span>
|
||||
</div>
|
||||
<div className='symbol symbol-35px symbol-circle'>
|
||||
<span className='symbol-label bg-light-primary text-primary 40px'>N</span>
|
||||
</div>
|
||||
<div className='symbol symbol-35px symbol-circle'>
|
||||
<img alt='Pic' src={toAbsoluteUrl('/media/avatars/300-23.jpg')} />
|
||||
</div>
|
||||
<a
|
||||
href='#'
|
||||
className='symbol symbol-35px symbol-circle'
|
||||
// data-bs-toggle='modal'
|
||||
// data-bs-target='#kt_modal_view_users'
|
||||
>
|
||||
<span
|
||||
className='symbol-label fs-8 fw-bolder'
|
||||
data-bs-toggle='tooltip'
|
||||
data-bs-trigger='hover'
|
||||
title='View more users'
|
||||
>
|
||||
+42
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='card-toolbar'>
|
||||
<div className='me-n3'>
|
||||
<button
|
||||
className='btn btn-sm btn-icon btn-active-light-primary'
|
||||
data-kt-menu-trigger='click'
|
||||
data-kt-menu-placement='bottom-end'
|
||||
data-kt-menu-flip='top-end'
|
||||
>
|
||||
<i className='bi bi-three-dots fs-2'></i>
|
||||
</button>
|
||||
<Dropdown1 />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChatInner />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {Group}
|
||||
@@ -0,0 +1,306 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import React, {FC} from 'react'
|
||||
import {KTSVG, toAbsoluteUrl} from '../../../../../_metronic/helpers'
|
||||
import {Dropdown1, ChatInner} from '../../../../../_metronic/partials'
|
||||
|
||||
const Private: FC = () => {
|
||||
return (
|
||||
<div className='d-flex flex-column flex-lg-row'>
|
||||
<div className='flex-column flex-lg-row-auto w-100 w-lg-300px w-xl-400px mb-10 mb-lg-0'>
|
||||
<div className='card card-flush'>
|
||||
<div className='card-header pt-7' id='kt_chat_contacts_header'>
|
||||
<form className='w-100 position-relative' autoComplete='off'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/general/gen021.svg'
|
||||
className='svg-icon-2 svg-icon-lg-1 svg-icon-gray-500 position-absolute top-50 ms-5 translate-middle-y'
|
||||
/>
|
||||
|
||||
<input
|
||||
type='text'
|
||||
className='form-control form-control-solid px-15'
|
||||
name='search'
|
||||
placeholder='Search by username or email...'
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className='card-body pt-5' id='kt_chat_contacts_body'>
|
||||
<div
|
||||
className='scroll-y me-n5 pe-5 h-200px h-lg-auto'
|
||||
data-kt-scroll='true'
|
||||
data-kt-scroll-activate='{default: false, lg: true}'
|
||||
data-kt-scroll-max-height='auto'
|
||||
data-kt-scroll-dependencies='#kt_header, #kt_toolbar, #kt_footer, #kt_chat_contacts_header'
|
||||
data-kt-scroll-wrappers='#kt_content, #kt_chat_contacts_body'
|
||||
data-kt-scroll-offset='0px'
|
||||
>
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<span className='symbol-label bg-light-danger text-danger fs-6 fw-bolder'>
|
||||
M
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Melody Macy
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>melody@altbox.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>5 hrs</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<img alt='Pic' src={toAbsoluteUrl('/media/avatars/300-1.jpg')} />
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Max Smith
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>max@kt.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>20 hrs</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<img alt='Pic' src={toAbsoluteUrl('/media/avatars/300-5.jpg')} />
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Sean Bean
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>sean@dellito.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>20 hrs</span>
|
||||
<span className='badge badge-sm badge-circle badge-light-success'>6</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<img alt='Pic' src={toAbsoluteUrl('/media/avatars/300-25.jpg')} />
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Brian Cox
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>brian@exchange.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>20 hrs</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<span className='symbol-label bg-light-warning text-warning fs-6 fw-bolder'>
|
||||
M
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Mikaela Collins
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>mikaela@pexcom.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>1 day</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<img alt='Pic' src={toAbsoluteUrl('/media/avatars/300-9.jpg')} />
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Francis Mitcham
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>f.mitcham@kpmg.com.au</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>5 hrs</span>
|
||||
<span className='badge badge-sm badge-circle badge-light-success'>6</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<span className='symbol-label bg-light-danger text-danger fs-6 fw-bolder'>
|
||||
O
|
||||
</span>
|
||||
<div className='symbol-badge bg-success start-100 top-100 border-4 h-15px w-15px ms-n2 mt-n2'></div>
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Olivia Wild
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>olivia@corpmail.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>1 week</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<span className='symbol-label bg-light-primary text-primary fs-6 fw-bolder'>
|
||||
N
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Neil Owen
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>owen.neil@gmail.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>20 hrs</span>
|
||||
<span className='badge badge-sm badge-circle badge-light-success'>6</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<img alt='Pic' src={toAbsoluteUrl('/media/avatars/300-23.jpg')} />
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Dan Wilson
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>dam@consilting.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>2 weeks</span>
|
||||
<span className='badge badge-sm badge-circle badge-light-warning'>9</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator separator-dashed d-none'></div>
|
||||
|
||||
<div className='d-flex flex-stack py-4'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='symbol symbol-45px symbol-circle'>
|
||||
<span className='symbol-label bg-light-danger text-danger fs-6 fw-bolder'>
|
||||
E
|
||||
</span>
|
||||
<div className='symbol-badge bg-success start-100 top-100 border-4 h-15px w-15px ms-n2 mt-n2'></div>
|
||||
</div>
|
||||
|
||||
<div className='ms-5'>
|
||||
<a href='#' className='fs-5 fw-bolder text-gray-900 text-hover-primary mb-2'>
|
||||
Emma Bold
|
||||
</a>
|
||||
<div className='fw-bold text-gray-400'>emma@intenso.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column align-items-end ms-2'>
|
||||
<span className='text-muted fs-7 mb-1'>1 day</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex-lg-row-fluid ms-lg-7 ms-xl-10'>
|
||||
<div className='card' id='kt_chat_messenger'>
|
||||
<div className='card-header' id='kt_chat_messenger_header'>
|
||||
<div className='card-title'>
|
||||
<div className='symbol-group symbol-hover'></div>
|
||||
<div className='d-flex justify-content-center flex-column me-3'>
|
||||
<a
|
||||
href='#'
|
||||
className='fs-4 fw-bolder text-gray-900 text-hover-primary me-1 mb-2 lh-1'
|
||||
>
|
||||
Brian Cox
|
||||
</a>
|
||||
|
||||
<div className='mb-0 lh-1'>
|
||||
<span className='badge badge-success badge-circle w-10px h-10px me-1'></span>
|
||||
<span className='fs-7 fw-bold text-gray-400'>Active</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='card-toolbar'>
|
||||
<div className='me-n3'>
|
||||
<button
|
||||
className='btn btn-sm btn-icon btn-active-light-primary'
|
||||
data-kt-menu-trigger='click'
|
||||
data-kt-menu-placement='bottom-end'
|
||||
data-kt-menu-flip='top-end'
|
||||
>
|
||||
<i className='bi bi-three-dots fs-2'></i>
|
||||
</button>
|
||||
<Dropdown1 />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ChatInner />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {Private}
|
||||
@@ -0,0 +1,39 @@
|
||||
import {Route, Routes, Outlet, Navigate} from 'react-router-dom'
|
||||
import {PageLink, PageTitle} from '../../../../_metronic/layout/core'
|
||||
import {UsersListWrapper} from './users-list/UsersList'
|
||||
|
||||
const usersBreadcrumbs: Array<PageLink> = [
|
||||
{
|
||||
title: 'User Management',
|
||||
path: '/apps/user-management/users',
|
||||
isSeparator: false,
|
||||
isActive: false,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
path: '',
|
||||
isSeparator: true,
|
||||
isActive: false,
|
||||
},
|
||||
]
|
||||
|
||||
const UsersPage = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route element={<Outlet />}>
|
||||
<Route
|
||||
path='users'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={usersBreadcrumbs}>Users list</PageTitle>
|
||||
<UsersListWrapper />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
<Route index element={<Navigate to='/apps/user-management/users' />} />
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
||||
export default UsersPage
|
||||
@@ -0,0 +1,32 @@
|
||||
import {ListViewProvider, useListView} from './core/ListViewProvider'
|
||||
import {QueryRequestProvider} from './core/QueryRequestProvider'
|
||||
import {QueryResponseProvider} from './core/QueryResponseProvider'
|
||||
import {UsersListHeader} from './components/header/UsersListHeader'
|
||||
import {UsersTable} from './table/UsersTable'
|
||||
import {UserEditModal} from './user-edit-modal/UserEditModal'
|
||||
import {KTCard} from '../../../../../_metronic/helpers'
|
||||
|
||||
const UsersList = () => {
|
||||
const {itemIdForUpdate} = useListView()
|
||||
return (
|
||||
<>
|
||||
<KTCard>
|
||||
<UsersListHeader />
|
||||
<UsersTable />
|
||||
</KTCard>
|
||||
{itemIdForUpdate !== undefined && <UserEditModal />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const UsersListWrapper = () => (
|
||||
<QueryRequestProvider>
|
||||
<QueryResponseProvider>
|
||||
<ListViewProvider>
|
||||
<UsersList />
|
||||
</ListViewProvider>
|
||||
</QueryResponseProvider>
|
||||
</QueryRequestProvider>
|
||||
)
|
||||
|
||||
export {UsersListWrapper}
|
||||
@@ -0,0 +1,32 @@
|
||||
import {KTSVG} from '../../../../../../../_metronic/helpers'
|
||||
import {useListView} from '../../core/ListViewProvider'
|
||||
import {UsersListFilter} from './UsersListFilter'
|
||||
|
||||
const UsersListToolbar = () => {
|
||||
const {setItemIdForUpdate} = useListView()
|
||||
const openAddUserModal = () => {
|
||||
setItemIdForUpdate(null)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='d-flex justify-content-end' data-kt-user-table-toolbar='base'>
|
||||
<UsersListFilter />
|
||||
|
||||
{/* begin::Export */}
|
||||
<button type='button' className='btn btn-light-primary me-3'>
|
||||
<KTSVG path='/media/icons/duotune/arrows/arr078.svg' className='svg-icon-2' />
|
||||
Export
|
||||
</button>
|
||||
{/* end::Export */}
|
||||
|
||||
{/* begin::Add user */}
|
||||
<button type='button' className='btn btn-primary' onClick={openAddUserModal}>
|
||||
<KTSVG path='/media/icons/duotune/arrows/arr075.svg' className='svg-icon-2' />
|
||||
Add User
|
||||
</button>
|
||||
{/* end::Add user */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {UsersListToolbar}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
import {useEffect, useState} from 'react'
|
||||
import {MenuComponent} from '../../../../../../../_metronic/assets/ts/components'
|
||||
import {initialQueryState, KTSVG} from '../../../../../../../_metronic/helpers'
|
||||
import {useQueryRequest} from '../../core/QueryRequestProvider'
|
||||
import {useQueryResponse} from '../../core/QueryResponseProvider'
|
||||
|
||||
const UsersListFilter = () => {
|
||||
const {updateState} = useQueryRequest()
|
||||
const {isLoading} = useQueryResponse()
|
||||
const [role, setRole] = useState<string | undefined>()
|
||||
const [lastLogin, setLastLogin] = useState<string | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
MenuComponent.reinitialization()
|
||||
}, [])
|
||||
|
||||
const resetData = () => {
|
||||
updateState({filter: undefined, ...initialQueryState})
|
||||
}
|
||||
|
||||
const filterData = () => {
|
||||
updateState({
|
||||
filter: {role, last_login: lastLogin},
|
||||
...initialQueryState,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* begin::Filter Button */}
|
||||
<button
|
||||
disabled={isLoading}
|
||||
type='button'
|
||||
className='btn btn-light-primary me-3'
|
||||
data-kt-menu-trigger='click'
|
||||
data-kt-menu-placement='bottom-end'
|
||||
>
|
||||
<KTSVG path='/media/icons/duotune/general/gen031.svg' className='svg-icon-2' />
|
||||
Filter
|
||||
</button>
|
||||
{/* end::Filter Button */}
|
||||
{/* begin::SubMenu */}
|
||||
<div className='menu menu-sub menu-sub-dropdown w-300px w-md-325px' data-kt-menu='true'>
|
||||
{/* begin::Header */}
|
||||
<div className='px-7 py-5'>
|
||||
<div className='fs-5 text-dark fw-bolder'>Filter Options</div>
|
||||
</div>
|
||||
{/* end::Header */}
|
||||
|
||||
{/* begin::Separator */}
|
||||
<div className='separator border-gray-200'></div>
|
||||
{/* end::Separator */}
|
||||
|
||||
{/* begin::Content */}
|
||||
<div className='px-7 py-5' data-kt-user-table-filter='form'>
|
||||
{/* begin::Input group */}
|
||||
<div className='mb-10'>
|
||||
<label className='form-label fs-6 fw-bold'>Role:</label>
|
||||
<select
|
||||
className='form-select form-select-solid fw-bolder'
|
||||
data-kt-select2='true'
|
||||
data-placeholder='Select option'
|
||||
data-allow-clear='true'
|
||||
data-kt-user-table-filter='role'
|
||||
data-hide-search='true'
|
||||
onChange={(e) => setRole(e.target.value)}
|
||||
value={role}
|
||||
>
|
||||
<option value=''></option>
|
||||
<option value='Administrator'>Administrator</option>
|
||||
<option value='Analyst'>Analyst</option>
|
||||
<option value='Developer'>Developer</option>
|
||||
<option value='Support'>Support</option>
|
||||
<option value='Trial'>Trial</option>
|
||||
</select>
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className='mb-10'>
|
||||
<label className='form-label fs-6 fw-bold'>Last login:</label>
|
||||
<select
|
||||
className='form-select form-select-solid fw-bolder'
|
||||
data-kt-select2='true'
|
||||
data-placeholder='Select option'
|
||||
data-allow-clear='true'
|
||||
data-kt-user-table-filter='two-step'
|
||||
data-hide-search='true'
|
||||
onChange={(e) => setLastLogin(e.target.value)}
|
||||
value={lastLogin}
|
||||
>
|
||||
<option value=''></option>
|
||||
<option value='Yesterday'>Yesterday</option>
|
||||
<option value='20 mins ago'>20 mins ago</option>
|
||||
<option value='5 hours ago'>5 hours ago</option>
|
||||
<option value='2 days ago'>2 days ago</option>
|
||||
</select>
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Actions */}
|
||||
<div className='d-flex justify-content-end'>
|
||||
<button
|
||||
type='button'
|
||||
disabled={isLoading}
|
||||
onClick={filterData}
|
||||
className='btn btn-light btn-active-light-primary fw-bold me-2 px-6'
|
||||
data-kt-menu-dismiss='true'
|
||||
data-kt-user-table-filter='reset'
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<button
|
||||
disabled={isLoading}
|
||||
type='button'
|
||||
onClick={resetData}
|
||||
className='btn btn-primary fw-bold px-6'
|
||||
data-kt-menu-dismiss='true'
|
||||
data-kt-user-table-filter='filter'
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
{/* end::Actions */}
|
||||
</div>
|
||||
{/* end::Content */}
|
||||
</div>
|
||||
{/* end::SubMenu */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export {UsersListFilter}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import {useQueryClient, useMutation} from 'react-query'
|
||||
import {QUERIES} from '../../../../../../../_metronic/helpers'
|
||||
import {useListView} from '../../core/ListViewProvider'
|
||||
import {useQueryResponse} from '../../core/QueryResponseProvider'
|
||||
import {deleteSelectedUsers} from '../../core/_requests'
|
||||
|
||||
const UsersListGrouping = () => {
|
||||
const {selected, clearSelected} = useListView()
|
||||
const queryClient = useQueryClient()
|
||||
const {query} = useQueryResponse()
|
||||
|
||||
const deleteSelectedItems = useMutation(() => deleteSelectedUsers(selected), {
|
||||
// 💡 response of the mutation is passed to onSuccess
|
||||
onSuccess: () => {
|
||||
// ✅ update detail view directly
|
||||
queryClient.invalidateQueries([`${QUERIES.USERS_LIST}-${query}`])
|
||||
clearSelected()
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='d-flex justify-content-end align-items-center'>
|
||||
<div className='fw-bolder me-5'>
|
||||
<span className='me-2'>{selected.length}</span> Selected
|
||||
</div>
|
||||
|
||||
<button
|
||||
type='button'
|
||||
className='btn btn-danger'
|
||||
onClick={async () => await deleteSelectedItems.mutateAsync()}
|
||||
>
|
||||
Delete Selected
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {UsersListGrouping}
|
||||
@@ -0,0 +1,22 @@
|
||||
import {useListView} from '../../core/ListViewProvider'
|
||||
import {UsersListToolbar} from './UserListToolbar'
|
||||
import {UsersListGrouping} from './UsersListGrouping'
|
||||
import {UsersListSearchComponent} from './UsersListSearchComponent'
|
||||
|
||||
const UsersListHeader = () => {
|
||||
const {selected} = useListView()
|
||||
return (
|
||||
<div className='card-header border-0 pt-6'>
|
||||
<UsersListSearchComponent />
|
||||
{/* begin::Card toolbar */}
|
||||
<div className='card-toolbar'>
|
||||
{/* begin::Group actions */}
|
||||
{selected.length > 0 ? <UsersListGrouping /> : <UsersListToolbar />}
|
||||
{/* end::Group actions */}
|
||||
</div>
|
||||
{/* end::Card toolbar */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {UsersListHeader}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {useState, useEffect} from 'react'
|
||||
import {initialQueryState, KTSVG, useDebounce} from '../../../../../../../_metronic/helpers'
|
||||
import {useQueryRequest} from '../../core/QueryRequestProvider'
|
||||
|
||||
const UsersListSearchComponent = () => {
|
||||
const {updateState} = useQueryRequest()
|
||||
const [searchTerm, setSearchTerm] = useState<string>('')
|
||||
// Debounce search term so that it only gives us latest value ...
|
||||
// ... if searchTerm has not been updated within last 500ms.
|
||||
// The goal is to only have the API call fire when user stops typing ...
|
||||
// ... so that we aren't hitting our API rapidly.
|
||||
const debouncedSearchTerm = useDebounce(searchTerm, 150)
|
||||
// Effect for API call
|
||||
useEffect(
|
||||
() => {
|
||||
if (debouncedSearchTerm !== undefined && searchTerm !== undefined) {
|
||||
updateState({search: debouncedSearchTerm, ...initialQueryState})
|
||||
}
|
||||
},
|
||||
[debouncedSearchTerm] // Only call effect if debounced search term changes
|
||||
// More details about useDebounce: https://usehooks.com/useDebounce/
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='card-title'>
|
||||
{/* begin::Search */}
|
||||
<div className='d-flex align-items-center position-relative my-1'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/general/gen021.svg'
|
||||
className='svg-icon-1 position-absolute ms-6'
|
||||
/>
|
||||
<input
|
||||
type='text'
|
||||
data-kt-user-table-filter='search'
|
||||
className='form-control form-control-solid w-250px ps-14'
|
||||
placeholder='Search user'
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{/* end::Search */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {UsersListSearchComponent}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
const UsersListLoading = () => {
|
||||
const styles = {
|
||||
borderRadius: '0.475rem',
|
||||
boxShadow: '0 0 50px 0 rgb(82 63 105 / 15%)',
|
||||
backgroundColor: '#fff',
|
||||
color: '#7e8299',
|
||||
fontWeight: '500',
|
||||
margin: '0',
|
||||
width: 'auto',
|
||||
padding: '1rem 2rem',
|
||||
top: 'calc(50% - 2rem)',
|
||||
left: 'calc(50% - 4rem)',
|
||||
}
|
||||
|
||||
return <div style={{...styles, position: 'absolute', textAlign: 'center'}}>Processing...</div>
|
||||
}
|
||||
|
||||
export {UsersListLoading}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import clsx from 'clsx'
|
||||
import {useQueryResponseLoading, useQueryResponsePagination} from '../../core/QueryResponseProvider'
|
||||
import {useQueryRequest} from '../../core/QueryRequestProvider'
|
||||
|
||||
const mappedLabel = (label: string): string => {
|
||||
if (label === '« Previous') {
|
||||
return 'Previous'
|
||||
}
|
||||
|
||||
if (label === 'Next »') {
|
||||
return 'Next'
|
||||
}
|
||||
|
||||
return label
|
||||
}
|
||||
|
||||
const UsersListPagination = () => {
|
||||
const pagination = useQueryResponsePagination()
|
||||
const isLoading = useQueryResponseLoading()
|
||||
const {updateState} = useQueryRequest()
|
||||
const updatePage = (page: number | null) => {
|
||||
if (!page || isLoading || pagination.page === page) {
|
||||
return
|
||||
}
|
||||
|
||||
updateState({page, items_per_page: pagination.items_per_page || 10})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='row'>
|
||||
<div className='col-sm-12 col-md-5 d-flex align-items-center justify-content-center justify-content-md-start'></div>
|
||||
<div className='col-sm-12 col-md-7 d-flex align-items-center justify-content-center justify-content-md-end'>
|
||||
<div id='kt_table_users_paginate'>
|
||||
<ul className='pagination'>
|
||||
{pagination.links
|
||||
?.map((link) => {
|
||||
return {...link, label: mappedLabel(link.label)}
|
||||
})
|
||||
.map((link) => (
|
||||
<li
|
||||
key={link.label}
|
||||
className={clsx('page-item', {
|
||||
active: pagination.page === link.page,
|
||||
disabled: isLoading,
|
||||
previous: link.label === 'Previous',
|
||||
next: link.label === 'Next',
|
||||
})}
|
||||
>
|
||||
<a
|
||||
className={clsx('page-link', {
|
||||
'page-text': link.label === 'Previous' || link.label === 'Next',
|
||||
'me-5': link.label === 'Previous',
|
||||
})}
|
||||
onClick={() => updatePage(link.page)}
|
||||
style={{cursor: 'pointer'}}
|
||||
>
|
||||
{mappedLabel(link.label)}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {UsersListPagination}
|
||||
@@ -0,0 +1,50 @@
|
||||
import {FC, useState, createContext, useContext, useMemo} from 'react'
|
||||
import {
|
||||
ID,
|
||||
calculatedGroupingIsDisabled,
|
||||
calculateIsAllDataSelected,
|
||||
groupingOnSelect,
|
||||
initialListView,
|
||||
ListViewContextProps,
|
||||
groupingOnSelectAll,
|
||||
WithChildren,
|
||||
} from '../../../../../../_metronic/helpers'
|
||||
import {useQueryResponse, useQueryResponseData} from './QueryResponseProvider'
|
||||
|
||||
const ListViewContext = createContext<ListViewContextProps>(initialListView)
|
||||
|
||||
const ListViewProvider: FC<WithChildren> = ({children}) => {
|
||||
const [selected, setSelected] = useState<Array<ID>>(initialListView.selected)
|
||||
const [itemIdForUpdate, setItemIdForUpdate] = useState<ID>(initialListView.itemIdForUpdate)
|
||||
const {isLoading} = useQueryResponse()
|
||||
const data = useQueryResponseData()
|
||||
const disabled = useMemo(() => calculatedGroupingIsDisabled(isLoading, data), [isLoading, data])
|
||||
const isAllSelected = useMemo(() => calculateIsAllDataSelected(data, selected), [data, selected])
|
||||
|
||||
return (
|
||||
<ListViewContext.Provider
|
||||
value={{
|
||||
selected,
|
||||
itemIdForUpdate,
|
||||
setItemIdForUpdate,
|
||||
disabled,
|
||||
isAllSelected,
|
||||
onSelect: (id: ID) => {
|
||||
groupingOnSelect(id, selected, setSelected)
|
||||
},
|
||||
onSelectAll: () => {
|
||||
groupingOnSelectAll(isAllSelected, setSelected, data)
|
||||
},
|
||||
clearSelected: () => {
|
||||
setSelected([])
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ListViewContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const useListView = () => useContext(ListViewContext)
|
||||
|
||||
export {ListViewProvider, useListView}
|
||||
@@ -0,0 +1,27 @@
|
||||
import {FC, useState, createContext, useContext} from 'react'
|
||||
import {
|
||||
QueryState,
|
||||
QueryRequestContextProps,
|
||||
initialQueryRequest,
|
||||
WithChildren,
|
||||
} from '../../../../../../_metronic/helpers'
|
||||
|
||||
const QueryRequestContext = createContext<QueryRequestContextProps>(initialQueryRequest)
|
||||
|
||||
const QueryRequestProvider: FC<WithChildren> = ({children}) => {
|
||||
const [state, setState] = useState<QueryState>(initialQueryRequest.state)
|
||||
|
||||
const updateState = (updates: Partial<QueryState>) => {
|
||||
const updatedState = {...state, ...updates} as QueryState
|
||||
setState(updatedState)
|
||||
}
|
||||
|
||||
return (
|
||||
<QueryRequestContext.Provider value={{state, updateState}}>
|
||||
{children}
|
||||
</QueryRequestContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const useQueryRequest = () => useContext(QueryRequestContext)
|
||||
export {QueryRequestProvider, useQueryRequest}
|
||||
@@ -0,0 +1,84 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {FC, useContext, useState, useEffect, useMemo} from 'react'
|
||||
import {useQuery} from 'react-query'
|
||||
import {
|
||||
createResponseContext,
|
||||
initialQueryResponse,
|
||||
initialQueryState,
|
||||
PaginationState,
|
||||
QUERIES,
|
||||
stringifyRequestQuery,
|
||||
WithChildren,
|
||||
} from '../../../../../../_metronic/helpers'
|
||||
import {getUsers} from './_requests'
|
||||
import {User} from './_models'
|
||||
import {useQueryRequest} from './QueryRequestProvider'
|
||||
|
||||
const QueryResponseContext = createResponseContext<User>(initialQueryResponse)
|
||||
const QueryResponseProvider: FC<WithChildren> = ({children}) => {
|
||||
const {state} = useQueryRequest()
|
||||
const [query, setQuery] = useState<string>(stringifyRequestQuery(state))
|
||||
const updatedQuery = useMemo(() => stringifyRequestQuery(state), [state])
|
||||
|
||||
useEffect(() => {
|
||||
if (query !== updatedQuery) {
|
||||
setQuery(updatedQuery)
|
||||
}
|
||||
}, [updatedQuery])
|
||||
|
||||
const {
|
||||
isFetching,
|
||||
refetch,
|
||||
data: response,
|
||||
} = useQuery(
|
||||
`${QUERIES.USERS_LIST}-${query}`,
|
||||
() => {
|
||||
return getUsers(query)
|
||||
},
|
||||
{cacheTime: 0, keepPreviousData: true, refetchOnWindowFocus: false}
|
||||
)
|
||||
|
||||
return (
|
||||
<QueryResponseContext.Provider value={{isLoading: isFetching, refetch, response, query}}>
|
||||
{children}
|
||||
</QueryResponseContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const useQueryResponse = () => useContext(QueryResponseContext)
|
||||
|
||||
const useQueryResponseData = () => {
|
||||
const {response} = useQueryResponse()
|
||||
if (!response) {
|
||||
return []
|
||||
}
|
||||
|
||||
return response?.data || []
|
||||
}
|
||||
|
||||
const useQueryResponsePagination = () => {
|
||||
const defaultPaginationState: PaginationState = {
|
||||
links: [],
|
||||
...initialQueryState,
|
||||
}
|
||||
|
||||
const {response} = useQueryResponse()
|
||||
if (!response || !response.payload || !response.payload.pagination) {
|
||||
return defaultPaginationState
|
||||
}
|
||||
|
||||
return response.payload.pagination
|
||||
}
|
||||
|
||||
const useQueryResponseLoading = (): boolean => {
|
||||
const {isLoading} = useQueryResponse()
|
||||
return isLoading
|
||||
}
|
||||
|
||||
export {
|
||||
QueryResponseProvider,
|
||||
useQueryResponse,
|
||||
useQueryResponseData,
|
||||
useQueryResponsePagination,
|
||||
useQueryResponseLoading,
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import {ID, Response} from '../../../../../../_metronic/helpers'
|
||||
export type User = {
|
||||
id?: ID
|
||||
name?: string
|
||||
avatar?: string
|
||||
email?: string
|
||||
position?: string
|
||||
role?: string
|
||||
last_login?: string
|
||||
two_steps?: boolean
|
||||
joined_day?: string
|
||||
online?: boolean
|
||||
initials?: {
|
||||
label: string
|
||||
state: string
|
||||
}
|
||||
}
|
||||
|
||||
export type UsersQueryResponse = Response<Array<User>>
|
||||
|
||||
export const initialUser: User = {
|
||||
avatar: 'avatars/300-6.jpg',
|
||||
position: 'Art Director',
|
||||
role: 'Administrator',
|
||||
name: '',
|
||||
email: '',
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import axios, {AxiosResponse} from 'axios'
|
||||
import {ID, Response} from '../../../../../../_metronic/helpers'
|
||||
import {User, UsersQueryResponse} from './_models'
|
||||
|
||||
const API_URL = process.env.REACT_APP_THEME_API_URL
|
||||
const USER_URL = `${API_URL}/user`
|
||||
const GET_USERS_URL = `${API_URL}/users/query`
|
||||
|
||||
const getUsers = (query: string): Promise<UsersQueryResponse> => {
|
||||
return axios
|
||||
.get(`${GET_USERS_URL}?${query}`)
|
||||
.then((d: AxiosResponse<UsersQueryResponse>) => d.data)
|
||||
}
|
||||
|
||||
const getUserById = (id: ID): Promise<User | undefined> => {
|
||||
return axios
|
||||
.get(`${USER_URL}/${id}`)
|
||||
.then((response: AxiosResponse<Response<User>>) => response.data)
|
||||
.then((response: Response<User>) => response.data)
|
||||
}
|
||||
|
||||
const createUser = (user: User): Promise<User | undefined> => {
|
||||
return axios
|
||||
.put(USER_URL, user)
|
||||
.then((response: AxiosResponse<Response<User>>) => response.data)
|
||||
.then((response: Response<User>) => response.data)
|
||||
}
|
||||
|
||||
const updateUser = (user: User): Promise<User | undefined> => {
|
||||
return axios
|
||||
.post(`${USER_URL}/${user.id}`, user)
|
||||
.then((response: AxiosResponse<Response<User>>) => response.data)
|
||||
.then((response: Response<User>) => response.data)
|
||||
}
|
||||
|
||||
const deleteUser = (userId: ID): Promise<void> => {
|
||||
return axios.delete(`${USER_URL}/${userId}`).then(() => {})
|
||||
}
|
||||
|
||||
const deleteSelectedUsers = (userIds: Array<ID>): Promise<void> => {
|
||||
const requests = userIds.map((id) => axios.delete(`${USER_URL}/${id}`))
|
||||
return axios.all(requests).then(() => {})
|
||||
}
|
||||
|
||||
export {getUsers, deleteUser, deleteSelectedUsers, getUserById, createUser, updateUser}
|
||||
@@ -0,0 +1,61 @@
|
||||
import {useMemo} from 'react'
|
||||
import {useTable, ColumnInstance, Row} from 'react-table'
|
||||
import {CustomHeaderColumn} from '../table/columns/CustomHeaderColumn'
|
||||
import {CustomRow} from '../table/columns/CustomRow'
|
||||
import {useQueryResponseData, useQueryResponseLoading} from '../core/QueryResponseProvider'
|
||||
import {usersColumns} from './columns/_columns'
|
||||
import {User} from '../core/_models'
|
||||
import {UsersListLoading} from '../components/loading/UsersListLoading'
|
||||
import {UsersListPagination} from '../components/pagination/UsersListPagination'
|
||||
import {KTCardBody} from '../../../../../../_metronic/helpers'
|
||||
|
||||
const UsersTable = () => {
|
||||
const users = useQueryResponseData()
|
||||
const isLoading = useQueryResponseLoading()
|
||||
const data = useMemo(() => users, [users])
|
||||
const columns = useMemo(() => usersColumns, [])
|
||||
const {getTableProps, getTableBodyProps, headers, rows, prepareRow} = useTable({
|
||||
columns,
|
||||
data,
|
||||
})
|
||||
|
||||
return (
|
||||
<KTCardBody className='py-4'>
|
||||
<div className='table-responsive'>
|
||||
<table
|
||||
id='kt_table_users'
|
||||
className='table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer'
|
||||
{...getTableProps()}
|
||||
>
|
||||
<thead>
|
||||
<tr className='text-start text-muted fw-bolder fs-7 text-uppercase gs-0'>
|
||||
{headers.map((column: ColumnInstance<User>) => (
|
||||
<CustomHeaderColumn key={column.id} column={column} />
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className='text-gray-600 fw-bold' {...getTableBodyProps()}>
|
||||
{rows.length > 0 ? (
|
||||
rows.map((row: Row<User>, i) => {
|
||||
prepareRow(row)
|
||||
return <CustomRow row={row} key={`row-${i}-${row.id}`} />
|
||||
})
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={7}>
|
||||
<div className='d-flex text-center w-100 align-content-center justify-content-center'>
|
||||
No matching records found
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<UsersListPagination />
|
||||
{isLoading && <UsersListLoading />}
|
||||
</KTCardBody>
|
||||
)
|
||||
}
|
||||
|
||||
export {UsersTable}
|
||||
@@ -0,0 +1,20 @@
|
||||
// @ts-nocheck
|
||||
import {FC} from 'react'
|
||||
import {ColumnInstance} from 'react-table'
|
||||
import {User} from '../../core/_models'
|
||||
|
||||
type Props = {
|
||||
column: ColumnInstance<User>
|
||||
}
|
||||
|
||||
const CustomHeaderColumn: FC<Props> = ({column}) => (
|
||||
<>
|
||||
{column.Header && typeof column.Header === 'string' ? (
|
||||
<th {...column.getHeaderProps()}>{column.render('Header')}</th>
|
||||
) : (
|
||||
column.render('Header')
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
export {CustomHeaderColumn}
|
||||
@@ -0,0 +1,26 @@
|
||||
// @ts-nocheck
|
||||
import clsx from 'clsx'
|
||||
import {FC} from 'react'
|
||||
import {Row} from 'react-table'
|
||||
import {User} from '../../core/_models'
|
||||
|
||||
type Props = {
|
||||
row: Row<User>
|
||||
}
|
||||
|
||||
const CustomRow: FC<Props> = ({row}) => (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map((cell) => {
|
||||
return (
|
||||
<td
|
||||
{...cell.getCellProps()}
|
||||
className={clsx({'text-end min-w-100px': cell.column.id === 'actions'})}
|
||||
>
|
||||
{cell.render('Cell')}
|
||||
</td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
)
|
||||
|
||||
export {CustomRow}
|
||||
@@ -0,0 +1,76 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import {FC, useEffect} from 'react'
|
||||
import {useMutation, useQueryClient} from 'react-query'
|
||||
import {MenuComponent} from '../../../../../../../_metronic/assets/ts/components'
|
||||
import {ID, KTSVG, QUERIES} from '../../../../../../../_metronic/helpers'
|
||||
import {useListView} from '../../core/ListViewProvider'
|
||||
import {useQueryResponse} from '../../core/QueryResponseProvider'
|
||||
import {deleteUser} from '../../core/_requests'
|
||||
|
||||
type Props = {
|
||||
id: ID
|
||||
}
|
||||
|
||||
const UserActionsCell: FC<Props> = ({id}) => {
|
||||
const {setItemIdForUpdate} = useListView()
|
||||
const {query} = useQueryResponse()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
useEffect(() => {
|
||||
MenuComponent.reinitialization()
|
||||
}, [])
|
||||
|
||||
const openEditModal = () => {
|
||||
setItemIdForUpdate(id)
|
||||
}
|
||||
|
||||
const deleteItem = useMutation(() => deleteUser(id), {
|
||||
// 💡 response of the mutation is passed to onSuccess
|
||||
onSuccess: () => {
|
||||
// ✅ update detail view directly
|
||||
queryClient.invalidateQueries([`${QUERIES.USERS_LIST}-${query}`])
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<a
|
||||
href='#'
|
||||
className='btn btn-light btn-active-light-primary btn-sm'
|
||||
data-kt-menu-trigger='click'
|
||||
data-kt-menu-placement='bottom-end'
|
||||
>
|
||||
Actions
|
||||
<KTSVG path='/media/icons/duotune/arrows/arr072.svg' className='svg-icon-5 m-0' />
|
||||
</a>
|
||||
{/* begin::Menu */}
|
||||
<div
|
||||
className='menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-gray-600 menu-state-bg-light-primary fw-bold fs-7 w-125px py-4'
|
||||
data-kt-menu='true'
|
||||
>
|
||||
{/* begin::Menu item */}
|
||||
<div className='menu-item px-3'>
|
||||
<a className='menu-link px-3' onClick={openEditModal}>
|
||||
Edit
|
||||
</a>
|
||||
</div>
|
||||
{/* end::Menu item */}
|
||||
|
||||
{/* begin::Menu item */}
|
||||
<div className='menu-item px-3'>
|
||||
<a
|
||||
className='menu-link px-3'
|
||||
data-kt-users-table-filter='delete_row'
|
||||
onClick={async () => await deleteItem.mutateAsync()}
|
||||
>
|
||||
Delete
|
||||
</a>
|
||||
</div>
|
||||
{/* end::Menu item */}
|
||||
</div>
|
||||
{/* end::Menu */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export {UserActionsCell}
|
||||
@@ -0,0 +1,61 @@
|
||||
import clsx from 'clsx'
|
||||
import {FC, PropsWithChildren, useMemo} from 'react'
|
||||
import {HeaderProps} from 'react-table'
|
||||
import {initialQueryState} from '../../../../../../../_metronic/helpers'
|
||||
import {useQueryRequest} from '../../core/QueryRequestProvider'
|
||||
import {User} from '../../core/_models'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
title?: string
|
||||
tableProps: PropsWithChildren<HeaderProps<User>>
|
||||
}
|
||||
const UserCustomHeader: FC<Props> = ({className, title, tableProps}) => {
|
||||
const id = tableProps.column.id
|
||||
const {state, updateState} = useQueryRequest()
|
||||
|
||||
const isSelectedForSorting = useMemo(() => {
|
||||
return state.sort && state.sort === id
|
||||
}, [state, id])
|
||||
const order: 'asc' | 'desc' | undefined = useMemo(() => state.order, [state])
|
||||
|
||||
const sortColumn = () => {
|
||||
// avoid sorting for these columns
|
||||
if (id === 'actions' || id === 'selection') {
|
||||
return
|
||||
}
|
||||
|
||||
if (!isSelectedForSorting) {
|
||||
// enable sort asc
|
||||
updateState({sort: id, order: 'asc', ...initialQueryState})
|
||||
return
|
||||
}
|
||||
|
||||
if (isSelectedForSorting && order !== undefined) {
|
||||
if (order === 'asc') {
|
||||
// enable sort desc
|
||||
updateState({sort: id, order: 'desc', ...initialQueryState})
|
||||
return
|
||||
}
|
||||
|
||||
// disable sort
|
||||
updateState({sort: undefined, order: undefined, ...initialQueryState})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<th
|
||||
{...tableProps.column.getHeaderProps()}
|
||||
className={clsx(
|
||||
className,
|
||||
isSelectedForSorting && order !== undefined && `table-sort-${order}`
|
||||
)}
|
||||
style={{cursor: 'pointer'}}
|
||||
onClick={sortColumn}
|
||||
>
|
||||
{title}
|
||||
</th>
|
||||
)
|
||||
}
|
||||
|
||||
export {UserCustomHeader}
|
||||
@@ -0,0 +1,42 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import clsx from 'clsx'
|
||||
import {FC} from 'react'
|
||||
import {toAbsoluteUrl} from '../../../../../../../_metronic/helpers'
|
||||
import {User} from '../../core/_models'
|
||||
|
||||
type Props = {
|
||||
user: User
|
||||
}
|
||||
|
||||
const UserInfoCell: FC<Props> = ({user}) => (
|
||||
<div className='d-flex align-items-center'>
|
||||
{/* begin:: Avatar */}
|
||||
<div className='symbol symbol-circle symbol-50px overflow-hidden me-3'>
|
||||
<a href='#'>
|
||||
{user.avatar ? (
|
||||
<div className='symbol-label'>
|
||||
<img src={toAbsoluteUrl(`/media/${user.avatar}`)} alt={user.name} className='w-100' />
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={clsx(
|
||||
'symbol-label fs-3',
|
||||
`bg-light-${user.initials?.state}`,
|
||||
`text-${user.initials?.state}`
|
||||
)}
|
||||
>
|
||||
{user.initials?.label}
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
</div>
|
||||
<div className='d-flex flex-column'>
|
||||
<a href='#' className='text-gray-800 text-hover-primary mb-1'>
|
||||
{user.name}
|
||||
</a>
|
||||
<span>{user.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
export {UserInfoCell}
|
||||
@@ -0,0 +1,11 @@
|
||||
import {FC} from 'react'
|
||||
|
||||
type Props = {
|
||||
last_login?: string
|
||||
}
|
||||
|
||||
const UserLastLoginCell: FC<Props> = ({last_login}) => (
|
||||
<div className='badge badge-light fw-bolder'>{last_login}</div>
|
||||
)
|
||||
|
||||
export {UserLastLoginCell}
|
||||
@@ -0,0 +1,26 @@
|
||||
import {FC, useMemo} from 'react'
|
||||
import {ID} from '../../../../../../../_metronic/helpers'
|
||||
import {useListView} from '../../core/ListViewProvider'
|
||||
|
||||
type Props = {
|
||||
id: ID
|
||||
}
|
||||
|
||||
const UserSelectionCell: FC<Props> = ({id}) => {
|
||||
const {selected, onSelect} = useListView()
|
||||
const isSelected = useMemo(() => selected.includes(id), [id, selected])
|
||||
return (
|
||||
<div className='form-check form-check-custom form-check-solid'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
data-kt-check={isSelected}
|
||||
data-kt-check-target='#kt_table_users .form-check-input'
|
||||
checked={isSelected}
|
||||
onChange={() => onSelect(id)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {UserSelectionCell}
|
||||
@@ -0,0 +1,28 @@
|
||||
import {FC, PropsWithChildren} from 'react'
|
||||
import {HeaderProps} from 'react-table'
|
||||
import {useListView} from '../../core/ListViewProvider'
|
||||
import {User} from '../../core/_models'
|
||||
|
||||
type Props = {
|
||||
tableProps: PropsWithChildren<HeaderProps<User>>
|
||||
}
|
||||
|
||||
const UserSelectionHeader: FC<Props> = ({tableProps}) => {
|
||||
const {isAllSelected, onSelectAll} = useListView()
|
||||
return (
|
||||
<th {...tableProps.column.getHeaderProps()} className='w-10px pe-2'>
|
||||
<div className='form-check form-check-sm form-check-custom form-check-solid me-3'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
data-kt-check={isAllSelected}
|
||||
data-kt-check-target='#kt_table_users .form-check-input'
|
||||
checked={isAllSelected}
|
||||
onChange={onSelectAll}
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
)
|
||||
}
|
||||
|
||||
export {UserSelectionHeader}
|
||||
@@ -0,0 +1,11 @@
|
||||
import {FC} from 'react'
|
||||
|
||||
type Props = {
|
||||
two_steps?: boolean
|
||||
}
|
||||
|
||||
const UserTwoStepsCell: FC<Props> = ({two_steps}) => (
|
||||
<> {two_steps && <div className='badge badge-light-success fw-bolder'>Enabled</div>}</>
|
||||
)
|
||||
|
||||
export {UserTwoStepsCell}
|
||||
@@ -0,0 +1,56 @@
|
||||
// @ts-nocheck
|
||||
import {Column} from 'react-table'
|
||||
import {UserInfoCell} from './UserInfoCell'
|
||||
import {UserLastLoginCell} from './UserLastLoginCell'
|
||||
import {UserTwoStepsCell} from './UserTwoStepsCell'
|
||||
import {UserActionsCell} from './UserActionsCell'
|
||||
import {UserSelectionCell} from './UserSelectionCell'
|
||||
import {UserCustomHeader} from './UserCustomHeader'
|
||||
import {UserSelectionHeader} from './UserSelectionHeader'
|
||||
import {User} from '../../core/_models'
|
||||
|
||||
const usersColumns: ReadonlyArray<Column<User>> = [
|
||||
{
|
||||
Header: (props) => <UserSelectionHeader tableProps={props} />,
|
||||
id: 'selection',
|
||||
Cell: ({...props}) => <UserSelectionCell id={props.data[props.row.index].id} />,
|
||||
},
|
||||
{
|
||||
Header: (props) => <UserCustomHeader tableProps={props} title='Name' className='min-w-125px' />,
|
||||
id: 'name',
|
||||
Cell: ({...props}) => <UserInfoCell user={props.data[props.row.index]} />,
|
||||
},
|
||||
{
|
||||
Header: (props) => <UserCustomHeader tableProps={props} title='Role' className='min-w-125px' />,
|
||||
accessor: 'role',
|
||||
},
|
||||
{
|
||||
Header: (props) => (
|
||||
<UserCustomHeader tableProps={props} title='Last login' className='min-w-125px' />
|
||||
),
|
||||
id: 'last_login',
|
||||
Cell: ({...props}) => <UserLastLoginCell last_login={props.data[props.row.index].last_login} />,
|
||||
},
|
||||
{
|
||||
Header: (props) => (
|
||||
<UserCustomHeader tableProps={props} title='Two steps' className='min-w-125px' />
|
||||
),
|
||||
id: 'two_steps',
|
||||
Cell: ({...props}) => <UserTwoStepsCell two_steps={props.data[props.row.index].two_steps} />,
|
||||
},
|
||||
{
|
||||
Header: (props) => (
|
||||
<UserCustomHeader tableProps={props} title='Joined day' className='min-w-125px' />
|
||||
),
|
||||
accessor: 'joined_day',
|
||||
},
|
||||
{
|
||||
Header: (props) => (
|
||||
<UserCustomHeader tableProps={props} title='Actions' className='text-end min-w-100px' />
|
||||
),
|
||||
id: 'actions',
|
||||
Cell: ({...props}) => <UserActionsCell id={props.data[props.row.index].id} />,
|
||||
},
|
||||
]
|
||||
|
||||
export {usersColumns}
|
||||
@@ -0,0 +1,44 @@
|
||||
import {useEffect} from 'react'
|
||||
import {UserEditModalHeader} from './UserEditModalHeader'
|
||||
import {UserEditModalFormWrapper} from './UserEditModalFormWrapper'
|
||||
|
||||
const UserEditModal = () => {
|
||||
useEffect(() => {
|
||||
document.body.classList.add('modal-open')
|
||||
return () => {
|
||||
document.body.classList.remove('modal-open')
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className='modal fade show d-block'
|
||||
id='kt_modal_add_user'
|
||||
role='dialog'
|
||||
tabIndex={-1}
|
||||
aria-modal='true'
|
||||
>
|
||||
{/* begin::Modal dialog */}
|
||||
<div className='modal-dialog modal-dialog-centered mw-650px'>
|
||||
{/* begin::Modal content */}
|
||||
<div className='modal-content'>
|
||||
<UserEditModalHeader />
|
||||
{/* begin::Modal body */}
|
||||
<div className='modal-body scroll-y mx-5 mx-xl-15 my-7'>
|
||||
<UserEditModalFormWrapper />
|
||||
</div>
|
||||
{/* end::Modal body */}
|
||||
</div>
|
||||
{/* end::Modal content */}
|
||||
</div>
|
||||
{/* end::Modal dialog */}
|
||||
</div>
|
||||
{/* begin::Modal Backdrop */}
|
||||
<div className='modal-backdrop fade show'></div>
|
||||
{/* end::Modal Backdrop */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export {UserEditModal}
|
||||
+407
@@ -0,0 +1,407 @@
|
||||
import {FC, useState} from 'react'
|
||||
import * as Yup from 'yup'
|
||||
import {useFormik} from 'formik'
|
||||
import {isNotEmpty, toAbsoluteUrl} from '../../../../../../_metronic/helpers'
|
||||
import {initialUser, User} from '../core/_models'
|
||||
import clsx from 'clsx'
|
||||
import {useListView} from '../core/ListViewProvider'
|
||||
import {UsersListLoading} from '../components/loading/UsersListLoading'
|
||||
import {createUser, updateUser} from '../core/_requests'
|
||||
import {useQueryResponse} from '../core/QueryResponseProvider'
|
||||
|
||||
type Props = {
|
||||
isUserLoading: boolean
|
||||
user: User
|
||||
}
|
||||
|
||||
const editUserSchema = Yup.object().shape({
|
||||
email: Yup.string()
|
||||
.email('Wrong email format')
|
||||
.min(3, 'Minimum 3 symbols')
|
||||
.max(50, 'Maximum 50 symbols')
|
||||
.required('Email is required'),
|
||||
name: Yup.string()
|
||||
.min(3, 'Minimum 3 symbols')
|
||||
.max(50, 'Maximum 50 symbols')
|
||||
.required('Name is required'),
|
||||
})
|
||||
|
||||
const UserEditModalForm: FC<Props> = ({user, isUserLoading}) => {
|
||||
const {setItemIdForUpdate} = useListView()
|
||||
const {refetch} = useQueryResponse()
|
||||
|
||||
const [userForEdit] = useState<User>({
|
||||
...user,
|
||||
avatar: user.avatar || initialUser.avatar,
|
||||
role: user.role || initialUser.role,
|
||||
position: user.position || initialUser.position,
|
||||
name: user.name || initialUser.name,
|
||||
email: user.email || initialUser.email,
|
||||
})
|
||||
|
||||
const cancel = (withRefresh?: boolean) => {
|
||||
if (withRefresh) {
|
||||
refetch()
|
||||
}
|
||||
setItemIdForUpdate(undefined)
|
||||
}
|
||||
|
||||
const blankImg = toAbsoluteUrl('/media/svg/avatars/blank.svg')
|
||||
const userAvatarImg = toAbsoluteUrl(`/media/${userForEdit.avatar}`)
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: userForEdit,
|
||||
validationSchema: editUserSchema,
|
||||
onSubmit: async (values, {setSubmitting}) => {
|
||||
setSubmitting(true)
|
||||
try {
|
||||
if (isNotEmpty(values.id)) {
|
||||
await updateUser(values)
|
||||
} else {
|
||||
await createUser(values)
|
||||
}
|
||||
} catch (ex) {
|
||||
console.error(ex)
|
||||
} finally {
|
||||
setSubmitting(true)
|
||||
cancel(true)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<form id='kt_modal_add_user_form' className='form' onSubmit={formik.handleSubmit} noValidate>
|
||||
{/* begin::Scroll */}
|
||||
<div
|
||||
className='d-flex flex-column scroll-y me-n7 pe-7'
|
||||
id='kt_modal_add_user_scroll'
|
||||
data-kt-scroll='true'
|
||||
data-kt-scroll-activate='{default: false, lg: true}'
|
||||
data-kt-scroll-max-height='auto'
|
||||
data-kt-scroll-dependencies='#kt_modal_add_user_header'
|
||||
data-kt-scroll-wrappers='#kt_modal_add_user_scroll'
|
||||
data-kt-scroll-offset='300px'
|
||||
>
|
||||
{/* begin::Input group */}
|
||||
<div className='fv-row mb-7'>
|
||||
{/* begin::Label */}
|
||||
<label className='d-block fw-bold fs-6 mb-5'>Avatar</label>
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Image input */}
|
||||
<div
|
||||
className='image-input image-input-outline'
|
||||
data-kt-image-input='true'
|
||||
style={{backgroundImage: `url('${blankImg}')`}}
|
||||
>
|
||||
{/* begin::Preview existing avatar */}
|
||||
<div
|
||||
className='image-input-wrapper w-125px h-125px'
|
||||
style={{backgroundImage: `url('${userAvatarImg}')`}}
|
||||
></div>
|
||||
{/* end::Preview existing avatar */}
|
||||
|
||||
{/* begin::Label */}
|
||||
{/* <label
|
||||
className='btn btn-icon btn-circle btn-active-color-primary w-25px h-25px bg-body shadow'
|
||||
data-kt-image-input-action='change'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Change avatar'
|
||||
>
|
||||
<i className='bi bi-pencil-fill fs-7'></i>
|
||||
|
||||
<input type='file' name='avatar' accept='.png, .jpg, .jpeg' />
|
||||
<input type='hidden' name='avatar_remove' />
|
||||
</label> */}
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Cancel */}
|
||||
{/* <span
|
||||
className='btn btn-icon btn-circle btn-active-color-primary w-25px h-25px bg-body shadow'
|
||||
data-kt-image-input-action='cancel'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Cancel avatar'
|
||||
>
|
||||
<i className='bi bi-x fs-2'></i>
|
||||
</span> */}
|
||||
{/* end::Cancel */}
|
||||
|
||||
{/* begin::Remove */}
|
||||
{/* <span
|
||||
className='btn btn-icon btn-circle btn-active-color-primary w-25px h-25px bg-body shadow'
|
||||
data-kt-image-input-action='remove'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Remove avatar'
|
||||
>
|
||||
<i className='bi bi-x fs-2'></i>
|
||||
</span> */}
|
||||
{/* end::Remove */}
|
||||
</div>
|
||||
{/* end::Image input */}
|
||||
|
||||
{/* begin::Hint */}
|
||||
{/* <div className='form-text'>Allowed file types: png, jpg, jpeg.</div> */}
|
||||
{/* end::Hint */}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className='fv-row mb-7'>
|
||||
{/* begin::Label */}
|
||||
<label className='required fw-bold fs-6 mb-2'>Full Name</label>
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
placeholder='Full name'
|
||||
{...formik.getFieldProps('name')}
|
||||
type='text'
|
||||
name='name'
|
||||
className={clsx(
|
||||
'form-control form-control-solid mb-3 mb-lg-0',
|
||||
{'is-invalid': formik.touched.name && formik.errors.name},
|
||||
{
|
||||
'is-valid': formik.touched.name && !formik.errors.name,
|
||||
}
|
||||
)}
|
||||
autoComplete='off'
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{formik.touched.name && formik.errors.name && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>
|
||||
<span role='alert'>{formik.errors.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* end::Input */}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className='fv-row mb-7'>
|
||||
{/* begin::Label */}
|
||||
<label className='required fw-bold fs-6 mb-2'>Email</label>
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
placeholder='Email'
|
||||
{...formik.getFieldProps('email')}
|
||||
className={clsx(
|
||||
'form-control form-control-solid mb-3 mb-lg-0',
|
||||
{'is-invalid': formik.touched.email && formik.errors.email},
|
||||
{
|
||||
'is-valid': formik.touched.email && !formik.errors.email,
|
||||
}
|
||||
)}
|
||||
type='email'
|
||||
name='email'
|
||||
autoComplete='off'
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{/* end::Input */}
|
||||
{formik.touched.email && formik.errors.email && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<span role='alert'>{formik.errors.email}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className='mb-7'>
|
||||
{/* begin::Label */}
|
||||
<label className='required fw-bold fs-6 mb-5'>Role</label>
|
||||
{/* end::Label */}
|
||||
{/* begin::Roles */}
|
||||
{/* begin::Input row */}
|
||||
<div className='d-flex fv-row'>
|
||||
{/* begin::Radio */}
|
||||
<div className='form-check form-check-custom form-check-solid'>
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
className='form-check-input me-3'
|
||||
{...formik.getFieldProps('role')}
|
||||
name='role'
|
||||
type='radio'
|
||||
value='Administrator'
|
||||
id='kt_modal_update_role_option_0'
|
||||
checked={formik.values.role === 'Administrator'}
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
|
||||
{/* end::Input */}
|
||||
{/* begin::Label */}
|
||||
<label className='form-check-label' htmlFor='kt_modal_update_role_option_0'>
|
||||
<div className='fw-bolder text-gray-800'>Administrator</div>
|
||||
<div className='text-gray-600'>
|
||||
Best for business owners and company administrators
|
||||
</div>
|
||||
</label>
|
||||
{/* end::Label */}
|
||||
</div>
|
||||
{/* end::Radio */}
|
||||
</div>
|
||||
{/* end::Input row */}
|
||||
<div className='separator separator-dashed my-5'></div>
|
||||
{/* begin::Input row */}
|
||||
<div className='d-flex fv-row'>
|
||||
{/* begin::Radio */}
|
||||
<div className='form-check form-check-custom form-check-solid'>
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
className='form-check-input me-3'
|
||||
{...formik.getFieldProps('role')}
|
||||
name='role'
|
||||
type='radio'
|
||||
value='Developer'
|
||||
id='kt_modal_update_role_option_1'
|
||||
checked={formik.values.role === 'Developer'}
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{/* end::Input */}
|
||||
{/* begin::Label */}
|
||||
<label className='form-check-label' htmlFor='kt_modal_update_role_option_1'>
|
||||
<div className='fw-bolder text-gray-800'>Developer</div>
|
||||
<div className='text-gray-600'>
|
||||
Best for developers or people primarily using the API
|
||||
</div>
|
||||
</label>
|
||||
{/* end::Label */}
|
||||
</div>
|
||||
{/* end::Radio */}
|
||||
</div>
|
||||
{/* end::Input row */}
|
||||
<div className='separator separator-dashed my-5'></div>
|
||||
{/* begin::Input row */}
|
||||
<div className='d-flex fv-row'>
|
||||
{/* begin::Radio */}
|
||||
<div className='form-check form-check-custom form-check-solid'>
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
className='form-check-input me-3'
|
||||
{...formik.getFieldProps('role')}
|
||||
name='role'
|
||||
type='radio'
|
||||
value='Analyst'
|
||||
id='kt_modal_update_role_option_2'
|
||||
checked={formik.values.role === 'Analyst'}
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
|
||||
{/* end::Input */}
|
||||
{/* begin::Label */}
|
||||
<label className='form-check-label' htmlFor='kt_modal_update_role_option_2'>
|
||||
<div className='fw-bolder text-gray-800'>Analyst</div>
|
||||
<div className='text-gray-600'>
|
||||
Best for people who need full access to analytics data, but don't need to update
|
||||
business settings
|
||||
</div>
|
||||
</label>
|
||||
{/* end::Label */}
|
||||
</div>
|
||||
{/* end::Radio */}
|
||||
</div>
|
||||
{/* end::Input row */}
|
||||
<div className='separator separator-dashed my-5'></div>
|
||||
{/* begin::Input row */}
|
||||
<div className='d-flex fv-row'>
|
||||
{/* begin::Radio */}
|
||||
<div className='form-check form-check-custom form-check-solid'>
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
className='form-check-input me-3'
|
||||
{...formik.getFieldProps('role')}
|
||||
name='role'
|
||||
type='radio'
|
||||
value='Support'
|
||||
id='kt_modal_update_role_option_3'
|
||||
checked={formik.values.role === 'Support'}
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{/* end::Input */}
|
||||
{/* begin::Label */}
|
||||
<label className='form-check-label' htmlFor='kt_modal_update_role_option_3'>
|
||||
<div className='fw-bolder text-gray-800'>Support</div>
|
||||
<div className='text-gray-600'>
|
||||
Best for employees who regularly refund payments and respond to disputes
|
||||
</div>
|
||||
</label>
|
||||
{/* end::Label */}
|
||||
</div>
|
||||
{/* end::Radio */}
|
||||
</div>
|
||||
{/* end::Input row */}
|
||||
<div className='separator separator-dashed my-5'></div>
|
||||
{/* begin::Input row */}
|
||||
<div className='d-flex fv-row'>
|
||||
{/* begin::Radio */}
|
||||
<div className='form-check form-check-custom form-check-solid'>
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
className='form-check-input me-3'
|
||||
{...formik.getFieldProps('role')}
|
||||
name='role'
|
||||
type='radio'
|
||||
id='kt_modal_update_role_option_4'
|
||||
value='Trial'
|
||||
checked={formik.values.role === 'Trial'}
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{/* end::Input */}
|
||||
{/* begin::Label */}
|
||||
<label className='form-check-label' htmlFor='kt_modal_update_role_option_4'>
|
||||
<div className='fw-bolder text-gray-800'>Trial</div>
|
||||
<div className='text-gray-600'>
|
||||
Best for people who need to preview content data, but don't need to make any
|
||||
updates
|
||||
</div>
|
||||
</label>
|
||||
{/* end::Label */}
|
||||
</div>
|
||||
{/* end::Radio */}
|
||||
</div>
|
||||
{/* end::Input row */}
|
||||
{/* end::Roles */}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
</div>
|
||||
{/* end::Scroll */}
|
||||
|
||||
{/* begin::Actions */}
|
||||
<div className='text-center pt-15'>
|
||||
<button
|
||||
type='reset'
|
||||
onClick={() => cancel()}
|
||||
className='btn btn-light me-3'
|
||||
data-kt-users-modal-action='cancel'
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
>
|
||||
Discard
|
||||
</button>
|
||||
|
||||
<button
|
||||
type='submit'
|
||||
className='btn btn-primary'
|
||||
data-kt-users-modal-action='submit'
|
||||
disabled={isUserLoading || formik.isSubmitting || !formik.isValid || !formik.touched}
|
||||
>
|
||||
<span className='indicator-label'>Submit</span>
|
||||
{(formik.isSubmitting || isUserLoading) && (
|
||||
<span className='indicator-progress'>
|
||||
Please wait...{' '}
|
||||
<span className='spinner-border spinner-border-sm align-middle ms-2'></span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{/* end::Actions */}
|
||||
</form>
|
||||
{(formik.isSubmitting || isUserLoading) && <UsersListLoading />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export {UserEditModalForm}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import {useQuery} from 'react-query'
|
||||
import {UserEditModalForm} from './UserEditModalForm'
|
||||
import {isNotEmpty, QUERIES} from '../../../../../../_metronic/helpers'
|
||||
import {useListView} from '../core/ListViewProvider'
|
||||
import {getUserById} from '../core/_requests'
|
||||
|
||||
const UserEditModalFormWrapper = () => {
|
||||
const {itemIdForUpdate, setItemIdForUpdate} = useListView()
|
||||
const enabledQuery: boolean = isNotEmpty(itemIdForUpdate)
|
||||
const {
|
||||
isLoading,
|
||||
data: user,
|
||||
error,
|
||||
} = useQuery(
|
||||
`${QUERIES.USERS_LIST}-user-${itemIdForUpdate}`,
|
||||
() => {
|
||||
return getUserById(itemIdForUpdate)
|
||||
},
|
||||
{
|
||||
cacheTime: 0,
|
||||
enabled: enabledQuery,
|
||||
onError: (err) => {
|
||||
setItemIdForUpdate(undefined)
|
||||
console.error(err)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!itemIdForUpdate) {
|
||||
return <UserEditModalForm isUserLoading={isLoading} user={{id: undefined}} />
|
||||
}
|
||||
|
||||
if (!isLoading && !error && user) {
|
||||
return <UserEditModalForm isUserLoading={isLoading} user={user} />
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export {UserEditModalFormWrapper}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import {KTSVG} from '../../../../../../_metronic/helpers'
|
||||
import {useListView} from '../core/ListViewProvider'
|
||||
|
||||
const UserEditModalHeader = () => {
|
||||
const {setItemIdForUpdate} = useListView()
|
||||
|
||||
return (
|
||||
<div className='modal-header'>
|
||||
{/* begin::Modal title */}
|
||||
<h2 className='fw-bolder'>Add User</h2>
|
||||
{/* end::Modal title */}
|
||||
|
||||
{/* begin::Close */}
|
||||
<div
|
||||
className='btn btn-icon btn-sm btn-active-icon-primary'
|
||||
data-kt-users-modal-action='close'
|
||||
onClick={() => setItemIdForUpdate(undefined)}
|
||||
style={{cursor: 'pointer'}}
|
||||
>
|
||||
<KTSVG path='/media/icons/duotune/arrows/arr061.svg' className='svg-icon-1' />
|
||||
</div>
|
||||
{/* end::Close */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {UserEditModalHeader}
|
||||
@@ -0,0 +1,104 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import {useEffect} from 'react'
|
||||
import {Outlet, Link} from 'react-router-dom'
|
||||
import {toAbsoluteUrl} from '../../../_res/helpers'
|
||||
|
||||
const AuthLayout = () => {
|
||||
useEffect(() => {
|
||||
const root = document.getElementById('root')
|
||||
if (root) {
|
||||
root.style.height = '100%'
|
||||
}
|
||||
return () => {
|
||||
if (root) {
|
||||
root.style.height = 'auto'
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className='d-flex flex-column flex-lg-row flex-column-fluid h-100'>
|
||||
{/* begin::Body */}
|
||||
<div className='d-flex flex-column flex-lg-row-fluid w-lg-50 p-10 order-2 order-lg-1'>
|
||||
{/* begin::Form */}
|
||||
<div className='d-flex flex-center flex-column flex-lg-row-fluid'>
|
||||
{/* begin::Wrapper */}
|
||||
<div className='w-lg-500px p-10'>
|
||||
<Outlet />
|
||||
</div>
|
||||
{/* end::Wrapper */}
|
||||
</div>
|
||||
{/* end::Form */}
|
||||
|
||||
{/* begin::Footer */}
|
||||
<div className='d-flex flex-center flex-wrap px-5'>
|
||||
{/* begin::Links */}
|
||||
<div className='d-flex fw-semibold text-primary fs-base'>
|
||||
<a href='#' className='px-5' target='_blank'>
|
||||
Terms
|
||||
</a>
|
||||
|
||||
<a href='#' className='px-5' target='_blank'>
|
||||
Plans
|
||||
</a>
|
||||
|
||||
<a href='#' className='px-5' target='_blank'>
|
||||
Contact Us
|
||||
</a>
|
||||
</div>
|
||||
{/* end::Links */}
|
||||
</div>
|
||||
{/* end::Footer */}
|
||||
</div>
|
||||
{/* end::Body */}
|
||||
|
||||
{/* begin::Aside */}
|
||||
<div
|
||||
className='d-flex flex-lg-row-fluid w-lg-50 bgi-size-cover bgi-position-center order-1 order-lg-2'
|
||||
style={{backgroundImage: `url(${toAbsoluteUrl('/media/misc/auth-bg.png')})`}}
|
||||
>
|
||||
{/* begin::Content */}
|
||||
<div className='d-flex flex-column flex-center py-15 px-5 px-md-15 w-100'>
|
||||
{/* begin::Logo */}
|
||||
<Link to='/' className='mb-12'>
|
||||
<img alt='Logo' src={toAbsoluteUrl('/media/logos/custom-1.png')} className='h-75px' />
|
||||
</Link>
|
||||
{/* end::Logo */}
|
||||
|
||||
{/* begin::Image */}
|
||||
<img
|
||||
className='mx-auto w-275px w-md-50 w-xl-500px mb-10 mb-lg-20'
|
||||
src={toAbsoluteUrl('/media/misc/auth-screens.png')}
|
||||
alt=''
|
||||
/>
|
||||
{/* end::Image */}
|
||||
|
||||
{/* begin::Title */}
|
||||
<h1 className='text-white fs-2qx fw-bolder text-center mb-7'>
|
||||
Fast, Efficient and Productive
|
||||
</h1>
|
||||
{/* end::Title */}
|
||||
|
||||
{/* begin::Text */}
|
||||
<div className='text-white fs-base text-center'>
|
||||
In this kind of post,{' '}
|
||||
<a href='#' className='opacity-75-hover text-warning fw-bold me-1'>
|
||||
the blogger
|
||||
</a>
|
||||
introduces a person they’ve interviewed <br /> and provides some background information
|
||||
about
|
||||
<a href='#' className='opacity-75-hover text-warning fw-bold me-1'>
|
||||
the interviewee
|
||||
</a>
|
||||
and their <br /> work following this is a transcript of the interview.
|
||||
</div>
|
||||
{/* end::Text */}
|
||||
</div>
|
||||
{/* end::Content */}
|
||||
</div>
|
||||
{/* end::Aside */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {AuthLayout}
|
||||
@@ -0,0 +1,18 @@
|
||||
import {Route, Routes} from 'react-router-dom'
|
||||
import {Registration} from './components/Registration'
|
||||
import {ForgotPassword} from './components/ForgotPassword'
|
||||
import {Login} from './components/Login'
|
||||
import {AuthLayout} from './AuthLayout'
|
||||
|
||||
const AuthPage = () => (
|
||||
<Routes>
|
||||
<Route element={<AuthLayout />}>
|
||||
<Route path='login' element={<Login />} />
|
||||
<Route path='registration' element={<Registration />} />
|
||||
<Route path='forgot-password' element={<ForgotPassword />} />
|
||||
<Route index element={<Login />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
)
|
||||
|
||||
export {AuthPage}
|
||||
@@ -0,0 +1,17 @@
|
||||
import {useEffect} from 'react'
|
||||
import {Navigate, Routes} from 'react-router-dom'
|
||||
import {useAuth} from './core/Auth'
|
||||
|
||||
export function Logout() {
|
||||
const {logout} = useAuth()
|
||||
useEffect(() => {
|
||||
logout()
|
||||
document.location.reload()
|
||||
}, [logout])
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Navigate to='/auth/login' />
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import {useState} from 'react'
|
||||
import * as Yup from 'yup'
|
||||
import clsx from 'clsx'
|
||||
import {Link} from 'react-router-dom'
|
||||
import {useFormik} from 'formik'
|
||||
import {requestPassword} from '../core/_requests'
|
||||
|
||||
const initialValues = {
|
||||
email: 'admin@demo.com',
|
||||
}
|
||||
|
||||
const forgotPasswordSchema = Yup.object().shape({
|
||||
email: Yup.string()
|
||||
.email('Wrong email format')
|
||||
.min(3, 'Minimum 3 symbols')
|
||||
.max(50, 'Maximum 50 symbols')
|
||||
.required('Email is required'),
|
||||
})
|
||||
|
||||
export function ForgotPassword() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [hasErrors, setHasErrors] = useState<boolean | undefined>(undefined)
|
||||
const formik = useFormik({
|
||||
initialValues,
|
||||
validationSchema: forgotPasswordSchema,
|
||||
onSubmit: (values, {setStatus, setSubmitting}) => {
|
||||
setLoading(true)
|
||||
setHasErrors(undefined)
|
||||
setTimeout(() => {
|
||||
requestPassword(values.email)
|
||||
.then(({data: {result}}) => {
|
||||
setHasErrors(false)
|
||||
setLoading(false)
|
||||
})
|
||||
.catch(() => {
|
||||
setHasErrors(true)
|
||||
setLoading(false)
|
||||
setSubmitting(false)
|
||||
setStatus('The login detail is incorrect')
|
||||
})
|
||||
}, 1000)
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<form
|
||||
className='form w-100 fv-plugins-bootstrap5 fv-plugins-framework'
|
||||
noValidate
|
||||
id='kt_login_password_reset_form'
|
||||
onSubmit={formik.handleSubmit}
|
||||
>
|
||||
<div className='text-center mb-10'>
|
||||
{/* begin::Title */}
|
||||
<h1 className='text-dark fw-bolder mb-3'>Forgot Password ?</h1>
|
||||
{/* end::Title */}
|
||||
|
||||
{/* begin::Link */}
|
||||
<div className='text-gray-500 fw-semibold fs-6'>
|
||||
Enter your email to reset your password.
|
||||
</div>
|
||||
{/* end::Link */}
|
||||
</div>
|
||||
|
||||
{/* begin::Title */}
|
||||
{hasErrors === true && (
|
||||
<div className='mb-lg-15 alert alert-danger'>
|
||||
<div className='alert-text font-weight-bold'>
|
||||
Sorry, looks like there are some errors detected, please try again.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasErrors === false && (
|
||||
<div className='mb-10 bg-light-info p-8 rounded'>
|
||||
<div className='text-info'>Sent password reset. Please check your email</div>
|
||||
</div>
|
||||
)}
|
||||
{/* end::Title */}
|
||||
|
||||
{/* begin::Form group */}
|
||||
<div className='fv-row mb-8'>
|
||||
<label className='form-label fw-bolder text-gray-900 fs-6'>Email</label>
|
||||
<input
|
||||
type='email'
|
||||
placeholder=''
|
||||
autoComplete='off'
|
||||
{...formik.getFieldProps('email')}
|
||||
className={clsx(
|
||||
'form-control bg-transparent',
|
||||
{'is-invalid': formik.touched.email && formik.errors.email},
|
||||
{
|
||||
'is-valid': formik.touched.email && !formik.errors.email,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
{formik.touched.email && formik.errors.email && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>
|
||||
<span role='alert'>{formik.errors.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Form group */}
|
||||
|
||||
{/* begin::Form group */}
|
||||
<div className='d-flex flex-wrap justify-content-center pb-lg-0'>
|
||||
<button type='submit' id='kt_password_reset_submit' className='btn btn-primary me-4'>
|
||||
<span className='indicator-label'>Submit</span>
|
||||
{loading && (
|
||||
<span className='indicator-progress'>
|
||||
Please wait...
|
||||
<span className='spinner-border spinner-border-sm align-middle ms-2'></span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<Link to='/auth/login'>
|
||||
<button
|
||||
type='button'
|
||||
id='kt_login_password_reset_form_cancel_button'
|
||||
className='btn btn-light'
|
||||
disabled={formik.isSubmitting || !formik.isValid}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</Link>{' '}
|
||||
</div>
|
||||
{/* end::Form group */}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import {useState} from 'react'
|
||||
import * as Yup from 'yup'
|
||||
import clsx from 'clsx'
|
||||
import {Link} from 'react-router-dom'
|
||||
import {useFormik} from 'formik'
|
||||
import {getUserByToken, login} from '../core/_requests'
|
||||
import {toAbsoluteUrl} from '../../../../_metronic/helpers'
|
||||
import {useAuth} from '../core/Auth'
|
||||
|
||||
const loginSchema = Yup.object().shape({
|
||||
email: Yup.string()
|
||||
.email('Wrong email format')
|
||||
.min(3, 'Minimum 3 symbols')
|
||||
.max(50, 'Maximum 50 symbols')
|
||||
.required('Email is required'),
|
||||
password: Yup.string()
|
||||
.min(3, 'Minimum 3 symbols')
|
||||
.max(50, 'Maximum 50 symbols')
|
||||
.required('Password is required'),
|
||||
})
|
||||
|
||||
const initialValues = {
|
||||
email: 'admin@demo.com',
|
||||
password: 'demo',
|
||||
}
|
||||
|
||||
/*
|
||||
Formik+YUP+Typescript:
|
||||
https://jaredpalmer.com/formik/docs/tutorial#getfieldprops
|
||||
https://medium.com/@maurice.de.beijer/yup-validation-and-typescript-and-formik-6c342578a20e
|
||||
*/
|
||||
|
||||
export function Login() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const {saveAuth, setCurrentUser} = useAuth()
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues,
|
||||
validationSchema: loginSchema,
|
||||
onSubmit: async (values, {setStatus, setSubmitting}) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const {data: auth} = await login(values.email, values.password)
|
||||
saveAuth(auth)
|
||||
const {data: user} = await getUserByToken(auth.api_token)
|
||||
setCurrentUser(user)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
saveAuth(undefined)
|
||||
setStatus('The login details are incorrect')
|
||||
setSubmitting(false)
|
||||
setLoading(false)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<form
|
||||
className='form w-100'
|
||||
onSubmit={formik.handleSubmit}
|
||||
noValidate
|
||||
id='kt_login_signin_form'
|
||||
>
|
||||
{/* begin::Heading */}
|
||||
<div className='text-center mb-11'>
|
||||
<h1 className='text-dark fw-bolder mb-3'>Sign In</h1>
|
||||
<div className='text-gray-500 fw-semibold fs-6'>Your Social Campaigns</div>
|
||||
</div>
|
||||
{/* begin::Heading */}
|
||||
|
||||
{/* begin::Login options */}
|
||||
<div className='row g-3 mb-9'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-md-6'>
|
||||
{/* begin::Google link */}
|
||||
<a
|
||||
href='#'
|
||||
className='btn btn-flex btn-outline btn-text-gray-700 btn-active-color-primary bg-state-light flex-center text-nowrap w-100'
|
||||
>
|
||||
<img
|
||||
alt='Logo'
|
||||
src={toAbsoluteUrl('/media/svg/brand-logos/google-icon.svg')}
|
||||
className='h-15px me-3'
|
||||
/>
|
||||
Sign in with Google
|
||||
</a>
|
||||
{/* end::Google link */}
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-md-6'>
|
||||
{/* begin::Google link */}
|
||||
<a
|
||||
href='#'
|
||||
className='btn btn-flex btn-outline btn-text-gray-700 btn-active-color-primary bg-state-light flex-center text-nowrap w-100'
|
||||
>
|
||||
<img
|
||||
alt='Logo'
|
||||
src={toAbsoluteUrl('/media/svg/brand-logos/apple-black.svg')}
|
||||
className='theme-light-show h-15px me-3'
|
||||
/>
|
||||
<img
|
||||
alt='Logo'
|
||||
src={toAbsoluteUrl('/media/svg/brand-logos/apple-black-dark.svg')}
|
||||
className='theme-dark-show h-15px me-3'
|
||||
/>
|
||||
Sign in with Apple
|
||||
</a>
|
||||
{/* end::Google link */}
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Login options */}
|
||||
|
||||
{/* begin::Separator */}
|
||||
<div className='separator separator-content my-14'>
|
||||
<span className='w-125px text-gray-500 fw-semibold fs-7'>Or with email</span>
|
||||
</div>
|
||||
{/* end::Separator */}
|
||||
|
||||
{formik.status ? (
|
||||
<div className='mb-lg-15 alert alert-danger'>
|
||||
<div className='alert-text font-weight-bold'>{formik.status}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className='mb-10 bg-light-info p-8 rounded'>
|
||||
<div className='text-info'>
|
||||
Any text before login box - please configure
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* begin::Form group */}
|
||||
<div className='fv-row mb-8'>
|
||||
<label className='form-label fs-6 fw-bolder text-dark'>Email</label>
|
||||
<input
|
||||
placeholder='Email'
|
||||
{...formik.getFieldProps('email')}
|
||||
className={clsx(
|
||||
'form-control bg-transparent',
|
||||
{'is-invalid': formik.touched.email && formik.errors.email},
|
||||
{
|
||||
'is-valid': formik.touched.email && !formik.errors.email,
|
||||
}
|
||||
)}
|
||||
type='email'
|
||||
name='email'
|
||||
autoComplete='off'
|
||||
/>
|
||||
{formik.touched.email && formik.errors.email && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<span role='alert'>{formik.errors.email}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Form group */}
|
||||
|
||||
{/* begin::Form group */}
|
||||
<div className='fv-row mb-3'>
|
||||
<label className='form-label fw-bolder text-dark fs-6 mb-0'>Password</label>
|
||||
<input
|
||||
type='password'
|
||||
autoComplete='off'
|
||||
{...formik.getFieldProps('password')}
|
||||
className={clsx(
|
||||
'form-control bg-transparent',
|
||||
{
|
||||
'is-invalid': formik.touched.password && formik.errors.password,
|
||||
},
|
||||
{
|
||||
'is-valid': formik.touched.password && !formik.errors.password,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
{formik.touched.password && formik.errors.password && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>
|
||||
<span role='alert'>{formik.errors.password}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Form group */}
|
||||
|
||||
{/* begin::Wrapper */}
|
||||
<div className='d-flex flex-stack flex-wrap gap-3 fs-base fw-semibold mb-8'>
|
||||
<div />
|
||||
|
||||
{/* begin::Link */}
|
||||
<Link to='/auth/forgot-password' className='link-primary'>
|
||||
Forgot Password ?
|
||||
</Link>
|
||||
{/* end::Link */}
|
||||
</div>
|
||||
{/* end::Wrapper */}
|
||||
|
||||
{/* begin::Action */}
|
||||
<div className='d-grid mb-10'>
|
||||
<button
|
||||
type='submit'
|
||||
id='kt_sign_in_submit'
|
||||
className='btn btn-primary'
|
||||
disabled={formik.isSubmitting || !formik.isValid}
|
||||
>
|
||||
{!loading && <span className='indicator-label'>Continue</span>}
|
||||
{loading && (
|
||||
<span className='indicator-progress' style={{display: 'block'}}>
|
||||
Please wait...
|
||||
<span className='spinner-border spinner-border-sm align-middle ms-2'></span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{/* end::Action */}
|
||||
|
||||
<div className='text-gray-500 text-center fw-semibold fs-6'>
|
||||
Not a Member yet?{' '}
|
||||
<Link to='/auth/registration' className='link-primary'>
|
||||
Sign up
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
/* eslint-disable react/jsx-no-target-blank */
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import {useState, useEffect} from 'react'
|
||||
import {useFormik} from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import clsx from 'clsx'
|
||||
import {getUserByToken, register} from '../core/_requests'
|
||||
import {Link} from 'react-router-dom'
|
||||
import {toAbsoluteUrl} from '../../../../_metronic/helpers'
|
||||
import {PasswordMeterComponent} from '../../../../_metronic/assets/ts/components'
|
||||
import {useAuth} from '../core/Auth'
|
||||
|
||||
const initialValues = {
|
||||
firstname: '',
|
||||
lastname: '',
|
||||
email: '',
|
||||
password: '',
|
||||
changepassword: '',
|
||||
acceptTerms: false,
|
||||
}
|
||||
|
||||
const registrationSchema = Yup.object().shape({
|
||||
firstname: Yup.string()
|
||||
.min(3, 'Minimum 3 symbols')
|
||||
.max(50, 'Maximum 50 symbols')
|
||||
.required('First name is required'),
|
||||
email: Yup.string()
|
||||
.email('Wrong email format')
|
||||
.min(3, 'Minimum 3 symbols')
|
||||
.max(50, 'Maximum 50 symbols')
|
||||
.required('Email is required'),
|
||||
lastname: Yup.string()
|
||||
.min(3, 'Minimum 3 symbols')
|
||||
.max(50, 'Maximum 50 symbols')
|
||||
.required('Last name is required'),
|
||||
password: Yup.string()
|
||||
.min(3, 'Minimum 3 symbols')
|
||||
.max(50, 'Maximum 50 symbols')
|
||||
.required('Password is required'),
|
||||
changepassword: Yup.string()
|
||||
.required('Password confirmation is required')
|
||||
.when('password', {
|
||||
is: (val: string) => (val && val.length > 0 ? true : false),
|
||||
then: Yup.string().oneOf([Yup.ref('password')], "Password and Confirm Password didn't match"),
|
||||
}),
|
||||
acceptTerms: Yup.bool().required('You must accept the terms and conditions'),
|
||||
})
|
||||
|
||||
export function Registration() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const {saveAuth, setCurrentUser} = useAuth()
|
||||
const formik = useFormik({
|
||||
initialValues,
|
||||
validationSchema: registrationSchema,
|
||||
onSubmit: async (values, {setStatus, setSubmitting}) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const {data: auth} = await register(
|
||||
values.email,
|
||||
values.firstname,
|
||||
values.lastname,
|
||||
values.password,
|
||||
values.changepassword
|
||||
)
|
||||
saveAuth(auth)
|
||||
const {data: user} = await getUserByToken(auth.api_token)
|
||||
setCurrentUser(user)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
saveAuth(undefined)
|
||||
setStatus('The registration details is incorrect')
|
||||
setSubmitting(false)
|
||||
setLoading(false)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
PasswordMeterComponent.bootstrap()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<form
|
||||
className='form w-100 fv-plugins-bootstrap5 fv-plugins-framework'
|
||||
noValidate
|
||||
id='kt_login_signup_form'
|
||||
onSubmit={formik.handleSubmit}
|
||||
>
|
||||
{/* begin::Heading */}
|
||||
<div className='text-center mb-11'>
|
||||
{/* begin::Title */}
|
||||
<h1 className='text-dark fw-bolder mb-3'>Sign Up</h1>
|
||||
{/* end::Title */}
|
||||
|
||||
<div className='text-gray-500 fw-semibold fs-6'>Your Social Campaigns</div>
|
||||
</div>
|
||||
{/* end::Heading */}
|
||||
|
||||
{/* begin::Login options */}
|
||||
<div className='row g-3 mb-9'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-md-6'>
|
||||
{/* begin::Google link */}
|
||||
<a
|
||||
href='#'
|
||||
className='btn btn-flex btn-outline btn-text-gray-700 btn-active-color-primary bg-state-light flex-center text-nowrap w-100'
|
||||
>
|
||||
<img
|
||||
alt='Logo'
|
||||
src={toAbsoluteUrl('/media/svg/brand-logos/google-icon.svg')}
|
||||
className='h-15px me-3'
|
||||
/>
|
||||
Sign in with Google
|
||||
</a>
|
||||
{/* end::Google link */}
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-md-6'>
|
||||
{/* begin::Google link */}
|
||||
<a
|
||||
href='#'
|
||||
className='btn btn-flex btn-outline btn-text-gray-700 btn-active-color-primary bg-state-light flex-center text-nowrap w-100'
|
||||
>
|
||||
<img
|
||||
alt='Logo'
|
||||
src={toAbsoluteUrl('/media/svg/brand-logos/apple-black.svg')}
|
||||
className='theme-light-show h-15px me-3'
|
||||
/>
|
||||
<img
|
||||
alt='Logo'
|
||||
src={toAbsoluteUrl('/media/svg/brand-logos/apple-black-dark.svg')}
|
||||
className='theme-dark-show h-15px me-3'
|
||||
/>
|
||||
Sign in with Apple
|
||||
</a>
|
||||
{/* end::Google link */}
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Login options */}
|
||||
|
||||
<div className='separator separator-content my-14'>
|
||||
<span className='w-125px text-gray-500 fw-semibold fs-7'>Or with email</span>
|
||||
</div>
|
||||
|
||||
{formik.status && (
|
||||
<div className='mb-lg-15 alert alert-danger'>
|
||||
<div className='alert-text font-weight-bold'>{formik.status}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* begin::Form group Firstname */}
|
||||
<div className='fv-row mb-8'>
|
||||
<label className='form-label fw-bolder text-dark fs-6'>First name</label>
|
||||
<input
|
||||
placeholder='First name'
|
||||
type='text'
|
||||
autoComplete='off'
|
||||
{...formik.getFieldProps('firstname')}
|
||||
className={clsx(
|
||||
'form-control bg-transparent',
|
||||
{
|
||||
'is-invalid': formik.touched.firstname && formik.errors.firstname,
|
||||
},
|
||||
{
|
||||
'is-valid': formik.touched.firstname && !formik.errors.firstname,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
{formik.touched.firstname && formik.errors.firstname && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>
|
||||
<span role='alert'>{formik.errors.firstname}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Form group */}
|
||||
<div className='fv-row mb-8'>
|
||||
{/* begin::Form group Lastname */}
|
||||
<label className='form-label fw-bolder text-dark fs-6'>Last name</label>
|
||||
<input
|
||||
placeholder='Last name'
|
||||
type='text'
|
||||
autoComplete='off'
|
||||
{...formik.getFieldProps('lastname')}
|
||||
className={clsx(
|
||||
'form-control bg-transparent',
|
||||
{
|
||||
'is-invalid': formik.touched.lastname && formik.errors.lastname,
|
||||
},
|
||||
{
|
||||
'is-valid': formik.touched.lastname && !formik.errors.lastname,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
{formik.touched.lastname && formik.errors.lastname && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>
|
||||
<span role='alert'>{formik.errors.lastname}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* end::Form group */}
|
||||
</div>
|
||||
|
||||
{/* begin::Form group Email */}
|
||||
<div className='fv-row mb-8'>
|
||||
<label className='form-label fw-bolder text-dark fs-6'>Email</label>
|
||||
<input
|
||||
placeholder='Email'
|
||||
type='email'
|
||||
autoComplete='off'
|
||||
{...formik.getFieldProps('email')}
|
||||
className={clsx(
|
||||
'form-control bg-transparent',
|
||||
{'is-invalid': formik.touched.email && formik.errors.email},
|
||||
{
|
||||
'is-valid': formik.touched.email && !formik.errors.email,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
{formik.touched.email && formik.errors.email && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>
|
||||
<span role='alert'>{formik.errors.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Form group */}
|
||||
|
||||
{/* begin::Form group Password */}
|
||||
<div className='fv-row mb-8' data-kt-password-meter='true'>
|
||||
<div className='mb-1'>
|
||||
<label className='form-label fw-bolder text-dark fs-6'>Password</label>
|
||||
<div className='position-relative mb-3'>
|
||||
<input
|
||||
type='password'
|
||||
placeholder='Password'
|
||||
autoComplete='off'
|
||||
{...formik.getFieldProps('password')}
|
||||
className={clsx(
|
||||
'form-control bg-transparent',
|
||||
{
|
||||
'is-invalid': formik.touched.password && formik.errors.password,
|
||||
},
|
||||
{
|
||||
'is-valid': formik.touched.password && !formik.errors.password,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
{formik.touched.password && formik.errors.password && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>
|
||||
<span role='alert'>{formik.errors.password}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* begin::Meter */}
|
||||
<div
|
||||
className='d-flex align-items-center mb-3'
|
||||
data-kt-password-meter-control='highlight'
|
||||
>
|
||||
<div className='flex-grow-1 bg-secondary bg-active-success rounded h-5px me-2'></div>
|
||||
<div className='flex-grow-1 bg-secondary bg-active-success rounded h-5px me-2'></div>
|
||||
<div className='flex-grow-1 bg-secondary bg-active-success rounded h-5px me-2'></div>
|
||||
<div className='flex-grow-1 bg-secondary bg-active-success rounded h-5px'></div>
|
||||
</div>
|
||||
{/* end::Meter */}
|
||||
</div>
|
||||
<div className='text-muted'>
|
||||
Use 8 or more characters with a mix of letters, numbers & symbols.
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Form group */}
|
||||
|
||||
{/* begin::Form group Confirm password */}
|
||||
<div className='fv-row mb-5'>
|
||||
<label className='form-label fw-bolder text-dark fs-6'>Confirm Password</label>
|
||||
<input
|
||||
type='password'
|
||||
placeholder='Password confirmation'
|
||||
autoComplete='off'
|
||||
{...formik.getFieldProps('changepassword')}
|
||||
className={clsx(
|
||||
'form-control bg-transparent',
|
||||
{
|
||||
'is-invalid': formik.touched.changepassword && formik.errors.changepassword,
|
||||
},
|
||||
{
|
||||
'is-valid': formik.touched.changepassword && !formik.errors.changepassword,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
{formik.touched.changepassword && formik.errors.changepassword && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>
|
||||
<span role='alert'>{formik.errors.changepassword}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Form group */}
|
||||
|
||||
{/* begin::Form group */}
|
||||
<div className='fv-row mb-8'>
|
||||
<label className='form-check form-check-inline' htmlFor='kt_login_toc_agree'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
id='kt_login_toc_agree'
|
||||
{...formik.getFieldProps('acceptTerms')}
|
||||
/>
|
||||
<span>
|
||||
I Accept the{' '}
|
||||
<a
|
||||
href='https://keenthemes.com/metronic/?page=faq'
|
||||
target='_blank'
|
||||
className='ms-1 link-primary'
|
||||
>
|
||||
Terms
|
||||
</a>
|
||||
.
|
||||
</span>
|
||||
</label>
|
||||
{formik.touched.acceptTerms && formik.errors.acceptTerms && (
|
||||
<div className='fv-plugins-message-container'>
|
||||
<div className='fv-help-block'>
|
||||
<span role='alert'>{formik.errors.acceptTerms}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Form group */}
|
||||
|
||||
{/* begin::Form group */}
|
||||
<div className='text-center'>
|
||||
<button
|
||||
type='submit'
|
||||
id='kt_sign_up_submit'
|
||||
className='btn btn-lg btn-primary w-100 mb-5'
|
||||
disabled={formik.isSubmitting || !formik.isValid || !formik.values.acceptTerms}
|
||||
>
|
||||
{!loading && <span className='indicator-label'>Submit</span>}
|
||||
{loading && (
|
||||
<span className='indicator-progress' style={{display: 'block'}}>
|
||||
Please wait...{' '}
|
||||
<span className='spinner-border spinner-border-sm align-middle ms-2'></span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<Link to='/auth/login'>
|
||||
<button
|
||||
type='button'
|
||||
id='kt_login_signup_form_cancel_button'
|
||||
className='btn btn-lg btn-light-primary w-100 mb-5'
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
{/* end::Form group */}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import {
|
||||
FC,
|
||||
useState,
|
||||
useEffect,
|
||||
createContext,
|
||||
useContext,
|
||||
useRef,
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
} from 'react'
|
||||
import {LayoutSplashScreen} from '../../../../_res/layout/core'
|
||||
import {AuthModel, UserModel} from './_models'
|
||||
import * as authHelper from './AuthHelpers'
|
||||
import {getUserByToken} from './_requests'
|
||||
import {WithChildren} from '../../../../_res/helpers'
|
||||
|
||||
type AuthContextProps = {
|
||||
auth: AuthModel | undefined
|
||||
saveAuth: (auth: AuthModel | undefined) => void
|
||||
currentUser: UserModel | undefined
|
||||
setCurrentUser: Dispatch<SetStateAction<UserModel | undefined>>
|
||||
logout: () => void
|
||||
}
|
||||
|
||||
const initAuthContextPropsState = {
|
||||
auth: authHelper.getAuth(),
|
||||
saveAuth: () => {},
|
||||
currentUser: undefined,
|
||||
setCurrentUser: () => {},
|
||||
logout: () => {},
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextProps>(initAuthContextPropsState)
|
||||
|
||||
const useAuth = () => {
|
||||
return useContext(AuthContext)
|
||||
}
|
||||
|
||||
const AuthProvider: FC<WithChildren> = ({children}) => {
|
||||
const [auth, setAuth] = useState<AuthModel | undefined>(authHelper.getAuth())
|
||||
const [currentUser, setCurrentUser] = useState<UserModel | undefined>()
|
||||
const saveAuth = (auth: AuthModel | undefined) => {
|
||||
setAuth(auth)
|
||||
if (auth) {
|
||||
authHelper.setAuth(auth)
|
||||
} else {
|
||||
authHelper.removeAuth()
|
||||
}
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
saveAuth(undefined)
|
||||
setCurrentUser(undefined)
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{auth, saveAuth, currentUser, setCurrentUser, logout}}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const AuthInit: FC<WithChildren> = ({children}) => {
|
||||
const {auth, logout, setCurrentUser} = useAuth()
|
||||
const didRequest = useRef(false)
|
||||
const [showSplashScreen, setShowSplashScreen] = useState(true)
|
||||
// We should request user by authToken (IN OUR EXAMPLE IT'S API_TOKEN) before rendering the application
|
||||
useEffect(() => {
|
||||
const requestUser = async (apiToken: string) => {
|
||||
try {
|
||||
if (!didRequest.current) {
|
||||
const {data} = await getUserByToken(apiToken)
|
||||
if (data) {
|
||||
setCurrentUser(data)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
if (!didRequest.current) {
|
||||
logout()
|
||||
}
|
||||
} finally {
|
||||
setShowSplashScreen(false)
|
||||
}
|
||||
|
||||
return () => (didRequest.current = true)
|
||||
}
|
||||
|
||||
if (auth && auth.api_token) {
|
||||
requestUser(auth.api_token)
|
||||
} else {
|
||||
logout()
|
||||
setShowSplashScreen(false)
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [])
|
||||
|
||||
return showSplashScreen ? <LayoutSplashScreen /> : <>{children}</>
|
||||
}
|
||||
|
||||
export {AuthProvider, AuthInit, useAuth}
|
||||
@@ -0,0 +1,65 @@
|
||||
import {AuthModel} from './_models'
|
||||
|
||||
const AUTH_LOCAL_STORAGE_KEY = 'kt-auth-react-v'
|
||||
const getAuth = (): AuthModel | undefined => {
|
||||
if (!localStorage) {
|
||||
return
|
||||
}
|
||||
|
||||
const lsValue: string | null = localStorage.getItem(AUTH_LOCAL_STORAGE_KEY)
|
||||
if (!lsValue) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const auth: AuthModel = JSON.parse(lsValue) as AuthModel
|
||||
if (auth) {
|
||||
// You can easily check auth_token expiration also
|
||||
return auth
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('AUTH LOCAL STORAGE PARSE ERROR', error)
|
||||
}
|
||||
}
|
||||
|
||||
const setAuth = (auth: AuthModel) => {
|
||||
if (!localStorage) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const lsValue = JSON.stringify(auth)
|
||||
localStorage.setItem(AUTH_LOCAL_STORAGE_KEY, lsValue)
|
||||
} catch (error) {
|
||||
console.error('AUTH LOCAL STORAGE SAVE ERROR', error)
|
||||
}
|
||||
}
|
||||
|
||||
const removeAuth = () => {
|
||||
if (!localStorage) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
localStorage.removeItem(AUTH_LOCAL_STORAGE_KEY)
|
||||
} catch (error) {
|
||||
console.error('AUTH LOCAL STORAGE REMOVE ERROR', error)
|
||||
}
|
||||
}
|
||||
|
||||
export function setupAxios(axios: any) {
|
||||
axios.defaults.headers.Accept = 'application/json'
|
||||
axios.interceptors.request.use(
|
||||
(config: {headers: {Authorization: string}}) => {
|
||||
const auth = getAuth()
|
||||
if (auth && auth.api_token) {
|
||||
config.headers.Authorization = `Bearer ${auth.api_token}`
|
||||
}
|
||||
|
||||
return config
|
||||
},
|
||||
(err: any) => Promise.reject(err)
|
||||
)
|
||||
}
|
||||
|
||||
export {getAuth, setAuth, removeAuth, AUTH_LOCAL_STORAGE_KEY}
|
||||
@@ -0,0 +1,67 @@
|
||||
export interface AuthModel {
|
||||
api_token: string
|
||||
refreshToken?: string
|
||||
}
|
||||
|
||||
export interface UserAddressModel {
|
||||
addressLine: string
|
||||
city: string
|
||||
state: string
|
||||
postCode: string
|
||||
}
|
||||
|
||||
export interface UserCommunicationModel {
|
||||
email: boolean
|
||||
sms: boolean
|
||||
phone: boolean
|
||||
}
|
||||
|
||||
export interface UserEmailSettingsModel {
|
||||
emailNotification?: boolean
|
||||
sendCopyToPersonalEmail?: boolean
|
||||
activityRelatesEmail?: {
|
||||
youHaveNewNotifications?: boolean
|
||||
youAreSentADirectMessage?: boolean
|
||||
someoneAddsYouAsAsAConnection?: boolean
|
||||
uponNewOrder?: boolean
|
||||
newMembershipApproval?: boolean
|
||||
memberRegistration?: boolean
|
||||
}
|
||||
updatesFromKeenthemes?: {
|
||||
newsAboutKeenthemesProductsAndFeatureUpdates?: boolean
|
||||
tipsOnGettingMoreOutOfKeen?: boolean
|
||||
thingsYouMissedSindeYouLastLoggedIntoKeen?: boolean
|
||||
newsAboutStartOnPartnerProductsAndOtherServices?: boolean
|
||||
tipsOnStartBusinessProducts?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface UserSocialNetworksModel {
|
||||
linkedIn: string
|
||||
facebook: string
|
||||
twitter: string
|
||||
instagram: string
|
||||
}
|
||||
|
||||
export interface UserModel {
|
||||
id: number
|
||||
username: string
|
||||
password: string | undefined
|
||||
email: string
|
||||
first_name: string
|
||||
last_name: string
|
||||
fullname?: string
|
||||
occupation?: string
|
||||
companyName?: string
|
||||
phone?: string
|
||||
roles?: Array<number>
|
||||
pic?: string
|
||||
language?: 'en' | 'de' | 'es' | 'fr' | 'ja' | 'zh' | 'ru'
|
||||
timeZone?: string
|
||||
website?: 'https://keenthemes.com'
|
||||
emailSettings?: UserEmailSettingsModel
|
||||
auth?: AuthModel
|
||||
communication?: UserCommunicationModel
|
||||
address?: UserAddressModel
|
||||
socialNetworks?: UserSocialNetworksModel
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import axios from 'axios'
|
||||
import {AuthModel, UserModel} from './_models'
|
||||
|
||||
const API_URL = process.env.REACT_APP_API_URL
|
||||
|
||||
export const GET_USER_BY_ACCESSTOKEN_URL = `${API_URL}/verify_token`
|
||||
export const LOGIN_URL = `${API_URL}/login`
|
||||
export const REGISTER_URL = `${API_URL}/register`
|
||||
export const REQUEST_PASSWORD_URL = `${API_URL}/forgot_password`
|
||||
|
||||
// Server should return AuthModel
|
||||
export function login(email: string, password: string) {
|
||||
return axios.post<AuthModel>(LOGIN_URL, {
|
||||
email,
|
||||
password,
|
||||
})
|
||||
}
|
||||
|
||||
// Server should return AuthModel
|
||||
export function register(
|
||||
email: string,
|
||||
firstname: string,
|
||||
lastname: string,
|
||||
password: string,
|
||||
password_confirmation: string
|
||||
) {
|
||||
return axios.post(REGISTER_URL, {
|
||||
email,
|
||||
first_name: firstname,
|
||||
last_name: lastname,
|
||||
password,
|
||||
password_confirmation,
|
||||
})
|
||||
}
|
||||
|
||||
// Server should return object => { result: boolean } (Is Email in DB)
|
||||
export function requestPassword(email: string) {
|
||||
return axios.post<{result: boolean}>(REQUEST_PASSWORD_URL, {
|
||||
email,
|
||||
})
|
||||
}
|
||||
|
||||
export function getUserByToken(token: string) {
|
||||
return axios.post<UserModel>(GET_USER_BY_ACCESSTOKEN_URL, {
|
||||
api_token: token,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from './core/_models'
|
||||
export * from './core/Auth'
|
||||
export * from './core/AuthHelpers'
|
||||
export * from './AuthPage'
|
||||
export * from './Logout'
|
||||
@@ -0,0 +1,37 @@
|
||||
import {useEffect} from 'react'
|
||||
import {Outlet} from 'react-router-dom'
|
||||
import {useThemeMode} from '../../../_metronic/partials'
|
||||
import {toAbsoluteUrl} from '../../../_metronic/helpers'
|
||||
|
||||
const BODY_CLASSES = ['bgi-size-cover', 'bgi-position-center', 'bgi-no-repeat']
|
||||
const ErrorsLayout = () => {
|
||||
const {mode} = useThemeMode()
|
||||
useEffect(() => {
|
||||
BODY_CLASSES.forEach((c) => document.body.classList.add(c))
|
||||
document.body.style.backgroundImage =
|
||||
mode === 'dark'
|
||||
? `url(${toAbsoluteUrl('/media/auth/bg7-dark.jpg')})`
|
||||
: `url(${toAbsoluteUrl('/media/auth/bg7.jpg')})`
|
||||
|
||||
return () => {
|
||||
BODY_CLASSES.forEach((c) => document.body.classList.remove(c))
|
||||
document.body.style.backgroundImage = 'none'
|
||||
}
|
||||
}, [mode])
|
||||
|
||||
return (
|
||||
<div className='d-flex flex-column flex-root'>
|
||||
<div className='d-flex flex-column flex-center flex-column-fluid'>
|
||||
<div className='d-flex flex-column flex-center text-center p-10'>
|
||||
<div className='card card-flush w-lg-650px py-5'>
|
||||
<div className='card-body py-15 py-lg-20'>
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {ErrorsLayout}
|
||||
@@ -0,0 +1,17 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import {Route, Routes} from 'react-router-dom'
|
||||
import {Error500} from './components/Error500'
|
||||
import {Error404} from './components/Error404'
|
||||
import {ErrorsLayout} from './ErrorsLayout'
|
||||
|
||||
const ErrorsPage = () => (
|
||||
<Routes>
|
||||
<Route element={<ErrorsLayout />}>
|
||||
<Route path='404' element={<Error404 />} />
|
||||
<Route path='500' element={<Error500 />} />
|
||||
<Route index element={<Error404 />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
)
|
||||
|
||||
export {ErrorsPage}
|
||||
@@ -0,0 +1,42 @@
|
||||
import {FC} from 'react'
|
||||
import {Link} from 'react-router-dom'
|
||||
import {toAbsoluteUrl} from '../../../../_metronic/helpers'
|
||||
|
||||
const Error404: FC = () => {
|
||||
return (
|
||||
<>
|
||||
{/* begin::Title */}
|
||||
<h1 className='fw-bolder fs-2hx text-gray-900 mb-4'>Oops!</h1>
|
||||
{/* end::Title */}
|
||||
|
||||
{/* begin::Text */}
|
||||
<div className='fw-semibold fs-6 text-gray-500 mb-7'>We can't find that page.</div>
|
||||
{/* end::Text */}
|
||||
|
||||
{/* begin::Illustration */}
|
||||
<div className='mb-3'>
|
||||
<img
|
||||
src={toAbsoluteUrl('/media/auth/404-error.png')}
|
||||
className='mw-100 mh-300px theme-light-show'
|
||||
alt=''
|
||||
/>
|
||||
<img
|
||||
src={toAbsoluteUrl('/media/auth/404-error-dark.png')}
|
||||
className='mw-100 mh-300px theme-dark-show'
|
||||
alt=''
|
||||
/>
|
||||
</div>
|
||||
{/* end::Illustration */}
|
||||
|
||||
{/* begin::Link */}
|
||||
<div className='mb-0'>
|
||||
<Link to='/dashboard' className='btn btn-sm btn-primary'>
|
||||
Return Home
|
||||
</Link>
|
||||
</div>
|
||||
{/* end::Link */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export {Error404}
|
||||
@@ -0,0 +1,44 @@
|
||||
import {FC} from 'react'
|
||||
import {Link} from 'react-router-dom'
|
||||
import {toAbsoluteUrl} from '../../../../_metronic/helpers'
|
||||
|
||||
const Error500: FC = () => {
|
||||
return (
|
||||
<>
|
||||
{/* begin::Title */}
|
||||
<h1 className='fw-bolder fs-2qx text-gray-900 mb-4'>System Error</h1>
|
||||
{/* end::Title */}
|
||||
|
||||
{/* begin::Text */}
|
||||
<div className='fw-semibold fs-6 text-gray-500 mb-7'>
|
||||
Something went wrong! Please try again later.
|
||||
</div>
|
||||
{/* end::Text */}
|
||||
|
||||
{/* begin::Illustration */}
|
||||
<div className='mb-11'>
|
||||
<img
|
||||
src={toAbsoluteUrl('/media/auth/500-error.png')}
|
||||
className='mw-100 mh-300px theme-light-show'
|
||||
alt=''
|
||||
/>
|
||||
<img
|
||||
src={toAbsoluteUrl('/media/auth/500-error-dark.png')}
|
||||
className='mw-100 mh-300px theme-dark-show'
|
||||
alt=''
|
||||
/>
|
||||
</div>
|
||||
{/* end::Illustration */}
|
||||
|
||||
{/* begin::Link */}
|
||||
<div className='mb-0'>
|
||||
<Link to='/dashboard' className='btn btn-sm btn-primary'>
|
||||
Return Home
|
||||
</Link>
|
||||
</div>
|
||||
{/* end::Link */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export {Error500}
|
||||
@@ -0,0 +1,37 @@
|
||||
import {PageLink} from '../../../_metronic/layout/core'
|
||||
|
||||
export const profileSubmenu: Array<PageLink> = [
|
||||
{
|
||||
title: 'Overview',
|
||||
path: '/crafted/pages/profile/overview',
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
title: 'Separator',
|
||||
path: '/crafted/pages/profile/overview',
|
||||
isActive: true,
|
||||
isSeparator: true,
|
||||
},
|
||||
{
|
||||
title: 'Account',
|
||||
path: '/crafted/pages/profile/account',
|
||||
isActive: false,
|
||||
},
|
||||
{
|
||||
title: 'Account',
|
||||
path: '/crafted/pages/profile/account',
|
||||
isActive: false,
|
||||
isSeparator: true,
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
path: '/crafted/pages/profile/settings',
|
||||
isActive: false,
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
path: '/crafted/pages/profile/settings',
|
||||
isActive: false,
|
||||
isSeparator: true,
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,228 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import React from 'react'
|
||||
import {KTSVG, toAbsoluteUrl} from '../../../_metronic/helpers'
|
||||
import {Link} from 'react-router-dom'
|
||||
import {Dropdown1} from '../../../_metronic/partials'
|
||||
import {useLocation} from 'react-router-dom'
|
||||
|
||||
const ProfileHeader: React.FC = () => {
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<div className='card mb-5 mb-xl-10'>
|
||||
<div className='card-body pt-9 pb-0'>
|
||||
<div className='d-flex flex-wrap flex-sm-nowrap mb-3'>
|
||||
<div className='me-7 mb-4'>
|
||||
<div className='symbol symbol-100px symbol-lg-160px symbol-fixed position-relative'>
|
||||
<img src={toAbsoluteUrl('/media/avatars/300-1.jpg')} alt='Metornic' />
|
||||
<div className='position-absolute translate-middle bottom-0 start-100 mb-6 bg-success rounded-circle border border-4 border-white h-20px w-20px'></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex-grow-1'>
|
||||
<div className='d-flex justify-content-between align-items-start flex-wrap mb-2'>
|
||||
<div className='d-flex flex-column'>
|
||||
<div className='d-flex align-items-center mb-2'>
|
||||
<a href='#' className='text-gray-800 text-hover-primary fs-2 fw-bolder me-1'>
|
||||
Max Smith
|
||||
</a>
|
||||
<a href='#'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/general/gen026.svg'
|
||||
className='svg-icon-1 svg-icon-primary'
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-wrap fw-bold fs-6 mb-4 pe-2'>
|
||||
<a
|
||||
href='#'
|
||||
className='d-flex align-items-center text-gray-400 text-hover-primary me-5 mb-2'
|
||||
>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/communication/com006.svg'
|
||||
className='svg-icon-4 me-1'
|
||||
/>
|
||||
Developer
|
||||
</a>
|
||||
<a
|
||||
href='#'
|
||||
className='d-flex align-items-center text-gray-400 text-hover-primary me-5 mb-2'
|
||||
>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/general/gen018.svg'
|
||||
className='svg-icon-4 me-1'
|
||||
/>
|
||||
SF, Bay Area
|
||||
</a>
|
||||
<a
|
||||
href='#'
|
||||
className='d-flex align-items-center text-gray-400 text-hover-primary mb-2'
|
||||
>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/communication/com011.svg'
|
||||
className='svg-icon-4 me-1'
|
||||
/>
|
||||
max@kt.com
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex my-4'>
|
||||
<a href='#' className='btn btn-sm btn-light me-2' id='kt_user_follow_button'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/arrows/arr012.svg'
|
||||
className='svg-icon-3 d-none'
|
||||
/>
|
||||
|
||||
<span className='indicator-label'>Follow</span>
|
||||
<span className='indicator-progress'>
|
||||
Please wait...
|
||||
<span className='spinner-border spinner-border-sm align-middle ms-2'></span>
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
href='#'
|
||||
className='btn btn-sm btn-primary me-3'
|
||||
data-bs-toggle='modal'
|
||||
data-bs-target='#kt_modal_offer_a_deal'
|
||||
>
|
||||
Hire Me
|
||||
</a>
|
||||
<div className='me-0'>
|
||||
<button
|
||||
className='btn btn-sm btn-icon btn-bg-light btn-active-color-primary'
|
||||
data-kt-menu-trigger='click'
|
||||
data-kt-menu-placement='bottom-end'
|
||||
data-kt-menu-flip='top-end'
|
||||
>
|
||||
<i className='bi bi-three-dots fs-3'></i>
|
||||
</button>
|
||||
<Dropdown1 />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-wrap flex-stack'>
|
||||
<div className='d-flex flex-column flex-grow-1 pe-8'>
|
||||
<div className='d-flex flex-wrap'>
|
||||
<div className='border border-gray-300 border-dashed rounded min-w-125px py-3 px-4 me-6 mb-3'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/arrows/arr066.svg'
|
||||
className='svg-icon-3 svg-icon-success me-2'
|
||||
/>
|
||||
<div className='fs-2 fw-bolder'>4500$</div>
|
||||
</div>
|
||||
|
||||
<div className='fw-bold fs-6 text-gray-400'>Earnings</div>
|
||||
</div>
|
||||
|
||||
<div className='border border-gray-300 border-dashed rounded min-w-125px py-3 px-4 me-6 mb-3'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/arrows/arr065.svg'
|
||||
className='svg-icon-3 svg-icon-danger me-2'
|
||||
/>
|
||||
<div className='fs-2 fw-bolder'>75</div>
|
||||
</div>
|
||||
|
||||
<div className='fw-bold fs-6 text-gray-400'>Projects</div>
|
||||
</div>
|
||||
|
||||
<div className='border border-gray-300 border-dashed rounded min-w-125px py-3 px-4 me-6 mb-3'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/arrows/arr066.svg'
|
||||
className='svg-icon-3 svg-icon-success me-2'
|
||||
/>
|
||||
<div className='fs-2 fw-bolder'>60%</div>
|
||||
</div>
|
||||
|
||||
<div className='fw-bold fs-6 text-gray-400'>Success Rate</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex align-items-center w-200px w-sm-300px flex-column mt-3'>
|
||||
<div className='d-flex justify-content-between w-100 mt-auto mb-2'>
|
||||
<span className='fw-bold fs-6 text-gray-400'>Profile Compleation</span>
|
||||
<span className='fw-bolder fs-6'>50%</span>
|
||||
</div>
|
||||
<div className='h-5px mx-3 w-100 bg-light mb-3'>
|
||||
<div
|
||||
className='bg-success rounded h-5px'
|
||||
role='progressbar'
|
||||
style={{width: '50%'}}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex overflow-auto h-55px'>
|
||||
<ul className='nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bolder flex-nowrap'>
|
||||
<li className='nav-item'>
|
||||
<Link
|
||||
className={
|
||||
`nav-link text-active-primary me-6 ` +
|
||||
(location.pathname === '/crafted/pages/profile/overview' && 'active')
|
||||
}
|
||||
to='/crafted/pages/profile/overview'
|
||||
>
|
||||
Overview
|
||||
</Link>
|
||||
</li>
|
||||
<li className='nav-item'>
|
||||
<Link
|
||||
className={
|
||||
`nav-link text-active-primary me-6 ` +
|
||||
(location.pathname === '/crafted/pages/profile/projects' && 'active')
|
||||
}
|
||||
to='/crafted/pages/profile/projects'
|
||||
>
|
||||
Projects
|
||||
</Link>
|
||||
</li>
|
||||
<li className='nav-item'>
|
||||
<Link
|
||||
className={
|
||||
`nav-link text-active-primary me-6 ` +
|
||||
(location.pathname === '/crafted/pages/profile/campaigns' && 'active')
|
||||
}
|
||||
to='/crafted/pages/profile/campaigns'
|
||||
>
|
||||
Campaigns
|
||||
</Link>
|
||||
</li>
|
||||
<li className='nav-item'>
|
||||
<Link
|
||||
className={
|
||||
`nav-link text-active-primary me-6 ` +
|
||||
(location.pathname === '/crafted/pages/profile/documents' && 'active')
|
||||
}
|
||||
to='/crafted/pages/profile/documents'
|
||||
>
|
||||
Documents
|
||||
</Link>
|
||||
</li>
|
||||
<li className='nav-item'>
|
||||
<Link
|
||||
className={
|
||||
`nav-link text-active-primary me-6 ` +
|
||||
(location.pathname === '/crafted/pages/profile/connections' && 'active')
|
||||
}
|
||||
to='/crafted/pages/profile/connections'
|
||||
>
|
||||
Connections
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {ProfileHeader}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface IconUserModel {
|
||||
name: string
|
||||
avatar?: string
|
||||
color?: string
|
||||
initials?: string
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import {Navigate, Routes, Route, Outlet} from 'react-router-dom'
|
||||
import {PageLink, PageTitle} from '../../../_metronic/layout/core'
|
||||
import {Overview} from './components/Overview'
|
||||
import {Projects} from './components/Projects'
|
||||
import {Campaigns} from './components/Campaigns'
|
||||
import {Documents} from './components/Documents'
|
||||
import {Connections} from './components/Connections'
|
||||
import {ProfileHeader} from './ProfileHeader'
|
||||
|
||||
const profileBreadCrumbs: Array<PageLink> = [
|
||||
{
|
||||
title: 'Profile',
|
||||
path: '/crafted/pages/profile/overview',
|
||||
isSeparator: false,
|
||||
isActive: false,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
path: '',
|
||||
isSeparator: true,
|
||||
isActive: false,
|
||||
},
|
||||
]
|
||||
|
||||
const ProfilePage = () => (
|
||||
<Routes>
|
||||
<Route
|
||||
element={
|
||||
<>
|
||||
<ProfileHeader />
|
||||
<Outlet />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Route
|
||||
path='overview'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={profileBreadCrumbs}>Overview</PageTitle>
|
||||
<Overview />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='projects'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={profileBreadCrumbs}>Projects</PageTitle>
|
||||
<Projects />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='campaigns'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={profileBreadCrumbs}>Campaigns</PageTitle>
|
||||
<Campaigns />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='documents'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={profileBreadCrumbs}>Documents</PageTitle>
|
||||
<Documents />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='connections'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={profileBreadCrumbs}>Connections</PageTitle>
|
||||
<Connections />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route index element={<Navigate to='/crafted/pages/profile/overview' />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
)
|
||||
|
||||
export default ProfilePage
|
||||
@@ -0,0 +1,189 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import React from 'react'
|
||||
import {Card5} from '../../../../_metronic/partials/content/cards/Card5'
|
||||
|
||||
export function Campaigns() {
|
||||
return (
|
||||
<>
|
||||
<div className='d-flex flex-wrap flex-stack mb-6'>
|
||||
<h3 className='fw-bolder my-2'>
|
||||
My Campaigns
|
||||
<span className='fs-6 text-gray-400 fw-bold ms-1'>30 Days</span>
|
||||
</h3>
|
||||
|
||||
<div className='d-flex align-items-center my-2'>
|
||||
<div className='w-100px me-5'>
|
||||
<select
|
||||
name='status'
|
||||
data-control='select2'
|
||||
data-hide-search='true'
|
||||
className='form-select form-select-white form-select-sm'
|
||||
defaultValue='1'
|
||||
>
|
||||
<option value='1'>30 Days</option>
|
||||
<option value='2'>90 Days</option>
|
||||
<option value='3'>6 Months</option>
|
||||
<option value='4'>1 Year</option>
|
||||
</select>
|
||||
</div>
|
||||
<button className='btn btn-primary btn-sm' data-bs-toggle='tooltip' title='Coming soon'>
|
||||
Add Campaign
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row g-6 g-xl-9'>
|
||||
<div className='col-sm-6 col-xl-4'>
|
||||
<Card5
|
||||
image='/media/svg/brand-logos/twitch.svg'
|
||||
title='Twitch Posts'
|
||||
description='$500.00'
|
||||
status='down'
|
||||
statusValue={40.5}
|
||||
statusDesc='more impressions'
|
||||
progress={0.5}
|
||||
progressType='MRR'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-sm-6 col-xl-4'>
|
||||
<Card5
|
||||
image='/media/svg/brand-logos/twitter.svg'
|
||||
title='Twitter Followers'
|
||||
description='807k'
|
||||
status='up'
|
||||
statusValue={17.62}
|
||||
statusDesc='Followers growth'
|
||||
progress={5}
|
||||
progressType='New trials'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-sm-6 col-xl-4'>
|
||||
<Card5
|
||||
image='/media/svg/brand-logos/spotify.svg'
|
||||
title='Spotify Listeners'
|
||||
description='1,073'
|
||||
status='down'
|
||||
statusValue={10.45}
|
||||
statusDesc='Less comments than usual'
|
||||
progress={40}
|
||||
progressType='Impressions'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-sm-6 col-xl-4'>
|
||||
<Card5
|
||||
image='/media/svg/brand-logos/pinterest-p.svg'
|
||||
title='Pinterest Posts'
|
||||
description='97'
|
||||
status='up'
|
||||
statusValue={26.1}
|
||||
statusDesc='More posts'
|
||||
progress={10}
|
||||
progressType='Spend'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-sm-6 col-xl-4'>
|
||||
<Card5
|
||||
image='/media/svg/brand-logos/github.svg'
|
||||
title='Github Contributes'
|
||||
description='4,109'
|
||||
status='down'
|
||||
statusValue={32.8}
|
||||
statusDesc='Less contributions'
|
||||
progress={40}
|
||||
progressType='Dispute'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-sm-6 col-xl-4'>
|
||||
<Card5
|
||||
image='/media/svg/brand-logos/youtube-play.svg'
|
||||
title='Youtube Subscribers'
|
||||
description='354'
|
||||
status='up'
|
||||
statusValue={29.45}
|
||||
statusDesc='Subscribers growth'
|
||||
progress={40}
|
||||
progressType='Subscribers'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-sm-6 col-xl-4'>
|
||||
<Card5
|
||||
image='/media/svg/brand-logos/telegram.svg'
|
||||
title='Telegram Posts'
|
||||
description='566'
|
||||
status='up'
|
||||
statusValue={11.4}
|
||||
statusDesc='more clicks'
|
||||
progress={40}
|
||||
progressType='Profit'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-sm-6 col-xl-4'>
|
||||
<Card5
|
||||
image='/media/svg/brand-logos/reddit.svg'
|
||||
title='Reddit Awards'
|
||||
description='2.1M'
|
||||
status='up'
|
||||
statusValue={46.7}
|
||||
statusDesc='more adds'
|
||||
progress={0.0}
|
||||
progressType='Retention'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-stack flex-wrap pt-10'>
|
||||
<div className='fs-6 fw-bold text-gray-700'>Showing 1 to 10 of 50 entries</div>
|
||||
|
||||
<ul className='pagination'>
|
||||
<li className='page-item previous'>
|
||||
<a href='#' className='page-link'>
|
||||
<i className='previous'></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item active'>
|
||||
<a href='#' className='page-link'>
|
||||
1
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
2
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
3
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
4
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
5
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
6
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item next'>
|
||||
<a href='#' className='page-link'>
|
||||
<i className='next'></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import React from 'react'
|
||||
import {Card3} from '../../../../_metronic/partials/content/cards/Card3'
|
||||
|
||||
export function Connections() {
|
||||
return (
|
||||
<>
|
||||
<div className='d-flex flex-wrap flex-stack mb-6'>
|
||||
<h3 className='fw-bolder my-2'>
|
||||
My Contacts
|
||||
<span className='fs-6 text-gray-400 fw-bold ms-1'>(59)</span>
|
||||
</h3>
|
||||
|
||||
<div className='d-flex my-2'>
|
||||
<select
|
||||
name='status'
|
||||
data-control='select2'
|
||||
data-hide-search='true'
|
||||
className='form-select form-select-white form-select-sm w-125px'
|
||||
defaultValue='Online'
|
||||
>
|
||||
<option value='Online'>Online</option>
|
||||
<option value='Pending'>Pending</option>
|
||||
<option value='Declined'>Declined</option>
|
||||
<option value='Accepted'>Accepted</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row g-6 g-xl-9'>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card3
|
||||
avatar='/media/avatars/300-6.jpg'
|
||||
name='Emma Smith'
|
||||
job='Art Director'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card3
|
||||
color='danger'
|
||||
name='Melody Macy'
|
||||
job='Marketing Analytic'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
online={true}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card3
|
||||
avatar='/media/avatars/300-1.jpg'
|
||||
name='Max Smith'
|
||||
job='Software Enginer'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card3
|
||||
avatar='/media/avatars/300-5.jpg'
|
||||
name='Sean Bean'
|
||||
job='Web Developer'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card3
|
||||
avatar='/media/avatars/300-25.jpg'
|
||||
name='Brian Cox'
|
||||
job='UI/UX Designer'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card3
|
||||
color='warning'
|
||||
name='Mikaela Collins'
|
||||
job='Head Of Marketing'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
online={true}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card3
|
||||
avatar='/media/avatars/300-9.jpg'
|
||||
name='Francis Mitcham'
|
||||
job='Software Arcitect'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card3
|
||||
color='danger'
|
||||
name='Olivia Wild'
|
||||
job='System Admin'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
online={true}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card3
|
||||
color='primary'
|
||||
name='Neil Owen'
|
||||
job='Account Manager'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
online={true}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card3
|
||||
avatar='/media/avatars/300-23.jpg'
|
||||
name='Dan Wilson'
|
||||
job='Web Desinger'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card3
|
||||
color='danger'
|
||||
name='Emma Bold'
|
||||
job='Corporate Finance'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
online={true}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md-6 col-xxl-4'>
|
||||
<Card3
|
||||
avatar='/media/avatars/300-12.jpg'
|
||||
name='Ana Crown'
|
||||
job='Customer Relationship'
|
||||
avgEarnings='$14,560'
|
||||
totalEarnings='$236,400'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-stack flex-wrap pt-10'>
|
||||
<div className='fs-6 fw-bold text-gray-700'>Showing 1 to 10 of 50 entries</div>
|
||||
|
||||
<ul className='pagination'>
|
||||
<li className='page-item previous'>
|
||||
<a href='#' className='page-link'>
|
||||
<i className='previous'></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item active'>
|
||||
<a href='#' className='page-link'>
|
||||
1
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
2
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
3
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
4
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
5
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
6
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item next'>
|
||||
<a href='#' className='page-link'>
|
||||
<i className='next'></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import React from 'react'
|
||||
import {KTSVG} from '../../../../_metronic/helpers'
|
||||
import {Card4} from '../../../../_metronic/partials/content/cards/Card4'
|
||||
|
||||
export function Documents() {
|
||||
return (
|
||||
<>
|
||||
<div className='d-flex flex-wrap flex-stack mb-6'>
|
||||
<h3 className='fw-bolder my-2'>
|
||||
My Documents
|
||||
<span className='fs-6 text-gray-400 fw-bold ms-1'>100+ resources</span>
|
||||
</h3>
|
||||
|
||||
<div className='d-flex my-2'>
|
||||
<div className='d-flex align-items-center position-relative me-4'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/general/gen021.svg'
|
||||
className='svg-icon-3 position-absolute ms-3'
|
||||
/>
|
||||
<input
|
||||
type='text'
|
||||
id='kt_filter_search'
|
||||
className='form-control form-control-white form-control-sm w-150px ps-9'
|
||||
placeholder='Search'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<a href='#' className='btn btn-primary btn-sm'>
|
||||
File Manager
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row g-6 g-xl-9 mb-6 mb-xl-9'>
|
||||
<div className='col-12 col-sm-12 col-xl'>
|
||||
<Card4
|
||||
icon='/media/svg/files/folder-document.svg'
|
||||
title='Finance'
|
||||
description='7 files'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-12 col-sm-12 col-xl'>
|
||||
<Card4
|
||||
icon='/media/svg/files/folder-document.svg'
|
||||
title='Customers'
|
||||
description='3 files'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-12 col-sm-12 col-xl'>
|
||||
<Card4
|
||||
icon='/media/svg/files/folder-document.svg'
|
||||
title='CRM Project'
|
||||
description='25 files'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row g-6 g-xl-9 mb-6 mb-xl-9'>
|
||||
<div className='col-12 col-sm-12 col-xl'>
|
||||
<Card4 icon='/media/svg/files/pdf.svg' title='Project Reqs..' description='3 days ago' />
|
||||
</div>
|
||||
<div className='col-12 col-sm-12 col-xl'>
|
||||
<Card4 icon='/media/svg/files/doc.svg' title='CRM App Docs..' description='3 days ago' />
|
||||
</div>
|
||||
<div className='col-12 col-sm-12 col-xl'>
|
||||
<Card4
|
||||
icon='/media/svg/files/css.svg'
|
||||
title='User CRUD Styles'
|
||||
description='4 days ago'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-12 col-sm-12 col-xl'>
|
||||
<Card4 icon='/media/svg/files/ai.svg' title='Metronic Logo' description='5 days ago' />
|
||||
</div>
|
||||
<div className='col-12 col-sm-12 col-xl'>
|
||||
<Card4 icon='/media/svg/files/sql.svg' title='Orders backup' description='1 week ago' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row g-6 g-xl-9 mb-6 mb-xl-9'>
|
||||
<div className='col-12 col-sm-12 col-xl'>
|
||||
<Card4
|
||||
icon='/media/svg/files/xml.svg'
|
||||
title='UTAIR CRM API Co..'
|
||||
description='2 week ago'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-12 col-sm-12 col-xl'>
|
||||
<Card4
|
||||
icon='/media/svg/files/tif.svg'
|
||||
title='Tower Hill App..'
|
||||
description='3 week ago'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
FeedsWidget2,
|
||||
FeedsWidget3,
|
||||
FeedsWidget4,
|
||||
FeedsWidget5,
|
||||
FeedsWidget6,
|
||||
ChartsWidget1,
|
||||
ListsWidget5,
|
||||
ListsWidget2,
|
||||
} from '../../../../_metronic/partials/widgets'
|
||||
|
||||
export function Overview() {
|
||||
return (
|
||||
<div className='row g-5 g-xxl-8'>
|
||||
<div className='col-xl-6'>
|
||||
<FeedsWidget2 className='mb-5 mb-xxl-8' />
|
||||
|
||||
<FeedsWidget3 className='mb-5 mb-xxl-8' />
|
||||
|
||||
<FeedsWidget4 className='mb-5 mb-xxl-8' />
|
||||
|
||||
<FeedsWidget5 className='mb-5 mb-xxl-8' />
|
||||
|
||||
<FeedsWidget6 className='mb-5 mb-xxl-8' />
|
||||
</div>
|
||||
|
||||
<div className='col-xl-6'>
|
||||
<ChartsWidget1 className='mb-5 mb-xxl-8' />
|
||||
|
||||
<ListsWidget5 className='mb-5 mb-xxl-8' />
|
||||
|
||||
<ListsWidget2 className='mb-5 mb-xxl-8' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import React from 'react'
|
||||
import {Card2} from '../../../../_metronic/partials/content/cards/Card2'
|
||||
import {IconUserModel} from '../ProfileModels'
|
||||
|
||||
export function Projects() {
|
||||
return (
|
||||
<>
|
||||
<div className='d-flex flex-wrap flex-stack mb-6'>
|
||||
<h3 className='fw-bolder my-2'>
|
||||
My Projects
|
||||
<span className='fs-6 text-gray-400 fw-bold ms-1'>Active</span>
|
||||
</h3>
|
||||
|
||||
<div className='d-flex flex-wrap my-2'>
|
||||
<div className='me-4'>
|
||||
<select
|
||||
name='status'
|
||||
data-control='select2'
|
||||
data-hide-search='true'
|
||||
className='form-select form-select-sm form-select-white w-125px'
|
||||
defaultValue='Active'
|
||||
>
|
||||
<option value='Active'>Active</option>
|
||||
<option value='Approved'>In Progress</option>
|
||||
<option value='Declined'>To Do</option>
|
||||
<option value='In Progress'>Completed</option>
|
||||
</select>
|
||||
</div>
|
||||
<a
|
||||
href='#'
|
||||
className='btn btn-primary btn-sm'
|
||||
data-bs-toggle='modal'
|
||||
data-bs-target='#kt_modal_create_project'
|
||||
>
|
||||
New Project
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row g-6 g-xl-9'>
|
||||
<div className='col-md-6 col-xl-4'>
|
||||
<Card2
|
||||
icon='/media/svg/brand-logos/plurk.svg'
|
||||
badgeColor='primary'
|
||||
status='In Progress'
|
||||
statusColor='primary'
|
||||
title='Fitnes App'
|
||||
description='CRM App application to HR efficiency'
|
||||
date='November 10, 2021'
|
||||
budget='$284,900.00'
|
||||
progress={50}
|
||||
users={users1}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-md-6 col-xl-4'>
|
||||
<Card2
|
||||
icon='/media/svg/brand-logos/disqus.svg'
|
||||
badgeColor='info'
|
||||
status='Pending'
|
||||
statusColor='info'
|
||||
title='Leaf CRM'
|
||||
description='CRM App application to HR efficiency'
|
||||
date='May 10, 2021'
|
||||
budget='$36,400.00'
|
||||
progress={30}
|
||||
users={users2}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-md-6 col-xl-4'>
|
||||
<Card2
|
||||
icon='/media/svg/brand-logos/figma-1.svg'
|
||||
badgeColor='success'
|
||||
status='Completed'
|
||||
statusColor='success'
|
||||
title='Atica Banking'
|
||||
description='CRM App application to HR efficiency'
|
||||
date='Mar 14, 2021'
|
||||
budget='$605,100.00'
|
||||
progress={100}
|
||||
users={users3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-md-6 col-xl-4'>
|
||||
<Card2
|
||||
icon='/media/svg/brand-logos/sentry-3.svg'
|
||||
badgeColor='info'
|
||||
status='Pending'
|
||||
statusColor='info'
|
||||
title='Finance Dispatch'
|
||||
description='CRM App application to HR efficiency'
|
||||
date='Mar 14, 2021'
|
||||
budget='$605,100.00'
|
||||
progress={60}
|
||||
users={users4}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-md-6 col-xl-4'>
|
||||
<Card2
|
||||
icon='/media/svg/brand-logos/xing-icon.svg'
|
||||
badgeColor='primary'
|
||||
status='In Progress'
|
||||
statusColor='primary'
|
||||
title='9 Degree'
|
||||
description='CRM App application to HR efficiency'
|
||||
date='Mar 14, 2021'
|
||||
budget='$605,100.00'
|
||||
progress={40}
|
||||
users={users5}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-md-6 col-xl-4'>
|
||||
<Card2
|
||||
icon='/media/svg/brand-logos/tvit.svg'
|
||||
badgeColor='primary'
|
||||
status='In Progress'
|
||||
statusColor='primary'
|
||||
title='9 Degree'
|
||||
description='CRM App application to HR efficiency'
|
||||
date='Mar 14, 2021'
|
||||
budget='$605,100.00'
|
||||
progress={70}
|
||||
users={users6}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-md-6 col-xl-4'>
|
||||
<Card2
|
||||
icon='/media/svg/brand-logos/aven.svg'
|
||||
badgeColor='primary'
|
||||
status='In Progress'
|
||||
statusColor='primary'
|
||||
title='Buldozer CRM'
|
||||
description='CRM App application to HR efficiency'
|
||||
date='Mar 14, 2021'
|
||||
budget='$605,100.00'
|
||||
progress={70}
|
||||
users={users7}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-md-6 col-xl-4'>
|
||||
<Card2
|
||||
icon='/media/svg/brand-logos/treva.svg'
|
||||
badgeColor='danger'
|
||||
status='Overdue'
|
||||
statusColor='danger'
|
||||
title='Aviasales App'
|
||||
description='CRM App application to HR efficiency'
|
||||
date='Mar 14, 2021'
|
||||
budget='$605,100.00'
|
||||
progress={10}
|
||||
users={users8}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-md-6 col-xl-4'>
|
||||
<Card2
|
||||
icon='/media/svg/brand-logos/kanba.svg'
|
||||
badgeColor='success'
|
||||
status='Completed'
|
||||
statusColor='success'
|
||||
title='Oppo CRM'
|
||||
description='CRM App application to HR efficiency'
|
||||
date='Mar 14, 2021'
|
||||
budget='$605,100.00'
|
||||
progress={100}
|
||||
users={users9}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-stack flex-wrap pt-10'>
|
||||
<div className='fs-6 fw-bold text-gray-700'>Showing 1 to 10 of 50 entries</div>
|
||||
|
||||
<ul className='pagination'>
|
||||
<li className='page-item previous'>
|
||||
<a href='#' className='page-link'>
|
||||
<i className='previous'></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item active'>
|
||||
<a href='#' className='page-link'>
|
||||
1
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
2
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
3
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
4
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
5
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item'>
|
||||
<a href='#' className='page-link'>
|
||||
6
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className='page-item next'>
|
||||
<a href='#' className='page-link'>
|
||||
<i className='next'></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const users1: Array<IconUserModel> = [
|
||||
{name: 'Emma Smith', avatar: '/media/avatars/300-6.jpg'},
|
||||
{name: 'Rudy Stone', avatar: '/media/avatars/300-1.jpg'},
|
||||
{name: 'Susan Redwood', initials: 'S', color: 'primary'},
|
||||
]
|
||||
|
||||
const users2 = [
|
||||
{name: 'Alan Warden', initials: 'A', color: 'warning'},
|
||||
{name: 'Brian Cox', avatar: '/media/avatars/300-5.jpg'},
|
||||
]
|
||||
|
||||
const users3 = [
|
||||
{name: 'Mad Masy', avatar: '/media/avatars/300-6.jpg'},
|
||||
{name: 'Cris Willson', avatar: '/media/avatars/300-1.jpg'},
|
||||
{name: 'Mike Garcie', initials: 'M', color: 'info'},
|
||||
]
|
||||
|
||||
const users4 = [
|
||||
{name: 'Nich Warden', initials: 'N', color: 'warning'},
|
||||
{name: 'Rob Otto', initials: 'R', color: 'success'},
|
||||
]
|
||||
|
||||
const users5 = [
|
||||
{name: 'Francis Mitcham', avatar: '/media/avatars/300-20.jpg'},
|
||||
{name: 'Michelle Swanston', avatar: '/media/avatars/300-7.jpg'},
|
||||
{name: 'Susan Redwood', initials: 'S', color: 'primary'},
|
||||
]
|
||||
|
||||
const users6 = [
|
||||
{name: 'Emma Smith', avatar: '/media/avatars/300-6.jpg'},
|
||||
{name: 'Rudy Stone', avatar: '/media/avatars/300-1.jpg'},
|
||||
{name: 'Susan Redwood', initials: 'S', color: 'primary'},
|
||||
]
|
||||
|
||||
const users7 = [
|
||||
{name: 'Meloday Macy', avatar: '/media/avatars/300-2.jpg'},
|
||||
{name: 'Rabbin Watterman', initials: 'S', color: 'success'},
|
||||
]
|
||||
|
||||
const users8 = [
|
||||
{name: 'Emma Smith', avatar: '/media/avatars/300-6.jpg'},
|
||||
{name: 'Rudy Stone', avatar: '/media/avatars/300-1.jpg'},
|
||||
{name: 'Susan Redwood', initials: 'S', color: 'primary'},
|
||||
]
|
||||
|
||||
const users9 = [
|
||||
{name: 'Meloday Macy', avatar: '/media/avatars/300-2.jpg'},
|
||||
{name: 'Rabbin Watterman', initials: 'S', color: 'danger'},
|
||||
]
|
||||
@@ -0,0 +1,36 @@
|
||||
import {FC} from 'react'
|
||||
import {IconUserModel} from '../ProfileModels'
|
||||
import {toAbsoluteUrl} from '../../../../_metronic/helpers'
|
||||
import {OverlayTrigger, Tooltip} from 'react-bootstrap'
|
||||
|
||||
type Props = {
|
||||
users?: Array<IconUserModel>
|
||||
}
|
||||
|
||||
const UsersList: FC<Props> = ({users = undefined}) => {
|
||||
return (
|
||||
<>
|
||||
{users &&
|
||||
users.map((user, i) => {
|
||||
return (
|
||||
<OverlayTrigger
|
||||
key={`${i}-${user.name}`}
|
||||
placement='top'
|
||||
overlay={<Tooltip id='tooltip-user-name'>{user.name}</Tooltip>}
|
||||
>
|
||||
<div className='symbol symbol-35px symbol-circle'>
|
||||
{user.avatar && <img src={toAbsoluteUrl(user.avatar)} alt='Pic' />}
|
||||
{user.initials && (
|
||||
<span className='symbol-label bg-primary text-inverse-primary fw-bolder'>
|
||||
{user.initials}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export {UsersList}
|
||||
@@ -0,0 +1,714 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import React, {useState} from 'react'
|
||||
import {defaultAccount, IAccount} from './AccountModel'
|
||||
|
||||
export function Account() {
|
||||
const [data, setData] = useState<IAccount>(defaultAccount)
|
||||
//const [hasError, setHasError] = useState(false);
|
||||
|
||||
const updateData = (fieldsToUpdate: Partial<IAccount>) => {
|
||||
const updatedData = {...data, ...fieldsToUpdate}
|
||||
setData(updatedData)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='card'>
|
||||
{/* begin::Form */}
|
||||
<form className='form d-flex flex-center'>
|
||||
<div className='card-body mw-800px py-20'>
|
||||
{/* begin::Form row */}
|
||||
<div className='row mb-8'>
|
||||
<label className='col-lg-3 col-form-label'>Username</label>
|
||||
<div className='col-lg-9'>
|
||||
<div className='spinner spinner-sm spinner-primary spinner-right'>
|
||||
<input
|
||||
className='form-control form-control-lg form-control-solid'
|
||||
type='text'
|
||||
value={data.username}
|
||||
onChange={(e) => updateData({username: e.target.value})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Form row */}
|
||||
|
||||
{/* begin::Form row */}
|
||||
<div className='row mb-8'>
|
||||
<label className='col-lg-3 col-form-label'>Email Address</label>
|
||||
<div className='col-lg-9'>
|
||||
<div className='input-group input-group-lg input-group-solid'>
|
||||
<span className='input-group-text pe-0'>
|
||||
<i className='la la-at fs-4'></i>
|
||||
</span>
|
||||
<input
|
||||
type='text'
|
||||
className='form-control form-control-lg form-control-solid'
|
||||
placeholder='Email'
|
||||
value={data.email}
|
||||
onChange={(e) => updateData({email: e.target.value})}
|
||||
/>
|
||||
</div>
|
||||
<div className='form-text'>
|
||||
Email will not be publicly displayed.{' '}
|
||||
<a href='#' className='fw-bold'>
|
||||
Learn more
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Form row */}
|
||||
|
||||
{/* begin::Form row */}
|
||||
<div className='row mb-8'>
|
||||
<label className='col-lg-3 col-form-label'>Language</label>
|
||||
<div className='col-lg-9'>
|
||||
<select
|
||||
className='form-select form-select-lg form-select-solid'
|
||||
data-control='select2'
|
||||
data-placeholder='Select Language...'
|
||||
value={data.language}
|
||||
onChange={(e) => updateData({language: e.target.value})}
|
||||
defaultValue={data.language}
|
||||
>
|
||||
<option value='id'>Bahasa Indonesia - Indonesian</option>
|
||||
<option value='msa'>Bahasa Melayu - Malay</option>
|
||||
<option value='ca'>Català - Catalan</option>
|
||||
<option value='cs'>Čeština - Czech</option>
|
||||
<option value='da'>Dansk - Danish</option>
|
||||
<option value='de'>Deutsch - German</option>
|
||||
<option value='en'>English</option>
|
||||
<option value='en-gb'>English UK - British English</option>
|
||||
<option value='es'>Español - Spanish</option>
|
||||
<option value='eu'>Euskara - Basque (beta)</option>
|
||||
<option value='fil'>Filipino</option>
|
||||
<option value='fr'>Français - French</option>
|
||||
<option value='ga'>Gaeilge - Irish (beta)</option>
|
||||
<option value='gl'>Galego - Galician (beta)</option>
|
||||
<option value='hr'>Hrvatski - Croatian</option>
|
||||
<option value='it'>Italiano - Italian</option>
|
||||
<option value='hu'>Magyar - Hungarian</option>
|
||||
<option value='nl'>Nederlands - Dutch</option>
|
||||
<option value='no'>Norsk - Norwegian</option>
|
||||
<option value='pl'>Polski - Polish</option>
|
||||
<option value='pt'>Português - Portuguese</option>
|
||||
<option value='ro'>Română - Romanian</option>
|
||||
<option value='sk'>Slovenčina - Slovak</option>
|
||||
<option value='fi'>Suomi - Finnish</option>
|
||||
<option value='sv'>Svenska - Swedish</option>
|
||||
<option value='vi'>Tiếng Việt - Vietnamese</option>
|
||||
<option value='tr'>Türkçe - Turkish</option>
|
||||
<option value='el'>Ελληνικά - Greek</option>
|
||||
<option value='bg'>Български език - Bulgarian</option>
|
||||
<option value='ru'>Русский - Russian</option>
|
||||
<option value='sr'>Српски - Serbian</option>
|
||||
<option value='uk'>Українська мова - Ukrainian</option>
|
||||
<option value='he'>עִבְרִית - Hebrew</option>
|
||||
<option value='ur'>اردو - Urdu (beta)</option>
|
||||
<option value='ar'>العربية - Arabic</option>
|
||||
<option value='fa'>فارسی - Persian</option>
|
||||
<option value='mr'>मराठी - Marathi</option>
|
||||
<option value='hi'>हिन्दी - Hindi</option>
|
||||
<option value='bn'>বাংলা - Bangla</option>
|
||||
<option value='gu'>ગુજરાતી - Gujarati</option>
|
||||
<option value='ta'>தமிழ் - Tamil</option>
|
||||
<option value='kn'>ಕನ್ನಡ - Kannada</option>
|
||||
<option value='th'>ภาษาไทย - Thai</option>
|
||||
<option value='ko'>한국어 - Korean</option>
|
||||
<option value='ja'>日本語 - Japanese</option>
|
||||
<option value='zh-cn'>简体中文 - Simplified Chinese</option>
|
||||
<option value='zh-tw'>繁體中文 - Traditional Chinese</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Form row */}
|
||||
|
||||
{/* begin::Form row */}
|
||||
<div className='row mb-8'>
|
||||
<label className='col-lg-3 col-form-label'>Time Zone</label>
|
||||
<div className='col-lg-9'>
|
||||
<select
|
||||
className='form-select form-select-lg form-select-solid'
|
||||
data-control='select2'
|
||||
data-placeholder='Select Timezone...'
|
||||
value={data.timeZone}
|
||||
defaultValue={data.timeZone}
|
||||
onChange={(e) => updateData({timeZone: e.target.value})}
|
||||
>
|
||||
<option data-offset='-39600' value='International Date Line West'>
|
||||
(GMT-11:00) International Date Line West
|
||||
</option>
|
||||
<option data-offset='-39600' value='Midway Island'>
|
||||
(GMT-11:00) Midway Island
|
||||
</option>
|
||||
<option data-offset='-39600' value='Samoa'>
|
||||
(GMT-11:00) Samoa
|
||||
</option>
|
||||
<option data-offset='-36000' value='Hawaii'>
|
||||
(GMT-10:00) Hawaii
|
||||
</option>
|
||||
<option data-offset='-28800' value='Alaska'>
|
||||
(GMT-08:00) Alaska
|
||||
</option>
|
||||
<option data-offset='-25200' value='Pacific Time (US & Canada)'>
|
||||
(GMT-07:00) Pacific Time (US & Canada)
|
||||
</option>
|
||||
<option data-offset='-25200' value='Tijuana'>
|
||||
(GMT-07:00) Tijuana
|
||||
</option>
|
||||
<option data-offset='-25200' value='Arizona'>
|
||||
(GMT-07:00) Arizona
|
||||
</option>
|
||||
<option data-offset='-21600' value='Mountain Time (US & Canada)'>
|
||||
(GMT-06:00) Mountain Time (US & Canada)
|
||||
</option>
|
||||
<option data-offset='-21600' value='Chihuahua'>
|
||||
(GMT-06:00) Chihuahua
|
||||
</option>
|
||||
<option data-offset='-21600' value='Mazatlan'>
|
||||
(GMT-06:00) Mazatlan
|
||||
</option>
|
||||
<option data-offset='-21600' value='Saskatchewan'>
|
||||
(GMT-06:00) Saskatchewan
|
||||
</option>
|
||||
<option data-offset='-21600' value='Central America'>
|
||||
(GMT-06:00) Central America
|
||||
</option>
|
||||
<option data-offset='-18000' value='Central Time (US & Canada)'>
|
||||
(GMT-05:00) Central Time (US & Canada)
|
||||
</option>
|
||||
<option data-offset='-18000' value='Guadalajara'>
|
||||
(GMT-05:00) Guadalajara
|
||||
</option>
|
||||
<option data-offset='-18000' value='Mexico City'>
|
||||
(GMT-05:00) Mexico City
|
||||
</option>
|
||||
<option data-offset='-18000' value='Monterrey'>
|
||||
(GMT-05:00) Monterrey
|
||||
</option>
|
||||
<option data-offset='-18000' value='Bogota'>
|
||||
(GMT-05:00) Bogota
|
||||
</option>
|
||||
<option data-offset='-18000' value='Lima'>
|
||||
(GMT-05:00) Lima
|
||||
</option>
|
||||
<option data-offset='-18000' value='Quito'>
|
||||
(GMT-05:00) Quito
|
||||
</option>
|
||||
<option data-offset='-14400' value='Eastern Time (US & Canada)'>
|
||||
(GMT-04:00) Eastern Time (US & Canada)
|
||||
</option>
|
||||
<option data-offset='-14400' value='Indiana (East)'>
|
||||
(GMT-04:00) Indiana (East)
|
||||
</option>
|
||||
<option data-offset='-14400' value='Caracas'>
|
||||
(GMT-04:00) Caracas
|
||||
</option>
|
||||
<option data-offset='-14400' value='La Paz'>
|
||||
(GMT-04:00) La Paz
|
||||
</option>
|
||||
<option data-offset='-14400' value='Georgetown'>
|
||||
(GMT-04:00) Georgetown
|
||||
</option>
|
||||
<option data-offset='-10800' value='Atlantic Time (Canada)'>
|
||||
(GMT-03:00) Atlantic Time (Canada)
|
||||
</option>
|
||||
<option data-offset='-10800' value='Santiago'>
|
||||
(GMT-03:00) Santiago
|
||||
</option>
|
||||
<option data-offset='-10800' value='Brasilia'>
|
||||
(GMT-03:00) Brasilia
|
||||
</option>
|
||||
<option data-offset='-10800' value='Buenos Aires'>
|
||||
(GMT-03:00) Buenos Aires
|
||||
</option>
|
||||
<option data-offset='-9000' value='Newfoundland'>
|
||||
(GMT-02:30) Newfoundland
|
||||
</option>
|
||||
<option data-offset='-7200' value='Greenland'>
|
||||
(GMT-02:00) Greenland
|
||||
</option>
|
||||
<option data-offset='-7200' value='Mid-Atlantic'>
|
||||
(GMT-02:00) Mid-Atlantic
|
||||
</option>
|
||||
<option data-offset='-3600' value='Cape Verde Is.'>
|
||||
(GMT-01:00) Cape Verde Is.
|
||||
</option>
|
||||
<option data-offset='0' value='Azores'>
|
||||
(GMT) Azores
|
||||
</option>
|
||||
<option data-offset='0' value='Monrovia'>
|
||||
(GMT) Monrovia
|
||||
</option>
|
||||
<option data-offset='0' value='UTC'>
|
||||
(GMT) UTC
|
||||
</option>
|
||||
<option data-offset='3600' value='Dublin'>
|
||||
(GMT+01:00) Dublin
|
||||
</option>
|
||||
<option data-offset='3600' value='Edinburgh'>
|
||||
(GMT+01:00) Edinburgh
|
||||
</option>
|
||||
<option data-offset='3600' value='Lisbon'>
|
||||
(GMT+01:00) Lisbon
|
||||
</option>
|
||||
<option data-offset='3600' value='London'>
|
||||
(GMT+01:00) London
|
||||
</option>
|
||||
<option data-offset='3600' value='Casablanca'>
|
||||
(GMT+01:00) Casablanca
|
||||
</option>
|
||||
<option data-offset='3600' value='West Central Africa'>
|
||||
(GMT+01:00) West Central Africa
|
||||
</option>
|
||||
<option data-offset='7200' value='Belgrade'>
|
||||
(GMT+02:00) Belgrade
|
||||
</option>
|
||||
<option data-offset='7200' value='Bratislava'>
|
||||
(GMT+02:00) Bratislava
|
||||
</option>
|
||||
<option data-offset='7200' value='Budapest'>
|
||||
(GMT+02:00) Budapest
|
||||
</option>
|
||||
<option data-offset='7200' value='Ljubljana'>
|
||||
(GMT+02:00) Ljubljana
|
||||
</option>
|
||||
<option data-offset='7200' value='Prague'>
|
||||
(GMT+02:00) Prague
|
||||
</option>
|
||||
<option data-offset='7200' value='Sarajevo'>
|
||||
(GMT+02:00) Sarajevo
|
||||
</option>
|
||||
<option data-offset='7200' value='Skopje'>
|
||||
(GMT+02:00) Skopje
|
||||
</option>
|
||||
<option data-offset='7200' value='Warsaw'>
|
||||
(GMT+02:00) Warsaw
|
||||
</option>
|
||||
<option data-offset='7200' value='Zagreb'>
|
||||
(GMT+02:00) Zagreb
|
||||
</option>
|
||||
<option data-offset='7200' value='Brussels'>
|
||||
(GMT+02:00) Brussels
|
||||
</option>
|
||||
<option data-offset='7200' value='Copenhagen'>
|
||||
(GMT+02:00) Copenhagen
|
||||
</option>
|
||||
<option data-offset='7200' value='Madrid'>
|
||||
(GMT+02:00) Madrid
|
||||
</option>
|
||||
<option data-offset='7200' value='Paris'>
|
||||
(GMT+02:00) Paris
|
||||
</option>
|
||||
<option data-offset='7200' value='Amsterdam'>
|
||||
(GMT+02:00) Amsterdam
|
||||
</option>
|
||||
<option data-offset='7200' value='Berlin'>
|
||||
(GMT+02:00) Berlin
|
||||
</option>
|
||||
<option data-offset='7200' value='Bern'>
|
||||
(GMT+02:00) Bern
|
||||
</option>
|
||||
<option data-offset='7200' value='Rome'>
|
||||
(GMT+02:00) Rome
|
||||
</option>
|
||||
<option data-offset='7200' value='duotone'>
|
||||
(GMT+02:00) duotone
|
||||
</option>
|
||||
<option data-offset='7200' value='Vienna'>
|
||||
(GMT+02:00) Vienna
|
||||
</option>
|
||||
<option data-offset='7200' value='Cairo'>
|
||||
(GMT+02:00) Cairo
|
||||
</option>
|
||||
<option data-offset='7200' value='Harare'>
|
||||
(GMT+02:00) Harare
|
||||
</option>
|
||||
<option data-offset='7200' value='Pretoria'>
|
||||
(GMT+02:00) Pretoria
|
||||
</option>
|
||||
<option data-offset='10800' value='Bucharest'>
|
||||
(GMT+03:00) Bucharest
|
||||
</option>
|
||||
<option data-offset='10800' value='Helsinki'>
|
||||
(GMT+03:00) Helsinki
|
||||
</option>
|
||||
<option data-offset='10800' value='Kiev'>
|
||||
(GMT+03:00) Kiev
|
||||
</option>
|
||||
<option data-offset='10800' value='Kyiv'>
|
||||
(GMT+03:00) Kyiv
|
||||
</option>
|
||||
<option data-offset='10800' value='Riga'>
|
||||
(GMT+03:00) Riga
|
||||
</option>
|
||||
<option data-offset='10800' value='Sofia'>
|
||||
(GMT+03:00) Sofia
|
||||
</option>
|
||||
<option data-offset='10800' value='Tallinn'>
|
||||
(GMT+03:00) Tallinn
|
||||
</option>
|
||||
<option data-offset='10800' value='Vilnius'>
|
||||
(GMT+03:00) Vilnius
|
||||
</option>
|
||||
<option data-offset='10800' value='Athens'>
|
||||
(GMT+03:00) Athens
|
||||
</option>
|
||||
<option data-offset='10800' value='Istanbul'>
|
||||
(GMT+03:00) Istanbul
|
||||
</option>
|
||||
<option data-offset='10800' value='Minsk'>
|
||||
(GMT+03:00) Minsk
|
||||
</option>
|
||||
<option data-offset='10800' value='Jerusalem'>
|
||||
(GMT+03:00) Jerusalem
|
||||
</option>
|
||||
<option data-offset='10800' value='Moscow'>
|
||||
(GMT+03:00) Moscow
|
||||
</option>
|
||||
<option data-offset='10800' value='St. Petersburg'>
|
||||
(GMT+03:00) St. Petersburg
|
||||
</option>
|
||||
<option data-offset='10800' value='Volgograd'>
|
||||
(GMT+03:00) Volgograd
|
||||
</option>
|
||||
<option data-offset='10800' value='Kuwait'>
|
||||
(GMT+03:00) Kuwait
|
||||
</option>
|
||||
<option data-offset='10800' value='Riyadh'>
|
||||
(GMT+03:00) Riyadh
|
||||
</option>
|
||||
<option data-offset='10800' value='Nairobi'>
|
||||
(GMT+03:00) Nairobi
|
||||
</option>
|
||||
<option data-offset='10800' value='Baghdad'>
|
||||
(GMT+03:00) Baghdad
|
||||
</option>
|
||||
<option data-offset='14400' value='Abu Dhabi'>
|
||||
(GMT+04:00) Abu Dhabi
|
||||
</option>
|
||||
<option data-offset='14400' value='Muscat'>
|
||||
(GMT+04:00) Muscat
|
||||
</option>
|
||||
<option data-offset='14400' value='Baku'>
|
||||
(GMT+04:00) Baku
|
||||
</option>
|
||||
<option data-offset='14400' value='Tbilisi'>
|
||||
(GMT+04:00) Tbilisi
|
||||
</option>
|
||||
<option data-offset='14400' value='Yerevan'>
|
||||
(GMT+04:00) Yerevan
|
||||
</option>
|
||||
<option data-offset='16200' value='Tehran'>
|
||||
(GMT+04:30) Tehran
|
||||
</option>
|
||||
<option data-offset='16200' value='Kabul'>
|
||||
(GMT+04:30) Kabul
|
||||
</option>
|
||||
<option data-offset='18000' value='Ekaterinburg'>
|
||||
(GMT+05:00) Ekaterinburg
|
||||
</option>
|
||||
<option data-offset='18000' value='Islamabad'>
|
||||
(GMT+05:00) Islamabad
|
||||
</option>
|
||||
<option data-offset='18000' value='Karachi'>
|
||||
(GMT+05:00) Karachi
|
||||
</option>
|
||||
<option data-offset='18000' value='Tashkent'>
|
||||
(GMT+05:00) Tashkent
|
||||
</option>
|
||||
<option data-offset='19800' value='Chennai'>
|
||||
(GMT+05:30) Chennai
|
||||
</option>
|
||||
<option data-offset='19800' value='Kolkata'>
|
||||
(GMT+05:30) Kolkata
|
||||
</option>
|
||||
<option data-offset='19800' value='Mumbai'>
|
||||
(GMT+05:30) Mumbai
|
||||
</option>
|
||||
<option data-offset='19800' value='New Delhi'>
|
||||
(GMT+05:30) New Delhi
|
||||
</option>
|
||||
<option data-offset='19800' value='Sri Jayawardenepura'>
|
||||
(GMT+05:30) Sri Jayawardenepura
|
||||
</option>
|
||||
<option data-offset='20700' value='Kathmandu'>
|
||||
(GMT+05:45) Kathmandu
|
||||
</option>
|
||||
<option data-offset='21600' value='Astana'>
|
||||
(GMT+06:00) Astana
|
||||
</option>
|
||||
<option data-offset='21600' value='Dhaka'>
|
||||
(GMT+06:00) Dhaka
|
||||
</option>
|
||||
<option data-offset='21600' value='Almaty'>
|
||||
(GMT+06:00) Almaty
|
||||
</option>
|
||||
<option data-offset='21600' value='Urumqi'>
|
||||
(GMT+06:00) Urumqi
|
||||
</option>
|
||||
<option data-offset='23400' value='Rangoon'>
|
||||
(GMT+06:30) Rangoon
|
||||
</option>
|
||||
<option data-offset='25200' value='Novosibirsk'>
|
||||
(GMT+07:00) Novosibirsk
|
||||
</option>
|
||||
<option data-offset='25200' value='Bangkok'>
|
||||
(GMT+07:00) Bangkok
|
||||
</option>
|
||||
<option data-offset='25200' value='Hanoi'>
|
||||
(GMT+07:00) Hanoi
|
||||
</option>
|
||||
<option data-offset='25200' value='Jakarta'>
|
||||
(GMT+07:00) Jakarta
|
||||
</option>
|
||||
<option data-offset='25200' value='Krasnoyarsk'>
|
||||
(GMT+07:00) Krasnoyarsk
|
||||
</option>
|
||||
<option data-offset='28800' value='Beijing'>
|
||||
(GMT+08:00) Beijing
|
||||
</option>
|
||||
<option data-offset='28800' value='Chongqing'>
|
||||
(GMT+08:00) Chongqing
|
||||
</option>
|
||||
<option data-offset='28800' value='Hong Kong'>
|
||||
(GMT+08:00) Hong Kong
|
||||
</option>
|
||||
<option data-offset='28800' value='Kuala Lumpur'>
|
||||
(GMT+08:00) Kuala Lumpur
|
||||
</option>
|
||||
<option data-offset='28800' value='Singapore'>
|
||||
(GMT+08:00) Singapore
|
||||
</option>
|
||||
<option data-offset='28800' value='Taipei'>
|
||||
(GMT+08:00) Taipei
|
||||
</option>
|
||||
<option data-offset='28800' value='Perth'>
|
||||
(GMT+08:00) Perth
|
||||
</option>
|
||||
<option data-offset='28800' value='Irkutsk'>
|
||||
(GMT+08:00) Irkutsk
|
||||
</option>
|
||||
<option data-offset='28800' value='Ulaan Bataar'>
|
||||
(GMT+08:00) Ulaan Bataar
|
||||
</option>
|
||||
<option data-offset='32400' value='Seoul'>
|
||||
(GMT+09:00) Seoul
|
||||
</option>
|
||||
<option data-offset='32400' value='Osaka'>
|
||||
(GMT+09:00) Osaka
|
||||
</option>
|
||||
<option data-offset='32400' value='Sapporo'>
|
||||
(GMT+09:00) Sapporo
|
||||
</option>
|
||||
<option data-offset='32400' value='Tokyo'>
|
||||
(GMT+09:00) Tokyo
|
||||
</option>
|
||||
<option data-offset='32400' value='Yakutsk'>
|
||||
(GMT+09:00) Yakutsk
|
||||
</option>
|
||||
<option data-offset='34200' value='Darwin'>
|
||||
(GMT+09:30) Darwin
|
||||
</option>
|
||||
<option data-offset='34200' value='Adelaide'>
|
||||
(GMT+09:30) Adelaide
|
||||
</option>
|
||||
<option data-offset='36000' value='Canberra'>
|
||||
(GMT+10:00) Canberra
|
||||
</option>
|
||||
<option data-offset='36000' value='Melbourne'>
|
||||
(GMT+10:00) Melbourne
|
||||
</option>
|
||||
<option data-offset='36000' value='Sydney'>
|
||||
(GMT+10:00) Sydney
|
||||
</option>
|
||||
<option data-offset='36000' value='Brisbane'>
|
||||
(GMT+10:00) Brisbane
|
||||
</option>
|
||||
<option data-offset='36000' value='Hobart'>
|
||||
(GMT+10:00) Hobart
|
||||
</option>
|
||||
<option data-offset='36000' value='Vladivostok'>
|
||||
(GMT+10:00) Vladivostok
|
||||
</option>
|
||||
<option data-offset='36000' value='Guam'>
|
||||
(GMT+10:00) Guam
|
||||
</option>
|
||||
<option data-offset='36000' value='Port Moresby'>
|
||||
(GMT+10:00) Port Moresby
|
||||
</option>
|
||||
<option data-offset='36000' value='Solomon Is.'>
|
||||
(GMT+10:00) Solomon Is.
|
||||
</option>
|
||||
<option data-offset='39600' value='Magadan'>
|
||||
(GMT+11:00) Magadan
|
||||
</option>
|
||||
<option data-offset='39600' value='New Caledonia'>
|
||||
(GMT+11:00) New Caledonia
|
||||
</option>
|
||||
<option data-offset='43200' value='Fiji'>
|
||||
(GMT+12:00) Fiji
|
||||
</option>
|
||||
<option data-offset='43200' value='Kamchatka'>
|
||||
(GMT+12:00) Kamchatka
|
||||
</option>
|
||||
<option data-offset='43200' value='Marshall Is.'>
|
||||
(GMT+12:00) Marshall Is.
|
||||
</option>
|
||||
<option data-offset='43200' value='Auckland'>
|
||||
(GMT+12:00) Auckland
|
||||
</option>
|
||||
<option data-offset='43200' value='Wellington'>
|
||||
(GMT+12:00) Wellington
|
||||
</option>
|
||||
<option data-offset='46800' value="Nuku'alofa">
|
||||
(GMT+13:00) Nuku'alofa
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Form row */}
|
||||
|
||||
{/* begin::Form row */}
|
||||
<div className='row align-items-center mb-3'>
|
||||
<label className='col-lg-3 col-form-label'>Communication</label>
|
||||
<div className='col-lg-9'>
|
||||
<div className='d-flex align-items-center'>
|
||||
<div className='form-check form-check-custom form-check-solid me-5'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
id='inlineCheckbox1'
|
||||
checked={data.communications.email}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
communications: {
|
||||
...data.communications,
|
||||
email: !data.communications.email,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label fw-bold' htmlFor='inlineCheckbox1'>
|
||||
Email
|
||||
</label>
|
||||
</div>
|
||||
<div className='form-check form-check-custom form-check-solid me-5'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
id='inlineCheckbox2'
|
||||
checked={data.communications.sms}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
communications: {
|
||||
...data.communications,
|
||||
sms: !data.communications.sms,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label fw-bold' htmlFor='inlineCheckbox2'>
|
||||
SMS
|
||||
</label>
|
||||
</div>
|
||||
<div className='form-check form-check-custom form-check-solid'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
id='inlineCheckbox3'
|
||||
checked={data.communications.phone}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
communications: {
|
||||
...data.communications,
|
||||
phone: !data.communications.phone,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label fw-bold' htmlFor='inlineCheckbox3'>
|
||||
Phone
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* begin::Form row */}
|
||||
|
||||
<div className='separator separator-dashed my-10'></div>
|
||||
|
||||
{/* begin::Form row */}
|
||||
<div className='row mb-8'>
|
||||
<label className='col-lg-3 col-form-label'>Login verification</label>
|
||||
<div className='col-lg-9'>
|
||||
<button type='button' className='btn btn-light-primary fw-bold btn-sm'>
|
||||
Setup login verification
|
||||
</button>
|
||||
<div className='form-text'>
|
||||
After you log in, you will be asked for additional information to confirm your
|
||||
identity and protect your account from being compromised.
|
||||
<a href='#' className='fw-bold'>
|
||||
Learn more
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Form row */}
|
||||
|
||||
{/* begin::Form row */}
|
||||
<div className='row mb-13'>
|
||||
<label className='col-lg-3 col-form-label'>Password reset verification</label>
|
||||
<div className='col-lg-9'>
|
||||
<div className='form-check form-check-custom form-check-solid me-5'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
id='customCheck5'
|
||||
checked={data.requireInfo}
|
||||
onChange={() => updateData({requireInfo: !data.requireInfo})}
|
||||
/>
|
||||
<label className='form-check-label fw-bold' htmlFor='customCheck5'>
|
||||
Require personal information to reset your password.
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className='form-text py-2'>
|
||||
For extra security, this requires you to confirm your email or phone number when you
|
||||
reset your password.
|
||||
<a href='#' className='fw-boldk'>
|
||||
Learn more
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
|
||||
<button type='button' className='btn btn-light-danger fw-bold btn-sm'>
|
||||
Deactivate your account ?
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Form row */}
|
||||
|
||||
{/* begin::Form row */}
|
||||
<div className='row'>
|
||||
<label className='col-lg-3 col-form-label'></label>
|
||||
<div className='col-lg-9'>
|
||||
<button type='reset' className='btn btn-primary fw-bolder px-6 py-3 me-3'>
|
||||
Save Changes
|
||||
</button>
|
||||
<button
|
||||
type='reset'
|
||||
className='btn btn-color-gray-600 btn-active-light-primary fw-bolder px-6 py-3'
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Form row */}
|
||||
</div>
|
||||
</form>
|
||||
{/* end::Form */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
export interface IAccount {
|
||||
username: string
|
||||
email: string
|
||||
language: string
|
||||
timeZone: string
|
||||
communications: {
|
||||
email: boolean
|
||||
sms: boolean
|
||||
phone: boolean
|
||||
}
|
||||
requireInfo: boolean
|
||||
}
|
||||
|
||||
export const defaultAccount: IAccount = {
|
||||
username: 'max_stone',
|
||||
email: 'nick.watson@loop.com',
|
||||
language: 'en',
|
||||
timeZone: 'Alaska',
|
||||
communications: {
|
||||
email: false,
|
||||
sms: true,
|
||||
phone: false,
|
||||
},
|
||||
requireInfo: false,
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
import React, {useState} from 'react'
|
||||
import {defaultSettings, ISettings} from './SettingsModel'
|
||||
|
||||
export function Settings() {
|
||||
const [data, setData] = useState<ISettings>(defaultSettings)
|
||||
const updateData = (fieldsToUpdate: Partial<ISettings>) => {
|
||||
const updatedData = {...data, ...fieldsToUpdate}
|
||||
setData(updatedData)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='card'>
|
||||
{/* begin::Form*/}
|
||||
<form className='form d-flex flex-center'>
|
||||
<div className='card-body mw-800px py-20'>
|
||||
<div className='row'>
|
||||
<label className='col-xl-3'></label>
|
||||
<div className='col-lg-9 col-xl-6'>
|
||||
<h5 className='fw-bold mb-6'>Setup Email Notification:</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mb-5 row align-items-center mb-2'>
|
||||
<label className='col-xl-3 col-lg-3 col-form-label fw-bold text-start text-lg-end'>
|
||||
Email Notifications
|
||||
</label>
|
||||
<div className='col-lg-9 col-xl-6 d-flex align-items-center'>
|
||||
<div className='form-check form-check-custom form-check-solid form-switch'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
checked={data.setupEmailNotifications.emailNotifications}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
setupEmailNotifications: {
|
||||
...data.setupEmailNotifications,
|
||||
emailNotifications: !data.setupEmailNotifications.emailNotifications,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mb-5 row align-items-center'>
|
||||
<label className='col-xl-3 col-lg-3 col-form-label fw-bold text-start text-lg-end'>
|
||||
Send Copy To Personal Email
|
||||
</label>
|
||||
<div className='col-lg-9 col-xl-6'>
|
||||
<div className='form-check form-check-custom form-check-solid form-switch'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
checked={data.setupEmailNotifications.sendCopyToPersonalEmail}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
setupEmailNotifications: {
|
||||
...data.setupEmailNotifications,
|
||||
sendCopyToPersonalEmail:
|
||||
!data.setupEmailNotifications.sendCopyToPersonalEmail,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator my-10'></div>
|
||||
|
||||
<div className='row'>
|
||||
<label className='col-xl-3'></label>
|
||||
<div className='col-lg-9 col-xl-6'>
|
||||
<h5 className='fw-bold mb-6'>Activity Related Emails:</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mb-8 row'>
|
||||
<label className='col-xl-3 col-lg-3 col-form-label fw-bold text-start text-lg-end'>
|
||||
When To Email
|
||||
</label>
|
||||
<div className='col-lg-9 col-xl-6'>
|
||||
<div className='form-check form-check-custom form-check-solid mb-3'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
id='kt_checkbox_1'
|
||||
checked={data.activityRelatedEmail.whenToEmail.youHaveNewNotifications}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
activityRelatedEmail: {
|
||||
...data.activityRelatedEmail,
|
||||
whenToEmail: {
|
||||
...data.activityRelatedEmail.whenToEmail,
|
||||
youHaveNewNotifications:
|
||||
!data.activityRelatedEmail.whenToEmail.youHaveNewNotifications,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label fw-bold text-gray-600' htmlFor='kt_checkbox_1'>
|
||||
You have new notifications
|
||||
</label>
|
||||
</div>
|
||||
<div className='form-check form-check-custom form-check-solid mb-3'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
id='kt_checkbox_2'
|
||||
checked={data.activityRelatedEmail.whenToEmail.youAreADirectMessage}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
activityRelatedEmail: {
|
||||
...data.activityRelatedEmail,
|
||||
whenToEmail: {
|
||||
...data.activityRelatedEmail.whenToEmail,
|
||||
youAreADirectMessage:
|
||||
!data.activityRelatedEmail.whenToEmail.youAreADirectMessage,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label fw-bold text-gray-600' htmlFor='kt_checkbox_2'>
|
||||
You're sent a direct message
|
||||
</label>
|
||||
</div>
|
||||
<div className='form-check form-check-custom form-check-solid'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
id='kt_checkbox_3'
|
||||
checked={data.activityRelatedEmail.whenToEmail.someoneAddsYouAsAConnection}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
activityRelatedEmail: {
|
||||
...data.activityRelatedEmail,
|
||||
whenToEmail: {
|
||||
...data.activityRelatedEmail.whenToEmail,
|
||||
someoneAddsYouAsAConnection:
|
||||
!data.activityRelatedEmail.whenToEmail.someoneAddsYouAsAConnection,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label fw-bold text-gray-600' htmlFor='kt_checkbox_3'>
|
||||
Someone adds you as a connection
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mb-8 row'>
|
||||
<label className='col-xl-3 col-lg-3 col-form-label fw-bold text-start text-lg-end'>
|
||||
When To Escalate Emails
|
||||
</label>
|
||||
<div className='col-lg-9 col-xl-6'>
|
||||
<div className='form-check form-check-custom form-check-solid mb-3'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
id='kt_checkbox_4'
|
||||
checked={data.activityRelatedEmail.whenToEscalateEmails.uponNewOrder}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
activityRelatedEmail: {
|
||||
...data.activityRelatedEmail,
|
||||
whenToEscalateEmails: {
|
||||
...data.activityRelatedEmail.whenToEscalateEmails,
|
||||
uponNewOrder:
|
||||
!data.activityRelatedEmail.whenToEscalateEmails.uponNewOrder,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label fw-bold text-gray-600' htmlFor='kt_checkbox_4'>
|
||||
Upon new order
|
||||
</label>
|
||||
</div>
|
||||
<div className='form-check form-check-custom form-check-solid mb-3'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
id='kt_checkbox_5'
|
||||
checked={data.activityRelatedEmail.whenToEscalateEmails.newMembershipApproval}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
activityRelatedEmail: {
|
||||
...data.activityRelatedEmail,
|
||||
whenToEscalateEmails: {
|
||||
...data.activityRelatedEmail.whenToEscalateEmails,
|
||||
newMembershipApproval:
|
||||
!data.activityRelatedEmail.whenToEscalateEmails.newMembershipApproval,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label fw-bold text-gray-600' htmlFor='kt_checkbox_5'>
|
||||
New membership approval
|
||||
</label>
|
||||
</div>
|
||||
<div className='form-check form-check-custom form-check-solid'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
id='kt_checkbox_6'
|
||||
checked={data.activityRelatedEmail.whenToEscalateEmails.memberRegistration}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
activityRelatedEmail: {
|
||||
...data.activityRelatedEmail,
|
||||
whenToEscalateEmails: {
|
||||
...data.activityRelatedEmail.whenToEscalateEmails,
|
||||
memberRegistration:
|
||||
!data.activityRelatedEmail.whenToEscalateEmails.memberRegistration,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label fw-bold text-gray-600' htmlFor='kt_checkbox_6'>
|
||||
Member registration
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='separator my-10'></div>
|
||||
|
||||
<div className='row'>
|
||||
<label className='col-xl-3'></label>
|
||||
<div className='col-lg-9 col-xl-6'>
|
||||
<h5 className='fw-bold mb-6'>Updates From Keenthemes:</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mb-8 row'>
|
||||
<label className='col-xl-3 col-lg-3 col-form-label fw-bold text-start text-lg-end'>
|
||||
Email You With
|
||||
</label>
|
||||
<div className='col-lg-9 col-xl-6'>
|
||||
<div className='form-check form-check-custom form-check-solid mb-3'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
id='kt_checkbox_7'
|
||||
checked={data.updatesFromKeenthemes.newsAboutKTProducts}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
updatesFromKeenthemes: {
|
||||
...data.updatesFromKeenthemes,
|
||||
newsAboutKTProducts: !data.updatesFromKeenthemes.newsAboutKTProducts,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label fw-bold text-gray-600' htmlFor='kt_checkbox_7'>
|
||||
News about Keenthemes products and feature updates
|
||||
</label>
|
||||
</div>
|
||||
<div className='form-check form-check-custom form-check-solid mb-3'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
id='kt_checkbox_8'
|
||||
checked={data.updatesFromKeenthemes.tipsOnGettingMore}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
updatesFromKeenthemes: {
|
||||
...data.updatesFromKeenthemes,
|
||||
tipsOnGettingMore: !data.updatesFromKeenthemes.tipsOnGettingMore,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label fw-bold text-gray-600' htmlFor='kt_checkbox_8'>
|
||||
Tips on getting more out of Keen
|
||||
</label>
|
||||
</div>
|
||||
<div className='form-check form-check-custom form-check-solid mb-3'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
id='kt_checkbox_9'
|
||||
checked={data.updatesFromKeenthemes.thingsYouMissed}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
updatesFromKeenthemes: {
|
||||
...data.updatesFromKeenthemes,
|
||||
tipsOnGettingMore: !data.updatesFromKeenthemes.thingsYouMissed,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label fw-bold text-gray-600' htmlFor='kt_checkbox_9'>
|
||||
Things you missed since you last logged into Keen
|
||||
</label>
|
||||
</div>
|
||||
<div className='form-check form-check-custom form-check-solid'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
id='kt_checkbox_10'
|
||||
checked={data.updatesFromKeenthemes.newsAboutKTPartners}
|
||||
onChange={() =>
|
||||
updateData({
|
||||
updatesFromKeenthemes: {
|
||||
...data.updatesFromKeenthemes,
|
||||
newsAboutKTPartners: !data.updatesFromKeenthemes.newsAboutKTPartners,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label className='form-check-label fw-bold text-gray-600' htmlFor='kt_checkbox_10'>
|
||||
News about Keenthemes on partner products and other services
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* begin::Form Group*/}
|
||||
<div className='mb-8 row pt-10'>
|
||||
<label className='col-lg-3 col-form-label'></label>
|
||||
<div className='col-lg-9'>
|
||||
<button type='reset' className='btn btn-primary fw-bolder px-6 py-3 me-3'>
|
||||
Save Changes
|
||||
</button>
|
||||
<button
|
||||
type='reset'
|
||||
className='btn btn-color-gray-600 btn-active-light-primary fw-bolder px-6 py-3'
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Form Group*/}
|
||||
</div>
|
||||
</form>
|
||||
{/* end::Form*/}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
export interface ISettings {
|
||||
setupEmailNotifications: {
|
||||
emailNotifications: boolean
|
||||
sendCopyToPersonalEmail: boolean
|
||||
}
|
||||
activityRelatedEmail: {
|
||||
whenToEmail: {
|
||||
youHaveNewNotifications: boolean
|
||||
youAreADirectMessage: boolean
|
||||
someoneAddsYouAsAConnection: boolean
|
||||
}
|
||||
whenToEscalateEmails: {
|
||||
uponNewOrder: boolean
|
||||
newMembershipApproval: boolean
|
||||
memberRegistration: boolean
|
||||
}
|
||||
}
|
||||
updatesFromKeenthemes: {
|
||||
newsAboutKTProducts: boolean
|
||||
tipsOnGettingMore: boolean
|
||||
thingsYouMissed: boolean
|
||||
newsAboutKTPartners: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const defaultSettings: ISettings = {
|
||||
setupEmailNotifications: {
|
||||
emailNotifications: true,
|
||||
sendCopyToPersonalEmail: false,
|
||||
},
|
||||
activityRelatedEmail: {
|
||||
whenToEmail: {
|
||||
youHaveNewNotifications: true,
|
||||
youAreADirectMessage: false,
|
||||
someoneAddsYouAsAConnection: false,
|
||||
},
|
||||
whenToEscalateEmails: {
|
||||
uponNewOrder: true,
|
||||
newMembershipApproval: false,
|
||||
memberRegistration: false,
|
||||
},
|
||||
},
|
||||
updatesFromKeenthemes: {
|
||||
newsAboutKTProducts: false,
|
||||
tipsOnGettingMore: false,
|
||||
thingsYouMissed: false,
|
||||
newsAboutKTPartners: false,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import {Navigate, Route, Routes, Outlet} from 'react-router-dom'
|
||||
import {PageLink, PageTitle} from '../../../_metronic/layout/core'
|
||||
import {Charts} from './components/Charts'
|
||||
import {Feeds} from './components/Feeds'
|
||||
import {Lists} from './components/Lists'
|
||||
import {Tables} from './components/Tables'
|
||||
import {Mixed} from './components/Mixed'
|
||||
import {Statistics} from './components/Statistics'
|
||||
|
||||
const widgetsBreadCrumbs: Array<PageLink> = [
|
||||
{
|
||||
title: 'Widgets',
|
||||
path: '/crafted/widgets/charts',
|
||||
isSeparator: false,
|
||||
isActive: false,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
path: '',
|
||||
isSeparator: true,
|
||||
isActive: false,
|
||||
},
|
||||
]
|
||||
|
||||
const WidgetsPage = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route element={<Outlet />}>
|
||||
<Route
|
||||
path='charts'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={widgetsBreadCrumbs}>Charts</PageTitle>
|
||||
<Charts />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='feeds'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={widgetsBreadCrumbs}>Feeds</PageTitle>
|
||||
<Feeds />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='lists'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={widgetsBreadCrumbs}>Lists</PageTitle>
|
||||
<Lists />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='mixed'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={widgetsBreadCrumbs}>Mixed</PageTitle>
|
||||
<Mixed />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='tables'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={widgetsBreadCrumbs}>Tables</PageTitle>
|
||||
<Tables />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='statistics'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={widgetsBreadCrumbs}>Statiscics</PageTitle>
|
||||
<Statistics />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route index element={<Navigate to='/crafted/widgets/lists' />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
||||
export default WidgetsPage
|
||||
@@ -0,0 +1,63 @@
|
||||
import React, {FC} from 'react'
|
||||
import {
|
||||
ChartsWidget1,
|
||||
ChartsWidget2,
|
||||
ChartsWidget3,
|
||||
ChartsWidget4,
|
||||
ChartsWidget5,
|
||||
ChartsWidget6,
|
||||
ChartsWidget7,
|
||||
ChartsWidget8,
|
||||
} from '../../../../_metronic/partials/widgets'
|
||||
|
||||
const Charts: FC = () => {
|
||||
return (
|
||||
<>
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
<div className='col-xl-6'>
|
||||
<ChartsWidget1 className='card-xl-stretch mb-xl-8' />
|
||||
</div>
|
||||
<div className='col-xl-6'>
|
||||
<ChartsWidget2 className='card-xl-stretch mb-5 mb-xl-8' />
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
<div className='col-xl-6'>
|
||||
<ChartsWidget3 className='card-xl-stretch mb-xl-8' />
|
||||
</div>
|
||||
<div className='col-xl-6'>
|
||||
<ChartsWidget4 className='card-xl-stretch mb-5 mb-xl-8' />
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
<div className='col-xl-6'>
|
||||
<ChartsWidget5 className='card-xl-stretch mb-xl-8' />
|
||||
</div>
|
||||
<div className='col-xl-6'>
|
||||
<ChartsWidget6 className='card-xl-stretch mb-5 mb-xl-8' />
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
<div className='col-xl-6'>
|
||||
<ChartsWidget7 className='card-xl-stretch mb-xl-8' />
|
||||
</div>
|
||||
<div className='col-xl-6'>
|
||||
<ChartsWidget8 className='card-xl-stretch mb-5 mb-xl-8' />
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export {Charts}
|
||||
@@ -0,0 +1,34 @@
|
||||
import React, {FC} from 'react'
|
||||
import {
|
||||
FeedsWidget2,
|
||||
FeedsWidget3,
|
||||
FeedsWidget4,
|
||||
FeedsWidget5,
|
||||
FeedsWidget6,
|
||||
} from '../../../../_metronic/partials/widgets'
|
||||
|
||||
const Feeds: FC = () => {
|
||||
return (
|
||||
<div className='row g-5 g-xl-8'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-6'>
|
||||
<FeedsWidget2 className='mb-5 mb-xl-8' />
|
||||
|
||||
<FeedsWidget3 className='mb-5 mb-xl-8' />
|
||||
|
||||
<FeedsWidget4 className='mb-5 mb-xl-8' />
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-6'>
|
||||
<FeedsWidget5 className='mb-5 mb-xl-8' />
|
||||
|
||||
<FeedsWidget6 className='mb-5 mb-xl-8' />
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {Feeds}
|
||||
@@ -0,0 +1,58 @@
|
||||
import React, {FC} from 'react'
|
||||
import {
|
||||
ListsWidget1,
|
||||
ListsWidget2,
|
||||
ListsWidget3,
|
||||
ListsWidget4,
|
||||
ListsWidget5,
|
||||
ListsWidget6,
|
||||
ListsWidget7,
|
||||
ListsWidget8,
|
||||
} from '../../../../_metronic/partials/widgets'
|
||||
|
||||
const Lists: FC = () => {
|
||||
return (
|
||||
<>
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
<div className='col-xl-4'>
|
||||
<ListsWidget1 className='card-xl-stretch mb-xl-8' />
|
||||
</div>
|
||||
<div className='col-xl-4'>
|
||||
<ListsWidget2 className='card-xl-stretch mb-xl-8' />
|
||||
</div>
|
||||
<div className='col-xl-4'>
|
||||
<ListsWidget3 className='card-xl-stretch mb-5 mb-xl-8' />
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
<div className='col-xl-4'>
|
||||
<ListsWidget4 className='card-xl-stretch mb-xl-8' />
|
||||
</div>
|
||||
<div className='col-xl-4'>
|
||||
<ListsWidget5 className='card-xl-stretch mb-xl-8' />
|
||||
</div>
|
||||
<div className='col-xl-4'>
|
||||
<ListsWidget6 className='card-xl-stretch mb-5 mb-xl-8' />
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
<div className='col-xl-6'>
|
||||
<ListsWidget7 className='card-xl-stretch mb-xl-8' />
|
||||
</div>
|
||||
<div className='col-xl-6'>
|
||||
<ListsWidget8 className='card-xl-stretch mb-5 mb-xl-8' />
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export {Lists}
|
||||
@@ -0,0 +1,399 @@
|
||||
import React, {FC} from 'react'
|
||||
import {
|
||||
MixedWidget1,
|
||||
MixedWidget2,
|
||||
MixedWidget3,
|
||||
MixedWidget4,
|
||||
MixedWidget5,
|
||||
MixedWidget6,
|
||||
MixedWidget7,
|
||||
MixedWidget8,
|
||||
MixedWidget9,
|
||||
MixedWidget10,
|
||||
MixedWidget11,
|
||||
} from '../../../../_metronic/partials/widgets'
|
||||
|
||||
const Mixed: FC = () => {
|
||||
return (
|
||||
<>
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget1 className='card-xl-stretch mb-xl-8' color='primary' />
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget1 className='card-xl-stretch mb-xl-8' color='danger' />
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget1 className='card-xl-stretch mb-5 mb-xl-8' color='success' />
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget2
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='info'
|
||||
chartHeight='200px'
|
||||
strokeColor='#4e12c4'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget2
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='danger'
|
||||
chartHeight='200px'
|
||||
strokeColor='#cb1e46'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget2
|
||||
className='card-xl-stretch mb-5 mb-xl-8'
|
||||
chartColor='primary'
|
||||
chartHeight='200px'
|
||||
strokeColor='#0078d0'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget3 className='card-xl-stretch mb-xl-8' chartColor='info' chartHeight='250px' />
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget3
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='danger'
|
||||
chartHeight='250px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget3
|
||||
className='card-xl-stretch mb-5 mb-xl-8'
|
||||
chartColor='primary'
|
||||
chartHeight='250px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget4
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
image='/media/svg/brand-logos/plurk.svg'
|
||||
color='danger'
|
||||
title='Monthly Subscription'
|
||||
date='Due: 27 Apr 2020'
|
||||
progress='75%'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget4
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
image='/media/svg/brand-logos/vimeo.svg'
|
||||
color='primary'
|
||||
title='Monthly Subscription'
|
||||
date='Due: 27 Apr 2020'
|
||||
progress='75%'
|
||||
/>
|
||||
{/*))?>*/}
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget4
|
||||
className='card-xl-stretch mb-5 mb-xl-8'
|
||||
image='/media/svg/brand-logos/kickstarter.svg'
|
||||
color='success'
|
||||
title='Monthly Subscription'
|
||||
date='Due: 27 Apr 2020'
|
||||
progress='75%'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget5
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
image='/media/svg/brand-logos/plurk.svg'
|
||||
time='7 hours ago'
|
||||
title='PitStop - Multiple Email Generator'
|
||||
description='Pitstop creates quick email campaigns.<br/>We help to strengthen your brand<br/>for your every purpose.'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget5
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
image='/media/svg/brand-logos/telegram.svg'
|
||||
time='10 days ago'
|
||||
title='ReactJS Admin Theme'
|
||||
description='Keenthemes uses the latest and greatest frameworks<br/>with ReactJS for complete modernization and<br/>future.'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget5
|
||||
className='card-xl-stretch mb-5 mb-xl-8'
|
||||
image='/media/svg/brand-logos/vimeo.svg'
|
||||
time='2 weeks ago'
|
||||
title='KT.com - High Quality Templates'
|
||||
description='Easy to use, incredibly flexible and secure<br/>with in-depth documentation that outlines<br/>everything for you'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget6
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='primary'
|
||||
chartHeight='150px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget6
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='danger'
|
||||
chartHeight='150px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget6
|
||||
className='card-xl-stretch mb-5 mb-xl-8'
|
||||
chartColor='success'
|
||||
chartHeight='150px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget7
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='primary'
|
||||
chartHeight='200px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget7
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='success'
|
||||
chartHeight='200px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget7
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='danger'
|
||||
chartHeight='200px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget8
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='primary'
|
||||
chartHeight='150px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget8
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='success'
|
||||
chartHeight='150px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget8
|
||||
className='card-xl-stretch mb-5 mb-xl-8'
|
||||
chartColor='danger'
|
||||
chartHeight='150px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget9
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='primary'
|
||||
chartHeight='150px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget9
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='success'
|
||||
chartHeight='150px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget9
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='danger'
|
||||
chartHeight='150px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget10
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='info'
|
||||
chartHeight='150px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget10
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='warning'
|
||||
chartHeight='150px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget10
|
||||
className='card-xl-stretch mb-5 mb-xl-8'
|
||||
chartColor='primary'
|
||||
chartHeight='150px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget11
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='info'
|
||||
chartHeight='200px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget11
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='warning'
|
||||
chartHeight='200px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-4'>
|
||||
<MixedWidget11
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
chartColor='primary'
|
||||
chartHeight='200px'
|
||||
/>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export {Mixed}
|
||||
@@ -0,0 +1,269 @@
|
||||
import React, {FC} from 'react'
|
||||
import {
|
||||
StatisticsWidget1,
|
||||
StatisticsWidget2,
|
||||
StatisticsWidget3,
|
||||
StatisticsWidget4,
|
||||
StatisticsWidget5,
|
||||
StatisticsWidget6,
|
||||
} from '../../../../_metronic/partials/widgets'
|
||||
|
||||
const Statistics: FC = () => {
|
||||
return (
|
||||
<>
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget1
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
image='abstract-4.svg'
|
||||
title='Meeting Schedule'
|
||||
time='3:30PM - 4:20PM'
|
||||
description='Create a headline that is informative<br/>and will capture readers'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget1
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
image='abstract-2.svg'
|
||||
title='Meeting Schedule'
|
||||
time='03 May 2020'
|
||||
description='Great blog posts don’t just happen Even the best bloggers need it'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget1
|
||||
className='card-xl-stretch mb-5 mb-xl-8'
|
||||
image='abstract-1.svg'
|
||||
title='UI Conference'
|
||||
time='10AM Jan, 2021'
|
||||
description='AirWays - A Front-end solution for airlines build with ReactJS'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget2
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
avatar='/media/svg/avatars/029-boy-11.svg'
|
||||
title='Arthur Goldstain'
|
||||
description='System & Software Architect'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget2
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
avatar='/media/svg/avatars/014-girl-7.svg'
|
||||
title='Lisa Bold'
|
||||
description='Marketing & Fanance Manager'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget2
|
||||
className='card-xl-stretch mb-5 mb-xl-8'
|
||||
avatar='/media/svg/avatars/004-boy-1.svg'
|
||||
title='Nick Stone'
|
||||
description='Customer Support Team'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget3
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
color='success'
|
||||
title='Weekly Sales'
|
||||
description='Your Weekly Sales Chart'
|
||||
change='+100'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget3
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
color='danger'
|
||||
title='Authors Progress'
|
||||
description='Marketplace Authors Chart'
|
||||
change='-260'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget3
|
||||
className='card-xl-stretch mb-5 mb-xl-8'
|
||||
color='primary'
|
||||
title='Sales Progress'
|
||||
description='Marketplace Sales Chart'
|
||||
change='+180'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget4
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
svgIcon='/media/icons/duotune/ecommerce/ecm002.svg'
|
||||
color='info'
|
||||
description='Sales Change'
|
||||
change='+256'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget4
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
svgIcon='/media/icons/duotune/general/gen025.svg'
|
||||
color='success'
|
||||
description='Weekly Income'
|
||||
change='750$'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget4
|
||||
className='card-xl-stretch mb-5 mb-xl-8'
|
||||
svgIcon='/media/icons/duotune/finance/fin006.svg'
|
||||
color='primary'
|
||||
description='New Users'
|
||||
change='+6.6K'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget5
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
svgIcon='/media/icons/duotune/ecommerce/ecm002.svg'
|
||||
color='danger'
|
||||
iconColor='white'
|
||||
title='Shopping Cart'
|
||||
description='Lands, Houses, Ranchos, Farms'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget5
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
svgIcon='/media/icons/duotune/ecommerce/ecm008.svg'
|
||||
color='primary'
|
||||
iconColor='white'
|
||||
title='Appartments'
|
||||
description='Flats, Shared Rooms, Duplex'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget5
|
||||
className='card-xl-stretch mb-5 mb-xl-8'
|
||||
svgIcon='/media/icons/duotune/graphs/gra005.svg'
|
||||
color='success'
|
||||
iconColor='white'
|
||||
title='Sales Stats'
|
||||
description='50% Increased for FY20'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
<div className='col-xl-3'>
|
||||
<StatisticsWidget5
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
svgIcon='/media/icons/duotune/general/gen032.svg'
|
||||
color='white'
|
||||
iconColor='primary'
|
||||
title='500M$'
|
||||
description='SAP UI Progress'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-xl-3'>
|
||||
<StatisticsWidget5
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
svgIcon='/media/icons/duotune/ecommerce/ecm008.svg'
|
||||
color='dark'
|
||||
iconColor='white'
|
||||
title='+3000'
|
||||
description='New Customers'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-xl-3'>
|
||||
<StatisticsWidget5
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
svgIcon='/media/icons/duotune/finance/fin006.svg'
|
||||
color='warning'
|
||||
iconColor='white'
|
||||
title='$50,000'
|
||||
description='Milestone Reached'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-xl-3'>
|
||||
<StatisticsWidget5
|
||||
className='card-xl-stretch mb-5 mb-xl-8'
|
||||
svgIcon='/media/icons/duotune/graphs/gra007.svg'
|
||||
color='info'
|
||||
iconColor='white'
|
||||
title='$50,000'
|
||||
description='Milestone Reached'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget6
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
color='success'
|
||||
title='Avarage'
|
||||
description='Project Progress'
|
||||
progress='50%'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget6
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
color='warning'
|
||||
title='48k Goal'
|
||||
description='Company Finance'
|
||||
progress='15%'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='col-xl-4'>
|
||||
<StatisticsWidget6
|
||||
className='card-xl-stretch mb-xl-8'
|
||||
color='primary'
|
||||
title='400k Impressions'
|
||||
description='Marketing Analysis'
|
||||
progress='76%'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export {Statistics}
|
||||
@@ -0,0 +1,98 @@
|
||||
import React, {FC} from 'react'
|
||||
import {
|
||||
TablesWidget1,
|
||||
TablesWidget2,
|
||||
TablesWidget3,
|
||||
TablesWidget4,
|
||||
TablesWidget5,
|
||||
TablesWidget6,
|
||||
TablesWidget7,
|
||||
TablesWidget8,
|
||||
TablesWidget9,
|
||||
TablesWidget10,
|
||||
TablesWidget11,
|
||||
TablesWidget12,
|
||||
TablesWidget13,
|
||||
} from '../../../../_metronic/partials/widgets'
|
||||
|
||||
const Tables: FC = () => {
|
||||
return (
|
||||
<>
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-6'>
|
||||
<TablesWidget1 className='card-xl-stretch mb-xl-8'></TablesWidget1>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-6'>
|
||||
<TablesWidget2 className='card-xl-stretch mb-5 mb-xl-8' />
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-6'>
|
||||
<TablesWidget3 className='card-xl-stretch mb-xl-8' />
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-6'>
|
||||
<TablesWidget4 className='card-xl-stretch mb-5 mb-xl-8' />
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-6'>
|
||||
<TablesWidget5 className='card-xl-stretch mb-xl-8' />
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-6'>
|
||||
<TablesWidget6 className='card-xl-stretch mb-5 mb-xl-8' />
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
{/* begin::Row */}
|
||||
<div className='row g-5 g-xl-8'>
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-6'>
|
||||
<TablesWidget7 className='card-xl-stretch mb-xl-8' />
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className='col-xl-6'>
|
||||
<TablesWidget8 className='card-xl-stretch mb-5 mb-xl-8' />
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
|
||||
<TablesWidget9 className='mb-5 mb-xl-8' />
|
||||
|
||||
<TablesWidget10 className='mb-5 mb-xl-8' />
|
||||
|
||||
<TablesWidget11 className='mb-5 mb-xl-8' />
|
||||
|
||||
<TablesWidget12 className='mb-5 mb-xl-8' />
|
||||
|
||||
<TablesWidget13 className='mb-5 mb-xl-8' />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export {Tables}
|
||||
@@ -0,0 +1,47 @@
|
||||
import {Route, Routes, Outlet, Navigate} from 'react-router-dom'
|
||||
import {PageLink, PageTitle} from '../../../_metronic/layout/core'
|
||||
import {Vertical} from './components/Vertical'
|
||||
import {Horizontal} from './components/Horizontal'
|
||||
|
||||
const wizardsBreadCrumbs: Array<PageLink> = [
|
||||
{
|
||||
title: 'Wizards',
|
||||
path: '/crafted/pages/wizards/horizontal',
|
||||
isSeparator: false,
|
||||
isActive: false,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
path: '',
|
||||
isSeparator: true,
|
||||
isActive: false,
|
||||
},
|
||||
]
|
||||
|
||||
const WizardsPage = () => (
|
||||
<Routes>
|
||||
<Route element={<Outlet />}>
|
||||
<Route
|
||||
path='horizontal'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={wizardsBreadCrumbs}>Horizontal</PageTitle>
|
||||
<Horizontal />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='vertical'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={wizardsBreadCrumbs}>Vertical</PageTitle>
|
||||
<Vertical />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route index element={<Navigate to='/crafted/pages/wizards/horizontal' />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
)
|
||||
|
||||
export default WizardsPage
|
||||
@@ -0,0 +1,61 @@
|
||||
import * as Yup from 'yup'
|
||||
|
||||
export interface ICreateAccount {
|
||||
accountType: string
|
||||
accountTeamSize: string
|
||||
accountName: string
|
||||
accountPlan: string
|
||||
businessName: string
|
||||
businessDescriptor: string
|
||||
businessType: string
|
||||
businessDescription: string
|
||||
businessEmail: string
|
||||
nameOnCard: string
|
||||
cardNumber: string
|
||||
cardExpiryMonth: string
|
||||
cardExpiryYear: string
|
||||
cardCvv: string
|
||||
saveCard: string
|
||||
}
|
||||
|
||||
const createAccountSchemas = [
|
||||
Yup.object({
|
||||
accountType: Yup.string().required().label('Account Type'),
|
||||
}),
|
||||
Yup.object({
|
||||
accountName: Yup.string().required().label('Account Name'),
|
||||
}),
|
||||
Yup.object({
|
||||
businessName: Yup.string().required().label('Business Name'),
|
||||
businessDescriptor: Yup.string().required().label('Shortened Descriptor'),
|
||||
businessType: Yup.string().required().label('Corporation Type'),
|
||||
businessEmail: Yup.string().required().label('Contact Email'),
|
||||
}),
|
||||
Yup.object({
|
||||
nameOnCard: Yup.string().required().label('Name On Card'),
|
||||
cardNumber: Yup.string().required().label('Card Number'),
|
||||
cardExpiryMonth: Yup.string().required().label('Expiration Month'),
|
||||
cardExpiryYear: Yup.string().required().label('Expiration Year'),
|
||||
cardCvv: Yup.string().required().label('CVV'),
|
||||
}),
|
||||
]
|
||||
|
||||
const inits: ICreateAccount = {
|
||||
accountType: 'personal',
|
||||
accountTeamSize: '50+',
|
||||
accountName: '',
|
||||
accountPlan: '1',
|
||||
businessName: 'Keenthemes Inc.',
|
||||
businessDescriptor: 'KEENTHEMES',
|
||||
businessType: '1',
|
||||
businessDescription: '',
|
||||
businessEmail: 'corp@support.com',
|
||||
nameOnCard: 'Max Doe',
|
||||
cardNumber: '4111 1111 1111 1111',
|
||||
cardExpiryMonth: '1',
|
||||
cardExpiryYear: '2025',
|
||||
cardCvv: '123',
|
||||
saveCard: '1',
|
||||
}
|
||||
|
||||
export {createAccountSchemas, inits}
|
||||
@@ -0,0 +1,151 @@
|
||||
import React, {FC, useEffect, useRef, useState} from 'react'
|
||||
import {Step1} from './steps/Step1'
|
||||
import {Step2} from './steps/Step2'
|
||||
import {Step3} from './steps/Step3'
|
||||
import {Step4} from './steps/Step4'
|
||||
import {Step5} from './steps/Step5'
|
||||
import {KTSVG} from '../../../../_metronic/helpers'
|
||||
import {StepperComponent} from '../../../../_metronic/assets/ts/components'
|
||||
import {Formik, Form, FormikValues} from 'formik'
|
||||
import {createAccountSchemas, ICreateAccount, inits} from './CreateAccountWizardHelper'
|
||||
|
||||
const Horizontal: FC = () => {
|
||||
const stepperRef = useRef<HTMLDivElement | null>(null)
|
||||
const stepper = useRef<StepperComponent | null>(null)
|
||||
const [currentSchema, setCurrentSchema] = useState(createAccountSchemas[0])
|
||||
const [initValues] = useState<ICreateAccount>(inits)
|
||||
const [isSubmitButton, setSubmitButton] = useState(false)
|
||||
|
||||
const loadStepper = () => {
|
||||
stepper.current = StepperComponent.createInsance(stepperRef.current as HTMLDivElement)
|
||||
}
|
||||
|
||||
const prevStep = () => {
|
||||
if (!stepper.current) {
|
||||
return
|
||||
}
|
||||
|
||||
setSubmitButton(stepper.current.currentStepIndex === stepper.current.totatStepsNumber! - 1)
|
||||
|
||||
stepper.current.goPrev()
|
||||
|
||||
setCurrentSchema(createAccountSchemas[stepper.current.currentStepIndex - 1])
|
||||
}
|
||||
|
||||
const submitStep = (values: ICreateAccount, actions: FormikValues) => {
|
||||
if (!stepper.current) {
|
||||
return
|
||||
}
|
||||
|
||||
setSubmitButton(stepper.current.currentStepIndex === stepper.current.totatStepsNumber! - 1)
|
||||
|
||||
setCurrentSchema(createAccountSchemas[stepper.current.currentStepIndex])
|
||||
|
||||
if (stepper.current.currentStepIndex !== stepper.current.totatStepsNumber) {
|
||||
stepper.current.goNext()
|
||||
} else {
|
||||
stepper.current.goto(1)
|
||||
actions.resetForm()
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!stepperRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
loadStepper()
|
||||
}, [stepperRef])
|
||||
|
||||
return (
|
||||
<div className='card'>
|
||||
<div className='card-body'>
|
||||
<div
|
||||
ref={stepperRef}
|
||||
className='stepper stepper-links d-flex flex-column pt-15'
|
||||
id='kt_create_account_stepper'
|
||||
>
|
||||
<div className='stepper-nav mb-5'>
|
||||
<div className='stepper-item current' data-kt-stepper-element='nav'>
|
||||
<h3 className='stepper-title'>Account Type</h3>
|
||||
</div>
|
||||
|
||||
<div className='stepper-item' data-kt-stepper-element='nav'>
|
||||
<h3 className='stepper-title'>Account Info</h3>
|
||||
</div>
|
||||
|
||||
<div className='stepper-item' data-kt-stepper-element='nav'>
|
||||
<h3 className='stepper-title'>Business Info</h3>
|
||||
</div>
|
||||
|
||||
<div className='stepper-item' data-kt-stepper-element='nav'>
|
||||
<h3 className='stepper-title'>Billing Details</h3>
|
||||
</div>
|
||||
|
||||
<div className='stepper-item' data-kt-stepper-element='nav'>
|
||||
<h3 className='stepper-title'>Completed</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Formik validationSchema={currentSchema} initialValues={initValues} onSubmit={submitStep}>
|
||||
{() => (
|
||||
<Form className='mx-auto mw-600px w-100 pt-15 pb-10' id='kt_create_account_form'>
|
||||
<div className='current' data-kt-stepper-element='content'>
|
||||
<Step1 />
|
||||
</div>
|
||||
|
||||
<div data-kt-stepper-element='content'>
|
||||
<Step2 />
|
||||
</div>
|
||||
|
||||
<div data-kt-stepper-element='content'>
|
||||
<Step3 />
|
||||
</div>
|
||||
|
||||
<div data-kt-stepper-element='content'>
|
||||
<Step4 />
|
||||
</div>
|
||||
|
||||
<div data-kt-stepper-element='content'>
|
||||
<Step5 />
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-stack pt-15'>
|
||||
<div className='mr-2'>
|
||||
<button
|
||||
onClick={prevStep}
|
||||
type='button'
|
||||
className='btn btn-lg btn-light-primary me-3'
|
||||
data-kt-stepper-action='previous'
|
||||
>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/arrows/arr063.svg'
|
||||
className='svg-icon-4 me-1'
|
||||
/>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type='submit' className='btn btn-lg btn-primary me-3'>
|
||||
<span className='indicator-label'>
|
||||
{!isSubmitButton && 'Continue'}
|
||||
{isSubmitButton && 'Submit'}
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/arrows/arr064.svg'
|
||||
className='svg-icon-3 ms-2 me-0'
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {Horizontal}
|
||||
@@ -0,0 +1,263 @@
|
||||
import {useEffect, useRef, useState} from 'react'
|
||||
import {KTSVG} from '../../../../_metronic/helpers'
|
||||
import {Step1} from './steps/Step1'
|
||||
import {Step2} from './steps/Step2'
|
||||
import {Step3} from './steps/Step3'
|
||||
import {Step4} from './steps/Step4'
|
||||
import {Step5} from './steps/Step5'
|
||||
import {StepperComponent} from '../../../../_metronic/assets/ts/components'
|
||||
import {Formik, Form, FormikValues} from 'formik'
|
||||
import {ICreateAccount, createAccountSchemas, inits} from './CreateAccountWizardHelper'
|
||||
|
||||
const Vertical = () => {
|
||||
const stepperRef = useRef<HTMLDivElement | null>(null)
|
||||
const stepper = useRef<StepperComponent | null>(null)
|
||||
const [currentSchema, setCurrentSchema] = useState(createAccountSchemas[0])
|
||||
const [initValues] = useState<ICreateAccount>(inits)
|
||||
|
||||
const loadStepper = () => {
|
||||
stepper.current = StepperComponent.createInsance(stepperRef.current as HTMLDivElement)
|
||||
}
|
||||
|
||||
const prevStep = () => {
|
||||
if (!stepper.current) {
|
||||
return
|
||||
}
|
||||
|
||||
stepper.current.goPrev()
|
||||
|
||||
setCurrentSchema(createAccountSchemas[stepper.current.currentStepIndex - 1])
|
||||
}
|
||||
|
||||
const submitStep = (values: ICreateAccount, actions: FormikValues) => {
|
||||
if (!stepper.current) {
|
||||
return
|
||||
}
|
||||
|
||||
setCurrentSchema(createAccountSchemas[stepper.current.currentStepIndex])
|
||||
|
||||
if (stepper.current.currentStepIndex !== stepper.current.totatStepsNumber) {
|
||||
stepper.current.goNext()
|
||||
} else {
|
||||
stepper.current.goto(1)
|
||||
actions.resetForm()
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!stepperRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
loadStepper()
|
||||
}, [stepperRef])
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={stepperRef}
|
||||
className='stepper stepper-pills stepper-column d-flex flex-column flex-xl-row flex-row-fluid'
|
||||
id='kt_create_account_stepper'
|
||||
>
|
||||
{/* begin::Aside*/}
|
||||
<div className='card d-flex justify-content-center justify-content-xl-start flex-row-auto w-100 w-xl-300px w-xxl-400px me-9'>
|
||||
{/* begin::Wrapper*/}
|
||||
<div className='card-body px-6 px-lg-10 px-xxl-15 py-20'>
|
||||
{/* begin::Nav*/}
|
||||
<div className='stepper-nav'>
|
||||
{/* begin::Step 1*/}
|
||||
<div className='stepper-item current' data-kt-stepper-element='nav'>
|
||||
{/* begin::Wrapper*/}
|
||||
<div className='stepper-wrapper'>
|
||||
{/* begin::Icon*/}
|
||||
<div className='stepper-icon w-40px h-40px'>
|
||||
<i className='stepper-check fas fa-check'></i>
|
||||
<span className='stepper-number'>1</span>
|
||||
</div>
|
||||
{/* end::Icon*/}
|
||||
|
||||
{/* begin::Label*/}
|
||||
<div className='stepper-label'>
|
||||
<h3 className='stepper-title'>Account Type</h3>
|
||||
|
||||
<div className='stepper-desc fw-semibold'>Setup Your Account Details</div>
|
||||
</div>
|
||||
{/* end::Label*/}
|
||||
</div>
|
||||
{/* end::Wrapper*/}
|
||||
|
||||
{/* begin::Line*/}
|
||||
<div className='stepper-line h-40px'></div>
|
||||
{/* end::Line*/}
|
||||
</div>
|
||||
{/* end::Step 1*/}
|
||||
|
||||
{/* begin::Step 2*/}
|
||||
<div className='stepper-item' data-kt-stepper-element='nav'>
|
||||
{/* begin::Wrapper*/}
|
||||
<div className='stepper-wrapper'>
|
||||
{/* begin::Icon*/}
|
||||
<div className='stepper-icon w-40px h-40px'>
|
||||
<i className='stepper-check fas fa-check'></i>
|
||||
<span className='stepper-number'>2</span>
|
||||
</div>
|
||||
{/* end::Icon*/}
|
||||
|
||||
{/* begin::Label*/}
|
||||
<div className='stepper-label'>
|
||||
<h3 className='stepper-title'>Account Settings</h3>
|
||||
<div className='stepper-desc fw-semibold'>Setup Your Account Settings</div>
|
||||
</div>
|
||||
{/* end::Label*/}
|
||||
</div>
|
||||
{/* end::Wrapper*/}
|
||||
|
||||
{/* begin::Line*/}
|
||||
<div className='stepper-line h-40px'></div>
|
||||
{/* end::Line*/}
|
||||
</div>
|
||||
{/* end::Step 2*/}
|
||||
|
||||
{/* begin::Step 3*/}
|
||||
<div className='stepper-item' data-kt-stepper-element='nav'>
|
||||
{/* begin::Wrapper*/}
|
||||
<div className='stepper-wrapper'>
|
||||
{/* begin::Icon*/}
|
||||
<div className='stepper-icon w-40px h-40px'>
|
||||
<i className='stepper-check fas fa-check'></i>
|
||||
<span className='stepper-number'>3</span>
|
||||
</div>
|
||||
{/* end::Icon*/}
|
||||
|
||||
{/* begin::Label*/}
|
||||
<div className='stepper-label'>
|
||||
<h3 className='stepper-title'>Business Info</h3>
|
||||
<div className='stepper-desc fw-semibold'>Your Business Related Info</div>
|
||||
</div>
|
||||
{/* end::Label*/}
|
||||
</div>
|
||||
{/* end::Wrapper*/}
|
||||
|
||||
{/* begin::Line*/}
|
||||
<div className='stepper-line h-40px'></div>
|
||||
{/* end::Line*/}
|
||||
</div>
|
||||
{/* end::Step 3*/}
|
||||
|
||||
{/* begin::Step 4*/}
|
||||
<div className='stepper-item' data-kt-stepper-element='nav'>
|
||||
{/* begin::Wrapper*/}
|
||||
<div className='stepper-wrapper'>
|
||||
{/* begin::Icon*/}
|
||||
<div className='stepper-icon w-40px h-40px'>
|
||||
<i className='stepper-check fas fa-check'></i>
|
||||
<span className='stepper-number'>4</span>
|
||||
</div>
|
||||
{/* end::Icon*/}
|
||||
|
||||
{/* begin::Label*/}
|
||||
<div className='stepper-label'>
|
||||
<h3 className='stepper-title'>Billing Details</h3>
|
||||
<div className='stepper-desc fw-semibold'>Set Your Payment Methods</div>
|
||||
</div>
|
||||
{/* end::Label*/}
|
||||
</div>
|
||||
{/* end::Wrapper*/}
|
||||
|
||||
{/* begin::Line*/}
|
||||
<div className='stepper-line h-40px'></div>
|
||||
{/* end::Line*/}
|
||||
</div>
|
||||
{/* end::Step 4*/}
|
||||
|
||||
{/* begin::Step 5*/}
|
||||
<div className='stepper-item' data-kt-stepper-element='nav'>
|
||||
{/* begin::Wrapper*/}
|
||||
<div className='stepper-wrapper'>
|
||||
{/* begin::Icon*/}
|
||||
<div className='stepper-icon w-40px h-40px'>
|
||||
<i className='stepper-check fas fa-check'></i>
|
||||
<span className='stepper-number'>5</span>
|
||||
</div>
|
||||
{/* end::Icon*/}
|
||||
|
||||
{/* begin::Label*/}
|
||||
<div className='stepper-label'>
|
||||
<h3 className='stepper-title'>Completed</h3>
|
||||
<div className='stepper-desc fw-semibold'>Woah, we are here</div>
|
||||
</div>
|
||||
{/* end::Label*/}
|
||||
</div>
|
||||
{/* end::Wrapper*/}
|
||||
</div>
|
||||
{/* end::Step 5*/}
|
||||
</div>
|
||||
{/* end::Nav*/}
|
||||
</div>
|
||||
{/* end::Wrapper*/}
|
||||
</div>
|
||||
{/* begin::Aside*/}
|
||||
|
||||
<div className='d-flex flex-row-fluid flex-center bg-body rounded'>
|
||||
<Formik validationSchema={currentSchema} initialValues={initValues} onSubmit={submitStep}>
|
||||
{() => (
|
||||
<Form className='py-20 w-100 w-xl-700px px-9' noValidate id='kt_create_account_form'>
|
||||
<div className='current' data-kt-stepper-element='content'>
|
||||
<Step1 />
|
||||
</div>
|
||||
|
||||
<div data-kt-stepper-element='content'>
|
||||
<Step2 />
|
||||
</div>
|
||||
|
||||
<div data-kt-stepper-element='content'>
|
||||
<Step3 />
|
||||
</div>
|
||||
|
||||
<div data-kt-stepper-element='content'>
|
||||
<Step4 />
|
||||
</div>
|
||||
|
||||
<div data-kt-stepper-element='content'>
|
||||
<Step5 />
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-stack pt-10'>
|
||||
<div className='mr-2'>
|
||||
<button
|
||||
onClick={prevStep}
|
||||
type='button'
|
||||
className='btn btn-lg btn-light-primary me-3'
|
||||
data-kt-stepper-action='previous'
|
||||
>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/arrows/arr063.svg'
|
||||
className='svg-icon-4 me-1'
|
||||
/>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type='submit' className='btn btn-lg btn-primary me-3'>
|
||||
<span className='indicator-label'>
|
||||
{stepper.current?.currentStepIndex !==
|
||||
stepper.current?.totatStepsNumber! - 1 && 'Continue'}
|
||||
{stepper.current?.currentStepIndex ===
|
||||
stepper.current?.totatStepsNumber! - 1 && 'Submit'}
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/arrows/arr064.svg'
|
||||
className='svg-icon-3 ms-2 me-0'
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {Vertical}
|
||||
@@ -0,0 +1,89 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import {FC} from 'react'
|
||||
import {KTSVG} from '../../../../../_metronic/helpers'
|
||||
import {Field, ErrorMessage} from 'formik'
|
||||
|
||||
const Step1: FC = () => {
|
||||
return (
|
||||
<div className='w-100'>
|
||||
<div className='pb-10 pb-lg-15'>
|
||||
<h2 className='fw-bolder d-flex align-items-center text-dark'>
|
||||
Choose Account Type
|
||||
<i
|
||||
className='fas fa-exclamation-circle ms-2 fs-7'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Billing is issued based on your selected account type'
|
||||
></i>
|
||||
</h2>
|
||||
|
||||
<div className='text-gray-400 fw-bold fs-6'>
|
||||
If you need more info, please check out
|
||||
<a href='/dashboard' className='link-primary fw-bolder'>
|
||||
{' '}
|
||||
Help Page
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='fv-row'>
|
||||
<div className='row'>
|
||||
<div className='col-lg-6'>
|
||||
<Field
|
||||
type='radio'
|
||||
className='btn-check'
|
||||
name='accountType'
|
||||
value='personal'
|
||||
id='kt_create_account_form_account_type_personal'
|
||||
/>
|
||||
<label
|
||||
className='btn btn-outline btn-outline-dashed btn-outline-default p-7 d-flex align-items-center mb-10'
|
||||
htmlFor='kt_create_account_form_account_type_personal'
|
||||
>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/communication/com005.svg'
|
||||
className='svg-icon-3x me-5'
|
||||
/>
|
||||
|
||||
<span className='d-block fw-bold text-start'>
|
||||
<span className='text-dark fw-bolder d-block fs-4 mb-2'>Personal Account</span>
|
||||
<span className='text-gray-400 fw-bold fs-6'>
|
||||
If you need more info, please check it out
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className='col-lg-6'>
|
||||
<Field
|
||||
type='radio'
|
||||
className='btn-check'
|
||||
name='accountType'
|
||||
value='corporate'
|
||||
id='kt_create_account_form_account_type_corporate'
|
||||
/>
|
||||
<label
|
||||
className='btn btn-outline btn-outline-dashed btn-outline-default p-7 d-flex align-items-center'
|
||||
htmlFor='kt_create_account_form_account_type_corporate'
|
||||
>
|
||||
<KTSVG path='/media/icons/duotune/finance/fin006.svg' className='svg-icon-3x me-5' />
|
||||
|
||||
<span className='d-block fw-bold text-start'>
|
||||
<span className='text-dark fw-bolder d-block fs-4 mb-2'>Corporate Account</span>
|
||||
<span className='text-gray-400 fw-bold fs-6'>
|
||||
Create corporate account to mane users
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className='text-danger mt-2'>
|
||||
<ErrorMessage name='accountType' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {Step1}
|
||||
@@ -0,0 +1,207 @@
|
||||
import React, {FC} from 'react'
|
||||
import {KTSVG} from '../../../../../_metronic/helpers'
|
||||
import {Field, ErrorMessage} from 'formik'
|
||||
|
||||
const Step2: FC = () => {
|
||||
return (
|
||||
<div className='w-100'>
|
||||
<div className='pb-10 pb-lg-15'>
|
||||
<h2 className='fw-bolder text-dark'>Account Info</h2>
|
||||
|
||||
<div className='text-gray-400 fw-bold fs-6'>
|
||||
If you need more info, please check out
|
||||
<a href='/dashboard' className='link-primary fw-bolder'>
|
||||
{' '}
|
||||
Help Page
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mb-10 fv-row'>
|
||||
<label className='d-flex align-items-center form-label mb-3'>
|
||||
Specify Team Size
|
||||
<i
|
||||
className='fas fa-exclamation-circle ms-2 fs-7'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Provide your team size to help us setup your billing'
|
||||
></i>
|
||||
</label>
|
||||
|
||||
<div className='row mb-2' data-kt-buttons='true'>
|
||||
<div className='col'>
|
||||
<Field
|
||||
type='radio'
|
||||
className='btn-check'
|
||||
name='accountTeamSize'
|
||||
value='1-1'
|
||||
id='kt_account_team_size_select_1'
|
||||
/>
|
||||
<label
|
||||
className='btn btn-outline btn-outline-dashed btn-outline-default w-100 p-4'
|
||||
htmlFor='kt_account_team_size_select_1'
|
||||
>
|
||||
<span className='fw-bolder fs-3'>1-1</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className='col'>
|
||||
<Field
|
||||
type='radio'
|
||||
className='btn-check'
|
||||
name='accountTeamSize'
|
||||
value='2-10'
|
||||
id='kt_account_team_size_select_2'
|
||||
/>
|
||||
<label
|
||||
className='btn btn-outline btn-outline-dashed btn-outline-default w-100 p-4'
|
||||
htmlFor='kt_account_team_size_select_2'
|
||||
>
|
||||
<span className='fw-bolder fs-3'>2-10</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className='col'>
|
||||
<Field
|
||||
type='radio'
|
||||
className='btn-check'
|
||||
name='accountTeamSize'
|
||||
value='10-50'
|
||||
id='kt_account_team_size_select_3'
|
||||
/>
|
||||
<label
|
||||
className='btn btn-outline btn-outline-dashed btn-outline-default w-100 p-4'
|
||||
htmlFor='kt_account_team_size_select_3'
|
||||
>
|
||||
<span className='fw-bolder fs-3'>10-50</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className='col'>
|
||||
<Field
|
||||
type='radio'
|
||||
className='btn-check'
|
||||
name='accountTeamSize'
|
||||
value='50+'
|
||||
id='kt_account_team_size_select_4'
|
||||
/>
|
||||
<label
|
||||
className='btn btn-outline btn-outline-dashed btn-outline-default w-100 p-4'
|
||||
htmlFor='kt_account_team_size_select_4'
|
||||
>
|
||||
<span className='fw-bolder fs-3'>50+</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='form-text'>
|
||||
Customers will see this shortened version of your statement descriptor
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mb-10 fv-row'>
|
||||
<label className='form-label mb-3'>Team Account Name</label>
|
||||
|
||||
<Field
|
||||
type='text'
|
||||
className='form-control form-control-lg form-control-solid'
|
||||
name='accountName'
|
||||
/>
|
||||
<div className='text-danger mt-2'>
|
||||
<ErrorMessage name='accountName' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mb-0 fv-row'>
|
||||
<label className='d-flex align-items-center form-label mb-5'>
|
||||
Select Account Plan
|
||||
<i
|
||||
className='fas fa-exclamation-circle ms-2 fs-7'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Monthly billing will be based on your account plan'
|
||||
></i>
|
||||
</label>
|
||||
|
||||
<div className='mb-0'>
|
||||
<label className='d-flex flex-stack mb-5 cursor-pointer'>
|
||||
<span className='d-flex align-items-center me-2'>
|
||||
<span className='symbol symbol-50px me-6'>
|
||||
<span className='symbol-label'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/finance/fin001.svg'
|
||||
className='svg-icon-1 svg-icon-gray-600'
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span className='d-flex flex-column'>
|
||||
<span className='fw-bolder text-gray-800 text-hover-primary fs-5'>
|
||||
Company Account
|
||||
</span>
|
||||
<span className='fs-6 fw-bold text-gray-400'>
|
||||
Use images to enhance your post flow
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span className='form-check form-check-custom form-check-solid'>
|
||||
<Field className='form-check-input' type='radio' name='accountPlan' value='1' />
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label className='d-flex flex-stack mb-5 cursor-pointer'>
|
||||
<span className='d-flex align-items-center me-2'>
|
||||
<span className='symbol symbol-50px me-6'>
|
||||
<span className='symbol-label'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/graphs/gra006.svg'
|
||||
className='svg-icon-1 svg-icon-gray-600'
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span className='d-flex flex-column'>
|
||||
<span className='fw-bolder text-gray-800 text-hover-primary fs-5'>
|
||||
Developer Account
|
||||
</span>
|
||||
<span className='fs-6 fw-bold text-gray-400'>Use images to your post time</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span className='form-check form-check-custom form-check-solid'>
|
||||
<Field className='form-check-input' type='radio' name='accountPlan' value='2' />
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label className='d-flex flex-stack mb-0 cursor-pointer'>
|
||||
<span className='d-flex align-items-center me-2'>
|
||||
<span className='symbol symbol-50px me-6'>
|
||||
<span className='symbol-label'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/graphs/gra008.svg'
|
||||
className='svg-icon-1 svg-icon-gray-600'
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span className='d-flex flex-column'>
|
||||
<span className='fw-bolder text-gray-800 text-hover-primary fs-5'>
|
||||
Testing Account
|
||||
</span>
|
||||
<span className='fs-6 fw-bold text-gray-400'>
|
||||
Use images to enhance time travel rivers
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span className='form-check form-check-custom form-check-solid'>
|
||||
<Field className='form-check-input' type='radio' name='accountPlan' value='3' />
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {Step2}
|
||||
@@ -0,0 +1,91 @@
|
||||
import React, {FC} from 'react'
|
||||
import {Field, ErrorMessage} from 'formik'
|
||||
|
||||
const Step3: FC = () => {
|
||||
return (
|
||||
<div className='w-100'>
|
||||
<div className='pb-10 pb-lg-12'>
|
||||
<h2 className='fw-bolder text-dark'>Business Details</h2>
|
||||
|
||||
<div className='text-gray-400 fw-bold fs-6'>
|
||||
If you need more info, please check out
|
||||
<a href='/dashboard' className='link-primary fw-bolder'>
|
||||
{' '}
|
||||
Help Page
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='fv-row mb-10'>
|
||||
<label className='form-label required'>Business Name</label>
|
||||
|
||||
<Field name='businessName' className='form-control form-control-lg form-control-solid' />
|
||||
<div className='text-danger mt-2'>
|
||||
<ErrorMessage name='businessName' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='fv-row mb-10'>
|
||||
<label className='d-flex align-items-center form-label'>
|
||||
<span className='required'>Shortened Descriptor</span>
|
||||
</label>
|
||||
|
||||
<Field
|
||||
name='businessDescriptor'
|
||||
className='form-control form-control-lg form-control-solid'
|
||||
/>
|
||||
<div className='text-danger mt-2'>
|
||||
<ErrorMessage name='businessDescriptor' />
|
||||
</div>
|
||||
|
||||
<div className='form-text'>
|
||||
Customers will see this shortened version of your statement descriptor
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='fv-row mb-10'>
|
||||
<label className='form-label required'>Corporation Type</label>
|
||||
|
||||
<Field
|
||||
as='select'
|
||||
name='businessType'
|
||||
className='form-select form-select-lg form-select-solid'
|
||||
>
|
||||
<option></option>
|
||||
<option value='1'>S Corporation</option>
|
||||
<option value='1'>C Corporation</option>
|
||||
<option value='2'>Sole Proprietorship</option>
|
||||
<option value='3'>Non-profit</option>
|
||||
<option value='4'>Limited Liability</option>
|
||||
<option value='5'>General Partnership</option>
|
||||
</Field>
|
||||
<div className='text-danger mt-2'>
|
||||
<ErrorMessage name='businessType' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='fv-row mb-10'>
|
||||
<label className='form-label'>Business Description</label>
|
||||
|
||||
<Field
|
||||
as='textarea'
|
||||
name='businessDescription'
|
||||
className='form-control form-control-lg form-control-solid'
|
||||
rows={3}
|
||||
></Field>
|
||||
</div>
|
||||
|
||||
<div className='fv-row mb-0'>
|
||||
<label className='fs-6 fw-bold form-label required'>Contact Email</label>
|
||||
|
||||
<Field name='businessEmail' className='form-control form-control-lg form-control-solid' />
|
||||
<div className='text-danger mt-2'>
|
||||
<ErrorMessage name='businessEmail' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {Step3}
|
||||
@@ -0,0 +1,167 @@
|
||||
import React, {FC} from 'react'
|
||||
import {KTSVG, toAbsoluteUrl} from '../../../../../_metronic/helpers'
|
||||
import {Field, ErrorMessage} from 'formik'
|
||||
|
||||
const Step4: FC = () => {
|
||||
return (
|
||||
<div className='w-100'>
|
||||
<div className='pb-10 pb-lg-15'>
|
||||
<h2 className='fw-bolder text-dark'>Billing Details</h2>
|
||||
|
||||
<div className='text-gray-400 fw-bold fs-6'>
|
||||
If you need more info, please check out
|
||||
<a href='/dashboard' className='text-primary fw-bolder'>
|
||||
{' '}
|
||||
Help Page
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column mb-7 fv-row'>
|
||||
<label className='d-flex align-items-center fs-6 fw-bold form-label mb-2'>
|
||||
<span className='required'>Name On Card</span>
|
||||
<i
|
||||
className='fas fa-exclamation-circle ms-2 fs-7'
|
||||
data-bs-toggle='tooltip'
|
||||
title="Specify a card holder's name"
|
||||
></i>
|
||||
</label>
|
||||
|
||||
<Field
|
||||
type='text'
|
||||
className='form-control form-control-solid'
|
||||
placeholder=''
|
||||
name='nameOnCard'
|
||||
/>
|
||||
<div className='text-danger mt-2'>
|
||||
<ErrorMessage name='nameOnCard' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column mb-7 fv-row'>
|
||||
<label className='required fs-6 fw-bold form-label mb-2'>Card Number</label>
|
||||
|
||||
<div className='position-relative'>
|
||||
<Field
|
||||
type='text'
|
||||
className='form-control form-control-solid'
|
||||
placeholder='Enter card number'
|
||||
name='cardNumber'
|
||||
/>
|
||||
<div className='text-danger mt-2'>
|
||||
<ErrorMessage name='cardNumber' />
|
||||
</div>
|
||||
|
||||
<div className='position-absolute translate-middle-y top-50 end-0 me-5'>
|
||||
<img src={toAbsoluteUrl('/media/svg/card-logos/visa.svg')} alt='' className='h-25px' />
|
||||
<img
|
||||
src={toAbsoluteUrl('/media/svg/card-logos/mastercard.svg')}
|
||||
alt=''
|
||||
className='h-25px'
|
||||
/>
|
||||
<img
|
||||
src={toAbsoluteUrl('/media/svg/card-logos/american-express.svg')}
|
||||
alt=''
|
||||
className='h-25px'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row mb-10'>
|
||||
<div className='col-md-8 fv-row'>
|
||||
<label className='required fs-6 fw-bold form-label mb-2'>Expiration Date</label>
|
||||
|
||||
<div className='row fv-row'>
|
||||
<div className='col-6'>
|
||||
<Field as='select' name='cardExpiryMonth' className='form-select form-select-solid'>
|
||||
<option></option>
|
||||
<option value='1'>1</option>
|
||||
<option value='2'>2</option>
|
||||
<option value='3'>3</option>
|
||||
<option value='4'>4</option>
|
||||
<option value='5'>5</option>
|
||||
<option value='6'>6</option>
|
||||
<option value='7'>7</option>
|
||||
<option value='8'>8</option>
|
||||
<option value='9'>9</option>
|
||||
<option value='10'>10</option>
|
||||
<option value='11'>11</option>
|
||||
<option value='12'>12</option>
|
||||
</Field>
|
||||
<div className='text-danger mt-2'>
|
||||
<ErrorMessage name='cardExpiryMonth' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='col-6'>
|
||||
<Field as='select' name='cardExpiryYear' className='form-select form-select-solid'>
|
||||
<option></option>
|
||||
<option value='2021'>2021</option>
|
||||
<option value='2022'>2022</option>
|
||||
<option value='2023'>2023</option>
|
||||
<option value='2024'>2024</option>
|
||||
<option value='2025'>2025</option>
|
||||
<option value='2026'>2026</option>
|
||||
<option value='2027'>2027</option>
|
||||
<option value='2028'>2028</option>
|
||||
<option value='2029'>2029</option>
|
||||
<option value='2030'>2030</option>
|
||||
<option value='2031'>2031</option>
|
||||
</Field>
|
||||
<div className='text-danger mt-2'>
|
||||
<ErrorMessage name='cardExpiryYear' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='col-md-4 fv-row'>
|
||||
<label className='d-flex align-items-center fs-6 fw-bold form-label mb-2'>
|
||||
<span className='required'>CVV</span>
|
||||
<i
|
||||
className='fas fa-exclamation-circle ms-2 fs-7'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Enter a card CVV code'
|
||||
></i>
|
||||
</label>
|
||||
|
||||
<div className='position-relative'>
|
||||
<Field
|
||||
type='text'
|
||||
className='form-control form-control-solid'
|
||||
minLength={3}
|
||||
maxLength={4}
|
||||
placeholder='CVV'
|
||||
name='cardCvv'
|
||||
/>
|
||||
<div className='text-danger mt-2'>
|
||||
<ErrorMessage name='cardCvv' />
|
||||
</div>
|
||||
|
||||
<div className='position-absolute translate-middle-y top-50 end-0 me-3'>
|
||||
<KTSVG path='/media/icons/duotune/finance/fin002.svg' className='svg-icon-2hx' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-stack'>
|
||||
<div className='me-5'>
|
||||
<label className='fs-6 fw-bold form-label'>Save Card for further billing?</label>
|
||||
<div className='fs-7 fw-bold text-gray-400'>
|
||||
If you need more info, please check budget planning
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label className='form-check form-switch form-check-custom form-check-solid'>
|
||||
<Field className='form-check-input' type='checkbox' value='1' checked={true} />
|
||||
<span className='form-check-label fw-bold text-gray-400'>Save Card</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {Step4}
|
||||
@@ -0,0 +1,51 @@
|
||||
import React, {FC} from 'react'
|
||||
import {KTSVG} from '../../../../../_metronic/helpers'
|
||||
import {Link} from 'react-router-dom'
|
||||
|
||||
const Step5: FC = () => {
|
||||
return (
|
||||
<div className='w-100'>
|
||||
<div className='pb-8 pb-lg-10'>
|
||||
<h2 className='fw-bolder text-dark'>Your Are Done!</h2>
|
||||
|
||||
<div className='text-gray-400 fw-bold fs-6'>
|
||||
If you need more info, please
|
||||
<Link to='/auth/login' className='link-primary fw-bolder'>
|
||||
{' '}
|
||||
Sign In
|
||||
</Link>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mb-0'>
|
||||
<div className='fs-6 text-gray-600 mb-5'>
|
||||
Writing headlines for blog posts is as much an art as it is a science and probably
|
||||
warrants its own post, but for all advise is with what works for your great & amazing
|
||||
audience.
|
||||
</div>
|
||||
|
||||
<div className='notice d-flex bg-light-warning rounded border-warning border border-dashed p-6'>
|
||||
<KTSVG
|
||||
path='/media/icons/duotune/general/gen044.svg'
|
||||
className='svg-icon-2tx svg-icon-warning me-4'
|
||||
/>
|
||||
<div className='d-flex flex-stack flex-grow-1'>
|
||||
<div className='fw-bold'>
|
||||
<h4 className='text-gray-800 fw-bolder'>We need your attention!</h4>
|
||||
<div className='fs-6 text-gray-600'>
|
||||
To start using great tools, please, please
|
||||
<a href='/dashboard' className='fw-bolder'>
|
||||
{' '}
|
||||
Create Team Platform
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {Step5}
|
||||
Reference in New Issue
Block a user