ok
This commit is contained in:
20
src/App.js
20
src/App.js
@@ -413,27 +413,27 @@ function App() {
|
|||||||
setModalContent(content)
|
setModalContent(content)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to close the modal
|
|
||||||
const closeModal = (closeTheseContent = []) => {
|
const closeModal = (closeTheseContent = []) => {
|
||||||
if (
|
if (
|
||||||
Array.isArray(closeTheseContent) &&
|
Array.isArray(closeTheseContent) &&
|
||||||
(closeTheseContent.length === 0 ||
|
(closeTheseContent.length === 0 || closeTheseContent.includes(modalContent))
|
||||||
closeTheseContent.includes(modalContent))
|
|
||||||
) {
|
) {
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
setModalContent(null);
|
setModalContent(null);
|
||||||
document.body.style.overflow = "auto";
|
document.body.style.overflow = "auto";
|
||||||
|
|
||||||
const queryParams = new URLSearchParams(location.search);
|
const queryParams = new URLSearchParams(location.search);
|
||||||
|
|
||||||
// Remove the 'modal' parameter
|
// Clear all query parameters
|
||||||
queryParams.delete("modal");
|
queryParams.keys() && [...queryParams.keys()].forEach(key => {
|
||||||
queryParams.delete("transactionId");
|
queryParams.delete(key);
|
||||||
|
});
|
||||||
// Update the URL without the 'modal' parameter
|
|
||||||
|
// Update the URL without any query parameters
|
||||||
navigate({ search: queryParams.toString() }, { replace: true });
|
navigate({ search: queryParams.toString() }, { replace: true });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// const askNotificationPermission = async () => {
|
// const askNotificationPermission = async () => {
|
||||||
|
|||||||
56
src/components/Coupon.css
Normal file
56
src/components/Coupon.css
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/* Coupon container */
|
||||||
|
.coupon {
|
||||||
|
display: flex;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
height: 50%;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Left side (with the rotated code and dotted line) */
|
||||||
|
.coupon-left {
|
||||||
|
width: 80px;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-right: 2px dotted #ccc;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-code {
|
||||||
|
writing-mode: vertical-rl;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dotted-line {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 10px;
|
||||||
|
width: 60px;
|
||||||
|
border-bottom: 2px dotted #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Right side (coupon details) */
|
||||||
|
.coupon-right {
|
||||||
|
padding: 10px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.coupon-value {
|
||||||
|
font-size: clamp(18px, 3vw, 24px); /* Minimum 18px, 6vw (responsive), Maximum 24px */
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-period,
|
||||||
|
.coupon-expiration {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #7f8c8d;
|
||||||
|
}
|
||||||
|
|
||||||
28
src/components/Coupon.js
Normal file
28
src/components/Coupon.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import './Coupon.css'; // Import a CSS file for styling
|
||||||
|
|
||||||
|
const Coupon = ({ code, value, period, type, expiration }) => {
|
||||||
|
// Format the value based on type
|
||||||
|
const formattedValue = type === 'fixed' ? `Rp ${value}` : value != 0 ? `${value}%` : 'kupon berlangganan';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='coupon'>
|
||||||
|
<div className='coupon-left'>
|
||||||
|
<div className='coupon-code'>{code == null ? '404' : code}</div>
|
||||||
|
<div className='dotted-line'></div>
|
||||||
|
</div>
|
||||||
|
<div className='coupon-right'>
|
||||||
|
<h2 className='coupon-value'>{code == null ? 'Kupon tidak ditemukan' : formattedValue}</h2>
|
||||||
|
{type && <span className='coupon-type'>{type}</span>} {/* Display type if provided */}
|
||||||
|
<p className='coupon-period'>
|
||||||
|
{code == null ? '-' : value == 0 ? `Masa berlangganan ${period} minggu` : `Masa kupon ${period} minggu`} {/* Fixed string concatenation */}
|
||||||
|
</p>
|
||||||
|
<p className='coupon-expiration'>
|
||||||
|
{expiration == null ? (code == null ? '-' : 'Tanpa kadaluarsa') : `Berlaku sampai: ${expiration}`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Coupon;
|
||||||
@@ -30,6 +30,7 @@ import { getImageUrl } from "../helpers/itemHelper.js";
|
|||||||
|
|
||||||
import CreateCoupon from "../pages/CreateCoupon";
|
import CreateCoupon from "../pages/CreateCoupon";
|
||||||
import CheckCoupon from "../pages/CheckCoupon";
|
import CheckCoupon from "../pages/CheckCoupon";
|
||||||
|
import CreateUserWithCoupon from "../pages/CreateUserWithCoupon";
|
||||||
|
|
||||||
const Modal = ({ user, shop, isOpen, onClose, modalContent, setModal, handleMoveToTransaction,welcomePageConfig }) => {
|
const Modal = ({ user, shop, isOpen, onClose, modalContent, setModal, handleMoveToTransaction,welcomePageConfig }) => {
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ const Modal = ({ user, shop, isOpen, onClose, modalContent, setModal, handleMove
|
|||||||
<div className={styles.modalContent} onClick={handleContentClick}>
|
<div className={styles.modalContent} onClick={handleContentClick}>
|
||||||
|
|
||||||
{modalContent === "edit_account" && <AccountUpdatePage user={user} />}
|
{modalContent === "edit_account" && <AccountUpdatePage user={user} />}
|
||||||
{modalContent === "join" && <Join />}
|
{modalContent === "join" && <Join setModal={setModal} />}
|
||||||
{modalContent === "reset-password" && <ResetPassword />}
|
{modalContent === "reset-password" && <ResetPassword />}
|
||||||
{modalContent === "req_notification" && <NotificationRequest setModal={setModal} />}
|
{modalContent === "req_notification" && <NotificationRequest setModal={setModal} />}
|
||||||
{modalContent === "blocked_notification" && <NotificationBlocked />}
|
{modalContent === "blocked_notification" && <NotificationBlocked />}
|
||||||
@@ -103,6 +104,7 @@ const Modal = ({ user, shop, isOpen, onClose, modalContent, setModal, handleMove
|
|||||||
|
|
||||||
{modalContent === "create_coupon" && <CreateCoupon />}
|
{modalContent === "create_coupon" && <CreateCoupon />}
|
||||||
{modalContent === "check_coupon" && <CheckCoupon />}
|
{modalContent === "check_coupon" && <CheckCoupon />}
|
||||||
|
{modalContent === "create_user" && <CreateUserWithCoupon />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import styles from './Join.module.css'; // Import the module.css file
|
||||||
|
import API_BASE_URL from '../config.js';
|
||||||
|
|
||||||
|
function getAuthToken() {
|
||||||
|
return localStorage.getItem('auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
const LinktreePage = ({ setModal }) => {
|
||||||
|
const [isUsingCoupon, setIsUsingCoupon] = useState(false);
|
||||||
|
const [couponCode, setCouponCode] = useState('');
|
||||||
|
const [couponStatus, setCouponStatus] = useState('');
|
||||||
|
const [couponDetails, setCouponDetails] = useState(null);
|
||||||
|
const [username, setUsername] = useState('');
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [retypePassword, setRetypePassword] = useState('');
|
||||||
|
|
||||||
|
const handleCheckCoupon = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/coupon/check/${couponCode}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${getAuthToken()}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
setCouponStatus('Coupon is valid');
|
||||||
|
setCouponDetails(data.coupon);
|
||||||
|
} else {
|
||||||
|
setCouponStatus('Coupon not found or expired');
|
||||||
|
setCouponDetails(null);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setCouponStatus('Error checking coupon.');
|
||||||
|
setCouponDetails(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateUserWithCoupon = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (password !== retypePassword) {
|
||||||
|
setCouponStatus('Passwords do not match');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/user/create-with-coupon`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${getAuthToken()}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
couponCode,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
setCouponStatus('User created successfully with coupon');
|
||||||
|
setCouponDetails(null);
|
||||||
|
console.log(data);
|
||||||
|
} else {
|
||||||
|
const errorData = await response.json();
|
||||||
|
setCouponStatus(errorData.message || 'Error creating user');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setCouponStatus('Error creating user.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.linktreePage}>
|
||||||
|
<div className={styles.dashboardContainer}>
|
||||||
|
<div className={styles.mainHeading}>Gunakan Kupon</div>
|
||||||
|
<form className={styles.linktreeForm} onSubmit={handleCreateUserWithCoupon}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Username"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
className={styles.usernameInput}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
placeholder="Email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
className={styles.usernameInput}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
placeholder="Password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
className={styles.usernameInput}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
placeholder="Re-type Password"
|
||||||
|
value={retypePassword}
|
||||||
|
onChange={(e) => setRetypePassword(e.target.value)}
|
||||||
|
className={styles.usernameInput}
|
||||||
|
/>
|
||||||
|
<button type="submit" className={styles.claimButton}>
|
||||||
|
<span>Buat Akun</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LinktreePage;
|
||||||
|
|||||||
@@ -946,7 +946,7 @@ const sortedMaterials = allMaterials.sort((a, b) => new Date(a.date) - new Date(
|
|||||||
onError={(e) => e.target.src = '/fallback-image.png'}
|
onError={(e) => e.target.src = '/fallback-image.png'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<a style={{left: 0, right: 0, bottom: 0, textAlign: 'center', color: '#254F1A', fontSize:'13px', position: 'fixed'}}>©2025 KEDIRITECHNOPARK</a>
|
<a style={{left: 0, right: 0, bottom: 0, textAlign: 'center', color: '#254F1A', fontSize:'13px', position: 'fixed'}}>©2025 KEDIRITECHNOPARK.COM</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,63 +1,162 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import styles from './Join.module.css'; // Import the module.css file
|
import styles from './Join.module.css'; // Import the module.css file
|
||||||
|
import API_BASE_URL from '../config.js';
|
||||||
|
|
||||||
|
import Coupon from '../components/Coupon';
|
||||||
|
|
||||||
|
function getAuthToken() {
|
||||||
|
return localStorage.getItem('auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
const LinktreePage = ({ data, setModal }) => {
|
||||||
|
const [isUsingCoupon, setIsUsingCoupon] = useState(false);
|
||||||
|
const [couponCode, setCouponCode] = useState('');
|
||||||
|
const [couponStatus, setCouponStatus] = useState(0);
|
||||||
|
const [couponDetails, setCouponDetails] = useState(null);
|
||||||
|
|
||||||
|
const handleCheckCoupon = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/coupon/check/${couponCode}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${getAuthToken()}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
setCouponStatus(200);
|
||||||
|
setCouponDetails(data.coupon);
|
||||||
|
} else {
|
||||||
|
setCouponStatus(404);
|
||||||
|
setCouponDetails(null);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setCouponStatus(404);
|
||||||
|
setCouponDetails(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const LinktreePage = ({ data }) => {
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.linktreePage}>
|
<div className={styles.linktreePage}>
|
||||||
|
{!isUsingCoupon ? (
|
||||||
|
<div className={styles.dashboardContainer}>
|
||||||
|
{/* Main Heading */}
|
||||||
|
<div className={styles.mainHeading}>Nikmati Kemudahan Mengelola Kafe</div>
|
||||||
|
|
||||||
<div className={styles.dashboardContainer}>
|
{/* Sub Heading */}
|
||||||
{/* Main Heading */}
|
<div className={styles.subHeading}>
|
||||||
<div className={styles.mainHeading}>
|
Daftarkan kedaimu sekarang dan mulai gunakan semua fitur unggulan kami.
|
||||||
Nikmati Kemudahan Mengelola Kafe
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sub Heading */}
|
|
||||||
<div className={styles.subHeading}>
|
|
||||||
Daftarkan kedaimu sekarang dan mulai gunakan semua fitur unggulan kami.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Form Section */}
|
|
||||||
<form className={styles.linktreeForm}>
|
|
||||||
<label htmlFor="username" className={styles.usernameLabel}>--------------------------------------------</label>
|
|
||||||
<input
|
|
||||||
id="username"
|
|
||||||
placeholder="nomor whatsapp atau email"
|
|
||||||
maxLength="30"
|
|
||||||
className={styles.usernameInput}
|
|
||||||
/>
|
|
||||||
<button type="submit" className={styles.claimButton}>
|
|
||||||
<span>➜</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{/* Footer Links */}
|
|
||||||
<div className={styles.footer}>
|
|
||||||
<div className={styles.footerLinks}>
|
|
||||||
<a
|
|
||||||
href="https://linktr.ee/discover/trending"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className={styles.footerLink}
|
|
||||||
>
|
|
||||||
Pelajari lebih lanjut
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://linktr.ee"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className={styles.footerLink}
|
|
||||||
>
|
|
||||||
Gunakan kupon
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.footerImage}>
|
|
||||||
<img
|
{/* Form Section */}
|
||||||
src="./laporan.png"
|
<form className={styles.linktreeForm}>
|
||||||
alt="Linktree visual"
|
<label htmlFor="username" className={styles.usernameLabel}>
|
||||||
|
--------------------------------------------
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="username"
|
||||||
|
placeholder="nomor whatsapp atau email"
|
||||||
|
maxLength="30"
|
||||||
|
className={styles.usernameInput}
|
||||||
/>
|
/>
|
||||||
|
<button type="submit" className={styles.claimButton}>
|
||||||
|
<span>➜</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Footer Links */}
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<div className={styles.footerLinks}>
|
||||||
|
<a
|
||||||
|
href="https://linktr.ee/discover/trending"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={styles.footerLink}
|
||||||
|
>
|
||||||
|
Pelajari lebih lanjut
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
onClick={() => setIsUsingCoupon(true)}
|
||||||
|
className={styles.footerLink}
|
||||||
|
>
|
||||||
|
Gunakan kupon
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className={styles.footerImage}>
|
||||||
|
<img src="./laporan.png" alt="Linktree visual" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
|
<div className={styles.dashboardContainer}>
|
||||||
|
{/* Main Heading */}
|
||||||
|
<div className={styles.mainHeading}>Daftar Menggunakan Kupon</div>
|
||||||
|
|
||||||
|
{/* Sub Heading */}
|
||||||
|
<div className={styles.subHeading}>
|
||||||
|
Kupon tidak hanya dapat digunakan untuk pembuatan akun penyewa, tetapi juga dapat digunakan untuk memperpanjang masa berlangganan.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Coupon Check Section */}
|
||||||
|
{couponStatus == 0 ?
|
||||||
|
<form className={styles.linktreeForm} onSubmit={handleCheckCoupon}>
|
||||||
|
<label htmlFor="coupon" className={styles.usernameLabel}>
|
||||||
|
--------------------------------------------
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="coupon"
|
||||||
|
placeholder="kode kupon"
|
||||||
|
maxLength="30"
|
||||||
|
className={styles.usernameInput}
|
||||||
|
value={couponCode}
|
||||||
|
onChange={(e) => setCouponCode(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button type="submit" className={styles.claimButton}>
|
||||||
|
<span>Cek</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
:
|
||||||
|
<>
|
||||||
|
<Coupon code={couponDetails?.code || null} value={couponDetails?.discountValue} period={couponDetails?.discountPeriods} expiration={couponDetails?.expirationDate} />
|
||||||
|
|
||||||
|
|
||||||
|
<form className={styles.linktreeForm}>
|
||||||
|
<label htmlFor="username" className={styles.usernameLabel}>
|
||||||
|
--------------------------------------------
|
||||||
|
</label>
|
||||||
|
<button type="submit" className={styles.claimButton} style={{ width: '266px' }} onClick={() => setModal('create_user', { codeStatus: 200, couponCode })}>
|
||||||
|
<span>Buat akun dengan kupon ini</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
{/* Footer Links */}
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<div className={styles.footerLinks}>
|
||||||
|
<a
|
||||||
|
href="https://linktr.ee/discover/trending"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={styles.footerLink}
|
||||||
|
>
|
||||||
|
Pelajari lebih lanjut
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
onClick={() => { setIsUsingCoupon(couponStatus == 0 ? false : true); setCouponCode(null); setCouponDetails(null); setCouponStatus(0) }}
|
||||||
|
className={styles.footerLink}
|
||||||
|
>
|
||||||
|
Kembali
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className={styles.footerImage}>
|
||||||
|
<img src="./laporan.png" alt="Linktree visual" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user