first commit

This commit is contained in:
Olu Amey
2023-03-25 23:06:51 -04:00
commit 8278a33443
2865 changed files with 170918 additions and 0 deletions
+203
View File
@@ -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}
+58
View File
@@ -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 &amp; Canada)'>
(GMT-07:00) Pacific Time (US &amp; Canada)
</option>
<option value='Tijuana'>(GMT-07:00) Tijuana</option>
<option value='Arizona'>(GMT-07:00) Arizona</option>
<option value='Mountain Time (US &amp; Canada)'>
(GMT-06:00) Mountain Time (US &amp; 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 &amp; Canada)'>
(GMT-05:00) Central Time (US &amp; 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 &amp; Canada)'>
(GMT-04:00) Eastern Time (US &amp; 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}
+59
View File
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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 === '&laquo; Previous') {
return 'Previous'
}
if (label === 'Next &raquo;') {
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}
@@ -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}
@@ -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}
@@ -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}
+104
View File
@@ -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 theyve 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}
+18
View File
@@ -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}
+17
View File
@@ -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>
)
}
+226
View File
@@ -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>
)
}
+101
View File
@@ -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}
+65
View File
@@ -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}
+67
View File
@@ -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
}
+47
View File
@@ -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,
})
}
+5
View File
@@ -0,0 +1,5 @@
export * from './core/_models'
export * from './core/Auth'
export * from './core/AuthHelpers'
export * from './AuthPage'
export * from './Logout'
+37
View File
@@ -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}
+17
View File
@@ -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}
+37
View File
@@ -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,
},
]
+228
View File
@@ -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}
+6
View File
@@ -0,0 +1,6 @@
export interface IconUserModel {
name: string
avatar?: string
color?: string
initials?: string
}
+85
View File
@@ -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 &amp; Canada)'>
(GMT-07:00) Pacific Time (US &amp; 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 &amp; Canada)'>
(GMT-06:00) Mountain Time (US &amp; 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 &amp; Canada)'>
(GMT-05:00) Central Time (US &amp; 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 &amp; Canada)'>
(GMT-04:00) Eastern Time (US &amp; 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,
},
}
+89
View File
@@ -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 dont 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}
+47
View File
@@ -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}