Slicing UI
This commit is contained in:
212
src/screens/Wa/Manage/Content/CreateSettings.jsx
Normal file
212
src/screens/Wa/Manage/Content/CreateSettings.jsx
Normal file
@@ -0,0 +1,212 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
const CreateSettings = () => {
|
||||
const location = useLocation();
|
||||
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => setIsMobile(window.innerWidth <= 768);
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
const Breadcrumb = ({ path }) => {
|
||||
return (
|
||||
<nav style={styles.breadcrumb}>
|
||||
{path.map((item, index) => (
|
||||
<span key={index}>
|
||||
{index > 0 && ' > '}
|
||||
<a
|
||||
href={item.link}
|
||||
style={{
|
||||
...styles.breadcrumbLink,
|
||||
fontWeight: location.pathname === item.link ? 'bold' : 'normal',
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</a>
|
||||
</span>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
const breadcrumbPath = [
|
||||
{ name: 'Wa Integration', link: '/wa-integration' },
|
||||
{ name: 'Add Setting', link: '/wa-createSettings' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<div style={isMobile ? styles.wrapperMobile : styles.wrapper}>
|
||||
<Breadcrumb path={breadcrumbPath} />
|
||||
<h2 style={styles.header}>Create Settings</h2>
|
||||
|
||||
<div style={isMobile ? styles.formWrapperMobile : styles.formWrapper}>
|
||||
{/* Left Form Section */}
|
||||
<div style={styles.formSection}>
|
||||
<div style={styles.inputGroup}>
|
||||
<label style={styles.label}>Application ID</label>
|
||||
<select style={styles.input}>
|
||||
<option value="">Select Application</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style={styles.inputGroup}>
|
||||
<label style={styles.label}>Inbound Configuration</label>
|
||||
<select style={styles.input}>
|
||||
<option value="">Select Configuration</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style={styles.inputGroup}>
|
||||
<label style={styles.label}>Webhook URL</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter Webhook URL"
|
||||
style={styles.input}
|
||||
/>
|
||||
</div>
|
||||
<div style={styles.inputGroup}>
|
||||
<label style={styles.label}>Event</label>
|
||||
<div style={styles.checkboxGroup}>
|
||||
<label>
|
||||
<input type="checkbox" />
|
||||
Message Created
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" />
|
||||
Message Updated
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<button style={styles.button}>Add Now</button>
|
||||
</div>
|
||||
|
||||
{/* Right WhatsApp Details Section */}
|
||||
<div style={styles.detailSection}>
|
||||
<h3 style={styles.detailHeader}>WhatsApp Channel Detail</h3>
|
||||
<div style={styles.detailItem}>
|
||||
<strong>Business Account Name:</strong> RekanVeri
|
||||
</div>
|
||||
<div style={styles.detailItem}>
|
||||
<strong>WABA ID:</strong> 112917284928438
|
||||
</div>
|
||||
<div style={styles.detailItem}>
|
||||
<strong>WhatsApp Number:</strong> 6287736766208
|
||||
</div>
|
||||
<div style={styles.detailItem}>
|
||||
<strong>Channel Name:</strong> RekanVeri
|
||||
</div>
|
||||
<div style={styles.detailItem}>
|
||||
<strong>Eligible for Inbound:</strong> YES
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateSettings;
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
padding: '1rem',
|
||||
width: '100%',
|
||||
margin: '0 auto',
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
wrapper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '8px',
|
||||
padding: '1.5rem',
|
||||
boxShadow: '0 2px 5px rgba(0, 0, 0, 0.1)',
|
||||
backgroundColor: '#fff',
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
},
|
||||
wrapperMobile: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '8px',
|
||||
padding: '1rem',
|
||||
boxShadow: '0 2px 5px rgba(0, 0, 0, 0.1)',
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
breadcrumb: {
|
||||
marginBottom: '1rem',
|
||||
fontSize: '0.9rem',
|
||||
},
|
||||
breadcrumbLink: {
|
||||
textDecoration: 'none',
|
||||
color: '#0542cc',
|
||||
},
|
||||
header: {
|
||||
marginBottom: '1rem',
|
||||
fontSize: '1.5rem',
|
||||
},
|
||||
formWrapper: {
|
||||
display: 'flex',
|
||||
gap: '2rem',
|
||||
marginTop: '1rem',
|
||||
},
|
||||
formWrapperMobile: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '1rem',
|
||||
marginTop: '1rem',
|
||||
},
|
||||
formSection: {
|
||||
flex: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '1rem',
|
||||
},
|
||||
inputGroup: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '0.5rem',
|
||||
},
|
||||
label: {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
input: {
|
||||
padding: '0.5rem',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px',
|
||||
width: '100%',
|
||||
},
|
||||
checkboxGroup: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '0.5rem',
|
||||
},
|
||||
button: {
|
||||
padding: '0.8rem 1.5rem',
|
||||
backgroundColor: '#0542cc',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
alignSelf: 'flex-start',
|
||||
},
|
||||
detailSection: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f5f9ff',
|
||||
padding: '1rem',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '8px',
|
||||
},
|
||||
detailHeader: {
|
||||
fontSize: '1.2rem',
|
||||
marginBottom: '1rem',
|
||||
color: '#0542cc',
|
||||
},
|
||||
detailItem: {
|
||||
marginBottom: '0.5rem',
|
||||
fontSize: '0.9rem',
|
||||
},
|
||||
};
|
||||
256
src/screens/Wa/Manage/Content/RegisterContent.jsx
Normal file
256
src/screens/Wa/Manage/Content/RegisterContent.jsx
Normal file
@@ -0,0 +1,256 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { WAB_1, WAB_2 } from '../../../../assets/images';
|
||||
import { RegisterStep } from '../Content';
|
||||
|
||||
// Header component
|
||||
const HeaderComponent = ({ isMobile }) => {
|
||||
return (
|
||||
<div style={isMobile ? styles.headerContainerMobile : styles.headerContainer}>
|
||||
<img src={WAB_1} alt="WAB_1" style={styles.image(isMobile ? 200 : 350, isMobile ? 100 : 170)} />
|
||||
<div style={styles.textContainer}>
|
||||
<h2 style={styles.textTitle}>API For Your WhatsApp Business</h2>
|
||||
<p style={styles.textBody}>
|
||||
WhatsApp is one of the most popular communication platforms globally. As a Facebook-owned company,
|
||||
setting up WhatsApp Business API requires a Facebook account and Facebook Business Manager.
|
||||
Start leveraging this powerful tool for seamless communication with your customers.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// Step Component (untuk menampilkan 4 langkah yang ada)
|
||||
const StepComponent = ({ isMigration, isMobile }) => {
|
||||
const stepsData = isMigration
|
||||
? [
|
||||
{ number: '01', description: 'A Viable Phone Number for WhatsApp' },
|
||||
{ number: '02', description: 'Your Business Legal address and details' },
|
||||
]
|
||||
: [
|
||||
{ number: '01', description: 'A Viable Phone Number for WhatsApp' },
|
||||
{ number: '02', description: 'Your Business Legal address and details' },
|
||||
{ number: '03', description: 'Select Number you want to connect' },
|
||||
{ number: '04', description: 'Verify your Facebook Business' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={isMobile ? styles.stepsContainerMobile : styles.stepsContainer}>
|
||||
{stepsData.map((step, index) => (
|
||||
<div key={index} style={styles.step}>
|
||||
<div style={styles.stepNumberContainer}>
|
||||
<span style={styles.stepNumber}>{step.number}</span>
|
||||
</div>
|
||||
<p style={styles.stepDescription}>{step.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Info Box Component untuk setiap informasi
|
||||
const InfoComponent = ({ title, description, onStartNow, isMobile }) => {
|
||||
return (
|
||||
<div style={isMobile ? styles.infoBoxMobile : styles.infoBox}>
|
||||
<h3>{title}</h3>
|
||||
<p>{description}</p>
|
||||
<button style={styles.startButton} onClick={onStartNow}>Start Now</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Need Component (Menggabungkan Step dan Info Pertama)
|
||||
const Need = ({ onStartNow, isMobile }) => {
|
||||
return (
|
||||
<div style={styles.regisBusinessContainer}>
|
||||
<div style={styles.rowContainer}>
|
||||
<div style={styles.col6}>
|
||||
<StepComponent isMobile={isMobile} />
|
||||
</div>
|
||||
<div style={styles.col6}>
|
||||
<InfoComponent
|
||||
title="What You Need?"
|
||||
description="To register for WhatsApp Business API, you'll need an active phone number,
|
||||
your business's legal address and details, select the number you want to connect, and
|
||||
verify your Facebook Business account to complete the setup."
|
||||
onStartNow={onStartNow}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Migration Component (Menggabungkan Step dan Info Kedua)
|
||||
const Migration = ({ isMobile }) => {
|
||||
return (
|
||||
<div style={styles.regisBusinessContainer}>
|
||||
<div style={styles.rowContainer}>
|
||||
<div style={styles.col6}>
|
||||
<div style={styles.leftSideContainer}>
|
||||
<img src={WAB_2} alt="WAB_2" style={styles.image(isMobile ? 100 : 180, isMobile ? 100 : 180)} />
|
||||
<StepComponent isMigration={true} isMobile={isMobile} />
|
||||
</div>
|
||||
</div>
|
||||
<div style={styles.col6}>
|
||||
<InfoComponent
|
||||
title="What You Need for Migration?"
|
||||
description="To complete the migration, you'll need a verified Facebook Business account
|
||||
and access to the phone number you wish to migrate."
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// Main component
|
||||
const RegisterContent = () => {
|
||||
const [isStarted, setIsStarted] = useState(false);
|
||||
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
|
||||
|
||||
// Update isMobile state on window resize
|
||||
useEffect(() => {
|
||||
const handleResize = () => setIsMobile(window.innerWidth <= 768);
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
const handleStartNow = () => {
|
||||
setIsStarted(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={styles.regisBusinessContainer}>
|
||||
{!isStarted && <HeaderComponent isMobile={isMobile} />}
|
||||
{!isStarted && <Need onStartNow={handleStartNow} isMobile={isMobile} />}
|
||||
{!isStarted && <Migration isMobile={isMobile} />}
|
||||
{isStarted && <RegisterStep />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const styles = {
|
||||
regisBusinessContainer: {
|
||||
padding: '1rem',
|
||||
margin: 'auto',
|
||||
fontFamily: 'Arial, sans-serif'
|
||||
},
|
||||
|
||||
// Header Content Style
|
||||
headerContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: '20px',
|
||||
},
|
||||
leftSideContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
},
|
||||
image: (width, height) => ({
|
||||
width: width,
|
||||
height: height,
|
||||
flex: '1 1 33%',
|
||||
alignSelf: 'flex-start',
|
||||
}),
|
||||
textContainer: {
|
||||
flex: '1 1 66%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-end',
|
||||
paddingLeft: '20px',
|
||||
},
|
||||
textTitle: {
|
||||
fontSize: '24px',
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '10px',
|
||||
},
|
||||
textBody: {
|
||||
fontSize: '16px',
|
||||
lineHeight: '1.5',
|
||||
},
|
||||
rowContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
marginTop: '20px',
|
||||
flexWrap: 'wrap',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #ddd',
|
||||
boxShadow: '0px 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
minHeight: '200px',
|
||||
},
|
||||
col6: {
|
||||
flex: '1 1 50%',
|
||||
padding: '10px',
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
|
||||
// Step Style
|
||||
stepsContainer: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '10px', // Jarak antar langkah
|
||||
position: 'relative', // Posisi relatif agar connectorLine bisa diposisikan dengan benar
|
||||
},
|
||||
step: {
|
||||
flex: '1 1 16.6%', // Menyusun setiap langkah untuk mengambil 1/6 dari lebar container
|
||||
padding: '10px',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center',
|
||||
marginBottom: '10px',
|
||||
position: 'relative', // Agar connectorLine bisa diposisikan di dalam step
|
||||
},
|
||||
stepNumberContainer: {
|
||||
width: '40px', // Menentukan lebar oval
|
||||
height: '40px', // Menentukan tinggi oval
|
||||
borderRadius: '50%', // Membuat bentuk oval
|
||||
backgroundColor: '#0542CC', // Warna latar belakang oval
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: '8px',
|
||||
},
|
||||
stepNumber: {
|
||||
fontSize: '16px',
|
||||
color: '#fff', // Warna teks putih di dalam oval
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
stepDescription: {
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.5',
|
||||
color: '#333',
|
||||
textAlign: 'center',
|
||||
margin: '10px 0',
|
||||
},
|
||||
|
||||
// Info Style
|
||||
infoBox: {
|
||||
padding: '20px',
|
||||
marginBottom: '20px',
|
||||
},
|
||||
startButton: {
|
||||
backgroundColor: '#0542cc',
|
||||
color: '#fff',
|
||||
padding: '10px 20px',
|
||||
border: 'none',
|
||||
borderRadius: '5px',
|
||||
cursor: 'pointer',
|
||||
marginTop: '15px',
|
||||
},
|
||||
};
|
||||
|
||||
export default RegisterContent;
|
||||
455
src/screens/Wa/Manage/Content/RegisterStep.jsx
Normal file
455
src/screens/Wa/Manage/Content/RegisterStep.jsx
Normal file
@@ -0,0 +1,455 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { WAB_2 } from '../../../../assets/images';
|
||||
|
||||
const HeaderComponent = () => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<div style={{ ...styles.headerContainer, flexDirection: isMobile ? 'column' : 'row' }}>
|
||||
<div style={styles.textContainer} className='p-3'>
|
||||
<h2 style={styles.textTitle}>Registration Step</h2>
|
||||
<p style={styles.textBody}>
|
||||
WhatsApp is one of the most popular communication platforms globally. As a Facebook-owned company,
|
||||
setting up WhatsApp Business API requires a Facebook account and Facebook Business Manager.
|
||||
Start leveraging this powerful tool for seamless communication with your customers.
|
||||
</p>
|
||||
</div>
|
||||
<img src={WAB_2} alt="WAB_2" style={styles.image(190, 190)} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const useIsMobile = () => {
|
||||
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => setIsMobile(window.innerWidth <= 768);
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return isMobile;
|
||||
};
|
||||
|
||||
|
||||
const InfoComponent = ({ title, description, onStartNow }) => {
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
|
||||
const handleCheckboxChange = (event) => {
|
||||
setIsChecked(event.target.checked);
|
||||
setErrorMessage('');
|
||||
};
|
||||
|
||||
const handleStartNow = () => {
|
||||
if (!isChecked) {
|
||||
setErrorMessage('Please confirm agreement on above!');
|
||||
} else {
|
||||
onStartNow();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={styles.contentContainer}>
|
||||
<h3>{title}</h3>
|
||||
<p>{description}</p>
|
||||
<ul style={styles.descriptionList}>
|
||||
<li>The number must be owned by your company.</li>
|
||||
<li>Do not use a personal number.</li>
|
||||
<li>It should not be actively registered on WhatsApp Messenger or WhatsApp Business App.</li>
|
||||
<li>You must be able to receive phone calls or SMS on the number during the setup process.</li>
|
||||
</ul>
|
||||
<div style={styles.checkboxContainer}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="agreement"
|
||||
style={styles.checkbox}
|
||||
checked={isChecked}
|
||||
onChange={handleCheckboxChange}
|
||||
/>
|
||||
<label htmlFor="agreement" style={styles.checkboxLabel}>
|
||||
I have a number with the above requirements
|
||||
</label>
|
||||
</div>
|
||||
{errorMessage && (
|
||||
<p style={{ color: 'red', fontSize: '0.875rem' }}>{errorMessage}</p>
|
||||
)}
|
||||
<button style={styles.startButton} onClick={handleStartNow}>
|
||||
Start Now
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// StepComponent
|
||||
const StepComponent = ({ activeStep }) => {
|
||||
const stepsData = [
|
||||
{ number: '01', description: 'Phone Number to WhatsApp' },
|
||||
{ number: '02', description: 'WhatsApp Business Channel' },
|
||||
{ number: '03', description: 'Verify Your Business on Facebook' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={styles.stepsContainer}>
|
||||
{stepsData.map((step, index) => {
|
||||
const isCurrent = activeStep === index;
|
||||
|
||||
return (
|
||||
<div key={index} style={styles.step}>
|
||||
<div
|
||||
style={{
|
||||
...styles.stepNumberContainer,
|
||||
backgroundColor: isCurrent ? '#0542cc' : '#d3d3d3',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
...styles.stepNumber,
|
||||
color: isCurrent ? '#fff' : '#808080',
|
||||
}}
|
||||
>
|
||||
{step.number}
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
...styles.stepDescription,
|
||||
color: isCurrent ? '#000' : 'gray',
|
||||
}}
|
||||
>
|
||||
{step.description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// FirstContentComponent
|
||||
const FirstContentComponent = ({ onStartNow }) => (
|
||||
<div style={styles.rowContainer}>
|
||||
<div style={styles.col6}>
|
||||
<StepComponent activeStep={0} /> {/* Displays step 1 as active */}
|
||||
</div>
|
||||
<div style={styles.col6}>
|
||||
<InfoComponent
|
||||
title="Following Specifications Step"
|
||||
onStartNow={onStartNow} // Handles "Start Now" button click
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// SecondContentComponent
|
||||
const SecondContentComponent = ({ onNextStep, onBack }) => {
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
|
||||
const handleCheckboxChange = (event) => {
|
||||
setIsChecked(event.target.checked);
|
||||
setErrorMessage(''); // Reset error message if checkbox is changed
|
||||
};
|
||||
|
||||
const handleNextStep = () => {
|
||||
if (!isChecked) {
|
||||
setErrorMessage('Please confirm agreement on above!');
|
||||
} else {
|
||||
onNextStep(); // Proceed to next step if checkbox is checked
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={styles.rowContainer}>
|
||||
<div style={styles.col6}>
|
||||
<StepComponent activeStep={1} /> {/* Displays step 2 as active */}
|
||||
</div>
|
||||
<div style={styles.col6}>
|
||||
<p>You'll need the following information for your WhatsApp Business Channel, Be sure to have them on hand.</p>
|
||||
<ul style={styles.descriptionList}>
|
||||
<li>Your brand's Display Name, shown to customers you chat with.</li>
|
||||
<li>Your company's Legal business name.</li>
|
||||
<li>Your company's official address.</li>
|
||||
</ul>
|
||||
<p>Create your WhatsApp Business Channel</p>
|
||||
<p>You'll be directed to Facebook Business Manager. Complete all the steps in the pop-up in order to create your WhatsApp Business Channel</p>
|
||||
|
||||
<div style={styles.checkboxContainer}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="agreement"
|
||||
style={styles.checkbox}
|
||||
checked={isChecked}
|
||||
onChange={handleCheckboxChange}
|
||||
/>
|
||||
<label htmlFor="agreement" style={styles.checkboxLabel}>
|
||||
Create Your Own Channel
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{errorMessage && (
|
||||
<p style={{ color: 'red', fontSize: '0.875rem' }}>{errorMessage}</p>
|
||||
)}
|
||||
|
||||
<button onClick={onBack} style={styles.backButton}>
|
||||
Back
|
||||
</button>
|
||||
<button onClick={handleNextStep} style={styles.startButton}>
|
||||
Next Step
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// LastContentComponent (ThirdComponent)
|
||||
const LastContentComponent = ({ onBack }) => {
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
|
||||
const handleCheckboxChange = (event) => {
|
||||
setIsChecked(event.target.checked);
|
||||
setErrorMessage(''); // Reset error message if checkbox is changed
|
||||
};
|
||||
|
||||
const handleFinish = () => {
|
||||
if (!isChecked) {
|
||||
setErrorMessage('Please confirm agreement on above!');
|
||||
} else {
|
||||
// Perform the final action here (e.g., submit form, complete registration)
|
||||
alert('Registration Complete');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={styles.rowContainer}>
|
||||
<div style={styles.col6}>
|
||||
<StepComponent activeStep={2} /> {/* Displays step 3 as active */}
|
||||
</div>
|
||||
<div style={styles.col6}>
|
||||
<p>Ensure your business is fully verified by completing the required steps on Facebook. This process is essential to activate and manage your WhatsApp Business API effectively.</p>
|
||||
|
||||
<div style={styles.checkboxContainer}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="agreement"
|
||||
style={styles.checkbox}
|
||||
checked={isChecked}
|
||||
onChange={handleCheckboxChange}
|
||||
/>
|
||||
<label htmlFor="agreement" style={styles.checkboxLabel}>
|
||||
Verify Your Business on Facebook
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{errorMessage && (
|
||||
<p style={{ color: 'red', fontSize: '0.875rem' }}>{errorMessage}</p>
|
||||
)}
|
||||
|
||||
<button onClick={onBack} style={styles.backButton}>
|
||||
Back
|
||||
</button>
|
||||
<button onClick={handleFinish} style={styles.startButton}>
|
||||
Finish
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Main Component
|
||||
const RegisterStep = () => {
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const handleStartNow = () => {
|
||||
setActiveStep(1); // Move to the second step
|
||||
};
|
||||
|
||||
const handleNextStep = () => {
|
||||
setActiveStep(activeStep + 1); // Move to the next step
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
setActiveStep(activeStep - 1); // Move to the previous step
|
||||
};
|
||||
|
||||
const responsiveStyles = isMobile
|
||||
? { flexDirection: 'column', alignItems: 'flex-start' }
|
||||
: { flexDirection: 'row' };
|
||||
|
||||
return (
|
||||
<div>
|
||||
<HeaderComponent />
|
||||
<div style={{ ...styles.rowContainer, ...responsiveStyles }}>
|
||||
{activeStep === 0 && (
|
||||
<FirstContentComponent onStartNow={handleStartNow} />
|
||||
)}
|
||||
{activeStep === 1 && (
|
||||
<SecondContentComponent onNextStep={handleNextStep} onBack={handleBack} />
|
||||
)}
|
||||
{activeStep === 2 && (
|
||||
<LastContentComponent onBack={handleBack} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default RegisterStep
|
||||
|
||||
const styles = {
|
||||
rowContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
marginTop: '20px',
|
||||
flexWrap: 'wrap',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #ddd',
|
||||
boxShadow: '0px 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
minHeight: '200px',
|
||||
},
|
||||
col6: {
|
||||
flex: '1 1 48%', // Membagi ruang menjadi 2 kolom
|
||||
padding: '10px',
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
headerContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: '20px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #ddd',
|
||||
boxShadow: '0px 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
minHeight: '250px',
|
||||
},
|
||||
leftSideContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
},
|
||||
image: (width, height) => ({
|
||||
width: width,
|
||||
height: height,
|
||||
flex: '1 1 33%',
|
||||
}),
|
||||
textContainer: {
|
||||
flex: '1 1 66%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-end',
|
||||
paddingLeft: '20px',
|
||||
},
|
||||
textTitle: {
|
||||
fontSize: '24px',
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '10px',
|
||||
},
|
||||
textBody: {
|
||||
fontSize: '16px',
|
||||
lineHeight: '1.5',
|
||||
},
|
||||
// Step Styles
|
||||
stepsContainer: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '10px', // Jarak antar langkah
|
||||
position: 'relative',
|
||||
},
|
||||
step: {
|
||||
flex: '1 1 16.6%', // Membagi langkah dalam 1/6 bagian dari lebar container
|
||||
padding: '10px',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center',
|
||||
marginBottom: '10px',
|
||||
position: 'relative',
|
||||
},
|
||||
stepNumberContainer: {
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: '8px',
|
||||
transition: 'background-color 0.3s ease',
|
||||
},
|
||||
stepNumber: {
|
||||
fontSize: '16px',
|
||||
color: '#fff',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
stepDescription: {
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.5',
|
||||
color: '#333',
|
||||
textAlign: 'center',
|
||||
margin: '10px 0',
|
||||
},
|
||||
// Content Styles
|
||||
contentContainer: {
|
||||
marginTop: '20px',
|
||||
padding: '20px',
|
||||
},
|
||||
infoBox: {
|
||||
padding: '20px',
|
||||
marginBottom: '20px',
|
||||
},
|
||||
descriptionList: {
|
||||
listStyleType: 'disc',
|
||||
paddingLeft: '20px',
|
||||
color: '#333',
|
||||
fontSize: '14px',
|
||||
},
|
||||
descriptionText: {
|
||||
marginLeft: '0',
|
||||
paddingLeft: '0',
|
||||
fontSize: '14px',
|
||||
color: '#333',
|
||||
},
|
||||
startButton: {
|
||||
backgroundColor: '#0542cc',
|
||||
color: '#fff',
|
||||
padding: '10px 20px',
|
||||
border: 'none',
|
||||
borderRadius: '5px',
|
||||
cursor: 'pointer',
|
||||
marginTop: '15px',
|
||||
},
|
||||
checkboxContainer: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginBottom: '10px',
|
||||
},
|
||||
checkbox: {
|
||||
marginRight: '10px',
|
||||
},
|
||||
checkboxLabel: {
|
||||
color: '#0542cc',
|
||||
fontSize: '14px',
|
||||
},
|
||||
backButton: {
|
||||
padding: '10px 20px',
|
||||
backgroundColor: '#fff', // Background color updated to white
|
||||
color: '#000', // Text color set to black
|
||||
border: '2px solid #0542cc', // Border color set to #0542cc
|
||||
borderRadius: '5px',
|
||||
cursor: 'pointer',
|
||||
marginRight: '10px',
|
||||
},
|
||||
|
||||
};
|
||||
9
src/screens/Wa/Manage/Content/index.js
Normal file
9
src/screens/Wa/Manage/Content/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import RegisterContent from "./RegisterContent";
|
||||
import RegisterStep from "./RegisterStep";
|
||||
import CreateSettings from "./CreateSettings";
|
||||
|
||||
export {
|
||||
RegisterContent,
|
||||
RegisterStep,
|
||||
CreateSettings
|
||||
}
|
||||
437
src/screens/Wa/Manage/Integration.jsx
Normal file
437
src/screens/Wa/Manage/Integration.jsx
Normal file
@@ -0,0 +1,437 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
FaChevronLeft,
|
||||
FaChevronRight,
|
||||
FaFastBackward,
|
||||
FaFastForward,
|
||||
FaSort,
|
||||
FaSortUp,
|
||||
FaSortDown,
|
||||
FaEdit,
|
||||
FaEye,
|
||||
FaTrash
|
||||
} from 'react-icons/fa'; // Icons for sorting
|
||||
import { NoAvailable } from '../../../assets/icon';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// Pagination Component
|
||||
const Pagination = ({ currentPage, totalPages, onPageChange }) => {
|
||||
const handlePrev = () => {
|
||||
if (currentPage > 1) {
|
||||
onPageChange(currentPage - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
if (currentPage < totalPages) {
|
||||
onPageChange(currentPage + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFirst = () => {
|
||||
onPageChange(1); // Go to first page
|
||||
};
|
||||
|
||||
const handleLast = () => {
|
||||
onPageChange(totalPages); // Go to last page
|
||||
};
|
||||
|
||||
// Logic to display only 3 pages in pagination
|
||||
const getPaginationRange = () => {
|
||||
const range = [];
|
||||
const totalPagesCount = totalPages;
|
||||
|
||||
let start = currentPage - 1;
|
||||
let end = currentPage + 1;
|
||||
|
||||
// Adjust start and end if near the boundaries
|
||||
if (currentPage === 1) {
|
||||
start = 1;
|
||||
end = Math.min(3, totalPagesCount);
|
||||
} else if (currentPage === totalPages) {
|
||||
start = Math.max(totalPagesCount - 2, 1);
|
||||
end = totalPagesCount;
|
||||
}
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
range.push(i);
|
||||
}
|
||||
|
||||
return range;
|
||||
};
|
||||
|
||||
const pageRange = getPaginationRange();
|
||||
|
||||
return (
|
||||
<div className="pagination-container d-flex justify-content-end mt-4">
|
||||
{/* First Page Button */}
|
||||
<button
|
||||
className="btn"
|
||||
onClick={handleFirst}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
<FaFastBackward /> {/* Double Arrow Left */}
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="btn"
|
||||
onClick={handlePrev}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
<FaChevronLeft /> {/* Single Arrow Left */}
|
||||
</button>
|
||||
|
||||
{/* Page Numbers */}
|
||||
{pageRange.map((pageNum) => (
|
||||
<button
|
||||
key={pageNum}
|
||||
className={`btn ${pageNum === currentPage ? 'btn-primary' : ''}`}
|
||||
onClick={() => onPageChange(pageNum)}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
))}
|
||||
|
||||
<button
|
||||
className="btn"
|
||||
onClick={handleNext}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
<FaChevronRight /> {/* Single Arrow Right */}
|
||||
</button>
|
||||
|
||||
{/* Last Page Button */}
|
||||
<button
|
||||
className="btn"
|
||||
onClick={handleLast}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
<FaFastForward /> {/* Double Arrow Right */}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Integration = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [isMobile, setIsMobile] = useState(false); // State to detect mobile view
|
||||
const [transactionData, setTransactionData] = useState([]);
|
||||
const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' }); // Sorting state
|
||||
const dataPerPage = 10; // Data per page (10 data per page)
|
||||
|
||||
const buttonData = [
|
||||
{ label: 'Copy', enabled: true },
|
||||
{ label: 'CSV', enabled: true },
|
||||
{ label: 'Excel', enabled: true },
|
||||
{ label: 'PDF', enabled: true },
|
||||
{ label: 'Print', enabled: true },
|
||||
{ label: 'Column Visibility', enabled: true },
|
||||
];
|
||||
|
||||
|
||||
// Generate 691 dummy transactions
|
||||
const generateDummyData = (numOfItems) => {
|
||||
const transactionData = [];
|
||||
|
||||
for (let i = 1; i <= numOfItems; i++) {
|
||||
transactionData.push({
|
||||
channelName: `Channel ${Math.floor(Math.random() * 5) + 1}`, // Random channel name (e.g., Channel 1, Channel 2, ...)
|
||||
channelId: `CH${String(i).padStart(3, '0')}`, // Random channel ID (e.g., CH001, CH002, ...)
|
||||
phoneNumber: `+1${Math.floor(Math.random() * 1000000000)}`, // Random phone number (e.g., +1234567890)
|
||||
status: ['Active', 'Inactive', 'Suspended'][Math.floor(Math.random() * 3)], // Random status
|
||||
creationDate: new Date(2023, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1).toLocaleDateString(), // Random date
|
||||
tier: ['Tier 1', 'Tier 2', 'Tier 3', 'Tier 4', 'Tier 5'][Math.floor(Math.random() * 5)], // Random tier
|
||||
officialBusinessAccount: `Business ${Math.floor(Math.random() * 5) + 1}`, // Random business account (e.g., Business 1, Business 2, ...)
|
||||
});
|
||||
}
|
||||
|
||||
return transactionData;
|
||||
};
|
||||
|
||||
|
||||
// Set the generated transaction data
|
||||
useEffect(() => {
|
||||
setTransactionData(generateDummyData(52)); // count data dummy transactions
|
||||
}, []);
|
||||
|
||||
// Sorting function
|
||||
const sortData = (data, config) => {
|
||||
const { key, direction } = config;
|
||||
return [...data].sort((a, b) => {
|
||||
if (a[key] < b[key]) {
|
||||
return direction === 'asc' ? -1 : 1;
|
||||
}
|
||||
if (a[key] > b[key]) {
|
||||
return direction === 'asc' ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
|
||||
// Handle column header sort click
|
||||
const handleSort = (key) => {
|
||||
let direction = 'asc';
|
||||
if (sortConfig.key === key && sortConfig.direction === 'asc') {
|
||||
direction = 'desc'; // Toggle direction if the same column is clicked
|
||||
}
|
||||
setSortConfig({ key, direction });
|
||||
};
|
||||
|
||||
// Get the paginated data
|
||||
const getPaginatedData = (data, page, perPage) => {
|
||||
const sortedData = sortData(data, sortConfig);
|
||||
const startIndex = (page - 1) * perPage;
|
||||
const endIndex = startIndex + perPage;
|
||||
return sortedData.slice(startIndex, endIndex);
|
||||
};
|
||||
|
||||
// Handle page change
|
||||
const handlePageChange = (page) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
// Calculate total pages based on the data and data per page
|
||||
const totalPages = Math.ceil(transactionData.length / dataPerPage);
|
||||
|
||||
// Paginated data
|
||||
const paginatedData = getPaginatedData(transactionData, currentPage, dataPerPage);
|
||||
|
||||
// Detect screen size and update isMobile state
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setIsMobile(window.innerWidth <= 768); // Change 768 to your breakpoint
|
||||
};
|
||||
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
// Define your handle functions
|
||||
const handleEdit = (transactionId) => {
|
||||
console.log(`Edit transaction with ID: ${transactionId}`);
|
||||
// Add your edit logic here
|
||||
};
|
||||
|
||||
const handleShow = (transactionId) => {
|
||||
console.log(`Show transaction with ID: ${transactionId}`);
|
||||
// Add your show logic here
|
||||
};
|
||||
|
||||
const handleDelete = (transactionId) => {
|
||||
console.log(`Delete transaction with ID: ${transactionId}`);
|
||||
// Add your delete logic here
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mt-5">
|
||||
{/* Welcome Message */}
|
||||
<div className="row-card border-left border-primary shadow mb-4" style={{ backgroundColor: '#E2FBEA' }}>
|
||||
<div className="d-flex flex-column justify-content-start align-items-start p-4">
|
||||
<div>
|
||||
<h4 className="mb-3 text-start">
|
||||
<i className="fas fa-warning fa-bold me-3"></i>Alert
|
||||
</h4>
|
||||
<p className="mb-0 text-start">
|
||||
Get started now by creating an Application ID and explore all the demo services available on the dashboard.
|
||||
Experience the ease and flexibility of trying out all our features firsthand.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={styles.contentContainer}>
|
||||
{/* Filter Form */}
|
||||
<div className="card p-3 mb-4">
|
||||
<div className="row d-flex justify-content-around"> {/* Using Flexbox */}
|
||||
|
||||
{/* Dropdown: Business Account */}
|
||||
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
|
||||
<select className="form-control">
|
||||
<option>Select Application</option>
|
||||
<option>App 1</option>
|
||||
<option>App 2</option>
|
||||
<option>App 3</option>
|
||||
<option>App 4</option>
|
||||
<option>App 5</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Dropdown: Status */}
|
||||
<div className={`col-sm-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
|
||||
<select className="form-control">
|
||||
<option>Select Status</option>
|
||||
<option>Status 1</option>
|
||||
<option>Status 2</option>
|
||||
<option>Status 3</option>
|
||||
<option>Status 4</option>
|
||||
<option>Status 5</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Apply and Cancel Buttons */}
|
||||
<div className={`col-12 ${isMobile ? 'd-flex justify-content-between' : 'col-md-2 d-flex align-items-end'}`} style={{ gap: '10px' }}>
|
||||
<button className="btn btn-primary w-48">Apply</button>
|
||||
<button className="btn btn-secondary w-48">Cancel</button>
|
||||
</div>
|
||||
|
||||
{/* Create Settings Button with Icon */}
|
||||
<div className={`col-12 ${isMobile ? 'd-flex justify-content-end' : 'col-md-2 d-flex justify-content-end align-items-end'}`} style={{ gap: '10px' }}>
|
||||
<Link to="/wa-createSettings" style={{ textDecoration: 'none' }}>
|
||||
<button className="btn btn-primary">
|
||||
<i className="fas fa-cogs me-2"></i> {/* Font Awesome icon */}
|
||||
Create Settings
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<div className="table-responsive">
|
||||
<table className="table table-bordered" style={styles.tableContainer}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>No.</th> {/* Kolom untuk Nomor Urut */}
|
||||
<th>
|
||||
<button className="btn" onClick={() => handleSort('channelName')} style={styles.buttonStyle}>
|
||||
<span>Channel Name</span>
|
||||
{sortConfig.key === 'channelName' &&
|
||||
(sortConfig.direction === 'asc' ?
|
||||
<FaSortUp style={styles.iconMarginLeft} /> :
|
||||
<FaSortDown style={styles.iconMarginLeft} />
|
||||
)
|
||||
}
|
||||
{sortConfig.key !== 'channelName' && <FaSort style={styles.iconMarginLeft} />}
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
<button className="btn" onClick={() => handleSort('channelId')} style={styles.buttonStyle}>
|
||||
Channel ID
|
||||
{sortConfig.key === 'channelId' &&
|
||||
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
|
||||
}
|
||||
{sortConfig.key !== 'channelId' && <FaSort style={styles.iconMarginLeft} />}
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
<button className="btn" onClick={() => handleSort('phoneNumber')} style={styles.buttonStyle}>
|
||||
Phone Number
|
||||
{sortConfig.key === 'phoneNumber' &&
|
||||
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
|
||||
}
|
||||
{sortConfig.key !== 'phoneNumber' && <FaSort style={styles.iconMarginLeft} />}
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
<button className="btn" onClick={() => handleSort('status')} style={styles.buttonStyle}>
|
||||
Status
|
||||
{sortConfig.key === 'status' &&
|
||||
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
|
||||
}
|
||||
{sortConfig.key !== 'status' && <FaSort style={styles.iconMarginLeft} />}
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
<button className="btn" onClick={() => handleSort('creationDate')} style={styles.buttonStyle}>
|
||||
Creation Date
|
||||
{sortConfig.key === 'creationDate' &&
|
||||
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
|
||||
}
|
||||
{sortConfig.key !== 'creationDate' && <FaSort style={styles.iconMarginLeft} />}
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
<button className="btn" onClick={() => handleSort('tier')} style={styles.buttonStyle}>
|
||||
Tier
|
||||
{sortConfig.key === 'tier' &&
|
||||
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
|
||||
}
|
||||
{sortConfig.key !== 'tier' && <FaSort style={styles.iconMarginLeft} />}
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
<button className="btn" onClick={() => handleSort('officialBusinessAccount')} style={styles.buttonStyle}>
|
||||
Official Business Account
|
||||
{sortConfig.key === 'officialBusinessAccount' &&
|
||||
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
|
||||
}
|
||||
{sortConfig.key !== 'officialBusinessAccount' && <FaSort style={styles.iconMarginLeft} />}
|
||||
</button>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{paginatedData.length > 0 ? (
|
||||
paginatedData.map((transaction, index) => (
|
||||
<tr key={index}>
|
||||
{/* Kolom Nomor Urut */}
|
||||
<td>{(currentPage - 1) * dataPerPage + index + 1}</td> {/* Nomor urut berdasarkan halaman dan index */}
|
||||
<td>{transaction.channelName}</td>
|
||||
<td>{transaction.channelId}</td>
|
||||
<td>{transaction.phoneNumber}</td>
|
||||
<td>{transaction.status}</td>
|
||||
<td>{transaction.creationDate}</td>
|
||||
<td>{transaction.tier}</td>
|
||||
<td>{transaction.officialBusinessAccount}</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan="7" className="text-center">
|
||||
<div className="d-flex flex-column align-items-center mt-5">
|
||||
<img src={NoAvailable} alt="No Data Available" className="mb-3" style={styles.iconStyle} />
|
||||
<p>Data not available</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Pagination */}
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={handlePageChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Integration;
|
||||
|
||||
const styles = {
|
||||
contentContainer: {
|
||||
padding: '20px',
|
||||
border: '0.1px solid rgba(0, 0, 0, 0.2)',
|
||||
borderLeft: '4px solid #0542cc',
|
||||
borderRadius: '10px',
|
||||
width: '100%',
|
||||
},
|
||||
tableContainer: {
|
||||
minHeight: '300px',
|
||||
maxHeight: '1500px',
|
||||
overflowY: 'auto',
|
||||
},
|
||||
iconStyle: {
|
||||
width: '50px',
|
||||
height: '50px',
|
||||
},
|
||||
// Add margin-left style for icons
|
||||
iconMarginLeft: {
|
||||
marginLeft: '0.5rem',
|
||||
verticalAlign: 'middle',
|
||||
},
|
||||
buttonStyle: {
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
padding: '0.5rem 1rem',
|
||||
},
|
||||
};
|
||||
|
||||
486
src/screens/Wa/Manage/Profile.jsx
Normal file
486
src/screens/Wa/Manage/Profile.jsx
Normal file
@@ -0,0 +1,486 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
FaChevronLeft,
|
||||
FaChevronRight,
|
||||
FaFastBackward,
|
||||
FaFastForward,
|
||||
FaSort,
|
||||
FaSortUp,
|
||||
FaSortDown,
|
||||
FaEdit,
|
||||
FaEye,
|
||||
FaTrash
|
||||
} from 'react-icons/fa'; // Icons for sorting
|
||||
import { NoAvailable } from '../../../assets/icon';
|
||||
|
||||
// Pagination Component
|
||||
const Pagination = ({ currentPage, totalPages, onPageChange }) => {
|
||||
const handlePrev = () => {
|
||||
if (currentPage > 1) {
|
||||
onPageChange(currentPage - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
if (currentPage < totalPages) {
|
||||
onPageChange(currentPage + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFirst = () => {
|
||||
onPageChange(1); // Go to first page
|
||||
};
|
||||
|
||||
const handleLast = () => {
|
||||
onPageChange(totalPages); // Go to last page
|
||||
};
|
||||
|
||||
// Logic to display only 3 pages in pagination
|
||||
const getPaginationRange = () => {
|
||||
const range = [];
|
||||
const totalPagesCount = totalPages;
|
||||
|
||||
let start = currentPage - 1;
|
||||
let end = currentPage + 1;
|
||||
|
||||
// Adjust start and end if near the boundaries
|
||||
if (currentPage === 1) {
|
||||
start = 1;
|
||||
end = Math.min(3, totalPagesCount);
|
||||
} else if (currentPage === totalPages) {
|
||||
start = Math.max(totalPagesCount - 2, 1);
|
||||
end = totalPagesCount;
|
||||
}
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
range.push(i);
|
||||
}
|
||||
|
||||
return range;
|
||||
};
|
||||
|
||||
const pageRange = getPaginationRange();
|
||||
|
||||
return (
|
||||
<div className="pagination-container d-flex justify-content-end mt-4">
|
||||
{/* First Page Button */}
|
||||
<button
|
||||
className="btn"
|
||||
onClick={handleFirst}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
<FaFastBackward /> {/* Double Arrow Left */}
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="btn"
|
||||
onClick={handlePrev}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
<FaChevronLeft /> {/* Single Arrow Left */}
|
||||
</button>
|
||||
|
||||
{/* Page Numbers */}
|
||||
{pageRange.map((pageNum) => (
|
||||
<button
|
||||
key={pageNum}
|
||||
className={`btn ${pageNum === currentPage ? 'btn-primary' : ''}`}
|
||||
onClick={() => onPageChange(pageNum)}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
))}
|
||||
|
||||
<button
|
||||
className="btn"
|
||||
onClick={handleNext}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
<FaChevronRight /> {/* Single Arrow Right */}
|
||||
</button>
|
||||
|
||||
{/* Last Page Button */}
|
||||
<button
|
||||
className="btn"
|
||||
onClick={handleLast}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
<FaFastForward /> {/* Double Arrow Right */}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Profile = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [isMobile, setIsMobile] = useState(false); // State to detect mobile view
|
||||
const [transactionData, setTransactionData] = useState([]);
|
||||
const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' }); // Sorting state
|
||||
const dataPerPage = 10; // Data per page (10 data per page)
|
||||
|
||||
const buttonData = [
|
||||
{ label: 'Copy', enabled: true },
|
||||
{ label: 'CSV', enabled: true },
|
||||
{ label: 'Excel', enabled: true },
|
||||
{ label: 'PDF', enabled: true },
|
||||
{ label: 'Print', enabled: true },
|
||||
{ label: 'Column Visibility', enabled: true },
|
||||
];
|
||||
|
||||
|
||||
// Generate 691 dummy transactions
|
||||
const generateDummyData = (numOfItems) => {
|
||||
const transactionData = [];
|
||||
|
||||
for (let i = 1; i <= numOfItems; i++) {
|
||||
transactionData.push({
|
||||
channelName: `Channel ${Math.floor(Math.random() * 5) + 1}`, // Random channel name (e.g., Channel 1, Channel 2, ...)
|
||||
channelId: `CH${String(i).padStart(3, '0')}`, // Random channel ID (e.g., CH001, CH002, ...)
|
||||
phoneNumber: `+1${Math.floor(Math.random() * 1000000000)}`, // Random phone number (e.g., +1234567890)
|
||||
status: ['Active', 'Inactive', 'Suspended'][Math.floor(Math.random() * 3)], // Random status
|
||||
creationDate: new Date(2023, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1).toLocaleDateString(), // Random date
|
||||
officialBusinessAccount: `Business ${Math.floor(Math.random() * 5) + 1}`, // Random business account (e.g., Business 1, Business 2, ...)
|
||||
});
|
||||
}
|
||||
|
||||
return transactionData;
|
||||
};
|
||||
|
||||
|
||||
// Set the generated transaction data
|
||||
useEffect(() => {
|
||||
setTransactionData(generateDummyData(52)); // count data dummy transactions
|
||||
}, []);
|
||||
|
||||
// Sorting function
|
||||
const sortData = (data, config) => {
|
||||
const { key, direction } = config;
|
||||
return [...data].sort((a, b) => {
|
||||
if (a[key] < b[key]) {
|
||||
return direction === 'asc' ? -1 : 1;
|
||||
}
|
||||
if (a[key] > b[key]) {
|
||||
return direction === 'asc' ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
|
||||
// Handle column header sort click
|
||||
const handleSort = (key) => {
|
||||
let direction = 'asc';
|
||||
if (sortConfig.key === key && sortConfig.direction === 'asc') {
|
||||
direction = 'desc'; // Toggle direction if the same column is clicked
|
||||
}
|
||||
setSortConfig({ key, direction });
|
||||
};
|
||||
|
||||
// Get the paginated data
|
||||
const getPaginatedData = (data, page, perPage) => {
|
||||
const sortedData = sortData(data, sortConfig);
|
||||
const startIndex = (page - 1) * perPage;
|
||||
const endIndex = startIndex + perPage;
|
||||
return sortedData.slice(startIndex, endIndex);
|
||||
};
|
||||
|
||||
// Handle page change
|
||||
const handlePageChange = (page) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
// Calculate total pages based on the data and data per page
|
||||
const totalPages = Math.ceil(transactionData.length / dataPerPage);
|
||||
|
||||
// Paginated data
|
||||
const paginatedData = getPaginatedData(transactionData, currentPage, dataPerPage);
|
||||
|
||||
// Detect screen size and update isMobile state
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setIsMobile(window.innerWidth <= 768); // Change 768 to your breakpoint
|
||||
};
|
||||
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
// Define your handle functions
|
||||
const handleEdit = (transactionId) => {
|
||||
console.log(`Edit transaction with ID: ${transactionId}`);
|
||||
// Add your edit logic here
|
||||
};
|
||||
|
||||
const handleShow = (transactionId) => {
|
||||
console.log(`Show transaction with ID: ${transactionId}`);
|
||||
// Add your show logic here
|
||||
};
|
||||
|
||||
const handleDelete = (transactionId) => {
|
||||
console.log(`Delete transaction with ID: ${transactionId}`);
|
||||
// Add your delete logic here
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mt-5">
|
||||
{/* Welcome Message */}
|
||||
<div className="row-card border-left border-primary shadow mb-4" style={{ backgroundColor: '#E2FBEA' }}>
|
||||
<div className="d-flex flex-column justify-content-start align-items-start p-4">
|
||||
<div>
|
||||
<h4 className="mb-3 text-start">
|
||||
<i className="fas fa-warning fa-bold me-3"></i>Alert
|
||||
</h4>
|
||||
<p className="mb-0 text-start">
|
||||
Get started now by creating an Application ID and explore all the demo services available on the dashboard.
|
||||
Experience the ease and flexibility of trying out all our features firsthand.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={styles.contentContainer}>
|
||||
{/* Filter Form */}
|
||||
<div className="card p-3 mb-4">
|
||||
<div className="row d-flex justify-content-arround"> {/* Menggunakan Flexbox */}
|
||||
{/* Dropdown: Business Account */}
|
||||
<div className={`col-sm-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
|
||||
<select className="form-control">
|
||||
<option>Select Business Account</option>
|
||||
<option>Account 1</option>
|
||||
<option>Account 2</option>
|
||||
<option>Account 3</option>
|
||||
<option>Account 4</option>
|
||||
<option>Account 5</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Input: Business Account Name */}
|
||||
<div className={`col-sm-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
|
||||
<input type="text" className="form-control" placeholder="Enter Business Account Name" />
|
||||
</div>
|
||||
|
||||
{/* Dropdown: Meta Manager ID */}
|
||||
<div className={`col-sm-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
|
||||
<select className="form-control">
|
||||
<option>Select Meta Manager</option>
|
||||
<option>Manager 1</option>
|
||||
<option>Manager 2</option>
|
||||
<option>Manager 3</option>
|
||||
<option>Manager 4</option>
|
||||
<option>Manager 5</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Dropdown: WABA ID */}
|
||||
<div className={`col-sm-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
|
||||
<select className="form-control">
|
||||
<option>Select WABA ID</option>
|
||||
<option>WABA 1</option>
|
||||
<option>WABA 2</option>
|
||||
<option>WABA 3</option>
|
||||
<option>WABA 4</option>
|
||||
<option>WABA 5</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
{buttonData.map((button, index) =>
|
||||
button.enabled ? (
|
||||
<button
|
||||
key={index}
|
||||
className={`btn btn-light ${isMobile ? 'mb-2' : ''}`} // Add margin on mobile
|
||||
style={styles.actionButton}
|
||||
>
|
||||
{button.label}
|
||||
</button>
|
||||
) : null
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Search Bar with Icon */}
|
||||
<div className="input-group" style={{ width: '250px', display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
className="form-control"
|
||||
/>
|
||||
<span className="input-group-text">
|
||||
<i className="fas fa-search"></i> {/* FontAwesome search icon */}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Table */}
|
||||
<div className="table-responsive">
|
||||
<table className="table table-bordered" style={styles.tableContainer}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>No.</th> {/* Kolom untuk Nomor Urut */}
|
||||
<th>
|
||||
<button className="btn" onClick={() => handleSort('channelName')} style={styles.buttonStyle}>
|
||||
<span>Channel Name</span>
|
||||
{sortConfig.key === 'channelName' &&
|
||||
(sortConfig.direction === 'asc' ?
|
||||
<FaSortUp style={styles.iconMarginLeft} /> :
|
||||
<FaSortDown style={styles.iconMarginLeft} />
|
||||
)
|
||||
}
|
||||
{sortConfig.key !== 'channelName' && <FaSort style={styles.iconMarginLeft} />}
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
<button className="btn" onClick={() => handleSort('channelId')} style={styles.buttonStyle}>
|
||||
Channel ID
|
||||
{sortConfig.key === 'channelId' &&
|
||||
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
|
||||
}
|
||||
{sortConfig.key !== 'channelId' && <FaSort style={styles.iconMarginLeft} />}
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
<button className="btn" onClick={() => handleSort('phoneNumber')} style={styles.buttonStyle}>
|
||||
Phone Number
|
||||
{sortConfig.key === 'phoneNumber' &&
|
||||
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
|
||||
}
|
||||
{sortConfig.key !== 'phoneNumber' && <FaSort style={styles.iconMarginLeft} />}
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
<button className="btn" onClick={() => handleSort('status')} style={styles.buttonStyle}>
|
||||
Status
|
||||
{sortConfig.key === 'status' &&
|
||||
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
|
||||
}
|
||||
{sortConfig.key !== 'status' && <FaSort style={styles.iconMarginLeft} />}
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
<button className="btn" onClick={() => handleSort('creationDate')} style={styles.buttonStyle}>
|
||||
Creation Date
|
||||
{sortConfig.key === 'creationDate' &&
|
||||
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
|
||||
}
|
||||
{sortConfig.key !== 'creationDate' && <FaSort style={styles.iconMarginLeft} />}
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
<button className="btn" onClick={() => handleSort('officialBusinessAccount')} style={styles.buttonStyle}>
|
||||
Official Business Account
|
||||
{sortConfig.key === 'officialBusinessAccount' &&
|
||||
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
|
||||
}
|
||||
{sortConfig.key !== 'officialBusinessAccount' && <FaSort style={styles.iconMarginLeft} />}
|
||||
</button>
|
||||
</th>
|
||||
<th style={{ textAlign: 'center', verticalAlign: 'middle' }}>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{paginatedData.length > 0 ? (
|
||||
paginatedData.map((transaction, index) => (
|
||||
<tr key={index}>
|
||||
{/* Kolom Nomor Urut */}
|
||||
<td>{(currentPage - 1) * dataPerPage + index + 1}</td> {/* Nomor urut berdasarkan halaman dan index */}
|
||||
<td>{transaction.channelName}</td>
|
||||
<td>{transaction.channelId}</td>
|
||||
<td>{transaction.phoneNumber}</td>
|
||||
<td>{transaction.status}</td>
|
||||
<td>{transaction.creationDate}</td>
|
||||
<td>{transaction.officialBusinessAccount}</td>
|
||||
<td style={{ textAlign: 'center' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '10px' }}>
|
||||
<button
|
||||
className="btn btn-warning"
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
onClick={() => handleEdit(transaction.transactionId)}
|
||||
>
|
||||
<FaEdit style={{ marginRight: '5px' }} />
|
||||
Edit
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="btn btn-success"
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
onClick={() => handleShow(transaction.transactionId)}
|
||||
>
|
||||
<FaEye style={{ marginRight: '5px' }} />
|
||||
Show
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="btn btn-danger"
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
onClick={() => handleDelete(transaction.transactionId)}
|
||||
>
|
||||
<FaTrash style={{ marginRight: '5px' }} />
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan="7" className="text-center">
|
||||
<div className="d-flex flex-column align-items-center mt-5">
|
||||
<img src={NoAvailable} alt="No Data Available" className="mb-3" style={styles.iconStyle} />
|
||||
<p>Data not available</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Pagination */}
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={handlePageChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Profile;
|
||||
|
||||
const styles = {
|
||||
contentContainer: {
|
||||
padding: '20px',
|
||||
border: '0.1px solid rgba(0, 0, 0, 0.2)',
|
||||
borderLeft: '4px solid #0542cc',
|
||||
borderRadius: '10px',
|
||||
width: '100%',
|
||||
},
|
||||
tableContainer: {
|
||||
minHeight: '300px',
|
||||
maxHeight: '1500px',
|
||||
overflowY: 'auto',
|
||||
},
|
||||
iconStyle: {
|
||||
width: '50px',
|
||||
height: '50px',
|
||||
},
|
||||
// Add margin-left style for icons
|
||||
iconMarginLeft: {
|
||||
marginLeft: '0.5rem',
|
||||
verticalAlign: 'middle',
|
||||
},
|
||||
buttonStyle: {
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
padding: '0.5rem 1rem',
|
||||
},
|
||||
};
|
||||
|
||||
29
src/screens/Wa/Manage/Registration.jsx
Normal file
29
src/screens/Wa/Manage/Registration.jsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react'
|
||||
import { RegisterContent } from './Content'
|
||||
|
||||
const Registration = () => {
|
||||
return (
|
||||
<div className='container' style={styles.container}>
|
||||
<div style={styles.section}>
|
||||
<h1>WA Manage</h1>
|
||||
<RegisterContent />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Registration
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
marginTop: '3%',
|
||||
padding: '0 15px',
|
||||
},
|
||||
section: {
|
||||
padding: '20px',
|
||||
border: '0.1px solid rgba(0, 0, 0, 0.2)',
|
||||
borderLeft: '4px solid #0542cc',
|
||||
borderRadius: '10px',
|
||||
width: '100%',
|
||||
},
|
||||
}
|
||||
346
src/screens/Wa/Manage/Template.jsx
Normal file
346
src/screens/Wa/Manage/Template.jsx
Normal file
@@ -0,0 +1,346 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
|
||||
const Template = () => {
|
||||
const BASE_URL = process.env.REACT_APP_BASE_URL;
|
||||
const API_KEY = process.env.REACT_APP_API_KEY;
|
||||
|
||||
const [ownership, setOwnership] = useState([]);
|
||||
const [buttonStates, setButtonStates] = useState([]);
|
||||
const [copiedIndex, setCopiedIndex] = useState(null);
|
||||
const [searchTerm, setSearchTerm] = useState(''); // state for search term
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(''); // state for debounced search term
|
||||
const [errorMessage, setErrorMessage] = useState(null); // state for error message
|
||||
|
||||
// Debounce search term input
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setDebouncedSearchTerm(searchTerm);
|
||||
}, 500); // Delay of 500ms before search term is sent
|
||||
return () => clearTimeout(timer); // Clear timeout when searchTerm changes
|
||||
}, [searchTerm]);
|
||||
|
||||
// Fetch applications based on the search term
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
const fetchApplications = async () => {
|
||||
if (debouncedSearchTerm) {
|
||||
// Fetch by search term
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/application/get-by-name/${debouncedSearchTerm}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'x-api-key': API_KEY,
|
||||
},
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
if (result.details && result.details.data) {
|
||||
if (isMounted) {
|
||||
setOwnership([result.details.data]);
|
||||
setButtonStates([{ isHovered: false, isActive: false }]);
|
||||
setErrorMessage(null); // Reset error if data found
|
||||
}
|
||||
} else {
|
||||
if (isMounted) {
|
||||
setOwnership([]);
|
||||
setErrorMessage('Data is not found.');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isMounted) {
|
||||
setOwnership([]);
|
||||
setErrorMessage('Data is not found.');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching applications:', error);
|
||||
if (isMounted) {
|
||||
setOwnership([]);
|
||||
setErrorMessage('Data is not found.');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fetch all applications when there's no search term
|
||||
const fetchAllApplications = async () => {
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/application/list`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-api-key': API_KEY,
|
||||
},
|
||||
});
|
||||
const result = await response.json();
|
||||
if (response.ok && result.status_code === 200) {
|
||||
if (isMounted) {
|
||||
setOwnership(result.details.data);
|
||||
setButtonStates(result.details.data.map(() => ({ isHovered: false, isActive: false })));
|
||||
setErrorMessage(null); // Reset error if data found
|
||||
}
|
||||
} else {
|
||||
if (isMounted) {
|
||||
setOwnership([]);
|
||||
setErrorMessage('Error fetching data. Please try again.');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching all applications:', error);
|
||||
if (isMounted) {
|
||||
setOwnership([]);
|
||||
setErrorMessage('Error fetching data. Please try again.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchAllApplications();
|
||||
}
|
||||
};
|
||||
|
||||
fetchApplications();
|
||||
|
||||
return () => {
|
||||
isMounted = false; // Set the flag to false on cleanup
|
||||
};
|
||||
|
||||
}, [debouncedSearchTerm]); // Trigger the effect when `debouncedSearchTerm` changes
|
||||
|
||||
const handleMouseEnter = (index) => {
|
||||
setButtonStates((prevStates) => {
|
||||
const newStates = [...prevStates];
|
||||
newStates[index].isHovered = true;
|
||||
return newStates;
|
||||
});
|
||||
};
|
||||
|
||||
const handleMouseLeave = (index) => {
|
||||
setButtonStates((prevStates) => {
|
||||
const newStates = [...prevStates];
|
||||
newStates[index].isHovered = false;
|
||||
return newStates;
|
||||
});
|
||||
};
|
||||
|
||||
const handleChange = (event) => {
|
||||
setSearchTerm(event.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
{/* Welcome Message */}
|
||||
<div className="row-card border-left border-primary shadow mb-4" style={styles.welcomeCard}>
|
||||
<div className="d-flex flex-column justify-content-start align-items-start p-4">
|
||||
<div>
|
||||
<h4 className="mb-3 text-start">
|
||||
<i className="fas fa-message fa-bold me-3"></i>Create WhatsApp Business Templates
|
||||
</h4>
|
||||
<p className="mb-0 text-start">
|
||||
Design pre-approved message templates tailored for various business needs, such as customer support, appointment reminders, order updates, and more. Streamline communication and ensure consistency with ready-to-use templates.
|
||||
</p>
|
||||
</div>
|
||||
<div className="d-flex flex-row mt-3">
|
||||
<Link to="/" style={{ textDecoration: 'none' }}>
|
||||
<button className="btn d-flex justify-content-center align-items-center me-2" style={styles.createButton}>
|
||||
<i className="fas fa-plus text-white me-2"></i>
|
||||
<p className="text-white mb-0">Create Template</p>
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={styles.wrapperCard}>
|
||||
<div className="card mt-3">
|
||||
<div className="row">
|
||||
{/* Filtering Options */}
|
||||
<div className="col-md-2">
|
||||
<select className="form-control">
|
||||
<option value="">Pilih WABA IDI</option>
|
||||
<option value="waba1">WABA 1</option>
|
||||
<option value="waba2">WABA 2</option>
|
||||
<option value="waba3">WABA 3</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="col-md-2">
|
||||
<select className="form-control">
|
||||
<option value="">Pilih Kategori</option>
|
||||
<option value="kategori1">Kategori 1</option>
|
||||
<option value="kategori2">Kategori 2</option>
|
||||
<option value="kategori3">Kategori 3</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="col-md-2">
|
||||
<select className="form-control">
|
||||
<option value="">Pilih Status</option>
|
||||
<option value="aktif">Aktif</option>
|
||||
<option value="non-aktif">Non-Aktif</option>
|
||||
<option value="pending">Pending</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="col-md-2 d-flex align-items-end" style={{ gap: '10px' }}>
|
||||
<button className="btn btn-primary w-100">Apply</button>
|
||||
<button className="btn btn-secondary w-100">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search Bar */}
|
||||
<div style={styles.searchInputWrapper}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Type your keywords here"
|
||||
style={styles.searchInput}
|
||||
value={searchTerm}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<button style={styles.searchButton}>
|
||||
<i className="fas fa-search" style={styles.searchIcon}></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Application Cards */}
|
||||
<div style={styles.cardContainer}>
|
||||
{/* Show message if no data or search result */}
|
||||
{ownership.length === 0 && !errorMessage && <p style={styles.noDataMessage}>Data is not found.</p>}
|
||||
|
||||
{ownership.map((item, index) => (
|
||||
<div key={item.id} style={styles.card}>
|
||||
<div style={styles.cardHeader}>
|
||||
<span style={styles.cardTitle}>{item.name}</span>
|
||||
</div>
|
||||
<div style={styles.cardContent}>
|
||||
<div style={styles.applicationIdWrapper}>
|
||||
<p><strong>WABA Ownership:</strong></p>
|
||||
<p>{item.name}</p>
|
||||
</div>
|
||||
|
||||
{/* Copy to Clipboard Button */}
|
||||
<CopyToClipboard
|
||||
key={index}
|
||||
text={item.name}
|
||||
onCopy={() => {
|
||||
setCopiedIndex(index);
|
||||
setTimeout(() => setCopiedIndex(null), 2000); // Hide "Copied" status after 2 seconds
|
||||
}}
|
||||
>
|
||||
<button
|
||||
style={{
|
||||
backgroundColor: copiedIndex === index ? '#4BA5E7' : '#0542cc',
|
||||
padding: '10px 20px',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onMouseEnter={() => handleMouseEnter(index)}
|
||||
onMouseLeave={() => handleMouseLeave(index)}
|
||||
>
|
||||
{copiedIndex === index ? (
|
||||
<i className="fas fa-check"></i> // Show check icon if copied
|
||||
) : (
|
||||
<i className="fas fa-copy"></i> // Show copy icon
|
||||
)}
|
||||
{copiedIndex === index ? 'Copied' : 'Copy'}
|
||||
</button>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Show error message */}
|
||||
{errorMessage && <p style={styles.errorMessage}>{errorMessage}</p>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
margin: '20px auto',
|
||||
maxWidth: '1200px',
|
||||
},
|
||||
welcomeCard: {
|
||||
backgroundColor: '#F1F3F6',
|
||||
borderColor: '#1e2b34',
|
||||
borderRadius: '5px',
|
||||
marginBottom: '20px',
|
||||
},
|
||||
createButton: {
|
||||
backgroundColor: '#1d3f68',
|
||||
color: '#fff',
|
||||
borderRadius: '5px',
|
||||
padding: '12px 20px',
|
||||
width: '150px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
wrapperCard: {
|
||||
padding: '20px',
|
||||
},
|
||||
searchInputWrapper: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginTop: '20px',
|
||||
marginBottom: '20px',
|
||||
},
|
||||
searchInput: {
|
||||
padding: '10px',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '5px',
|
||||
width: '300px',
|
||||
marginRight: '10px',
|
||||
},
|
||||
searchButton: {
|
||||
backgroundColor: '#1d3f68',
|
||||
border: 'none',
|
||||
color: 'white',
|
||||
padding: '10px 15px',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
searchIcon: {
|
||||
fontSize: '16px',
|
||||
},
|
||||
cardContainer: {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
|
||||
gap: '20px',
|
||||
},
|
||||
card: {
|
||||
backgroundColor: '#fff',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '10px',
|
||||
padding: '20px',
|
||||
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
|
||||
},
|
||||
cardHeader: {
|
||||
marginBottom: '10px',
|
||||
},
|
||||
cardTitle: {
|
||||
fontSize: '18px',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
cardContent: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
applicationIdWrapper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
noDataMessage: {
|
||||
color: 'gray',
|
||||
},
|
||||
errorMessage: {
|
||||
color: 'red',
|
||||
}
|
||||
};
|
||||
|
||||
export default Template;
|
||||
11
src/screens/Wa/Manage/index.js
Normal file
11
src/screens/Wa/Manage/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import Registration from './Registration';
|
||||
import Profile from './Profile';
|
||||
import Template from './Template';
|
||||
import Integration from './Integration';
|
||||
|
||||
export {
|
||||
Registration,
|
||||
Profile,
|
||||
Template,
|
||||
Integration
|
||||
}
|
||||
542
src/screens/Wa/Verify/Section/Auth.jsx
Normal file
542
src/screens/Wa/Verify/Section/Auth.jsx
Normal file
@@ -0,0 +1,542 @@
|
||||
import React, { useState, useRef, useEffect } from 'react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faChevronLeft, faChevronDown } from '@fortawesome/free-solid-svg-icons';
|
||||
import Select from 'react-select'
|
||||
|
||||
const BASE_URL = process.env.REACT_APP_BASE_URL
|
||||
const API_KEY = process.env.REACT_APP_API_KEY
|
||||
|
||||
const Verify = ({ phoneId, onVerify }) => {
|
||||
const [otpCode, setOtpCode] = useState('');
|
||||
const [generateCode, setGenerateCode] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handleClick = async () => {
|
||||
console.log('Verify Click');
|
||||
console.log('otp code: ', otpCode);
|
||||
console.log('generate code: ', generateCode);
|
||||
console.log('phone number: ', phoneId);
|
||||
|
||||
// Validation logic
|
||||
if (!otpCode) {
|
||||
setError("OTP Code is required."); // Set error message directly
|
||||
return; // Exit the function if there are errors
|
||||
} else if (otpCode.length !== 6) {
|
||||
setError("OTP Code must be exactly 6 characters long."); // Set error message directly
|
||||
return; // Exit the function if there are errors
|
||||
}
|
||||
|
||||
setError(''); // Clear error if validation passes
|
||||
onVerify(); // Call the onVerify function passed from Auth
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div style={styles.section}>
|
||||
{/* Results Data */}
|
||||
<div className="form-group row align-items-center mt-4">
|
||||
<div className="col-md-6">
|
||||
<div style={styles.selectWrapper}>
|
||||
<input
|
||||
type="text"
|
||||
id="generateCode"
|
||||
className="form-control"
|
||||
placeholder="e133d538-dc40-4c43-b042-06aad864d634"
|
||||
value={generateCode}
|
||||
onChange={(e) => setGenerateCode(e.target.value)}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-md-6">
|
||||
<div className="input-group">
|
||||
<span className="input-group-prepend">
|
||||
<span className="input-group-text">+62</span>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
id="phoneId"
|
||||
className="form-control"
|
||||
placeholder="Phone Number"
|
||||
value={phoneId}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* OTP Code */}
|
||||
<div className="form-group row align-items-center mt-3">
|
||||
<div className="col-md-6">
|
||||
<input
|
||||
type="text"
|
||||
id="otpCode"
|
||||
className="form-control"
|
||||
placeholder="OTP Code"
|
||||
value={otpCode}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
// Allow only digits and enforce exactly 6 digits
|
||||
if (/^\d{0,6}$/.test(value)) {
|
||||
setOtpCode(value);
|
||||
}
|
||||
}}
|
||||
maxLength={6} // Prevents entering more than 6 digits
|
||||
/>
|
||||
|
||||
{error && <small className="text-danger">{error}</small>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Verify Button */}
|
||||
<div style={styles.submitButton}>
|
||||
<button onClick={handleClick} className="btn d-flex justify-content-center align-items-center me-2" style={{ backgroundColor: '#0542CC' }}>
|
||||
<p className="text-white mb-0">Verify</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const Preview = () => {
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(inputValue);
|
||||
console.log('Copied');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="form-group row align-items-center mt-4">
|
||||
<div className="col-10">
|
||||
<div style={styles.selectWrapper}>
|
||||
<input
|
||||
type="text"
|
||||
id="inputValue"
|
||||
className="form-control"
|
||||
placeholder="7976 is your verification code. For your security, do not share this code."
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-2 d-flex">
|
||||
<div style={styles.selectWrapper}>
|
||||
<button className="btn btn-primary" onClick={handleCopy}>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Auth = () => {
|
||||
|
||||
const [isSelectOpen, setIsSelectOpen] = useState(false);
|
||||
const [applicationId, setApplicationId] = useState('');
|
||||
const [expiryId, setExpiryId] = useState(0);
|
||||
const [otpId, setOtpId] = useState('');
|
||||
const [phoneId, setPhoneId] = useState('');
|
||||
const [templateId, setTemplateId] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
|
||||
const [applicationIds, setApplicationIds] = useState([]);
|
||||
const [showVerify, setShowVerify] = useState(false);
|
||||
const [showPreview, setShowPreview] = useState(false);
|
||||
|
||||
const [errors, setErrors] = useState({});
|
||||
const [inputValueApplication, setInputValueApplication] = useState(''); // Controlled input value for Application ID
|
||||
const [isServer, setIsServer] = useState(true);
|
||||
const [selectedQuota, setSelectedQuota] = useState(0);
|
||||
|
||||
const applicationOptions = applicationIds.map(app => ({
|
||||
value: app.id,
|
||||
label: app.name
|
||||
}));
|
||||
|
||||
const handleApplicationChange = (selectedOption) => {
|
||||
const selectedId = selectedOption.value;
|
||||
const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId));
|
||||
|
||||
setApplicationId(selectedOption ? selectedOption.value : '');
|
||||
|
||||
if (selectedApp) {
|
||||
setSelectedQuota(selectedApp.quota);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChangeApplication = (newInputValue) => {
|
||||
// Limit input to 15 characters for Application ID
|
||||
if (newInputValue.length <= 15) {
|
||||
setInputValueApplication(newInputValue);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchApplicationIds = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await fetch(`${BASE_URL}/application/list`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'x-api-key': API_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch application IDs');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Response Data:', data);
|
||||
|
||||
if (data.status_code === 200) {
|
||||
setApplicationIds(data.details.data);
|
||||
setIsServer(true);
|
||||
} else {
|
||||
setIsServer(false);
|
||||
throw new Error(data.details.message || 'Failed to fetch application IDs');
|
||||
}
|
||||
} catch (error) {
|
||||
setErrors(error.message || 'Error fetching application IDs');
|
||||
setIsServer(false);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchApplicationIds();
|
||||
}, []);
|
||||
|
||||
const handleFocus = () => {
|
||||
setIsSelectOpen(true);
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
setIsSelectOpen(false);
|
||||
};
|
||||
|
||||
const handleVerify = () => {
|
||||
setShowPreview(true); // Show the preview when verified
|
||||
};
|
||||
|
||||
const validate = () => {
|
||||
const newErrors = {};
|
||||
if (!applicationId) newErrors.applicationId = "Application ID is required.";
|
||||
if (expiryId <= 0) newErrors.expiryId = "Expiry Time must be greater than 0.";
|
||||
if (!otpId) newErrors.otpId = "OTP Length is required.";
|
||||
if (!phoneId) newErrors.phoneId = "Phone Number is required.";
|
||||
if (!templateId) newErrors.templateId = "Template Name is required.";
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleClick = async () => {
|
||||
if (validate()) {
|
||||
// Log the input data
|
||||
console.log('Application ID:', applicationId);
|
||||
console.log('Expiry ID:', expiryId);
|
||||
console.log('OTP Length:', otpId);
|
||||
console.log('Phone Number:', '08' + phoneId); // Prepend "08" to the phone number
|
||||
console.log('Template ID:', templateId);
|
||||
|
||||
console.log('Make Auth Demo');
|
||||
setShowVerify(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={styles.section}>
|
||||
{/* Inject keyframes for the spinner */}
|
||||
<style>
|
||||
{`
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
|
||||
{isLoading && (
|
||||
<div style={styles.loadingOverlay}>
|
||||
<div style={styles.spinner}></div>
|
||||
</div>
|
||||
)}
|
||||
{/* Application ID Selection */}
|
||||
<div className="form-group row align-items-center">
|
||||
<div className="col-md-6">
|
||||
<Select
|
||||
id="applicationId"
|
||||
value={applicationOptions.find(option => option.value === applicationId)}
|
||||
onChange={handleApplicationChange}
|
||||
options={applicationOptions}
|
||||
placeholder="Select Application ID"
|
||||
isSearchable
|
||||
menuPortalTarget={document.body}
|
||||
menuPlacement="auto"
|
||||
inputValue={inputValueApplication}
|
||||
onInputChange={handleInputChangeApplication} // Limit input length for Application ID
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-md-6">
|
||||
<p className="text-secondary" style={{ fontSize: '16px', fontWeight: '400', margin: '0', marginTop: '8px' }}>
|
||||
Remaining Quota
|
||||
</p>
|
||||
<div style={styles.remainingQuota}>
|
||||
<span style={styles.quotaText}>{selectedQuota}</span>
|
||||
<span style={styles.timesText}>(times)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Expiry and OTP */}
|
||||
<div className="form-group row align-items-center">
|
||||
<div className="col-md-6">
|
||||
<div style={styles.selectWrapper}>
|
||||
<input
|
||||
type="number"
|
||||
id="expiryId"
|
||||
className="form-control"
|
||||
style={styles.select}
|
||||
value={expiryId}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
// Check if the value is empty or a valid number
|
||||
if (value === '' || /^[0-9]+$/.test(value)) {
|
||||
setExpiryId(value === '' ? '' : parseInt(value, 10)); // Update state as number
|
||||
}
|
||||
}}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
placeholder="Expiry Time"
|
||||
/>
|
||||
{errors.expiryId && <small className="text-danger">{errors.expiryId}</small>}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="col-md-6">
|
||||
<input
|
||||
type="text"
|
||||
id="otpId"
|
||||
className="form-control"
|
||||
placeholder="OTP Length"
|
||||
value={otpId}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
// Allow only digits and enforce length constraints
|
||||
if (/^\d{0,6}$/.test(value)) {
|
||||
setOtpId(value);
|
||||
}
|
||||
}}
|
||||
minLength={6} // Enforce minimum length (for validation)
|
||||
maxLength={6} // Enforce maximum length
|
||||
/>
|
||||
{errors.otpId && <small className="text-danger">{errors.otpId}</small>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Message and Phone */}
|
||||
<div className="form-group row align-items-center mt-4">
|
||||
<div className="col-md-6">
|
||||
<div style={styles.selectWrapper}>
|
||||
<input
|
||||
type="text"
|
||||
id="templateId"
|
||||
className="form-control"
|
||||
placeholder="Template Name"
|
||||
value={templateId}
|
||||
onChange={(e) => setTemplateId(e.target.value)}
|
||||
/>
|
||||
{errors.templateId && <small className="text-danger">{errors.templateId}</small>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-md-6">
|
||||
<div className="input-group">
|
||||
<span className="input-group-prepend">
|
||||
<span className="input-group-text">+62</span>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
id="phoneId"
|
||||
className="form-control"
|
||||
placeholder="Phone Number"
|
||||
value={phoneId}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
// Allow only digits and enforce length constraints
|
||||
if (/^\d*$/.test(value) && value.length <= 14) {
|
||||
setPhoneId(value);
|
||||
}
|
||||
}}
|
||||
minLength={9} // Enforce minimum length
|
||||
maxLength={14} // Enforce maximum length
|
||||
/>
|
||||
|
||||
</div>
|
||||
{errors.phoneId && <small className="text-danger">{errors.phoneId}</small>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div style={styles.submitButton}>
|
||||
<button onClick={handleClick} className="btn d-flex justify-content-center align-items-center me-2" style={{ backgroundColor: '#0542CC' }}>
|
||||
<p className="text-white mb-0">Make Authentication</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showVerify && <Verify phoneId={phoneId} onVerify={handleVerify} />}
|
||||
{showPreview && <Preview />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Auth;
|
||||
|
||||
|
||||
const styles = {
|
||||
section: {
|
||||
margin: '1rem 0 1rem 0'
|
||||
},
|
||||
formGroup: {
|
||||
marginTop: '-45px',
|
||||
},
|
||||
selectWrapper: {
|
||||
position: 'relative',
|
||||
marginTop: '0',
|
||||
},
|
||||
select: {
|
||||
width: '100%',
|
||||
paddingRight: '30px',
|
||||
flex: 1,
|
||||
fontSize: '16px',
|
||||
border: 'none',
|
||||
outline: 'none',
|
||||
},
|
||||
chevronIcon: {
|
||||
position: 'absolute',
|
||||
right: '10px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
remainingQuota: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: '4px',
|
||||
},
|
||||
quotaText: {
|
||||
fontSize: '40px',
|
||||
color: '#0542cc',
|
||||
fontWeight: '600',
|
||||
},
|
||||
timesText: {
|
||||
marginLeft: '8px',
|
||||
verticalAlign: 'super',
|
||||
fontSize: '20px',
|
||||
},
|
||||
buttonContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center', // Center vertically within container
|
||||
position: 'absolute',
|
||||
right: '10px',
|
||||
top: '0',
|
||||
bottom: '0',
|
||||
height: '47px',
|
||||
margin: 'auto 0',
|
||||
},
|
||||
button: {
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: '0', // Kurangi padding tombol
|
||||
margin: '0', // Hilangkan margin antar tombol
|
||||
fontSize: '12px',
|
||||
},
|
||||
icon: {
|
||||
fontSize: '14px',
|
||||
},
|
||||
submitButton: {
|
||||
marginLeft: 'auto',
|
||||
marginTop: '1.2rem',
|
||||
textAlign: 'start',
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
},
|
||||
uploadError: {
|
||||
color: 'red',
|
||||
fontSize: '12px',
|
||||
marginTop: '5px',
|
||||
},
|
||||
|
||||
containerResultStyle: {
|
||||
padding: '20px',
|
||||
border: '1px solid #0053b3',
|
||||
borderRadius: '5px',
|
||||
width: '100%',
|
||||
margin: '20px auto',
|
||||
},
|
||||
resultContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
},
|
||||
tableStyle: {
|
||||
width: '60%',
|
||||
borderCollapse: 'collapse',
|
||||
},
|
||||
imageContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
width: '30%',
|
||||
marginLeft: '10px'
|
||||
},
|
||||
imageStyle: {
|
||||
width: '193px',
|
||||
height: '242px',
|
||||
borderRadius: '5px',
|
||||
},
|
||||
loadingOverlay: {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
zIndex: 1000,
|
||||
},
|
||||
spinner: {
|
||||
border: '4px solid rgba(0, 0, 0, 0.1)',
|
||||
borderLeftColor: '#0542cc',
|
||||
borderRadius: '50%',
|
||||
width: '90px',
|
||||
height: '90px',
|
||||
animation: 'spin 1s ease-in-out infinite',
|
||||
},
|
||||
preview: {
|
||||
padding: '20px',
|
||||
border: '0.1px solid rgba(0, 0, 0, 0.2)',
|
||||
borderLeft: '4px solid #0542cc',
|
||||
borderRadius: '10px',
|
||||
width: '100%',
|
||||
marginTop: '1rem',
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
};
|
||||
691
src/screens/Wa/Verify/Section/Message.jsx
Normal file
691
src/screens/Wa/Verify/Section/Message.jsx
Normal file
@@ -0,0 +1,691 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faChevronDown, faChevronLeft, faCloudUploadAlt, faTimes, faImage } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FileUploader } from 'react-drag-drop-files';
|
||||
|
||||
|
||||
const BASE_URL = process.env.REACT_APP_BASE_URL
|
||||
const API_KEY = process.env.REACT_APP_API_KEY
|
||||
|
||||
const fileTypes = ['JPG', 'JPEG'];
|
||||
|
||||
const CustomLabel = ({ children, ...props }) => {
|
||||
return <label {...props}>{children}</label>;
|
||||
};
|
||||
|
||||
|
||||
const SingleMessage = ({ isSelectOpen, handleFocus, handleBlur }) => {
|
||||
const [applicationId, setApplicationId] = useState('');
|
||||
const [phoneId, setPhoneId] = useState('');
|
||||
const [variable_1, setVariable_1] = useState('');
|
||||
const [templateName, setTemplateName] = useState('');
|
||||
const [variable_2, setVariable_2] = useState('');
|
||||
const [preview, setPreview] = useState(null);
|
||||
const [errors, setErrors] = useState({});
|
||||
const [applicationIds, setApplicationIds] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchApplicationIds = async () => {
|
||||
setIsLoading(true);
|
||||
const url = `${BASE_URL}/application/list`;
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'x-api-key': API_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.status_code === 200) {
|
||||
setApplicationIds(data.details.data);
|
||||
} else {
|
||||
console.error('Failed to fetch data:', data.details.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching application IDs:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchApplicationIds();
|
||||
}, []);
|
||||
|
||||
const handleValidation = () => {
|
||||
const newErrors = {};
|
||||
if (!applicationId) newErrors.applicationId = "Applications ID is required.";
|
||||
if (!phoneId) newErrors.phoneId = "Phone Number is required.";
|
||||
if (!variable_1) newErrors.variable_1 = "Variable 1 is required.";
|
||||
if (!templateName) newErrors.templateName = "Template Name is required.";
|
||||
if (!variable_2) newErrors.variable_2 = "Variable 2 is required.";
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0; // Return true if no errors
|
||||
};
|
||||
|
||||
const handleButtonClick = () => {
|
||||
if (handleValidation()) {
|
||||
const formattedPhoneId = `08${phoneId}`;
|
||||
|
||||
// Log the input data
|
||||
console.log({
|
||||
applicationId,
|
||||
formattedPhoneId,
|
||||
variable_1,
|
||||
templateName,
|
||||
variable_2
|
||||
});
|
||||
|
||||
setPreview({
|
||||
message: `Thank you for RSVP, ${variable_1}! We're excited to see you soon.`,
|
||||
details: `Check the details and mark on your calendar:\nDate: Thursday, October 10, 2024 | 6:00 PM - 9:00 PM\nLocation: Stories SCBD, South Jakarta, Fairground Building, SCBD Lot No.14`
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Inject keyframes for the spinner */}
|
||||
<style>
|
||||
{`
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
|
||||
{isLoading && (
|
||||
<div style={styles.loadingOverlay}>
|
||||
<div style={styles.spinner}></div>
|
||||
</div>
|
||||
)}
|
||||
<div className="form-group row align-items-center">
|
||||
<div className="col-md-6">
|
||||
<div style={styles.selectWrapper}>
|
||||
<select
|
||||
id="applicationId"
|
||||
className="form-control"
|
||||
style={styles.select}
|
||||
value={applicationId}
|
||||
onChange={(e) => setApplicationId(e.target.value)}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
>
|
||||
<option value="">Select Application ID</option>
|
||||
{applicationIds.map((app) => (
|
||||
<option key={app.id} value={app.id}>
|
||||
{app.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<FontAwesomeIcon
|
||||
icon={isSelectOpen ? faChevronDown : faChevronLeft}
|
||||
style={styles.chevronIcon}
|
||||
/>
|
||||
</div>
|
||||
{errors.applicationId && <p className="text-danger">{errors.applicationId}</p>}
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<p className="text-secondary" style={{ fontSize: '16px', fontWeight: '400', margin: '0', marginTop: '8px' }}>
|
||||
Remaining Quota
|
||||
</p>
|
||||
<div style={styles.remainingQuota}>
|
||||
<span style={styles.quotaText}>0</span>
|
||||
<span style={styles.timesText}>(times)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group row align-items-center mt-4">
|
||||
<div className="col-md-6">
|
||||
<div className="input-group">
|
||||
<span className="input-group-prepend">
|
||||
<span className="input-group-text">+62</span>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
id="phoneId"
|
||||
className="form-control"
|
||||
placeholder="Phone Number"
|
||||
value={phoneId}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
// Allow only digits and enforce length constraints
|
||||
if (/^\d*$/.test(value) && value.length <= 14) {
|
||||
setPhoneId(value);
|
||||
}
|
||||
}}
|
||||
minLength={9}
|
||||
maxLength={14}
|
||||
/>
|
||||
</div>
|
||||
{errors.phoneId && <p className="text-danger">{errors.phoneId}</p>}
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<div className="input-group">
|
||||
<input
|
||||
type="text"
|
||||
id="variable_1"
|
||||
className="form-control"
|
||||
placeholder="Variables 1"
|
||||
value={variable_1}
|
||||
onChange={(e) => setVariable_1(e.target.value)}
|
||||
maxLength={15}
|
||||
/>
|
||||
</div>
|
||||
{errors.variable_1 && <p className="text-danger">{errors.variable_1}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group row align-items-center mt-4">
|
||||
<div className="col-md-6">
|
||||
<div className="input-group">
|
||||
<input
|
||||
type="text"
|
||||
id="templateName"
|
||||
className="form-control"
|
||||
placeholder="Template Name"
|
||||
value={templateName}
|
||||
onChange={(e) => setTemplateName(e.target.value)}
|
||||
maxLength={15}
|
||||
/>
|
||||
</div>
|
||||
{errors.templateName && <p className="text-danger">{errors.templateName}</p>}
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<div className="input-group">
|
||||
<input
|
||||
type="text"
|
||||
id="variable_2"
|
||||
className="form-control"
|
||||
placeholder="Variables 2"
|
||||
value={variable_2}
|
||||
onChange={(e) => setVariable_2(e.target.value)}
|
||||
maxLength={15}
|
||||
/>
|
||||
</div>
|
||||
{errors.variable_2 && <p className="text-danger">{errors.variable_2}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={styles.submitButton}>
|
||||
<button onClick={handleButtonClick} className="btn d-flex justify-content-center align-items-center me-2" style={{ backgroundColor: '#0542CC' }}>
|
||||
<p className="text-white mb-0">Make SMS Demo</p>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Preview Section */}
|
||||
{preview && (
|
||||
<div style={styles.preview}>
|
||||
<div style={styles.waMessagePreview}>
|
||||
<p><strong>[Sips & Synergy Executive]</strong></p>
|
||||
<p>{preview.message}</p>
|
||||
<p>{preview.details}</p>
|
||||
<button className="btn btn-primary mt-3">Maps Event Location</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const BulkMessage = ({ isSelectOpen, handleFocus, handleBlur, handleClick, onImageUpload }) => {
|
||||
const [applicationId, setApplicationId] = useState('');
|
||||
const [templateName, setTemplateName] = useState('');
|
||||
const [imageData, setImageData] = useState(null);
|
||||
const [selectedImageName, setSelectedImageName] = useState('');
|
||||
const [errors, setErrors] = useState({});
|
||||
const fileInputRef = useRef(null);
|
||||
const [applicationIds, setApplicationIds] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const fetchApplicationIds = async () => {
|
||||
setIsLoading(true);
|
||||
const url = `${BASE_URL}/application/list`;
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'x-api-key': API_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.status_code === 200) {
|
||||
setApplicationIds(data.details.data);
|
||||
} else {
|
||||
console.error('Failed to fetch data:', data.details.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching application IDs:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchApplicationIds();
|
||||
}, []);
|
||||
|
||||
const handleValidation = () => {
|
||||
const newErrors = {};
|
||||
if (!applicationId) newErrors.applicationId = "Application ID is required.";
|
||||
if (!templateName) newErrors.templateName = "Template Name is required.";
|
||||
if (!imageData) newErrors.imageData = "Image is required.";
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleImageUpload = (file) => {
|
||||
if (file) {
|
||||
setImageData(file);
|
||||
setSelectedImageName(file.name);
|
||||
if (onImageUpload) {
|
||||
onImageUpload(file);
|
||||
}
|
||||
} else {
|
||||
setErrors((prevErrors) => ({
|
||||
...prevErrors,
|
||||
imageData: "Image upload failed."
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleButtonClick = () => {
|
||||
if (handleValidation()) {
|
||||
handleClick();
|
||||
console.log('Form Data:', {
|
||||
applicationId,
|
||||
templateName,
|
||||
imageData
|
||||
});
|
||||
} else {
|
||||
console.log('Validation errors:', errors);
|
||||
}
|
||||
};
|
||||
|
||||
const handleImageCancel = () => {
|
||||
setImageData(null);
|
||||
setSelectedImageName('');
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Inject keyframes for the spinner */}
|
||||
<style>
|
||||
{`
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
|
||||
|
||||
<div className="form-group row align-items-center">
|
||||
<div className="col-md-6">
|
||||
<div style={styles.selectWrapper}>
|
||||
<select
|
||||
id="applicationId"
|
||||
className="form-control"
|
||||
style={styles.select}
|
||||
value={applicationId}
|
||||
onChange={(e) => setApplicationId(e.target.value)}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
>
|
||||
<option value="">Select Application ID</option>
|
||||
{applicationIds.map((app) => (
|
||||
<option key={app.id} value={app.id}>
|
||||
{app.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<FontAwesomeIcon
|
||||
icon={isSelectOpen ? faChevronDown : faChevronLeft}
|
||||
style={styles.chevronIcon}
|
||||
/>
|
||||
</div>
|
||||
{errors.applicationId && <p className="text-danger">{errors.applicationId}</p>}
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<p className="text-secondary" style={{ fontSize: '16px', fontWeight: '400', margin: '0', marginTop: '8px' }}>
|
||||
Remaining Quota
|
||||
</p>
|
||||
<div style={styles.remainingQuota}>
|
||||
<span style={styles.quotaText}>0</span>
|
||||
<span style={styles.timesText}>(times)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group row align-items-center mt-4">
|
||||
<div className="col-md-6">
|
||||
<div className="input-group">
|
||||
<input
|
||||
type="text"
|
||||
id="templateName"
|
||||
className="form-control"
|
||||
placeholder="Template Name"
|
||||
value={templateName}
|
||||
onChange={(e) => setTemplateName(e.target.value)}
|
||||
maxLength={15}
|
||||
/>
|
||||
</div>
|
||||
{errors.templateName && <p className="text-danger">{errors.templateName}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upload Section */}
|
||||
<div className='col-md-6'>
|
||||
<div className="row form-group mt-4">
|
||||
<CustomLabel htmlFor="uploadPhoto" style={styles.customLabel}>
|
||||
Upload Face Photo
|
||||
</CustomLabel>
|
||||
<FileUploader
|
||||
handleChange={handleImageUpload}
|
||||
name="file"
|
||||
types={fileTypes}
|
||||
multiple={false}
|
||||
onDrop={(files) => {
|
||||
// Ensure files array is not empty
|
||||
if (files.length > 0) {
|
||||
handleImageUpload(files[0]);
|
||||
}
|
||||
}}
|
||||
children={
|
||||
<div style={styles.uploadArea}>
|
||||
<FontAwesomeIcon icon={faCloudUploadAlt} style={styles.uploadIcon} />
|
||||
<p style={styles.uploadText}>Drag and Drop Here</p>
|
||||
<p>Or</p>
|
||||
<a href="#" onClick={() => fileInputRef.current.click()}>Browse</a>
|
||||
<p className="text-muted">Recommended size: 300x300 (Max File Size: 2MB)</p>
|
||||
<p className="text-muted">Supported file types: JPG, JPEG</p>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<input
|
||||
type="file"
|
||||
id="fileUpload"
|
||||
ref={fileInputRef}
|
||||
style={{ display: 'none' }}
|
||||
accept="image/jpeg, image/jpg"
|
||||
onChange={e => {
|
||||
if (e.target.files.length > 0) {
|
||||
handleImageUpload(e.target.files[0]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{errors.imageData && <small style={{ color: 'red' }}>{errors.imageData}</small>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Display uploaded image name */}
|
||||
{selectedImageName && (
|
||||
<div className="col-md-6 mt-4">
|
||||
<div style={styles.fileWrapper}>
|
||||
<FontAwesomeIcon icon={faImage} style={styles.imageIcon} />
|
||||
<div style={{ marginRight: '18rem', marginTop: '0.2rem' }}>
|
||||
<h5>Uploaded File:</h5>
|
||||
<p>{selectedImageName}</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<button
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer' }}
|
||||
onClick={handleImageCancel}
|
||||
aria-label="Remove uploaded image"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faTimes}
|
||||
style={styles.closeIcon}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={styles.selectWrapper}>
|
||||
<div style={styles.submitButton}>
|
||||
<button onClick={handleButtonClick} className="btn d-flex justify-content-center align-items-center me-2" style={{ backgroundColor: '#0542CC' }}>
|
||||
<p className="text-white mb-0">Make SMS Demo</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Message = () => {
|
||||
const [isSelectOpen, setIsSelectOpen] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState('single');
|
||||
const fileInputRef = useRef(null);
|
||||
|
||||
const handleFocus = () => {
|
||||
setIsSelectOpen(true);
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
setIsSelectOpen(false);
|
||||
};
|
||||
|
||||
const handleClick = async () => {
|
||||
console.log('Make SMS Demo');
|
||||
};
|
||||
|
||||
const handleImageUpload = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file && (file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg')) {
|
||||
console.log('File uploaded:', file.name); // Handle the file upload
|
||||
} else {
|
||||
alert('Please upload a valid image file (JPG, JPEG).');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Tab buttons */}
|
||||
<div>
|
||||
<button
|
||||
onClick={() => setActiveTab('single')}
|
||||
style={activeTab === 'single' ? styles.tabButton : styles.tabInactiveButton}
|
||||
>
|
||||
Single Message
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('bulk')}
|
||||
style={activeTab === 'bulk' ? styles.tabButton : styles.tabInactiveButton}
|
||||
>
|
||||
Bulk Message
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Conditional content based on active tab */}
|
||||
{activeTab === 'single' && <SingleMessage
|
||||
isSelectOpen={isSelectOpen}
|
||||
handleFocus={handleFocus}
|
||||
handleBlur={handleBlur}
|
||||
handleClick={handleClick}
|
||||
/>}
|
||||
{activeTab === 'bulk' && <BulkMessage
|
||||
isSelectOpen={isSelectOpen}
|
||||
handleFocus={handleFocus}
|
||||
handleBlur={handleBlur}
|
||||
handleClick={handleClick}
|
||||
handleImageUpload={handleImageUpload}
|
||||
fileInputRef={fileInputRef}
|
||||
/>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default Message;
|
||||
|
||||
const styles = {
|
||||
selectWrapper: {
|
||||
position: 'relative',
|
||||
marginTop: '0',
|
||||
},
|
||||
select: {
|
||||
width: '100%',
|
||||
paddingRight: '30px',
|
||||
flex: 1,
|
||||
fontSize: '16px',
|
||||
outline: 'none',
|
||||
},
|
||||
chevronIcon: {
|
||||
position: 'absolute',
|
||||
right: '10px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
remainingQuota: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: '4px',
|
||||
},
|
||||
quotaText: {
|
||||
fontSize: '40px',
|
||||
color: '#0542cc',
|
||||
fontWeight: '600',
|
||||
},
|
||||
timesText: {
|
||||
marginLeft: '8px',
|
||||
verticalAlign: 'super',
|
||||
fontSize: '20px',
|
||||
},
|
||||
tabButton: {
|
||||
display: 'inline-block',
|
||||
padding: '10px 20px',
|
||||
fontSize: '16px',
|
||||
cursor: 'pointer',
|
||||
backgroundColor: '#0542cc',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
marginRight: '10px',
|
||||
borderRadius: '7px'
|
||||
},
|
||||
tabInactiveButton: {
|
||||
display: 'inline-block',
|
||||
padding: '10px 20px',
|
||||
fontSize: '16px',
|
||||
cursor: 'pointer',
|
||||
backgroundColor: '#fff',
|
||||
color: '#0542cc',
|
||||
border: '1px solid #0542cc',
|
||||
marginRight: '10px',
|
||||
borderRadius: '7px'
|
||||
},
|
||||
submitButton: {
|
||||
marginLeft: 'auto',
|
||||
marginTop: '1rem',
|
||||
textAlign: 'start',
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
},
|
||||
uploadArea: {
|
||||
backgroundColor: '#e6f2ff',
|
||||
height: '250px',
|
||||
cursor: 'pointer',
|
||||
paddingTop: '22px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
border: '1px solid #ced4da',
|
||||
borderRadius: '0.25rem',
|
||||
},
|
||||
uploadIcon: {
|
||||
fontSize: '40px',
|
||||
color: '#0542cc',
|
||||
marginBottom: '7px',
|
||||
},
|
||||
uploadText: {
|
||||
color: '#1f2d3d',
|
||||
fontWeight: '400',
|
||||
fontSize: '16px',
|
||||
lineHeight: '13px',
|
||||
},
|
||||
uploadError: {
|
||||
color: 'red',
|
||||
},
|
||||
waMessagePreview: {
|
||||
backgroundColor: '#F8F9FA',
|
||||
padding: '15px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #dcdcdc',
|
||||
marginBottom: '20px',
|
||||
},
|
||||
preview: {
|
||||
marginTop: '20px',
|
||||
},
|
||||
// Bulk Message
|
||||
uploadArea: {
|
||||
backgroundColor: '#e6f2ff',
|
||||
height: '250px',
|
||||
cursor: 'pointer',
|
||||
marginTop: '1rem',
|
||||
paddingTop: '22px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
border: '1px solid #ced4da',
|
||||
borderRadius: '0.25rem',
|
||||
},
|
||||
uploadIcon: {
|
||||
fontSize: '40px',
|
||||
color: '#0542cc',
|
||||
marginBottom: '7px',
|
||||
},
|
||||
uploadText: {
|
||||
color: '#1f2d3d',
|
||||
fontWeight: '400',
|
||||
fontSize: '16px',
|
||||
lineHeight: '13px',
|
||||
},
|
||||
fileWrapper: {
|
||||
backgroundColor: '#fff',
|
||||
border: '0.2px solid gray',
|
||||
padding: '15px 0 0 17px',
|
||||
borderRadius: '5px',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
imageIcon: {
|
||||
color: '#0542cc',
|
||||
fontSize: '24px',
|
||||
marginBottom: '1rem'
|
||||
},
|
||||
closeIcon: {
|
||||
color: 'red',
|
||||
cursor: 'pointer',
|
||||
fontSize: '26px',
|
||||
marginRight: '1rem',
|
||||
marginBottom: '1rem'
|
||||
},
|
||||
customLabel: {
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
color: '#212529'
|
||||
},
|
||||
submitButton: {
|
||||
marginTop: '20px',
|
||||
},
|
||||
};
|
||||
7
src/screens/Wa/Verify/Section/index.js
Normal file
7
src/screens/Wa/Verify/Section/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import Auth from "./Auth";
|
||||
import Message from "./Message";
|
||||
|
||||
export {
|
||||
Auth,
|
||||
Message
|
||||
}
|
||||
114
src/screens/Wa/Verify/Verify.jsx
Normal file
114
src/screens/Wa/Verify/Verify.jsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Link, Routes, Route, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Auth as AuthWa,
|
||||
Message as MessageWa
|
||||
} from './Section';
|
||||
|
||||
const Verify = () => {
|
||||
const verifyTabs = [
|
||||
{ name: 'Auth', link: 'wa-auth' },
|
||||
{ name: 'Message', link: 'wa-message' }
|
||||
];
|
||||
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Redirect otomatis ke rute default saat akses ke /wa-verify
|
||||
useEffect(() => {
|
||||
if (window.location.pathname === '/wa-verify') {
|
||||
navigate('wa-auth', { replace: true });
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
// Update state isMobile berdasarkan ukuran layar
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setIsMobile(window.innerWidth <= 768);
|
||||
};
|
||||
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="container" style={styles.container}>
|
||||
{/* Static Content */}
|
||||
<div className="row-card border-left border-primary shadow mb-4" style={styles.welcomeCard}>
|
||||
<div className="d-flex flex-column justify-content-start align-items-start p-4">
|
||||
<h4 className="mb-3 text-start">
|
||||
<i className="fas fa-warning fa-bold me-3"></i>Alert
|
||||
</h4>
|
||||
<p className="mb-0 text-start">
|
||||
Get started now by creating an Application ID and explore all the demo services available on the dashboard.
|
||||
Experience the ease and flexibility of trying out all our features firsthand.
|
||||
</p>
|
||||
<div className="d-flex flex-row mt-3">
|
||||
<Link to="/createApps" style={{ textDecoration: 'none' }}>
|
||||
<button className="btn d-flex justify-content-center align-items-center me-2" style={styles.createButton}>
|
||||
<i className="fas fa-plus text-white me-2"></i>
|
||||
<p className="text-white mb-0">Create New App ID</p>
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tab Navigation */}
|
||||
<div style={styles.section}>
|
||||
<div className={`d-flex ${isMobile ? 'flex-column' : 'flex-row'} justify-content-between align-items-center mb-3`}>
|
||||
<div className="d-flex flex-wrap">
|
||||
{verifyTabs.map((tab) => (
|
||||
<Link
|
||||
key={tab.link}
|
||||
to={tab.link}
|
||||
className={`btn ${window.location.pathname.includes(tab.link) ? 'btn-primary' : 'btn-light'} me-2 mb-2`}
|
||||
style={styles.tabLink}
|
||||
>
|
||||
{tab.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Dynamic Tab Content */}
|
||||
<div className="tab-content">
|
||||
<Routes>
|
||||
<Route path="wa-auth" element={<AuthWa />} />
|
||||
<Route path="wa-message" element={<MessageWa />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Verify;
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
marginTop: '3%',
|
||||
padding: '0 15px',
|
||||
},
|
||||
welcomeCard: {
|
||||
backgroundColor: '#E2FBEA',
|
||||
borderLeft: '4px solid #0542CC',
|
||||
borderRadius: '5px',
|
||||
marginBottom: '20px',
|
||||
},
|
||||
createButton: {
|
||||
backgroundColor: '#0542CC',
|
||||
},
|
||||
section: {
|
||||
padding: '20px',
|
||||
border: '0.1px solid rgba(0, 0, 0, 0.2)',
|
||||
borderLeft: '4px solid #0542CC',
|
||||
borderRadius: '10px',
|
||||
width: '100%',
|
||||
},
|
||||
tabLink: {
|
||||
padding: '10px 20px',
|
||||
},
|
||||
};
|
||||
5
src/screens/Wa/Verify/index.js
Normal file
5
src/screens/Wa/Verify/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import Verify from "./Verify";
|
||||
|
||||
export {
|
||||
Verify
|
||||
}
|
||||
Reference in New Issue
Block a user