Slicing UI

This commit is contained in:
2024-11-25 17:51:46 +07:00
parent 231c637fa9
commit 392a8a08fb
54 changed files with 8055 additions and 76 deletions

View 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',
},
};

View 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;

View 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',
},
};

View File

@@ -0,0 +1,9 @@
import RegisterContent from "./RegisterContent";
import RegisterStep from "./RegisterStep";
import CreateSettings from "./CreateSettings";
export {
RegisterContent,
RegisterStep,
CreateSettings
}

View 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',
},
};

View 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',
},
};

View 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%',
},
}

View 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;

View 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
}

View 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',
},
};

View 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',
},
};

View File

@@ -0,0 +1,7 @@
import Auth from "./Auth";
import Message from "./Message";
export {
Auth,
Message
}

View 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',
},
};

View File

@@ -0,0 +1,5 @@
import Verify from "./Verify";
export {
Verify
}