|
Server : Apache/2.4.18 (Ubuntu) System : Linux canvaswebdesign 3.13.0-71-generic #114-Ubuntu SMP Tue Dec 1 02:34:22 UTC 2015 x86_64 User : oppastar ( 1041) PHP Version : 7.0.33-0ubuntu0.16.04.15 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority, Directory : /var/www/laciasmara.com/public_html/shop/application/views/ |
Upload File : |
<style>
/* Base Styles */
:root {
--primary-color: #7A4397;
--primary-hover: #66357a;
--secondary-color: #f3f4f6;
--success-color: #10b981;
--warning-color: #f59e0b;
--danger-color: #ef4444;
--gold-color: #fbbf24;
--text-primary: #333333;
--text-secondary: #6b7280;
--border-color: #e5e7eb;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
main {
line-height: 1.6;
color: var(--text-color);
background-color: var(--background-color);
}
/* Container */
.checkout-container {
margin: 0 auto;
padding: 20px 60px;
display: flex;
flex-wrap: wrap;
gap: 24px;
}
.checkout-left {
flex: 1.7;
display: flex;
flex-direction: column;
gap: 4px;
}
/* Kolom kanan (payment dan summary) */
.checkout-right {
flex: 1;
display: flex;
flex-direction: column;
background-color: white;
border-radius: 8px;
height: 100%;
position: sticky;
top: 125px;
}
.checkout-title {
width: 100%;
font-size: 24px;
margin: 0;
}
/* Insurance */
.insurance-header {
display: flex;
align-items: center;
margin-top: 8px;
padding: 0;
gap: 12px;
}
.insurance-section-title {
display: flex;
align-items: center;
gap: 8px;
color: #333;
font-weight: 500;
font-size: 16px;
margin: 0;
}
.insurance-section-title i {
font-size: 18px;
color: #7A4397;
}
/* Checkbox styling */
.insurance-checkbox-wrapper {
position: relative;
cursor: pointer;
user-select: none;
display: inline-flex;
align-items: center;
}
.insurance-checkbox-wrapper input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.checkmark {
height: 16px;
width: 16px;
background-color: #fff;
border: 2px solid #ddd;
border-radius: 4px;
position: relative;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.insurance-checkbox-wrapper:hover input~.checkmark {
border-color: #7A4397;
box-shadow: 0 0 0 3px rgba(122, 67, 151, 0.1);
}
.insurance-checkbox-wrapper input:checked~.checkmark {
background-color: #7A4397;
border-color: #7A4397;
}
.insurance-checkbox-wrapper input:focus~.checkmark {
outline: 2px solid #7A4397;
outline-offset: 2px;
}
.checkmark:after {
content: "";
position: absolute;
display: none;
left: 6px;
top: 2px;
width: 6px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.insurance-checkbox-wrapper input:checked~.checkmark:after {
display: block;
}
/* Responsive sizing */
@media (max-width: 768px) {
.checkmark {
height: 14px;
width: 14px;
}
.checkmark:after {
left: 3px;
top: 1px;
width: 3px;
height: 7px;
}
.insurance-section-title i {
font-size: 14px;
}
}
/* Gift Section */
.checkout-gift {
background: #fff;
padding: 0;
margin: 0;
}
.gift-header {
display: flex;
align-items: center;
margin-bottom: 8px;
gap: 12px;
}
.gift-section-title {
color: #333;
font-size: 16px;
margin: 0;
font-weight: 500;
}
/* Checkbox styling */
.gift-checkbox-wrapper {
position: relative;
cursor: pointer;
user-select: none;
display: inline-flex;
align-items: center;
}
.gift-checkbox-wrapper input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.gift-checkbox-wrapper .checkmark {
height: 16px;
width: 16px;
background-color: #fff;
border: 2px solid #ddd;
border-radius: 4px;
position: relative;
}
.gift-checkbox-wrapper:hover input~.checkmark {
border-color: #7A4397;
}
.gift-checkbox-wrapper input:checked~.checkmark {
background-color: #7A4397;
border-color: #7A4397;
}
.checkmark:after {
content: "";
position: absolute;
display: none;
left: 4px;
top: 0px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.gift-checkbox-wrapper input:checked~.checkmark:after {
display: block;
}
/* Form styling */
.gift-form {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.gift-form.active {
max-height: 500px;
}
.gift-form-group {
margin-bottom: 1.25rem;
}
.gift-form-group label {
display: block;
margin-bottom: 0.5rem;
color: #555;
font-size: 0.9rem;
}
.gift-form-group input,
.gift-form-group textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
transition: border-color 0.2s ease;
box-sizing: border-box;
}
.gift-form-group input:focus,
.gift-form-group textarea:focus {
outline: none;
border-color: #7A4397;
box-shadow: 0 0 0 2px rgba(122, 67, 151, 0.1);
}
/* Notes styling - integrated into gift form */
.checkout-notes {
position: relative;
}
.checkout-notes-input {
width: 100%;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 4px;
resize: vertical;
min-height: 80px;
box-sizing: border-box;
font-size: 14px;
transition: border-color 0.2s ease;
}
.checkout-notes-input:focus {
outline: none;
border-color: #7A4397;
box-shadow: 0 0 0 2px rgba(122, 67, 151, 0.1);
}
.checkout-notes-count {
position: absolute;
right: 12px;
bottom: 8px;
color: #666;
font-size: 12px;
background: rgba(255, 255, 255, 0.9);
padding: 2px 4px;
border-radius: 2px;
}
/* Responsive */
@media (max-width: 768px) {
.checkout-gift {
padding: 0;
}
.gift-form-group input,
.gift-form-group textarea,
.checkout-notes-input {
font-size: 13px;
}
}
/* Address Section */
.checkout-shipping {
background: #fff;
border-radius: 8px;
padding: 0;
margin-bottom: 8px;
}
.checkout-address-section {
margin-bottom: 20px;
}
/* Address Card */
.checkout-address-card {
border-radius: 12px;
padding: 20px;
margin-bottom: 12px;
box-shadow: var(--shadow);
border: 1px solid var(--border-color);
background: white;
transition: all 0.2s ease;
}
.checkout-address-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
/* Address Label Badge */
.address-label-badge {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
}
.label-text {
color: #333;
font-size: 12px;
font-weight: 500;
}
.default-badge {
background: #d1d5db;
color: #333;
padding: 2px 6px;
border-radius: 4px;
font-size: 10px;
font-weight: 500;
}
/* Recipient Info */
.address-recipient {
margin-bottom: 12px;
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
}
.recipient-name {
margin: 0;
font-size: 16px;
font-weight: 600;
color: var(--text-color);
}
.recipient-phone {
color: #666;
font-size: 13px;
margin: 0;
}
/* Address Details */
.address-details {
margin-bottom: 16px;
}
.full-address {
margin: 0 0 8px 0;
font-size: 14px;
line-height: 1.5;
color: var(--text-color);
}
.address-notes {
color: orange;
font-style: italic;
font-size: 14px;
}
/* Address Actions */
.address-actions {
display: flex;
gap: 10px;
}
.btn-change-address {
display: flex;
align-items: center;
gap: 6px;
background: none;
border: 1px solid var(--primary-color);
color: var(--primary-color);
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s ease;
}
.btn-change-address:hover {
background: var(--primary-color);
color: white;
}
.btn-icon {
width: 16px;
height: 16px;
}
/* Empty Address State */
.empty-address {
text-align: center;
padding: 40px 20px;
border: 2px dashed var(--border-color);
background: #f9f9f9;
}
.empty-address-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.empty-icon {
width: 48px;
height: 48px;
color: #ccc;
}
.empty-address h4 {
margin: 0;
color: #666;
}
.empty-address p {
margin: 0;
color: #888;
font-size: 14px;
}
.btn-add-address {
display: flex;
align-items: center;
gap: 6px;
background: var(--primary-color);
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
margin-top: 8px;
}
/* Modal Address Styles */
.address-selection-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.address-selection-modal-content {
background: white;
border-radius: 12px;
max-width: 600px;
width: 90%;
max-height: 80vh;
display: flex;
flex-direction: column;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.address-selection-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #eee;
}
.address-selection-modal-header h3 {
margin: 0;
font-size: 18px;
color: var(--text-color);
}
.address-selection-modal-close {
background: none;
border: none;
cursor: pointer;
padding: 8px;
border-radius: 50%;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s ease;
}
.address-selection-modal-close:hover {
background: #f0f0f0;
}
.address-selection-modal-body {
flex: 1;
overflow-y: auto;
padding: 0 20px;
}
.address-list {
display: flex;
flex-direction: column;
gap: 12px;
padding: 20px 0;
}
.address-option {
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 20px;
position: relative;
background: white;
user-select: none;
}
.address-option:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border-color: var(--primary-color);
}
.address-option.selected {
border-color: var(--primary-color);
background: var(--background-light);
box-shadow: 0 4px 12px rgba(122, 67, 151, 0.15);
}
.address-option-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
.address-label-badge {
display: flex;
gap: 8px;
align-items: center;
}
/* Custom Checkbox/Checkmark Design */
.custom-checkbox {
width: 18px;
height: 18px;
border: 1px solid var(--border-color);
border-radius: 4px;
background: white;
position: relative;
transition: all 0.3s ease;
flex-shrink: 0;
}
.address-option:hover .custom-checkbox {
border-color: var(--primary-color);
}
.address-option.selected .custom-checkbox {
background: var(--primary-color);
border-color: var(--primary-color);
}
/* Checkmark icon */
.custom-checkbox::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0);
width: 14px;
height: 14px;
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='20,6 9,17 4,12'%3e%3c/polyline%3e%3c/svg%3e") no-repeat center;
background-size: contain;
transition: transform 0.2s ease;
}
.address-option.selected .custom-checkbox::after {
transform: translate(-50%, -50%) scale(1);
}
/* Hide the original radio button */
.address-option input[type="radio"] {
display: none;
}
.address-option-content {
margin-top: 8px;
}
.address-text {
margin: 8px 0;
font-size: 14px;
color: var(--text-color);
line-height: 1.4;
}
.notes {
color: orange;
font-style: italic;
font-size: 13px;
margin: 8px 0 0 0;
}
.address-selection-modal-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
padding: 20px;
border-top: 1px solid #eee;
}
.address-selection-btn-secondary {
background: #f8f9fa;
color: #6c757d;
border: 1px solid #dee2e6;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s ease;
}
.address-selection-btn-secondary:hover {
background: #e9ecef;
border-color: #adb5bd;
}
.address-selection-btn-primary {
background: var(--primary-color);
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s ease;
}
.address-selection-btn-primary:hover {
background: var(--primary-hover);
}
.address-selection-btn-primary:disabled {
background: #ccc;
cursor: not-allowed;
}
/* Responsive */
@media (max-width: 768px) {
.address-selection-modal-content {
width: 90%;
margin: 10px;
}
.btn-change-address {
width: 100%;
justify-content: center;
}
.address-recipient,
.address-option-header {
margin-bottom: 8px;
}
.address-text {
font-size: 12px;
line-height: 1.2;
}
.address-option {
padding: 16px;
}
.address-selection-modal-footer {
flex-direction: column-reverse;
}
.address-selection-btn-secondary,
.address-selection-btn-primary {
width: 100%;
justify-content: center;
}
}
/* Animation for modal appearance */
.address-selection-modal-overlay {
animation: fadeIn 0.3s ease;
}
.address-selection-modal-content {
animation: slideUp 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Product Section */
.checkout-products {
background: #fff;
border-radius: 8px;
padding: 16px;
/* box-shadow: var(--shadow); */
border: 1px solid var(--border-color);
}
.checkout-section-title {
font-size: 20px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid var(--secondary-color);
display: flex;
align-items: center;
gap: 8px;
}
/* Product Item */
.checkout-product-item {
border-bottom: 0.75px solid var(--border-color);
padding-bottom: 20px;
margin-bottom: 20px;
}
.checkout-product-item:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.checkout-product-details {
display: flex;
gap: 16px;
align-items: flex-start;
}
/* Product Image */
.checkout-product-image {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 8px;
border: 1px solid var(--border-color);
flex-shrink: 0;
}
/* Product Info */
.checkout-product-info {
flex: 1;
min-width: 0;
}
.checkout-product-name {
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 8px;
margin-top: 0;
line-height: 1.4;
}
.product-meta {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 12px;
}
.meta-item {
display: flex;
align-items: center;
gap: 4px;
font-size: 14px;
color: var(--text-secondary);
}
.meta-item .icon {
width: 16px;
height: 16px;
opacity: 0.7;
}
.variant-badge {
background: var(--secondary-color);
color: var(--text-primary);
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
/* Pricing Section */
.pricing-section {
margin-top: 12px;
}
.price-display {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
}
.price-current {
color: var(--primary-color);
}
.price-original {
font-size: 14px;
color: var(--text-secondary);
text-decoration: line-through;
font-weight: 400;
}
.quantity-multiplier {
color: var(--text-secondary);
font-weight: 400;
}
/* Mobile Responsive */
@media (max-width: 768px) {
.checkout-products {
padding: 16px;
}
.checkout-product-details {
gap: 12px;
}
.checkout-product-image {
width: 80px;
height: 80px;
}
.product-meta {
flex-direction: column-reverse;
gap: 8px;
}
.pricing-section {
padding: 0px;
}
.checkout-section-title {
font-size: 18px;
}
.meta-item {
font-size: 13px;
}
.price-current,
.quantity-multiplier {
font-size: 13px;
}
.price-original {
font-size: 13px;
}
.checkout-change-btn {
font-size: 13px;
}
}
@media (max-width: 480px) {
.checkout-product-name {
font-size: 15px;
}
.checkout-container {
gap: 8px;
}
.price-display {
flex-wrap: wrap;
gap: 6px;
}
}
/* Shipping Options */
.checkout-shipping-options {
background: #fff;
border-radius: 8px;
padding: 12px;
margin-bottom: 20px;
box-sizing: border-box;
}
.checkout-shipping-selection {
margin-bottom: 12px;
}
.checkout-shipping-selection h4 {
font-weight: 500;
color: #333;
margin: 0;
}
.checkout-insurance {
display: flex;
align-items: center;
gap: 8px;
}
/* Payment Section */
.checkout-payment {
background: #fff;
border-radius: 8px;
padding: 0;
box-sizing: border-box;
}
.checkout-view-all {
color: var(--primary-color);
text-decoration: none;
}
.checkout-payment-option {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 8px;
cursor: pointer;
box-sizing: border-box;
flex-grow: 1;
width: 100%;
}
.checkout-payment-icon {
width: 40px;
height: auto;
}
/* Shipping */
.checkout-shipping-method {
position: relative;
margin-bottom: 20px;
background: #fff;
border-radius: 12px;
padding: 0;
margin-bottom: 20px;
box-sizing: border-box;
}
/* Payment Category Styles */
.payment-category {
margin-bottom: 16px;
}
.payment-category:last-child {
margin-bottom: 0;
}
.payment-category-title {
font-size: 14px;
margin-bottom: 8px;
margin-top: 0;
font-weight: 500;
color: #333;
}
.checkout-payment-options {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
/* Summary Section */
.checkout-summary {
background: #fff;
border-radius: 12px;
padding: 0;
margin-top: 0;
}
.checkout-summary-details {
margin-bottom: 20px;
}
.checkout-summary-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f3f4f6;
font-size: 14px;
}
.checkout-summary-row:last-child {
border-bottom: none;
}
.checkout-summary-row span:first-child {
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 8px;
}
.checkout-summary-row span:last-child {
font-weight: 500;
color: var(--text-primary);
}
/* Discount Section */
.discount-section {
background: #fef2f2;
border: 1px solid #fecaca;
border-radius: 8px;
margin: 8px 0;
overflow: hidden;
}
.discount-toggle {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
cursor: pointer;
background: #fef2f2;
border: none;
width: 100%;
font-size: 14px;
color: var(--danger-color);
font-weight: 500;
}
.discount-toggle:hover {
background: #fee2e2;
}
.discount-content {
display: none;
padding: 0 16px 12px;
background: #fff;
}
.discount-content.active {
display: block;
}
.discount-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
font-size: 13px;
color: var(--text-secondary);
}
.discount-item span:last-child {
color: var(--danger-color);
font-weight: 500;
}
/* Special styling for different row types */
.discount-row span:last-child {
color: var(--danger-color) !important;
font-weight: 600;
}
.point-redeem-row span:last-child {
color: var(--danger-color) !important;
font-weight: 600;
}
.bonus-point-row span:last-child {
color: var(--gold-color) !important;
font-weight: 600;
}
.savings-row {
background: #f0f9ff;
border: 1px solid #bae6fd;
border-radius: 8px;
padding: 12px 16px !important;
margin: 8px 0;
}
.savings-row span:first-child {
color: var(--primary-color) !important;
font-weight: 600;
}
.savings-row span:last-child {
color: var(--primary-color) !important;
font-weight: 600;
}
.checkout-summary-total {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 700;
font-size: 16px;
margin: 20px 0;
padding: 16px;
background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
border-radius: 8px;
border: 2px solid var(--primary-color);
}
.checkout-summary-total span:first-child {
color: var(--text-primary);
}
.checkout-summary-total span:last-child {
color: var(--primary-color);
}
.checkout-pay-btn {
width: 100%;
background: linear-gradient(135deg, var(--primary-color) 0%, #6a3085 100%);
color: #fff;
border: none;
padding: 16px;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: 16px;
transition: all 0.3s ease;
}
.checkout-pay-btn:hover {
background: linear-gradient(135deg, #6a3085 0%, #7A4397 100%);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
}
.checkout-terms {
text-align: center;
font-size: 14px;
color: #666;
line-height: 1.5;
}
.checkout-terms-link {
color: var(--primary-color);
text-decoration: none;
}
.checkout-terms-link:hover {
text-decoration: underline;
}
/* Icons */
.summary-icon {
width: 16px;
height: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.summary-icon-success {
color: var(--success-color);
}
.summary-icon-warning {
color: var(--warning-color);
}
.summary-icon-danger {
color: var(--danger-color);
}
.summary-icon-gold {
color: var(--gold-color);
}
.summary-icon-primary {
color: var(--primary-color);
}
/* Responsive Design */
@media (max-width: 768px) {
.checkout-section-title {
font-size: 18px;
}
.checkout-summary-total {
font-size: 16px;
padding: 12px;
}
.checkout-summary-row {
font-size: 13px;
padding: 10px 0;
}
.checkout-pay-btn {
padding: 14px;
font-size: 15px;
}
.discount-toggle {
font-size: 13px;
padding: 10px 12px;
}
.discount-item {
font-size: 12px;
}
}
@media (max-width: 480px) {
.checkout-summary-row {
font-size: 12px;
}
.checkout-summary-total {
font-size: 15px;
}
}
/* Checkout Point */
.checkout-point {
background: white;
}
.point-balance {
background: linear-gradient(135deg, #764ba2 0%, #7a4397 100%);
color: white;
padding: 12px;
border-radius: 8px;
margin-bottom: 20px;
text-align: center;
position: relative;
overflow: hidden;
}
@keyframes shimmer {
0% {
transform: translateX(-100%) translateY(-100%);
}
100% {
transform: translateX(100%) translateY(100%);
}
}
.point-balance-label {
font-size: 14px;
opacity: 0.9;
margin-bottom: 4px;
}
.point-balance-value {
font-size: 20px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.conversion-info {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 12px;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #6c757d;
}
.conversion-info::before {
content: "💡";
font-size: 16px;
}
.checkout-point-container {
position: relative;
}
.input-group {
display: flex;
gap: 12px;
align-items: stretch;
}
.point-input-wrapper {
flex: 1;
position: relative;
}
.point-input {
width: 100%;
height: 48px;
padding: 12px 40px 12px 16px;
border: 2px solid #e9ecef;
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
background: white;
outline: none;
box-sizing: border-box;
}
.point-input:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.point-input.error {
border-color: #dc3545;
background-color: #fff5f5;
}
.point-input.success {
border-color: #28a745;
background-color: #f0fff4;
}
.input-icon {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
color: #6c757d;
font-size: 12px;
}
.point-redeem-btn {
width: 100px;
height: 48px;
padding: 8px 10px;
font-size: 14px;
font-weight: 600;
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
flex-shrink: 0;
box-sizing: border-box;
}
.point-redeem-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3);
}
.point-redeem-btn:active {
transform: translateY(0);
}
.point-redeem-btn:disabled {
background: #6c757d;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.point-redeem-btn.loading {
background: #6c757d;
cursor: not-allowed;
}
.point-redeem-btn.loading::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
margin: -10px 0 0 -10px;
border: 2px solid transparent;
border-top: 2px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.feedback-message {
margin-top: 12px;
padding: 12px 16px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
display: none;
align-items: center;
gap: 8px;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.feedback-message.success {
background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
border: 1px solid #b8dabd;
color: #155724;
}
.feedback-message.error {
background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);
border: 1px solid #f1b0b7;
color: #721c24;
}
.feedback-message i {
font-size: 16px;
}
.quick-select {
margin-top: 16px;
display: none;
}
.quick-select-label {
font-size: 14px;
font-weight: 500;
color: #495057;
margin-bottom: 8px;
display: block;
}
.quick-select-buttons {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.quick-select-btn {
padding: 8px 16px;
font-size: 12px;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 20px;
cursor: pointer;
transition: all 0.2s ease;
color: #495057;
}
.quick-select-btn:hover {
background: #e9ecef;
border-color: #adb5bd;
}
.quick-select-btn.active {
background: linear-gradient(135deg, #764ba2 0%, #7A4397 100%);
color: white;
border-color: #7A4397;
}
.discount-preview {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 6px;
padding: 12px;
margin-top: 16px;
display: none;
align-items: center;
gap: 8px;
font-size: 14px;
color: #856404;
}
.discount-preview span i {
margin-right: 8px;
}
/* Responsive Design */
@media (max-width: 768px) {
.point-balance-label,
.conversion-info {
font-size: 13px;
}
.input-group {
flex-direction: column;
gap: 12px;
}
.point-input-wrapper {
max-width: 100%;
}
.point-redeem-btn {
width: 100%;
font-size: 14px;
}
.point-balance-value {
font-size: 16px;
}
.point-input {
height: 44px;
font-size: 13px;
}
.point-redeem-btn {
height: 44px;
font-size: 13px;
}
}
@media (max-width: 480px) {
.point-input {
height: 44px;
font-size: 12px;
}
.point-redeem-btn {
height: 44px;
font-size: 12px;
}
}
/* Voucher */
.checkout-voucher {
padding: 0;
}
.voucher-button {
display: flex;
align-items: center;
width: 100%;
padding: 12px 16px;
background-color: #f8f8f8;
border: 1px solid #7A4397;
border-radius: 8px;
cursor: pointer;
text-decoration: none;
box-sizing: border-box;
transition: all 0.2s ease;
position: relative;
}
.voucher-button:hover:not(.disabled) {
background-color: #f0f0f0;
border-color: #6a3085;
transform: translateY(-1px);
}
.voucher-button:focus {
outline: 2px solid #7A4397;
outline-offset: 2px;
}
.voucher-button.disabled {
opacity: 0.6;
cursor: not-allowed;
pointer-events: none;
}
.voucher-content {
display: flex;
align-items: center;
gap: 12px;
flex-grow: 1;
}
.voucher-icon {
width: 20px;
height: 20px;
stroke: #7A4397;
flex-shrink: 0;
}
.voucher-text {
color: #333;
font-weight: 600;
font-size: 16px;
flex-grow: 1;
}
.checkout-arrow-icon {
width: 16px;
height: 16px;
stroke: #999;
flex-shrink: 0;
margin-left: auto;
}
.discount-info-text {
color: #e74c3c;
font-size: 12px;
margin-top: 4px;
}
/* Shipment methods */
.shipping-method-option {
background: white;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 16px;
cursor: pointer;
transition: all 0.2s ease;
}
.shipping-method-option:hover {
border-color: #7A4397;
box-shadow: 0 2px 8px rgba(0, 123, 255, 0.1);
}
.shipping-method-option input[type="radio"] {
display: none;
}
.shipping-method-option:has(input[type="radio"]:checked) {
border: 2px solid #7A4397 !important;
background-color: #f8f9ff !important;
}
/* Fallback for browsers that don't support :has() */
.shipping-method-option.selected {
border: 2px solid #7A4397 !important;
background-color: #f8f9ff !important;
}
.shipping-method-label {
display: block;
cursor: pointer;
margin: 0;
}
@media (max-width: 768px) {
.voucher-button {
padding: 14px 16px;
}
.voucher-text {
font-size: 13px;
}
}
.loading-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.loading-content {
text-align: center;
padding: 20px;
box-sizing: border-box;
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.loading-content p {
color: white;
}
.loading-content .spinner {
width: 50px;
height: 50px;
border: 6px solid white;
border-top-color: #7A4397;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.info-message {
display: block;
margin-top: 10px;
padding: 12px 16px;
background-color: #fef3c7;
border-left: 5px solid #facc15;
color: #7a4397;
font-size: 14px;
border-radius: 8px;
line-height: 1.6;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease-in-out;
}
.info-message small {
font-weight: 500;
}
.info-message strong {
color: #5b21b6;
/* Memberikan kontras pada teks penting */
}
.info-message:hover {
background-color: #fef08a;
/* Warna lebih terang saat hover */
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* Responsive Design */
@media (max-width: 768px) {
.info-message small {
font-size: 13px;
}
.voucher-card {
padding: 8px;
}
.checkout-shipping-selection h4 {
font-size: 14px;
}
.shipping-method-option input[type="radio"] {
margin-bottom: 5px;
}
.shipping-method-option label {
font-size: 13px;
}
}
/* Responsive Adjustments */
@media (max-width: 768px) {
.change-address-modal-content {
padding: 15px;
width: 80%;
}
.change-address-modal h3 {
font-size: 16px;
}
.change-address-btn-submit,
.change-address-btn-close {
font-size: 13px;
padding: 8px 10px;
}
}
.shipping-method-selection>p {
margin: 0 0 8px 0;
color: #333;
font-size: 14px;
}
.shipping-method-options {
display: flex;
flex-direction: column;
gap: 8px;
}
@media (max-width: 768px) {
.checkout-container {
flex-direction: column;
padding: 20px;
}
.checkout-address-label {
font-size: 10px;
}
.checkout-terms,
.checkout-payment-option span,
.checkout-product-price,
.gift-form-group label,
.checkout-address-detail,
.checkout-address-name {
font-size: 13px;
}
.shipping-method-option {
flex-direction: row;
padding: 12px;
}
.checkout-product-name,
.insurance-section-title {
font-size: 14px;
}
.gift-section-title {
font-size: 13px;
}
.payment-category-title,
.insurance-section-title .insurance-fee {
font-size: 13px;
}
.checkout-left,
.checkout-right {
flex: 1;
}
.checkout-right {
top: 0;
}
.checkout-pay-btn {
padding: 12px;
font-size: 14px;
}
.gift-form-group input {
padding: 8px;
font-size: 13px;
}
}
@media (max-width: 480px) {
.insurance-section-title {
font-size: 12px;
}
.insurance-section-title i {
font-size: 12px;
}
.insurance-section-title .insurance-fee {
font-size: 12px;
}
.checkmark {
height: 12px;
width: 12px;
}
.checkmark:after {
left: 3px;
top: 1px;
width: 3px;
height: 6px;
}
}
.doku-payment-wrapper {
flex-direction: column;
align-items: flex-start;
}
.doku-main-option {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
}
.doku-description {
color: #333;
font-size: 12px;
margin-left: 30px;
margin-bottom: 0;
margin-top: 0;
/* 40px (lebar ikon) + 12px (gap) */
}
.doku-payment-methods {
width: 100%;
padding-left: 30px;
}
.doku-payment-logos {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
}
.payment-partner-icon {
height: auto;
width: 40px;
}
.method-info {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 16px;
}
.method-details {
flex: 1;
}
.method-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 4px;
}
.method-icon {
width: 48px;
height: 48px;
object-fit: contain;
border-radius: 4px;
flex-shrink: 0;
}
.method-name {
font-weight: 600;
color: #333;
font-size: 14px;
flex: 1;
margin-bottom: 0;
}
.etd-info {
font-size: 12px;
color: #636e72;
font-weight: 500;
opacity: 0.8;
}
/* Price section styles */
.method-price {
text-align: right;
display: flex;
flex-direction: column;
gap: 4px;
align-items: flex-end;
}
.price-group {
display: flex;
flex-direction: column;
gap: 2px;
align-items: flex-end;
}
.price-row {
display: flex;
align-items: center;
gap: 8px;
}
.shipping-price {
font-weight: 600;
color: #333;
font-size: 14px;
}
.original-price {
font-size: 14px;
color: #636e72;
text-decoration: line-through;
font-weight: 500;
opacity: 0.8;
}
.free-shipping {
background: #28a745;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
}
.discount-info {
font-size: 12px;
color: #28a745;
font-weight: 500;
}
.no-shipping-available {
text-align: center;
padding: 20px;
color: #666;
font-style: italic;
}
/* Responsive design */
@media (max-width: 768px) {
.method-icon {
width: 36px;
height: 36px;
}
.method-header {
gap: 10px;
}
.method-info {
gap: 8px;
}
.method-price {
text-align: left;
align-items: flex-start;
}
.method-name {
font-size: 13px;
}
.etd-info {
font-size: 11px;
}
.original-price {
font-size: 12px;
}
.free-shipping {
font-size: 13px;
}
.shipping-price {
font-size: 13px;
}
.doku-description {
font-size: 11px;
}
}
.insurance-fee {
color: #6c757d;
font-weight: 400;
font-size: 14px;
}
.hidden {
display: none !important;
}
.shipping-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.shipping-modal-overlay.active {
opacity: 1;
visibility: visible;
}
/* Modal Container */
.shipping-modal-container {
background: white;
border-radius: 8px;
max-width: 800px;
width: 100%;
overflow: hidden;
transform: scale(0.9);
transition: transform 0.3s ease;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
}
.shipping-modal-overlay.active .modal-container {
transform: scale(1);
}
/* Modal Header */
.shipping-modal-header {
background: linear-gradient(135deg, #7a4397, #9c4db4);
color: white;
padding: 16px;
text-align: center;
position: relative;
}
.shipping-modal-header h2 {
font-size: 24px;
font-weight: 600;
margin: 0;
}
.shipping-close-btn {
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
width: 32px;
height: 32px;
border-radius: 50%;
cursor: pointer;
font-size: 16px;
transition: all 0.3s ease;
}
.close-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
/* Progress Bar */
.shipping-progress-container {
padding: 24px;
background: #f8f9fa;
}
.shipping-progress-bar {
display: flex;
justify-content: space-between;
position: relative;
gap: 10px;
}
.step-wrapper {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
position: relative;
z-index: 2;
gap: 12px;
}
.shipping-progress-line {
position: absolute;
top: 25px;
left: 0;
right: 0;
height: 3px;
background: #e0e0e0;
z-index: 1;
}
.shipping-progress-line-fill {
height: 100%;
background: linear-gradient(135deg, #7a4397, #9c4db4);
width: 0%;
transition: width 0.5s ease;
}
.shipping-step {
background: white;
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 16px;
position: relative;
z-index: 2;
border: 3px solid #e0e0e0;
color: #666;
transition: all 0.3s ease;
}
.shipping-step.active {
background: linear-gradient(135deg, #7a4397, #9c4db4);
color: white;
border-color: #7a4397;
}
.shipping-step.completed {
background: linear-gradient(135deg, #7a4397, #9c4db4);
color: white;
border-color: #7a4397;
}
.shipping-step-labels {
display: flex;
justify-content: space-between;
margin-top: 10px;
}
.shipping-step-label {
text-align: center;
font-size: 12px;
color: #666;
font-weight: 500;
}
.shipping-step-label.active {
color: #7a4397;
font-weight: 600;
}
/* Modal Content */
.shipping-modal-content {
padding: 24px;
max-height: 60vh;
overflow-y: auto;
}
.shipping-step-content {
display: none;
}
.shipping-step-content.active {
display: block;
}
/* Step 1: Search Location */
.shipping-search-location {
text-align: left;
}
.shipping-search-location h3 {
color: #333;
margin-top: 0;
margin-bottom: 20px;
font-size: 20px;
font-weight: 800;
}
.shipping-search-input-group {
position: relative;
margin-bottom: 20px;
}
.shipping-search-input-group input {
width: 100%;
padding: 15px;
border: 2px solid #e0e0e0;
border-radius: 10px;
font-size: 16px;
outline: none;
transition: border-color 0.3s ease;
box-sizing: border-box;
}
.shipping-search-input-group input:focus {
border-color: #7a4397;
}
.shipping-current-location-btn {
background-color: white;
width: 100%;
box-sizing: border-box;
color: #333;
border: 1px solid #e0e0e0;
padding: 12px 24px;
border-radius: 10px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 10px;
}
.pac-container {
z-index: 9999 !important;
}
/* Step 2: Map */
.shipping-map-container {
text-align: left;
}
.shipping-map-container h3 {
color: #333;
margin-top: 0;
margin-bottom: 20px;
font-size: 20px;
font-weight: 800;
}
#map {
width: 100%;
height: 300px;
border-radius: 10px;
margin-bottom: 20px;
}
/* Step 3: Address Details */
.shipping-address-details h3 {
color: #333;
margin-bottom: 20px;
font-size: 16px;
margin-top: 0;
}
.shipping-form-group {
margin-bottom: 20px;
}
.shipping-form-group label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #333;
}
.shipping-form-group input,
.shipping-form-group textarea {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
outline: none;
transition: border-color 0.3s ease;
box-sizing: border-box;
}
.shipping-form-group input:focus,
.shipping-form-group textarea:focus {
border-color: #7a4397;
}
.shipping-form-group textarea {
resize: vertical;
min-height: 80px;
}
.shipping-form-row {
display: flex;
gap: 15px;
}
.shipping-form-row .shipping-form-group {
flex: 1;
}
.shipping-checkbox-group {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
.shipping-checkbox-group input[type="checkbox"] {
width: auto;
margin: 0;
}
.shipping-checkbox-group label {
margin: 0;
font-weight: normal;
cursor: pointer;
}
/* Modal Footer */
.shipping-modal-footer {
padding: 24px 30px;
border-top: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
.shipping-btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 600;
}
.shipping-btn-secondary {
background: #6c757d;
color: white;
}
.shipping-btn-secondary:hover {
background: #5a6268;
}
.shipping-btn-primary {
background: linear-gradient(135deg, #7a4397, #9c4db4);
color: white;
}
.shipping-btn-primary:hover {
background: linear-gradient(135deg, #6a3387, #8c3da4);
transform: translateY(-2px);
}
.shipping-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pac-container {
width: 47% !important;
box-sizing: border-box !important;
margin-top: 0px !important;
}
.shipping-address-list {
display: grid;
gap: 1.5rem;
margin-top: 1rem;
}
.shipping-empty-state {
text-align: center;
padding: 4rem 2rem;
background: #f8f9fa;
border-radius: 12px;
border: 2px dashed #e0e0e0;
}
.shipping-empty-icon {
font-size: 4rem;
color: #ccc;
margin-bottom: 1rem;
}
.shipping-empty-state h3 {
color: #666;
margin-bottom: 0.5rem;
font-size: 1.5rem;
}
.shipping-empty-state p {
color: #888;
margin-bottom: 2rem;
font-size: 1rem;
}
.shipping-address-card {
background: white;
border: 1.5px solid #f0f0f0;
border-radius: 12px;
padding: 1rem;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.shipping-address-card.default {
background: rgba(122, 67, 151, 0.08);
border-color: rgba(122, 67, 151, 0.2);
}
.shipping-address-card:hover {
border-color: #7a4397;
box-shadow: 0 4px 20px rgba(122, 67, 151, 0.1);
transform: translateY(-2px);
}
.shipping-address-header {
margin-bottom: 1rem;
}
.shipping-address-label {
display: flex;
align-items: center;
gap: 0.75rem;
}
.shipping-label-text {
font-weight: 700;
font-size: 12px;
color: #333;
}
.shipping-default-badge {
background: #d1d5db;
color: #333;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.5px;
}
.shipping-address-body {
margin-bottom: 16px;
}
.shipping-recipient-info {
margin-bottom: 1rem;
}
.shipping-recipient-name,
.shipping-recipient-phone {
display: flex;
align-items: center;
gap: 0.5rem;
color: #666;
font-size: 14px;
margin-bottom: 0.5rem;
}
.shipping-recipient-name i,
.shipping-recipient-phone i {
color: #7a4397;
width: 14px;
font-size: 12px;
}
.shipping-recipient-name span,
.shipping-recipient-phone span {
font-weight: 600;
color: #333;
}
.shipping-full-address {
display: flex;
gap: 0.75rem;
align-items: flex-start;
}
.shipping-full-address>i {
color: #7a4397;
margin-top: 0.25rem;
font-size: 14px;
}
.shipping-address-text {
flex: 1;
}
.shipping-address-main {
font-weight: 500;
color: #333;
margin-bottom: 0.5rem;
line-height: 1.5;
font-size: 12px;
}
.shipping-address-notes {
margin-top: 0.5rem;
}
.shipping-address-notes small {
color: #888;
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 12px;
}
.shipping-address-notes i {
color: #7a4397;
}
.shipping-address-footer {
display: flex;
gap: 1rem;
}
.shipping-btn-card {
padding: 0;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
border: none;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
}
.shipping-btn-edit {
background: transparent;
color: #7a4397;
}
.shipping-btn-outline {
background: transparent;
color: #7a4397;
}
.shipping-address-footer .shipping-btn-card+.shipping-btn-card {
position: relative;
padding-left: 1rem;
}
.shipping-address-footer .shipping-btn-card+.shipping-btn-card::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 1px;
height: 100%;
background-color: #d1d5db;
}
/* Responsive */
@media (max-width: 768px) {
.add-address-btn {
width: 100%;
}
.shipping-progress-container,
.shipping-modal-footer,
.shipping-modal-content {
padding: 16px;
}
.shipping-modal-container {
width: 100%;
}
.shipping-modal-header h2 {
font-size: 20px;
}
.shipping-close-btn {
width: 24px;
height: 24px;
font-size: 12px;
}
.shipping-search-input-group input {
padding: 12px;
border: 1px solid #e0e0e0;
font-size: 14px;
}
.shipping-step {
width: 40px;
height: 40px;
font-size: 14px;
}
.shipping-address-details h3,
.shipping-map-container h3,
.shipping-search-location h3 {
font-size: 16px;
}
.shipping-step-label {
font-size: 10px;
}
.shipping-form-row {
flex-direction: column;
}
.shipping-modal-footer {
gap: 10px;
}
.shipping-search-and-button {
flex-direction: column;
align-items: center;
}
.pac-container {
width: 82% !important;
box-sizing: border-box !important;
}
.shipping-btn {
padding: 8px 16px;
font-size: 12px;
}
#map {
height: 150px;
}
.shipping-address-card {
padding: 1rem;
}
.shipping-label-text {
font-size: 11px;
}
.shipping-recipient-name,
.shipping-recipient-phone {
font-size: 13px;
}
.shipping-address-main {
font-size: 11px;
}
.shipping-address-notes small {
font-size: 11px;
}
.shipping-address-footer {
flex-direction: column;
gap: 0.75rem;
}
.shipping-btn-card {
padding: 8px 16px;
border-radius: 8px;
font-size: 12px;
width: 100%;
}
.shipping-btn-edit,
.shipping-btn-outline {
border: 1px solid #7a4397;
padding: 12px 24px;
border-radius: 8px;
}
.shipping-address-label {
gap: 0.5rem;
}
.shipping-default-badge {
font-size: 11px;
}
}
</style>
<main class="checkout-container">
<input type="hidden" name="<?= $this->security->get_csrf_token_name() ?>" value="<?= $this->security->get_csrf_hash() ?>" class="csrf_token">
<h1 class="checkout-title">Checkout</h1>
<div class="checkout-left">
<!-- Shipping Address Section -->
<h2 class="checkout-section-title">Alamat Pengiriman Kamu</h2>
<section class="checkout-shipping">
<?php if (!empty($id_customer)) : ?>
<div class="checkout-address-section">
<?php if (!empty($addresses) && is_array($addresses) && count($addresses) > 0): ?>
<?php
// Cari alamat default atau alamat pertama jika tidak ada default
$selected_address = null;
foreach ($addresses as $addr) {
if ($addr->is_default == 1) {
$selected_address = $addr;
break;
}
}
if (!$selected_address && count($addresses) > 0) {
$selected_address = $addresses[0];
}
?>
<div class="checkout-address-card" id="selected-address-card">
<div class="address-label-badge">
<span class="label-text"><?= $selected_address->label ?></span>
<?php if ($selected_address->is_default == 1): ?>
<span class="default-badge">Utama</span>
<?php endif; ?>
</div>
<div class="address-recipient">
<h4 class="recipient-name"><?= $selected_address->recipient_name ?></h4>
<span class="recipient-phone">(<?= $selected_address->phone ?>)</span>
</div>
<div class="address-details">
<p class="full-address">
<?= $selected_address->address ?>
<?php if (!empty($selected_address->notes)): ?>
<br><small class="address-notes">Catatan: <?= $selected_address->notes ?></small>
<?php endif; ?>
</p>
</div>
<div class="address-actions">
<button class="btn-change-address" id="btn-change-address" data-current-id="<?= $selected_address->id ?>">
<i data-feather="map-pin" class="btn-icon"></i>
Pilih Alamat Lain
</button>
</div>
</div>
<?php else: ?>
<div class="checkout-address-card empty-address">
<div class="empty-address-content">
<i data-feather="map-pin" class="empty-icon"></i>
<h4>Belum ada alamat pengiriman</h4>
<p>Tambahkan alamat pengiriman untuk melanjutkan pemesanan</p>
<button class="btn-add-address" id="btn-add-address">
<i data-feather="plus" class="btn-icon"></i>
Tambah Alamat
</button>
</div>
</div>
<?php endif; ?>
</div>
<!-- Modal untuk memilih alamat -->
<div id="address-selection-modal" class="address-selection-modal-overlay" style="display: none;">
<div class="address-selection-modal-content">
<div class="address-selection-modal-header">
<h3>Pilih Alamat Pengiriman</h3>
<button class="address-selection-modal-close" id="close-address-modal">
<i data-feather="x"></i>
</button>
</div>
<div class="address-selection-modal-body">
<div class="address-list" id="address-list">
<?php if (!empty($addresses) && is_array($addresses)): ?>
<?php foreach ($addresses as $address): ?>
<div class="address-option" data-address-id="<?= $address->id ?>">
<div class="address-option-header">
<div class="address-label-badge">
<span class="label-text"><?= $address->label ?></span>
<?php if ($address->is_default == 1): ?>
<span class="default-badge">Utama</span>
<?php endif; ?>
</div>
<div class="custom-checkbox"></div>
<input type="radio" name="selected_address" value="<?= $address->id ?>"
<?= ($selected_address && $selected_address->id == $address->id) ? 'checked' : '' ?>>
</div>
<div class="address-option-content">
<div class="address-recipient">
<h4 class="recipient-name"><?= $address->recipient_name ?></h4>
<span class="recipient-phone">(<?= $address->phone ?>)</span>
</div>
<p class="address-text"><?= $address->address ?></p>
<?php if (!empty($address->notes)): ?>
<p class="notes">Catatan: <?= $address->notes ?></p>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="no-addresses">
<p>Belum ada alamat tersimpan. Silakan tambah alamat terlebih dahulu.</p>
</div>
<?php endif; ?>
</div>
</div>
<div class="address-selection-modal-footer">
<button class="address-selection-btn-secondary" id="cancel-address-selection">Batal</button>
<button class="address-selection-btn-secondary" id="add-address-selection">Tambah Alamat</button>
<button class="address-selection-btn-primary" id="confirm-address-selection">Pilih Alamat</button>
</div>
</div>
</div>
<?php endif; ?>
<!-- Shipping Method -->
<h2 class="checkout-section-title">Metode Pengiriman</h2>
<div class="shipping-method-selection">
<p>Dikirim dari Kota Jakarta</p>
<div class="shipping-method-options">
<?php if (!empty($all_shipping_methods)): ?>
<?php foreach ($all_shipping_methods as $index => $shipping_method): ?>
<div class="shipping-method-option">
<input type="radio"
name="shipping_method"
id="shipping_<?= $shipping_method['id'] ?>"
value="<?= $shipping_method['id'] ?>"
<?= $index === 0 ? 'checked' : '' ?>>
<label for="shipping_<?= $shipping_method['id'] ?>" class="shipping-method-label">
<div class="method-info">
<div class="method-details">
<div class="method-header">
<?php if (!empty($shipping_method['icon_url'])): ?>
<img src="<?= $shipping_method['icon_url'] ?>" alt="<?= $shipping_method['carrier'] ?>" class="method-icon">
<?php endif; ?>
<div class="method-name"><?= ucwords($shipping_method['display_name']) ?></div>
<div class="method-price">
<div class="price-group">
<?php if ($shipping_method['is_free']): ?>
<div class="price-row">
<?php if ($shipping_method['original_price'] > 0): ?>
<span class="original-price">IDR <?= number_format($shipping_method['original_price'], 0, ',', '.') ?></span>
<?php endif; ?>
<span class="free-shipping">GRATIS ONGKIR</span>
</div>
<?php elseif ($shipping_method['discount_amount'] > 0): ?>
<div class="price-row">
<span class="shipping-price">IDR <?= number_format($shipping_method['final_price'], 0, ',', '.') ?></span>
<span class="original-price">IDR <?= number_format($shipping_method['original_price'], 0, ',', '.') ?></span>
</div>
<div class="discount-info">Hemat IDR <?= number_format($shipping_method['discount_amount'], 0, ',', '.') ?></div>
<?php else: ?>
<div class="price-row">
<span class="shipping-price">IDR <?= number_format($shipping_method['final_price'], 0, ',', '.') ?></span>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="etd-info">Estimasi tiba <?= $shipping_method['etd_info']['formatted_text'] ?></div>
</div>
</div>
</label>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="no-shipping-available">
<span>Tidak ada metode pengiriman yang tersedia untuk semua produk.</span>
</div>
<?php endif; ?>
</div>
</div>
<!-- Insurance -->
<div class="insurance-header">
<?php $insuranceFee = ($grand_total * 0.2 / 100) + 5000 ?>
<h2 class="insurance-section-title">
<i class="fa-solid fa-shield-heart fa-bounce"></i>
Pakai Asuransi Pengiriman
<span class="insurance-fee" id="use-insurance">(IDR 0)</span>
</h2>
<label class="insurance-checkbox-wrapper" for="shipping_insurance">
<input type="checkbox" id="shipping_insurance" name="shipping_insurance" aria-label="Pakai Asuransi Pengiriman">
<span class="checkmark"></span>
</label>
</div>
</section>
<!-- Gift Section -->
<h2 class="checkout-section-title">Catatan</h2>
<section class="checkout-gift">
<div class="checkout-notes">
<textarea
id="gift_notes"
name="gift_notes"
placeholder="Beri tahu kami jika ini untuk hadiah atau tambahkan pesan khusus..."
maxlength="200"
class="checkout-notes-input"
aria-label="Pesan untuk hadiah"></textarea>
<span class="checkout-notes-count">0/200</span>
</div>
<div class="gift-header">
<h2 class="gift-section-title">Kirim ini ke Orang Lain?</h2>
<label class="gift-checkbox-wrapper" for="is_gift">
<input type="checkbox" id="is_gift" name="is_gift" aria-label="Kirim sebagai hadiah">
<span class="checkmark"></span>
</label>
</div>
<div class="gift-form" id="gift_form">
<div class="gift-form-group">
<label for="gift_recipient">Nama Penerima Hadiah</label>
<input
type="text"
id="gift_recipient"
name="gift_recipient"
placeholder="Masukkan nama penerima"
aria-label="Nama penerima hadiah">
</div>
<div class="gift-form-group">
<label for="gift_phone">Nomor Telepon Penerima</label>
<input
type="tel"
id="gift_phone"
name="gift_phone"
placeholder="Contoh: 08123456789"
pattern="[0-9]{10,13}"
aria-label="Nomor telepon penerima hadiah">
</div>
</div>
</section>
<!-- Product Section -->
<h2 class="checkout-section-title">Rincian Produk</h2>
<section class="checkout-products">
<?php foreach ($cart_items as $item): ?>
<div class="checkout-product-item">
<div class="checkout-product-details"
data-rowid="<?= $item['rowid']; ?>"
data-qty="<?= $item['qty']; ?>"
data-price="<?= $item['price']; ?>"
data-product-id="<?= $item['product_id']; ?>"
data-detail-id="<?= $item['id']; ?>"
data-product-name="<?= $item['name'] ?>"
data-attribute-detail-id="<?= $item['options']['attribute_detail_ids'] ?>"
data-attribute="<?= $item['variant'] ?>"
data-sku="<?= $item['sku'] ?>"
data-stock="<?= $item['options']['stock_sell'] ?>">
<img src="<?= base_url('uploads/product/' . $item['options']['image']); ?>"
alt="<?= $item['name']; ?>"
class="checkout-product-image">
<div class="checkout-product-info">
<h3 class="checkout-product-name"><?= $item['name']; ?></h3>
<div class="product-meta">
<div class="meta-item">
<span>SKU: <?= $item['sku']; ?></span>
</div>
<?php if (!empty($item['variant'])): ?>
<div class="variant-badge"><?= $item['variant']; ?></div>
<?php endif; ?>
</div>
<div class="pricing-section">
<?php if (isset($item['is_discounted']) && $item['is_discounted'] && isset($item['real_price']) && $item['real_price'] > $item['price']): ?>
<!-- Tampilkan dengan format: harga diskon (harga asli) x qty -->
<div class="price-display">
<span class="price-original">IDR <?= number_format($item['real_price'], 0, ',', '.'); ?></span>
<span class="price-current">IDR <?= number_format($item['price'], 0, ',', '.'); ?></span>
<span class="quantity-multiplier">x <?= $item['qty']; ?></span>
</div>
<?php else: ?>
<!-- Tampilkan format normal: harga x qty -->
<div class="price-display">
<span class="price-current">IDR <?= number_format($item['price'], 0, ',', '.'); ?></span>
<span class="quantity-multiplier">x <?= $item['qty']; ?></span>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</section>
</div>
<div class="checkout-right">
<!-- Payment Method Section -->
<h2 class="checkout-section-title">Metode Pembayaran</h2>
<section class="checkout-payment">
<!-- Transfer Bank Section -->
<div class="payment-category">
<h3 class="payment-category-title">Transfer Bank Manual</h3>
<div class="checkout-payment-options">
<label class="checkout-payment-option">
<input type="radio" name="payment_method" value="bank transfer BCA" checked>
<img src="https://www.bca.co.id/-/media/Feature/Card/List-Card/Tentang-BCA/Brand-Assets/Logo-BCA/Logo-BCA_Biru.png" alt="BCA" class="checkout-payment-icon">
<span>Bank BCA</span>
</label>
<label class="checkout-payment-option">
<input type="radio" name="payment_method" value="bank transfer MANDIRI">
<img src="https://storage.googleapis.com/laciasmara-photos/laciasmara_test/Laciasmara_payment/01-Mandiri%20Master%20Brand%20Logo.png" alt="Mandiri" class="checkout-payment-icon">
<span>Bank Mandiri</span>
</label>
</div>
</div>
<!-- Pembayaran Lainnya Section -->
<div class="payment-category">
<h3 class="payment-category-title">Pembayaran Lainnya</h3>
<div class="checkout-payment-options">
<label class="checkout-payment-option doku-payment-wrapper">
<div class="doku-main-option">
<input type="radio" name="payment_method" value="DOKU">
<img src="https://www.laciasmara.com/shop/assets/frontend/img/doku.png" alt="Doku" class="checkout-payment-icon">
<span>Doku</span>
</div>
<p class="doku-description">Bayar dengan Kartu Kredit, Bank Virtual Account, atau Alfa Group melalui DOKU.</p>
<div class="doku-payment-methods">
<div class="doku-payment-logos">
<img src="https://storage.googleapis.com/laciasmara-photos/laciasmara_test/Laciasmara_payment/01-Mandiri%20Master%20Brand%20Logo%20Blue.png" alt="VA Mandiri" class="payment-partner-icon">
<img src="https://storage.googleapis.com/laciasmara-photos/laciasmara_test/Laciasmara_payment/mc_symbol_opt_73_3x.png" alt="Mastercard" class="payment-partner-icon">
<img src="https://corporate.visa.com/content/dam/VCOM/corporate/about-visa/images/visa-brandmark-blue-1960x622.png" alt="Visa" class="payment-partner-icon">
<img src="https://storage.googleapis.com/laciasmara-photos/laciasmara_test/Laciasmara_payment/358102_card_jcb_payment_icon.png" alt="JCB" class="payment-partner-icon">
<img src="https://storage.googleapis.com/laciasmara-photos/laciasmara_test/Laciasmara_payment/ALFAMART_LOGO_BARU.png" alt="Alfamart" class="payment-partner-icon">
</div>
</div>
</label>
<?php if ($this->cart->total() > 1000000): ?>
<label class="checkout-payment-option">
<input type="radio" name="payment_method" value="Paypal">
<img src="https://www.paypalobjects.com/webstatic/mktg/logo/pp_cc_mark_111x69.jpg" alt="PayPal" class="checkout-payment-icon">
<span>PayPal</span>
</label>
<?php endif; ?>
</div>
<span id="paypalInformation" style="display: none;" class="info-message">
<small>
Jangan lupa untuk kembali ke <strong>Laciasmara</strong> dengan mengklik tombol di bagian bawah halaman pembayaran sukses.
</small>
</span>
</div>
</section>
<!-- Point -->
<h2 class="checkout-section-title">Point Redeem</h2>
<section class="checkout-point">
<!-- Point Balance Display -->
<div class="point-balance">
<div class="point-balance-label">Poin Kamu Saat Ini</div>
<div class="point-balance-value" id="current-points-display"><?= isset($point_reward_data->current_pointreward) ? $point_reward_data->current_pointreward : '0'; ?></div>
</div>
<!-- Conversion Info -->
<div class="conversion-info">
<span>1 poin = IDR 100 | Minimum penukaran: 1 poin</span>
</div>
<!-- Input Container -->
<div class="checkout-point-container">
<div class="input-group">
<div class="point-input-wrapper">
<input type="number"
id="redeem-points"
class="point-input"
placeholder="Masukkan jumlah poin"
min="0"
max="<?= isset($point_reward_data->current_pointreward) ? $point_reward_data->current_pointreward : '0'; ?>">
<span class="input-icon"><i class="fa-regular fa-star"></i></span>
</div>
<button type="button" id="redeem-button" class="point-redeem-btn">
Tukar Poin
</button>
</div>
<!-- Quick Select Buttons -->
<div class="quick-select">
<label class="quick-select-label">Pilihan Cepat:</label>
<div class="quick-select-buttons">
<button type="button" class="quick-select-btn" data-points="100">100 poin</button>
<button type="button" class="quick-select-btn" data-points="250">250 poin</button>
<button type="button" class="quick-select-btn" data-points="500">500 poin</button>
<button type="button" class="quick-select-btn" data-points="1000">1000 poin</button>
</div>
</div>
<!-- Discount Preview -->
<div class="discount-preview" id="discount-preview">
<span><i class="fa-solid fa-gift"></i>Hemat sampai <strong id="discount-amount">IDR 0</strong></span>
</div>
</div>
<!-- Feedback Message -->
<div class="feedback-message" id="point-redeem-feedback"></div>
</section>
<!-- Voucher -->
<?php
$cartHasDiscount = $this->session->userdata('cart_has_discounted_items');
$shouldDisable = $cartHasDiscount == 'yes';
?>
<?php if ($id_customer && empty($id_reseller)): ?>
<h2 class="checkout-section-title">Voucher</h2>
<section class="checkout-voucher">
<a
href="#"
class="voucher-button <?= $shouldDisable ? 'disabled' : '' ?>"
id="showVoucherModal"
data-vouchers='<?= json_encode($customer_vouchers) ?>'
<?= $shouldDisable ? 'disabled' : '' ?>>
<div class="voucher-content">
<i data-feather="tag" class="voucher-icon"></i>
<span class="voucher-text"><?= $shouldDisable ? 'Voucher tidak berlaku untuk produk diskon' : 'Pilih Voucher' ?></span>
</div>
<i data-feather="chevron-right" class="checkout-arrow-icon"></i>
</a>
<?php if ($shouldDisable): ?>
<span class="discount-info-text" style="color: #ff0000; font-size: 12px;">Voucher tidak berlaku untuk produk yang sudah diskon.</span>
<?php endif; ?>
</section>
<?php endif; ?>
<!-- Order Summary -->
<h2 class="checkout-section-title">Ringkasan Transaksi Kamu</h2>
<section class="checkout-summary">
<div class="checkout-summary-details">
<!-- 1. Total Harga (sebelum diskon) -->
<div class="checkout-summary-row">
<span>
<!-- <i class="fas fa-shopping-cart summary-icon summary-icon-primary"></i> -->
Total Harga (<?= $this->cart->total_items() ?> Item)
</span>
<span>IDR <?= number_format($grand_total_before_discount, 0, ',', '.') ?></span>
<span style="display:none;" id="subtotal_product"><?= number_format($grand_total, 0, ',', '.') ?></span>
</div>
<!-- 2. Biaya Ongkos Kirim -->
<div class="checkout-summary-row">
<span>
<!-- <i class="fas fa-truck summary-icon summary-icon-success"></i> -->
Biaya Ongkos Kirim
</span>
<span id="original-shipping-fee">IDR 0</span>
</div>
<!-- 3. Asuransi Pengiriman -->
<div class="checkout-summary-row" id="insurance-section">
<span>
<!-- <i class="fa-solid fa-shield-heart summary-icon summary-icon-success"></i> -->
Asuransi Pengiriman
</span>
<span id="insurance-fee">IDR 2.500</span>
</div>
<!-- 4. Bagian Diskon (Dropdown) -->
<div class="discount-section">
<button class="discount-toggle" onclick="toggleDiscount()">
<span>
<i class="fas fa-percentage summary-icon summary-icon-danger"></i>
Total Diskon
</span>
<span>
<span id="total-discount">IDR 0</span>
<i class="fas fa-chevron-down" id="discount-arrow" style="margin-left: 8px; font-size: 12px;"></i>
</span>
</button>
<div class="discount-content" id="discount-content">
<div class="discount-item">
<span>Diskon Produk</span>
<span>-IDR <span id="total-product-discount"><?= number_format($total_product_discount, 0, ',', '.') ?></span></span>
</div>
<div class="discount-item" id="voucher-redeemed-section" style="display: none;">
<span>Diskon Voucher <span id="voucher-redeem-name"></span></span>
<span><span id="voucher-redeem-value">0</span></span>
</div>
<div class="discount-item">
<span>Diskon Ongkir</span>
<span><span id="shipping-fee" class="shipping-fee">0</span></span>
</div>
</div>
</div>
<!-- 5. Pakai Poin -->
<div class="checkout-summary-row point-redeem-row" id="point-section" style="display: none;">
<span>
<!-- <i class="fas fa-star summary-icon summary-icon-danger"></i> -->
Pakai Poin
</span>
<span id="point-redeem">IDR 0</span>
</div>
<!-- 6. Bonus Poin -->
<div class="checkout-summary-row bonus-point-row">
<span>
<i class="fas fa-gift summary-icon summary-icon-gold"></i>
Bonus Poin
</span>
<span>+<?= round($grand_total / 10000) ?></span>
</div>
<!-- 7. Total Tagihan -->
<div class="checkout-summary-total">
<span>
<!-- <i class="fas fa-receipt summary-icon summary-icon-primary" style="margin-right: 8px;"></i> -->
Total Tagihan
</span>
<span>IDR <span id="final_grand_total">0</span></span>
</div>
<!-- 8. Kamu Hemat -->
<div class="checkout-summary-row savings-row">
<span>
<i class="fas fa-piggy-bank summary-icon summary-icon-primary"></i>
Kamu Hemat
</span>
<span id="checkout-savings"></span>
</div>
</div>
<button class="checkout-pay-btn">
<i class="fas fa-credit-card"></i>
Bayar Sekarang
</button>
<p class="checkout-terms">
Dengan melanjutkan pembayaran, kamu menyetujui
<a href="https://www.laciasmara.com/shop/page/Syarat-dan-Ketentuan" class="checkout-terms-link">Syarat dan Ketentuan Pembelian</a>
</p>
</section>
</div>
<!-- Modal Apply Voucher -->
<div class="apply-voucher-modal" id="voucherModal">
<div class="apply-voucher-modal-content">
<div class="apply-voucher-modal-header">
<h3 class="apply-voucher-modal-title">Pilih Voucher</h3>
<button class="apply-voucher-close-button">×</button>
</div>
<div class="voucher-list" id="voucherList">
<!-- Vouchers will be inserted here -->
</div>
<div class="manual-input">
<h3 class="apply-voucher-modal-title">Atau Masukkan Kode Voucher:</h3>
<!-- <label for="manualVoucher" class="manual-input-label">Masukkan Kode Voucher:</label> -->
<div class="manual-input-container">
<input
type="text"
id="inputReedemVoucher"
class="manual-input-field"
name="voucherRedeem"
placeholder="Masukkan kode voucher Anda" />
<button class="manual-input-button" id="redeemVoucher">Tukar</button>
</div>
</div>
<button class="apply-button" id="applyVoucher" style="display: none;" disabled>
Pakai Voucher
</button>
</div>
</div>
<div class="shipping-modal-overlay" id="addressModal">
<div class="shipping-modal-container">
<!-- Modal Header -->
<div class="shipping-modal-header">
<h2>Tambah Alamat</h2>
<button class="shipping-close-btn" onclick="closeAddAddressModal()">
<i class="fas fa-times"></i>
</button>
</div>
<!-- Progress Bar -->
<div class="shipping-progress-container">
<div class="shipping-progress-bar">
<div class="shipping-progress-line">
<div class="shipping-progress-line-fill" id="progressFill"></div>
</div>
<div class="step-wrapper">
<div class="shipping-step active" id="step1">1</div>
<div class="shipping-step-label active" id="label1">Cari lokasi pengirimanmu</div>
</div>
<div class="step-wrapper">
<div class="shipping-step" id="step2">2</div>
<div class="shipping-step-label" id="label2">Tentukan pinpoint lokasi</div>
</div>
<div class="step-wrapper">
<div class="shipping-step" id="step3">3</div>
<div class="shipping-step-label" id="label3">Lengkapi detail alamat</div>
</div>
</div>
</div>
<!-- Modal Content -->
<div class="shipping-modal-content">
<!-- Step 1: Search Location -->
<div class="shipping-step-content active" id="content1">
<div class="shipping-search-location">
<h3>Cari Lokasi Pengiriman</h3>
<div class="shipping-search-input-group">
<input type="text" id="addressSearch" placeholder="Masukkan nama jalan, gedung, atau area">
</div>
<button class="shipping-current-location-btn" onclick="getCurrentLocation()">
<i class="fas fa-location-crosshairs"></i>
Gunakan Lokasi Saat Ini
</button>
</div>
</div>
<!-- Step 2: Map -->
<div class="shipping-step-content" id="content2">
<div class="shipping-map-container">
<h3>Tentukan Pinpoint Lokasi</h3>
<div id="map"></div>
<p style="color: #666; font-size: 14px;">
<i class="fas fa-info-circle"></i>
Drag pin untuk menyesuaikan lokasi yang tepat
</p>
</div>
</div>
<!-- Step 3: Address Details -->
<div class="shipping-step-content" id="content3">
<div class="shipping-address-details">
<h3>Lengkapi detail alamat</h3>
<form id="addressForm">
<div class="shipping-form-group">
<label for="addressLabel">Label Alamat *</label>
<input type="text" id="addressLabel" placeholder="Contoh: Rumah, Kantor, Apartemen">
</div>
<div class="shipping-form-group">
<label for="fullAddress">Alamat Lengkap *</label>
<textarea id="fullAddress" placeholder="Alamat lengkap akan diisi otomatis"></textarea>
</div>
<div class="shipping-form-group">
<label for="addressNotes">Catatan Alamat</label>
<textarea id="addressNotes" placeholder="Contoh: Rumah cat hijau, sebelah warung Pak Budi"></textarea>
</div>
<div class="shipping-form-row">
<div class="shipping-form-group">
<label for="receiverName">Nama Penerima *</label>
<input type="text" id="receiverName" placeholder="Nama lengkap penerima">
</div>
<div class="shipping-form-group">
<label for="receiverPhone">Nomor HP *</label>
<input type="tel" id="receiverPhone" placeholder="08xxxxxxxxxx">
</div>
</div>
<div class="shipping-checkbox-group">
<input type="checkbox" id="isMainAddress">
<label for="isMainAddress">Jadikan sebagai alamat utama</label>
</div>
<!-- Hidden fields for coordinates -->
<input type="hidden" id="latitude">
<input type="hidden" id="longitude">
<input type="hidden" id="province">
<input type="hidden" id="city">
<input type="hidden" id="district">
<input type="hidden" id="subdistrict">
<input type="hidden" id="postal-code">
</form>
</div>
</div>
</div>
<!-- Modal Footer -->
<div class="shipping-modal-footer">
<button class="shipping-btn shipping-btn-secondary" id="backBtn" onclick="prevStep()" style="display: none;">
<i class="fas fa-arrow-left"></i> Kembali
</button>
<div style="flex: 1;"></div>
<button class="shipping-btn shipping-btn-primary" id="nextBtn" onclick="nextStep()">
Lanjutkan <i class="fas fa-arrow-right"></i>
</button>
</div>
</div>
</div>
<style>
/* Modal Container */
.apply-voucher-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
}
.apply-voucher-modal-content {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: white;
border-radius: 16px 16px 0 0;
padding: 20px;
box-sizing: border-box;
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
/* Modal Header */
.apply-voucher-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #eee;
}
.apply-voucher-modal-title {
font-size: 18px;
font-weight: bold;
color: #333;
}
.apply-voucher-close-button {
background: none;
border: none;
font-size: 24px;
color: #999;
cursor: pointer;
padding: 0;
}
.voucher-list {
max-height: 60vh;
overflow-y: auto;
padding: 0 4px;
}
/* Voucher Card */
.voucher-card {
display: flex;
align-items: center;
padding: 16px;
margin-bottom: 12px;
border: 1px solid #7A4397;
border-radius: 8px;
background-color: #fff;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.voucher-card:hover {
/* transform: translateY(-2px); */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.voucher-card.selected {
background-color: #f8f1ff;
border-color: #7A4397;
}
.voucher-info {
flex-grow: 1;
}
.voucher-name {
font-size: 14px;
color: #333;
margin-bottom: 4px;
}
.voucher-value {
font-size: 18px;
font-weight: bold;
color: #7A4397;
}
.radio-button {
width: 20px;
height: 20px;
border: 2px solid #7A4397;
border-radius: 50%;
margin-left: 12px;
position: relative;
}
.selected .radio-button::after {
content: '';
position: absolute;
width: 12px;
height: 12px;
background-color: #7A4397;
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* Apply Button */
.apply-button {
width: 100%;
padding: 16px;
background-color: #7A4397;
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: bold;
margin-top: 16px;
cursor: pointer;
transition: background-color 0.2s;
}
.apply-button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 32px 16px;
color: #666;
}
.empty-state-icon {
font-size: 48px;
margin-bottom: 16px;
}
/* Manual Input Section */
.manual-input {
padding: 0;
margin-bottom: 48px;
box-sizing: border-box;
border-top: 1px solid #eee;
}
.manual-input-label {
display: block;
font-size: 14px;
color: #333;
margin-bottom: 8px;
}
.manual-input-container {
display: flex;
gap: 8px;
}
.manual-input-field {
flex: 1;
padding: 12px;
font-size: 14px;
border: 1px solid #7A4397;
border-radius: 8px;
box-sizing: border-box;
}
.manual-input-button {
padding: 12px 16px;
background-color: #7A4397;
color: white;
border: none;
border-radius: 8px;
font-size: 14px;
cursor: pointer;
box-sizing: border-box;
transition: background-color 0.2s;
}
.manual-input-button:hover {
background-color: #68287A;
}
.manual-input-button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
/* Hide Empty State when there are vouchers */
.voucher-list:not(:empty) #emptyState {
display: none;
}
/* Responsive adjustments */
@media (min-width: 768px) {
.modal-content {
width: 480px;
left: 50%;
transform: translateX(-50%);
border-radius: 16px;
margin: 32px 0;
top: 50%;
transform: translate(-50%, -50%);
}
@keyframes slideUp {
from {
transform: translate(-50%, 100%);
}
to {
transform: translate(-50%, -50%);
}
}
}
</style>
<div id="loading-modal" class="loading-modal" style="display: none;">
<div class="loading-content">
<div class="spinner"></div>
<p>Loading, please wait...</p>
</div>
</div>
</main>
<script async src="https://maps.googleapis.com/maps/api/js?key=<?php echo $this->config->item('google_maps_api_key'); ?>&loading=async&libraries=places,marker&callback=initMap&language=id®ion=ID&v=beta" defer></script>
<script>
// CSRF
const CSRF_TOKEN = "<?= $this->security->get_csrf_token_name(); ?>";
const CSRF_HASH = "<?= $this->security->get_csrf_hash(); ?>";
const notyf = new Notyf({
duration: 3500,
position: {
x: 'right',
y: 'top'
},
types: [{
type: 'success',
background: '#7A4397',
icon: {
className: 'fas fa-check',
tagName: 'i',
color: 'white'
}
},
{
type: 'error',
background: '#dc3545',
icon: {
className: 'fas fa-times',
tagName: 'i',
color: 'white'
}
}
]
});
let currentStep = 1;
let map;
let marker;
let autocomplete;
let geocoder;
let selectedPlace = null;
const searchAddressInput = document.querySelector('.shipping-search-box input');
const addressCards = document.querySelectorAll('.shipping-address-card');
const BASE_URLS = {
SHIPPING: "<?= base_url('shipping/'); ?>",
CALCULATE_SHIPPING: "<?= base_url('shipping/calculate_shipping_fee'); ?>",
SET_VOUCHER: "<?= base_url('shipping/set_voucher') ?>",
INSERT_ORDER: "<?= base_url('shipping/insert_order_data') ?>",
INSERT_ORDER_DETAIL: "<?= base_url('shipping/create_order_detail') ?>",
PAYMENT_PROCESSING: "<?= base_url('payment/process') ?>",
DELETE_ORDER: "<?= base_url('shipping/delete_order_data') ?>",
GET_AVAILABLE_SHIPPING_METHODS: "<?= base_url('shipping/get_available_shipping_methods') ?>",
ADD_ADDRESS: "<?= base_url('account/shipping') ?>",
};
const BUTTONS = {
CHANGE_ADDRESS: document.getElementById('btn-change-address'),
ADDRESS_SELECTION_CLOSES: document.getElementById('close-address-modal'),
ADDRESS_SELECTION_CANCEL: document.getElementById('cancel-address-selection'),
ADDRESS_SELECTION_CONFIRM: document.getElementById('confirm-address-selection'),
ADDRESS_SELECTION_ADD: document.getElementById('add-address-selection'),
ADD_ADDRESS: document.getElementById('btn-add-address'),
};
const OPTIONS = {
ADDRESS_SELECTION: document.querySelectorAll('.address-option'),
}
const MODALS = {
CHANGE_ADDRESS: document.getElementById('address-modal'),
loadingModal: document.getElementById('loading-modal'),
ADDRESS_SELECTION: document.getElementById('address-selection-modal'),
ADDRESS_CARD: document.getElementById('selected-address-card')
};
// Change Address Form
const CHANGE_ADDRESS_FORM = {
form: document.getElementById('address-form'),
province: document.getElementById('ca_province'),
district: document.getElementById('ca_district'),
subdistrict: document.getElementById('ca_subdistrict'),
}
const customerAddresses = <?= json_encode($addresses) ?>;
// Initial Shipping Data
let currentAddressData = <?= !empty($addresses) && !empty($selected_address) ? json_encode($selected_address) : 'null' ?>;
const SHIPPING_DATA = {
shippingMethodInputs: document.querySelectorAll('input[name^="shipping_method"]'),
shippingFeeElement: document.getElementById('shipping-fee'),
originalShippingFeeElement: document.getElementById('original-shipping-fee'),
recipient_name: currentAddressData ? currentAddressData.recipient_name || '' : '',
recipient_phone: currentAddressData ? currentAddressData.phone || '' : '',
notes: currentAddressData ? currentAddressData.notes || '' : '',
province: currentAddressData ? currentAddressData.province || '' : '',
city: currentAddressData ? currentAddressData.city || '' : '',
district: currentAddressData ? currentAddressData.district || '' : '',
subdistrict: currentAddressData ? currentAddressData.subdistrict || '' : '',
district_id: currentAddressData ? currentAddressData.rajaongkir_district_id || 0 : 0,
subdistrict_id: currentAddressData ? currentAddressData.rajaongkir_subdistrict_id || 0 : 0,
postcode: currentAddressData ? currentAddressData.postal_code || '' : '',
full_address: currentAddressData ? currentAddressData.address || '' : '',
address_id: currentAddressData ? currentAddressData.id : null,
totalShippingFee: 0,
originalShippingFee: 0,
shippingDetails: '',
isFreeShipping: false,
discountApplied: 0,
}
// Shipping Method
const SHIPPING_METHODS = {
input: document.querySelectorAll('input[name^="shipping_method"]')
}
// Insurance
const SHIPPING_INSURANCE = {
insuranceCheckbox: document.querySelector('input[name="shipping_insurance"]'),
insuranceSection: document.getElementById('insurance-section'),
insuranceFeeElement: document.getElementById('insurance-fee'),
insuranceFee: 0
}
// Shipping notes & gift
const SHIPPING_NOTES = {
giftCheckbox: document.getElementById('is_gift'),
giftForm: document.getElementById('gift_form'),
giftPhone: document.getElementById('gift_phone'),
characterCount: document.querySelector('.character-count'),
receiverName: document.getElementById('gift_recipient'),
receiverPhone: document.getElementById('gift_phone'),
notesInput: document.querySelector('.checkout-notes-input'),
notesCount: document.querySelector('.checkout-notes-count'),
}
// Redeem Point
const POINT_ELEMENTS = {
pointSection: document.getElementById('point-section'),
pointRedeemedElement: document.getElementById('point-redeem'),
redeemButton: document.getElementById('redeem-button'),
pointInput: document.getElementById('redeem-points'),
currentPoints: 0,
pointEarned: 0,
pointRedeemed: 0,
feedbackMessage: document.getElementById('point-redeem-feedback'),
discountPreview: document.getElementById('discount-preview'),
discountAmount: document.getElementById('discount-amount'),
currentPointsDisplay: document.getElementById('current-points-display'),
quickSelectButtons: document.querySelectorAll('.quick-select-btn'),
pointValue: 100,
minimumRedeem: 1,
}
// Voucher
const VOUCHER_ELEMENTS = {
selectedVoucher: null,
isVoucherRedeemed: false,
voucherRedeemedValue: 0,
voucherRedeemedName: '',
voucherRedeemedCode: '',
voucherRedeemedType: '',
voucherRedeemedAmount: 0,
voucherDiscount: 0,
voucherRedeemedSection: document.getElementById('voucher-redeemed-section'),
voucherNameElement: document.getElementById('voucher-redeem-name'),
voucherValueElement: document.getElementById('voucher-redeem-value'),
modal: document.getElementById('voucherModal'),
showModalButton: document.getElementById('showVoucherModal'),
closeButton: document.querySelector('.apply-voucher-close-button'),
voucherList: document.getElementById('voucherList'),
applyButton: document.getElementById('applyVoucher'),
redeemVoucherButton: document.getElementById('redeemVoucher'),
redeemVoucherInput: document.getElementById('inputReedemVoucher')
}
// Payment method
const PAYMENT_METHOD_ELEMENTS = {
payButton: document.querySelector('.checkout-pay-btn'),
paymentOptions: document.querySelectorAll('.checkout-payment-option input[type="radio"]'),
paypalInformation: document.getElementById('paypalInformation'),
selectedPaymentMethod: 'bank transfer BCA',
}
// Discount Summary
const DISCOUNT_SUMMARY_ELEMENTS = {
productDiscount: 0,
voucherDiscount: 0,
shippingDiscount: 0,
totalDiscount: 0,
totalDiscountDisplay: document.getElementById('total-discount'),
}
// Total Price
const TOTAL_PRICE_SUMMARY = {
subtotal: 0,
totalProductDiscount: 0,
shippingFee: 0,
shippingFeeDiscount: 0,
totalPrice: 0,
totalSavings: 0
}
let currentShippingMethods = [];
let selectedShippingMethod = null;
document.addEventListener('DOMContentLoaded', async () => {
try {
const initialShippingMethods = <?= json_encode($all_shipping_methods) ?>;
const initialSelectedMethod = <?= json_encode($initial_shipping_method) ?>;
// Set global variables
currentShippingMethods = initialShippingMethods || [];
selectedShippingMethod = initialSelectedMethod;
// Setup handlers
setupAddressSelectionModal();
setupShippingMethodHandler();
// Set initial shipping fee display
if (selectedShippingMethod) {
updateShippingFeeDisplay(selectedShippingMethod);
}
// Setup other handlers
setupShippingNotesHandler();
setupInsuranceHandler();
setupPaymentMethodsHandler();
setupPaymentButtonHandler();
setupPointsHandler();
setupVouchersHandler();
updateTotalPrice();
toggleDiscount();
} catch (error) {
console.error('Error initializing shipping data:', error);
}
});
// ======== Shipping Address =========
const normalizeShippingData = (shippingFeeData) => {
if (Array.isArray(shippingFeeData)) {
return shippingFeeData[0] || {};
}
// Jika sudah object, return as is
return shippingFeeData;
};
const setupAddressSelectionModal = () => {
if (BUTTONS.ADD_ADDRESS) {
BUTTONS.ADD_ADDRESS.addEventListener('click', () => {
openAddAddressModal();
});
}
if (BUTTONS.ADDRESS_SELECTION_ADD) {
BUTTONS.ADDRESS_SELECTION_ADD.addEventListener('click', () => {
window.location.href = BASE_URLS.ADD_ADDRESS;
});
}
if (BUTTONS.CHANGE_ADDRESS) {
BUTTONS.CHANGE_ADDRESS.addEventListener('click', () => {
MODALS.ADDRESS_SELECTION.style.display = 'flex';
if (typeof feather !== 'undefined') {
feather.replace();
}
});
}
[BUTTONS.ADDRESS_SELECTION_CLOSES, BUTTONS.ADDRESS_SELECTION_CANCEL].forEach(btn => {
if (btn) {
btn.addEventListener('click', function() {
MODALS.ADDRESS_SELECTION.style.display = 'none';
});
}
});
// Improved: Make entire address card clickable, not just radio button
OPTIONS.ADDRESS_SELECTION.forEach(option => {
option.addEventListener('click', function(e) {
// Prevent event if clicking directly on radio button (to avoid double trigger)
if (e.target.type === 'radio') {
return;
}
const radio = this.querySelector('input[type="radio"]');
if (radio) {
radio.checked = true;
// Trigger change event for any other listeners
radio.dispatchEvent(new Event('change', {
bubbles: true
}));
}
// Visual feedback - update all options
updateAddressOptionVisuals(this);
});
// Also handle direct radio button clicks for visual feedback
const radio = option.querySelector('input[type="radio"]');
if (radio) {
radio.addEventListener('change', function() {
if (this.checked) {
updateAddressOptionVisuals(option);
}
});
}
// // Add hover effect for better UX
// option.addEventListener('mouseenter', function() {
// if (!this.querySelector('input[type="radio"]').checked) {
// this.style.borderColor = 'var(--primary-color)';
// this.style.opacity = '0.8';
// }
// });
// option.addEventListener('mouseleave', function() {
// if (!this.querySelector('input[type="radio"]').checked) {
// this.style.borderColor = '#ddd';
// this.style.opacity = '1';
// }
// });
const checkedOption = document.querySelector('.address-option input[type="radio"]:checked');
if (checkedOption) {
updateAddressOptionVisuals(checkedOption.closest('.address-option'));
}
});
// Helper function to update visual feedback
function updateAddressOptionVisuals(selectedOption) {
OPTIONS.ADDRESS_SELECTION.forEach(opt => {
opt.classList.remove('selected');
});
selectedOption.classList.add('selected');
BUTTONS.ADDRESS_SELECTION_CONFIRM.disabled = false;
}
if (BUTTONS.ADDRESS_SELECTION_CONFIRM) {
BUTTONS.ADDRESS_SELECTION_CONFIRM.addEventListener('click', async function() {
const selectedRadio = document.querySelector('input[name="selected_address"]:checked');
if (!selectedRadio) {
alert('Silakan pilih alamat terlebih dahulu');
return;
}
const selectedAddressId = selectedRadio.value;
selectedAddressData = customerAddresses.find(addr => addr.id == selectedAddressId);
if (!selectedAddressData) {
console.error('Address data not found for ID:', selectedAddressId);
return;
}
MODALS.loadingModal.style.display = 'flex';
try {
// 1. Update address card
updateAddressCard(selectedAddressData);
// 2. Fetch shipping methods untuk alamat baru
const newShippingMethods = await fetchShippingMethods(selectedAddressData);
// 3. Update global variables
currentShippingMethods = newShippingMethods || [];
selectedShippingMethod = currentShippingMethods.length > 0 ? currentShippingMethods[0] : null;
// 4. Update shipping methods UI
updateShippingMethodsUI(currentShippingMethods);
// 5. Update SHIPPING_DATA dengan address data
updateShippingData(selectedAddressData, currentShippingMethods);
// 6. Update shipping fee display dengan method pertama sebagai default
if (selectedShippingMethod) {
updateShippingFeeDisplay(selectedShippingMethod);
} else {
updateShippingFeeDisplay(null);
}
// 7. Close modal
MODALS.ADDRESS_SELECTION.style.display = 'none';
} catch (error) {
console.error('Error updating address:', error);
alert('Terjadi kesalahan saat memperbarui alamat. Silakan coba lagi.');
} finally {
MODALS.loadingModal.style.display = 'none';
updateTotalPrice();
}
});
}
}
function updateAddressCard(addressData) {
const addressCard = document.getElementById('selected-address-card');
if (!addressCard) return;
const labelText = addressCard.querySelector('.label-text');
const defaultBadge = addressCard.querySelector('.default-badge');
if (labelText) {
labelText.textContent = addressData.label;
}
// Handle default badge
if (addressData.is_default == 1) {
if (!defaultBadge) {
const labelBadge = addressCard.querySelector('.address-label-badge');
const badge = document.createElement('span');
badge.className = 'default-badge';
badge.textContent = 'Utama';
labelBadge.appendChild(badge);
}
} else {
if (defaultBadge) {
defaultBadge.remove();
}
}
// Update recipient info
const recipientName = addressCard.querySelector('.recipient-name');
const recipientPhone = addressCard.querySelector('.recipient-phone');
if (recipientName) {
recipientName.textContent = addressData.recipient_name;
}
if (recipientPhone) {
recipientPhone.textContent = `(${addressData.phone})`;
}
// Update address details
const fullAddress = addressCard.querySelector('.full-address');
if (fullAddress) {
let addressHTML = addressData.address;
if (addressData.notes) {
addressHTML += `<br><small class="address-notes">Catatan: ${addressData.notes}</small>`;
}
fullAddress.innerHTML = addressHTML;
}
// Update button data attribute
const changeAddressBtn = addressCard.querySelector('#btn-change-address');
if (changeAddressBtn) {
changeAddressBtn.setAttribute('data-current-id', addressData.id);
}
}
// Fungsi untuk fetch shipping methods
async function fetchShippingMethods(addressData) {
try {
const formData = new FormData();
formData.append('address_id', addressData.id || 0);
formData.append('destination_latitude', addressData.latitude);
formData.append('destination_longitude', addressData.longitude);
formData.append('destination_postal_code', addressData.postal_code);
formData.append(CSRF_TOKEN, CSRF_HASH);
const response = await fetch(BASE_URLS.GET_AVAILABLE_SHIPPING_METHODS, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
body: formData
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
if (!response.ok) {
if (response.status === 403) {
throw new Error('Access forbidden. Please refresh the page and try again.');
} else if (response.status === 401) {
throw new Error('Session expired. Please login again.');
} else {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
}
const result = await response.json();
if (result.success === false) {
throw new Error(result.error || 'Failed to fetch shipping methods');
}
return result.data || result;
} catch (error) {
console.error('Error fetching shipping methods:', error);
throw error;
}
}
// Display Shipping Methods
function updateShippingFeeDisplay(shippingMethodData) {
if (!shippingMethodData) {
// Handle case when no shipping available
PAYMENT_METHOD_ELEMENTS.payButton.disabled = true;
PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = 'Layanan tidak tersedia';
PAYMENT_METHOD_ELEMENTS.payButton.style.backgroundColor = '#ccc';
PAYMENT_METHOD_ELEMENTS.payButton.style.cursor = 'not-allowed';
PAYMENT_METHOD_ELEMENTS.payButton.style.opacity = '0.9';
// Reset shipping data
SHIPPING_DATA.totalShippingFee = 0;
SHIPPING_DATA.originalShippingFee = 0;
SHIPPING_DATA.isFreeShipping = false;
SHIPPING_DATA.discountApplied = 0;
return;
}
// Update SHIPPING_DATA dengan data method yang dipilih
SHIPPING_DATA.totalShippingFee = shippingMethodData.final_price;
SHIPPING_DATA.originalShippingFee = shippingMethodData.original_price;
SHIPPING_DATA.isFreeShipping = shippingMethodData.is_free;
SHIPPING_DATA.discountApplied = shippingMethodData.discount_amount;
// Update display elements
if (shippingMethodData.is_free) {
SHIPPING_DATA.originalShippingFeeElement.textContent = `IDR ${shippingMethodData.original_price.toLocaleString('id-ID')}`;
SHIPPING_DATA.shippingFeeElement.textContent = `-IDR ${shippingMethodData.original_price.toLocaleString('id-ID')}`;
} else {
SHIPPING_DATA.originalShippingFeeElement.textContent = `IDR ${shippingMethodData.original_price.toLocaleString('id-ID')}`;
SHIPPING_DATA.shippingFeeElement.textContent = `-IDR ${shippingMethodData.discount_amount.toLocaleString('id-ID')}`;
}
// Update hidden actual shipping fee input
let actualShippingFeeElement = document.getElementById('actual-shipping-fee');
if (!actualShippingFeeElement) {
actualShippingFeeElement = document.createElement('input');
actualShippingFeeElement.type = 'hidden';
actualShippingFeeElement.id = 'actual-shipping-fee';
actualShippingFeeElement.name = 'actual_shipping_fee';
document.body.appendChild(actualShippingFeeElement);
}
actualShippingFeeElement.value = shippingMethodData.final_price;
// Add hidden input for selected shipping method ID
let selectedMethodElement = document.getElementById('selected-shipping-method');
if (!selectedMethodElement) {
selectedMethodElement = document.createElement('input');
selectedMethodElement.type = 'hidden';
selectedMethodElement.id = 'selected-shipping-method';
selectedMethodElement.name = 'selected_shipping_method';
document.body.appendChild(selectedMethodElement);
}
selectedMethodElement.value = shippingMethodData.id;
// Enable pay button
PAYMENT_METHOD_ELEMENTS.payButton.disabled = false;
PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = 'Bayar Sekarang';
PAYMENT_METHOD_ELEMENTS.payButton.style.backgroundColor = '';
PAYMENT_METHOD_ELEMENTS.payButton.style.cursor = 'pointer';
PAYMENT_METHOD_ELEMENTS.payButton.style.opacity = '1';
}
// Helper function untuk format number
function formatNumber(number) {
return parseInt(number).toLocaleString('id-ID');
}
// Helper function untuk capitalize words
function capitalizeWords(str) {
return str.replace(/\w\S*/g, (txt) => {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
}
// Fungsi untuk update SHIPPING_DATA
function updateShippingData(addressData, shippingMethods = null) {
// Update currentAddressData
currentAddressData = addressData;
// Update address-related SHIPPING_DATA
SHIPPING_DATA.recipient_name = addressData.recipient_name || '';
SHIPPING_DATA.recipient_phone = addressData.phone || '';
SHIPPING_DATA.notes = addressData.notes || '';
SHIPPING_DATA.province = addressData.province || '';
SHIPPING_DATA.city = addressData.city || '';
SHIPPING_DATA.district = addressData.district || '';
SHIPPING_DATA.subdistrict = addressData.subdistrict || '';
SHIPPING_DATA.district_id = addressData.rajaongkir_district_id || 0;
SHIPPING_DATA.subdistrict_id = addressData.rajaongkir_subdistrict_id || 0;
SHIPPING_DATA.postcode = addressData.postal_code || '';
SHIPPING_DATA.full_address = addressData.address || '';
SHIPPING_DATA.address_id = addressData.id || null;
// Update shipping methods data dan variabel global
if (shippingMethods && shippingMethods.length > 0) {
// Update variabel global
currentShippingMethods = shippingMethods;
selectedShippingMethod = shippingMethods[0]; // Set yang pertama sebagai default
// Update SHIPPING_DATA dengan method pertama
const firstShipping = shippingMethods[0];
SHIPPING_DATA.totalShippingFee = firstShipping.final_price || 0;
SHIPPING_DATA.originalShippingFee = firstShipping.original_price || 0;
SHIPPING_DATA.isFreeShipping = firstShipping.is_free || false;
SHIPPING_DATA.discountApplied = firstShipping.discount_amount || 0;
// Update display elements untuk shipping fee
if (SHIPPING_DATA.shippingFeeElement) {
if (firstShipping.is_free) {
SHIPPING_DATA.shippingFeeElement.textContent =
`-IDR ${firstShipping.original_price.toLocaleString('id-ID')}`;
} else {
SHIPPING_DATA.shippingFeeElement.textContent =
`-IDR ${firstShipping.discount_amount.toLocaleString('id-ID')}`;
}
}
if (SHIPPING_DATA.originalShippingFeeElement) {
SHIPPING_DATA.originalShippingFeeElement.textContent =
`IDR ${firstShipping.original_price.toLocaleString('id-ID')}`;
}
// Update hidden inputs
updateHiddenShippingInputs(firstShipping);
} else {
// Reset shipping data jika tidak ada methods tersedia
currentShippingMethods = [];
selectedShippingMethod = null;
SHIPPING_DATA.totalShippingFee = 0;
SHIPPING_DATA.originalShippingFee = 0;
SHIPPING_DATA.isFreeShipping = false;
SHIPPING_DATA.discountApplied = 0;
// Clear display elements
if (SHIPPING_DATA.shippingFeeElement) {
SHIPPING_DATA.shippingFeeElement.textContent = 'IDR 0';
}
if (SHIPPING_DATA.originalShippingFeeElement) {
SHIPPING_DATA.originalShippingFeeElement.textContent = 'IDR 0';
}
// Clear hidden inputs
clearHiddenShippingInputs();
}
}
function openAddAddressModal() {
document.getElementById('addressModal').classList.add('active');
document.body.style.overflow = 'hidden';
// Initialize map when modal opens
setTimeout(() => {
if (typeof google !== 'undefined' && google.maps) {
initMap();
} else {
console.log('Google Maps not loaded yet');
}
}, 300);
}
function closeAddAddressModal() {
document.getElementById('addressModal').classList.remove('active');
document.body.style.overflow = 'auto';
resetModal();
}
function resetModal() {
currentStep = 1;
updateStepDisplay();
document.getElementById('addressForm').reset();
document.getElementById('addressSearch').value = '';
selectedPlace = null;
}
// Step navigation
function nextStep() {
if (currentStep < 3) {
if (validateCurrentStep()) {
currentStep++;
updateStepDisplay();
// Initialize map when entering step 2
if (currentStep === 2) {
setTimeout(() => {
if (map && selectedPlace) {
map.setCenter(selectedPlace.geometry.location);
map.setZoom(17);
if (marker.position !== undefined) {
marker.position = selectedPlace.geometry.location;
} else {
marker.setPosition(selectedPlace.geometry.location);
}
}
}, 100);
}
}
} else {
// Save address (step 3)
saveAddress();
}
}
function prevStep() {
if (currentStep > 1) {
currentStep--;
updateStepDisplay();
}
}
function updateStepDisplay() {
// Update progress bar
const progressFill = document.getElementById('progressFill');
const progressPercentage = ((currentStep - 1) / 2) * 100;
progressFill.style.width = progressPercentage + '%';
// Update steps
for (let i = 1; i <= 3; i++) {
const step = document.getElementById(`step${i}`);
const label = document.getElementById(`label${i}`);
const content = document.getElementById(`content${i}`);
// Reset all classes first
step.classList.remove('active', 'completed');
label.classList.remove('active');
content.classList.remove('active');
if (i < currentStep) {
step.classList.add('completed');
} else if (i === currentStep) {
step.classList.add('active');
label.classList.add('active');
content.classList.add('active');
}
}
// Update footer buttons
const backBtn = document.getElementById('backBtn');
const nextBtn = document.getElementById('nextBtn');
if (currentStep === 1) {
backBtn.style.display = 'none';
nextBtn.innerHTML = 'Lanjutkan <i class="fas fa-arrow-right"></i>';
} else if (currentStep === 2) {
backBtn.style.display = 'block';
nextBtn.innerHTML = 'Lanjutkan <i class="fas fa-arrow-right"></i>';
} else {
backBtn.style.display = 'block';
nextBtn.innerHTML = 'Simpan Alamat <i class="fas fa-save"></i>';
}
}
function validateCurrentStep() {
if (currentStep === 1) {
if (!selectedPlace) {
notyf.error('Silakan pilih lokasi terlebih dahulu');
return false;
}
} else if (currentStep === 2) {
// Map validation (always valid as long as marker exists)
return true;
} else if (currentStep === 3) {
// Form validation
const label = document.getElementById('addressLabel').value;
const fullAddress = document.getElementById('fullAddress').value;
const receiverName = document.getElementById('receiverName').value;
const receiverPhone = document.getElementById('receiverPhone').value;
if (!label || !fullAddress || !receiverName || !receiverPhone) {
notyf.error('Silakan lengkapi semua field yang wajib diisi');
return false;
}
}
return true;
}
function setDefaultAddress(addressId) {
const csrfName = '<?php echo $this->security->get_csrf_token_name(); ?>';
const csrfHash = '<?php echo $this->security->get_csrf_hash(); ?>';
const formData = new FormData();
formData.append(csrfName, csrfHash);
fetch(`<?= base_url('account/set_default_address/') ?>${addressId}`, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
notyf.success(data.message);
setTimeout(() => location.reload(), 1000);
} else {
notyf.error(data.message);
}
})
.catch(error => {
console.error('Error:', error);
notyf.error('Terjadi kesalahan saat mengubah alamat utama');
});
}
function saveAddress() {
if (!validateCurrentStep()) return;
// Collect address data
const addressData = {
label: document.getElementById('addressLabel').value,
fullAddress: document.getElementById('fullAddress').value,
notes: document.getElementById('addressNotes').value,
receiverName: document.getElementById('receiverName').value,
receiverPhone: document.getElementById('receiverPhone').value,
isMainAddress: document.getElementById('isMainAddress').checked,
latitude: document.getElementById('latitude').value,
longitude: document.getElementById('longitude').value,
province: document.getElementById('province') ? document.getElementById('province').value : '',
city: document.getElementById('city') ? document.getElementById('city').value : '',
district: document.getElementById('district') ? document.getElementById('district').value : '',
subdistrict: document.getElementById('subdistrict') ? document.getElementById('subdistrict').value : '',
postalCode: document.getElementById('postal-code') ? document.getElementById('postal-code').value : ''
};
// Show loading state
const saveButton = document.getElementById('nextBtn');
if (saveButton) {
saveButton.disabled = true;
saveButton.innerHTML = 'Menyimpan...';
}
// Create form data for POST request
const formData = new FormData();
Object.keys(addressData).forEach(key => {
formData.append(key, addressData[key]);
});
const csrfName = '<?php echo $this->security->get_csrf_token_name(); ?>';
const csrfHash = '<?php echo $this->security->get_csrf_hash(); ?>';
if (csrfName && csrfHash) {
formData.append(csrfName, csrfHash);
}
// Send POST request
fetch('<?php echo base_url('account/save_address'); ?>', {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
if (data.success) {
notyf.success(data.message || 'Alamat berhasil disimpan!');
closeAddAddressModal();
setTimeout(() => {
location.reload();
}, 1000);
} else {
notyf.error(data.message || 'Gagal menyimpan alamat. Silakan coba lagi.');
}
})
.catch(error => {
console.error('Error:', error);
notyf.error('Terjadi kesalahan. Silakan coba lagi.');
})
.finally(() => {
if (saveButton) {
saveButton.disabled = false;
saveButton.innerHTML = 'Simpan Alamat <i class="fas fa-save"></i>';
}
});
}
// Google Maps functions
async function initMap() {
try {
const {
Map
} = await google.maps.importLibrary("maps");
const {
AdvancedMarkerElement
} = await google.maps.importLibrary("marker");
const defaultLocation = {
lat: -6.194985,
lng: 106.823070
};
map = new Map(document.getElementById('map'), {
center: defaultLocation,
zoom: 13,
mapId: '1873c42fd77c25f9fad736e6',
mapTypeControl: false,
streetViewControl: false,
fullscreenControl: false
});
marker = new AdvancedMarkerElement({
map: map,
position: defaultLocation,
gmpDraggable: true,
title: 'Drag untuk memilih lokasi'
});
geocoder = new google.maps.Geocoder();
setupAutocomplete();
setupMapEventListeners();
} catch (error) {
console.error('Error initializing map:', error);
initMapFallback();
}
}
function initMapFallback() {
const defaultLocation = {
lat: -6.2088,
lng: 106.8456
};
map = new google.maps.Map(document.getElementById('map'), {
center: defaultLocation,
zoom: 13,
mapTypeControl: false,
streetViewControl: false,
fullscreenControl: false
});
marker = new google.maps.Marker({
position: defaultLocation,
map: map,
draggable: true,
title: 'Drag untuk memilih lokasi'
});
geocoder = new google.maps.Geocoder();
setupAutocomplete();
setupMapEventListeners();
}
function setupAutocomplete() {
const searchInput = document.getElementById('addressSearch');
if (!searchInput) return;
autocomplete = new google.maps.places.Autocomplete(searchInput, {
componentRestrictions: {
country: 'ID'
},
fields: ['formatted_address', 'geometry', 'address_components', 'name'],
});
autocomplete.addListener('place_changed', function() {
const place = autocomplete.getPlace();
if (!place.geometry) {
notyf.error('Alamat tidak ditemukan');
return;
}
selectedPlace = place;
reverseGeocode(place.geometry.location);
});
}
function setupMapEventListeners() {
if (marker.addListener) {
marker.addListener('dragend', function() {
let position;
if (marker.position && marker.position.lat) {
position = marker.position;
} else {
position = marker.getPosition();
}
reverseGeocode(position);
});
}
if (map.addListener) {
map.addListener('click', function(event) {
if (marker.position !== undefined) {
marker.position = event.latLng;
} else {
marker.setPosition(event.latLng);
}
reverseGeocode(event.latLng);
});
}
if (searchAddressInput) {
searchAddressInput.addEventListener('input', function() {
const query = this.value.toLowerCase().trim();
addressCards.forEach(card => {
const label = card.querySelector('.shipping-label-text')?.textContent.toLowerCase() || '';
const name = card.querySelector('.shipping-recipient-name span')?.textContent.toLowerCase() || '';
const phone = card.querySelector('.shipping-recipient-phone span')?.textContent.toLowerCase() || '';
const addressText = card.querySelector('.shipping-address-main')?.textContent.toLowerCase() || '';
const matches = label.includes(query) || name.includes(query) || phone.includes(query) || addressText.includes(query);
card.style.display = matches ? '' : 'none';
});
});
}
}
function getCurrentLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
function(position) {
const userLocation = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
// Create a place object similar to autocomplete result
geocoder.geocode({
location: userLocation
}, function(results, status) {
if (status === 'OK' && results[0]) {
selectedPlace = {
formatted_address: results[0].formatted_address,
geometry: {
location: userLocation
},
address_components: results[0].address_components
};
document.getElementById('addressSearch').value = results[0].formatted_address;
reverseGeocode(selectedPlace.geometry.location);
}
});
},
function(error) {
alert('Tidak dapat mengakses lokasi saat ini');
}
);
} else {
alert('Geolocation tidak didukung oleh browser ini');
}
}
function reverseGeocode(location) {
geocoder.geocode({
location: location
}, function(results, status) {
if (status === 'OK' && results[0]) {
// Update address details
const components = results[0].address_components;
let addressData = {
province: '',
city: '',
district: '',
subdistrict: '',
postal_code: ''
};
if (components) {
components.forEach(function(component) {
const types = component.types;
if (types.includes('administrative_area_level_1')) {
addressData.province = component.long_name;
} else if (types.includes('administrative_area_level_2')) {
addressData.city = component.long_name;
} else if (types.includes('administrative_area_level_3')) {
addressData.district = component.long_name;
} else if (types.includes('administrative_area_level_4')) {
addressData.subdistrict = component.long_name;
} else if (types.includes('postal_code')) {
addressData.postal_code = component.long_name;
}
});
}
// Fill form fields
document.getElementById('fullAddress').value = results[0].formatted_address;
// PERBAIKAN KOORDINAT - Handle berbagai format location
let lat, lng;
// Cek apakah ini LatLng object dengan method lat() dan lng()
if (typeof location.lat === 'function' && typeof location.lng === 'function') {
lat = location.lat();
lng = location.lng();
}
// Cek apakah ada property position (seperti di marker)
else if (location.position && typeof location.position.lat === 'function') {
lat = location.position.lat();
lng = location.position.lng();
}
// Cek apakah ini object literal dengan property lat/lng
else if (typeof location.lat === 'number' && typeof location.lng === 'number') {
lat = location.lat;
lng = location.lng;
}
// Cek apakah menggunakan latitude/longitude
else if (typeof location.latitude === 'number' && typeof location.longitude === 'number') {
lat = location.latitude;
lng = location.longitude;
}
// Fallback - coba getPosition() jika ada
else if (typeof location.getPosition === 'function') {
const pos = location.getPosition();
lat = pos.lat();
lng = pos.lng();
} else {
console.error('Unknown location format:', location);
console.error('Available properties:', Object.keys(location));
return;
}
// Set nilai ke input field
document.getElementById('latitude').value = lat;
document.getElementById('longitude').value = lng;
// Fill other fields
if (document.getElementById('province')) {
document.getElementById('province').value = addressData.province;
}
if (document.getElementById('city')) {
document.getElementById('city').value = addressData.city;
}
if (document.getElementById('district')) {
document.getElementById('district').value = addressData.district;
}
if (document.getElementById('subdistrict')) {
document.getElementById('subdistrict').value = addressData.subdistrict;
}
if (document.getElementById('postal-code')) {
document.getElementById('postal-code').value = addressData.postal_code;
}
} else {
console.error('Geocoding failed:', status);
}
});
}
// Close modal when clicking outside
document.getElementById('addressModal').addEventListener('click', function(e) {
if (e.target === this) {
closeAddAddressModal();
}
});
// Close modal with Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && document.getElementById('addressModal').classList.contains('active')) {
closeAddAddressModal();
}
});
// ======== End of Shipping Address =========
// ======== Shipping Method =========
const checkInitialShippingFee = () => {
MODALS.loadingModal.style.display = 'flex';
try {
const shippingFeeData = <?= json_encode($initial_shipping_method) ?>;
updateShippingFeeDisplay(shippingFeeData);
} catch (error) {
console.error('Error calculating shipping fee:', error);
PAYMENT_METHOD_ELEMENTS.payButton.disabled = true;
PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = 'Layanan tidak tersedia';
PAYMENT_METHOD_ELEMENTS.payButton.style.backgroundColor = '#ccc';
PAYMENT_METHOD_ELEMENTS.payButton.style.cursor = 'not-allowed';
PAYMENT_METHOD_ELEMENTS.payButton.style.opacity = '0.9';
} finally {
MODALS.loadingModal.style.display = 'none';
updateTotalPrice();
}
}
function setupShippingMethodHandler() {
const shippingContainer = document.querySelector('.shipping-method-options');
if (!shippingContainer) return;
// Remove existing listeners
const existingHandler = shippingContainer._shippingHandler;
if (existingHandler) {
shippingContainer.removeEventListener('change', existingHandler);
}
const shippingHandler = function(event) {
if (!event.target.matches('input[name^="shipping_method"]')) return;
const selectedMethodId = event.target.value;
// Cari method yang dipilih dari data yang sudah ada
const selectedMethod = currentShippingMethods.find(method => method.id === selectedMethodId);
if (selectedMethod) {
selectedShippingMethod = selectedMethod;
updateShippingFeeDisplay(selectedMethod);
updateTotalPrice();
} else {
console.error('Shipping method not found:', selectedMethodId);
}
};
shippingContainer._shippingHandler = shippingHandler;
shippingContainer.addEventListener('change', shippingHandler);
}
// Fungsi untuk update shipping methods UI
function updateShippingMethodsUI(shippingMethods) {
const shippingOptions = document.querySelector('.shipping-method-options');
if (!shippingOptions) return;
// Clear existing options
shippingOptions.innerHTML = '';
if (!shippingMethods || shippingMethods.length === 0) {
shippingOptions.innerHTML = `
<div class="no-shipping-available">
<span>Tidak ada metode pengiriman yang tersedia untuk alamat ini.</span>
</div>
`;
return;
}
// Generate new shipping options
shippingMethods.forEach((shippingMethod, index) => {
const isChecked = index === 0;
let priceHTML = '';
if (shippingMethod.is_free) {
priceHTML = `
<div class="price-row">
${shippingMethod.original_price > 0 ?
`<span class="original-price">IDR ${formatNumber(shippingMethod.original_price)}</span>` :
''
}
<span class="free-shipping">GRATIS ONGKIR</span>
</div>
`;
} else if (shippingMethod.discount_amount > 0) {
priceHTML = `
<div class="price-row">
<span class="shipping-price">IDR ${formatNumber(shippingMethod.final_price)}</span>
<span class="original-price">IDR ${formatNumber(shippingMethod.original_price)}</span>
</div>
<div class="discount-info">Hemat IDR ${formatNumber(shippingMethod.discount_amount)}</div>
`;
} else {
priceHTML = `<span class="shipping-price">IDR ${formatNumber(shippingMethod.final_price)}</span>`;
}
const optionHTML = `
<div class="shipping-method-option">
<input type="radio"
name="shipping_method"
id="shipping_${shippingMethod.id}"
value="${shippingMethod.id}"
${isChecked ? 'checked' : ''}>
<label for="shipping_${shippingMethod.id}" class="shipping-method-label">
<div class="method-info">
<div class="method-details">
<div class="method-header">
<img src="${shippingMethod.icon_url}" alt="${shippingMethod.carrier}" class="method-icon">
<div class="method-name">${capitalizeWords(shippingMethod.name)}</div>
</div>
<div class="etd-info">Estimasi tiba ${shippingMethod.etd_info.formatted_text}</div>
</div>
<div class="method-price">
<div class="price-group">
${priceHTML}
</div>
</div>
</div>
</label>
</div>
`;
shippingOptions.insertAdjacentHTML('beforeend', optionHTML);
});
}
// ======== End of Shipping Method =========
// ======== Insurance =========
const setupInsuranceHandler = () => {
if (SHIPPING_INSURANCE.insuranceCheckbox) {
SHIPPING_INSURANCE.insuranceCheckbox.addEventListener('change', function() {
if (SHIPPING_INSURANCE.insuranceCheckbox.checked) {
SHIPPING_INSURANCE.insuranceSection.style.display = 'flex';
} else {
SHIPPING_INSURANCE.insuranceSection.style.display = 'none';
}
updateTotalPrice();
});
}
}
// ======== End of Insurance =========
// ======== Notes =========
const setupShippingNotesHandler = () => {
if (SHIPPING_NOTES.notesInput && SHIPPING_NOTES.notesCount) {
SHIPPING_NOTES.notesInput.addEventListener('input', function() {
const remaining = this.value.length;
SHIPPING_NOTES.notesCount.textContent = `${remaining}/200`;
});
}
SHIPPING_NOTES.giftCheckbox.addEventListener('change', function() {
if (this.checked) {
SHIPPING_NOTES.giftForm.style.display = 'block';
setTimeout(() => {
SHIPPING_NOTES.giftForm.classList.add('active');
}, 10);
} else {
SHIPPING_NOTES.giftForm.classList.remove('active');
setTimeout(() => {
SHIPPING_NOTES.giftForm.style.display = 'none';
}, 300);
// Reset form
const inputs = SHIPPING_NOTES.giftForm.querySelectorAll('input, textarea');
inputs.forEach(input => input.value = '');
}
});
SHIPPING_NOTES.giftPhone?.addEventListener('input', function(e) {
// Remove non-numeric characters
this.value = this.value.replace(/\D/g, '');
// Limit length
if (this.value.length > 13) {
this.value = this.value.slice(0, 13);
}
});
}
// ======== End of Notes =========
// ======== Payment Methods =========
const setupPaymentMethodsHandler = () => {
PAYMENT_METHOD_ELEMENTS.paymentOptions.forEach(option => {
option.addEventListener('change', function() {
// Remove active class from all options
PAYMENT_METHOD_ELEMENTS.paymentOptions.forEach(opt => {
opt.closest('.checkout-payment-option').classList.remove('active');
});
// Add active class to selected option
if (this.checked) {
this.closest('.checkout-payment-option').classList.add('active');
if (this.value === 'Paypal') {
PAYMENT_METHOD_ELEMENTS.paypalInformation.style.display = 'block';
} else {
PAYMENT_METHOD_ELEMENTS.paypalInformation.style.display = 'none';
}
}
});
});
PAYMENT_METHOD_ELEMENTS.paymentOptions.forEach((option) => {
option.addEventListener('change', function() {
PAYMENT_METHOD_ELEMENTS.selectedPaymentMethod = document.querySelector('input[name="payment_method"]:checked')?.value;
});
});
}
const setupPaymentButtonHandler = () => {
PAYMENT_METHOD_ELEMENTS.payButton.addEventListener('click', async function(e) {
e.preventDefault();
await handlePaymentSubmission();
PAYMENT_METHOD_ELEMENTS.payButton.disabled = false;
PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = `
Bayar Sekarang
`;
})
}
// ======== End of Payment Methods =========
// ======== Point Redeem =========
const setupPointsHandler = () => {
POINT_ELEMENTS.pointEarned = <?= $this->cart->total() ?> / 10000;
POINT_ELEMENTS.currentPoints = parseInt(POINT_ELEMENTS.pointInput.getAttribute('max'));
POINT_ELEMENTS.currentPointsDisplay.textContent = POINT_ELEMENTS.currentPoints.toLocaleString('id-ID');
POINT_ELEMENTS.pointInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
redeemButton.click();
}
});
POINT_ELEMENTS.pointInput.addEventListener('input', function() {
const value = parseInt(this.value) || 0;
// Reset classes
this.classList.remove('error', 'success');
// Hide feedback
hideFeedback();
if (value > 0) {
// Show discount preview
const discount = value * POINT_ELEMENTS.pointValue;
POINT_ELEMENTS.discountAmount.textContent = `IDR ${discount.toLocaleString('id-ID')}`;
POINT_ELEMENTS.discountPreview.style.display = 'flex';
// Validation
if (value < POINT_ELEMENTS.minimumRedeem) {
this.classList.add('error');
showFeedback(`Minimum penukaran adalah ${POINT_ELEMENTS.minimumRedeem} poin`, 'error');
} else if (value > POINT_ELEMENTS.currentPoints) {
this.classList.add('error');
showFeedback('Poin yang dimasukkan melebihi poin yang kamu miliki', 'error');
} else {
this.classList.add('success');
}
POINT_ELEMENTS.pointRedeemed = value;
} else {
POINT_ELEMENTS.discountPreview.style.display = 'none';
}
});
// Quick select buttons
POINT_ELEMENTS.quickSelectButtons.forEach(button => {
const buttonPoints = parseInt(button.dataset.points);
if (buttonPoints > POINT_ELEMENTS.currentPoints) {
button.style.display = 'none';
} else {
button.addEventListener('click', function() {
const points = parseInt(this.dataset.points);
POINT_ELEMENTS.quickSelectButtons.forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
POINT_ELEMENTS.pointInput.value = points;
POINT_ELEMENTS.pointInput.dispatchEvent(new Event('input'));
});
}
});
// Main redeem function
POINT_ELEMENTS.redeemButton.addEventListener('click', function() {
const pointsToRedeem = parseInt(POINT_ELEMENTS.pointInput.value) || 0;
// Validation
if (pointsToRedeem <= 0) {
showFeedback('Silakan masukkan jumlah poin yang ingin ditukar', 'error');
return;
}
if (pointsToRedeem < POINT_ELEMENTS.minimumRedeem) {
showFeedback(`Minimum penukaran adalah ${POINT_ELEMENTS.minimumRedeem} poin`, 'error');
return;
}
if (pointsToRedeem > POINT_ELEMENTS.currentPoints) {
showFeedback('Poin yang dimasukkan melebihi poin yang Anda miliki', 'error');
return;
}
// Show loading state
setLoadingState(true);
setTimeout(() => {
const pointDiscount = pointsToRedeem * POINT_ELEMENTS.pointValue;
// Show success message
showFeedback(`Berhasil menukar ${pointsToRedeem} poin, kamu hemat IDR ${pointDiscount.toLocaleString('id-ID')}`, 'success');
// Clear input
POINT_ELEMENTS.pointInput.value = '';
POINT_ELEMENTS.discountPreview.style.display = 'none';
// Remove quick select active state
POINT_ELEMENTS.quickSelectButtons.forEach(btn => btn.classList.remove('active'));
window.pointDiscount = pointDiscount;
POINT_ELEMENTS.pointSection.style.display = 'flex';
POINT_ELEMENTS.pointRedeemedElement.textContent = `- IDR ${pointDiscount.toLocaleString('id-ID')}`;
updateTotalPrice();
setLoadingState(false);
}, 1500);
});
}
// ======== End of Point Redeem =========
// ======== Voucher Redeem =========
const setupVouchersHandler = () => {
// Voucher Reedem click listener
VOUCHER_ELEMENTS.redeemVoucherButton.addEventListener('click', function(event) {
event.preventDefault();
const voucherCode = VOUCHER_ELEMENTS.redeemVoucherInput.value.trim();
if (!voucherCode) {
notyf.error('Masukkan kode voucher terlebih dahulu!');
return;
}
const csrfToken = document.querySelector('.csrf_token').value;
MODALS.loadingModal.style.display = 'flex';
document.body.style.overflow = 'auto';
$.ajax({
url: BASE_URLS.SET_VOUCHER,
method: 'POST',
data: {
[document.querySelector('.csrf_token').name]: csrfToken,
voucher: voucherCode
},
success: function(result) {
if (typeof result === 'string') {
result = JSON.parse(result);
}
try {
if (result.success) {
VOUCHER_ELEMENTS.isVoucherRedeemed = true;
VOUCHER_ELEMENTS.voucherRedeemedValue = result.voucher.discount_value;
VOUCHER_ELEMENTS.voucherRedeemedName = result.voucher.name;
VOUCHER_ELEMENTS.voucherRedeemedCode = result.voucher.code;
VOUCHER_ELEMENTS.voucherRedeemedType = result.voucher.discount_type;
VOUCHER_ELEMENTS.voucherRedeemedSection.style.display = 'flex';
updateTotalPrice();
updateTotalDiscount();
VOUCHER_ELEMENTS.modal.style.display = 'none';
MODALS.loadingModal.style.display = 'none';
document.body.style.overflow = 'auto';
notyf.success(result.message || 'Voucher berhasil ditukarkan!');
} else {
VOUCHER_ELEMENTS.voucherRedeemedValue = 0;
VOUCHER_ELEMENTS.voucherRedeemedSection.style.display = 'none';
VOUCHER_ELEMENTS.modal.style.display = 'none';
MODALS.loadingModal.style.display = 'none';
document.body.style.overflow = 'auto';
notyf.error(result.message || 'Terjadi kesalahan saat menukarkan voucher');
}
} catch (error) {
console.error('Terjadi kesalahan:', error);
VOUCHER_ELEMENTS.modal.style.display = 'none';
MODALS.loadingModal.style.display = 'none';
document.body.style.overflow = 'auto'; // Pastikan scroll kembali normal
notyf.error('Terjadi kesalahan saat menukarkan voucher');
}
},
error: function(xhr, status, error) {
console.error('Error redeeming voucher:', error);
notyf.error('Terjadi kesalahan saat menukarkan voucher. Silakan coba lagi.');
MODALS.loadingModal.style.display = 'none';
VOUCHER_ELEMENTS.modal.style.display = 'none';
}
});
});
if (VOUCHER_ELEMENTS.showModalButton) {
VOUCHER_ELEMENTS.showModalButton.addEventListener('click', function(e) {
if (this.classList.contains('disabled') || this.getAttribute('aria-disabled') === 'true') {
// Jika tidak bisa diklik, tampilkan pesan atau lakukan sesuatu
e.preventDefault(); // Mencegah aksi default
notyf.error('Voucher tidak dapat digunakan pada produk diskon.');
return;
}
// Jika tombol aktif, lanjutkan membuka modal
e.preventDefault();
VOUCHER_ELEMENTS.modal.style.display = 'block';
document.body.style.overflow = 'hidden';
// Get vouchers from data attribute
const vouchers = JSON.parse(this.getAttribute('data-vouchers'));
renderVouchers(vouchers);
});
}
VOUCHER_ELEMENTS.closeButton.addEventListener('click', closeModal);
VOUCHER_ELEMENTS.modal.addEventListener('click', function(e) {
if (e.target === VOUCHER_ELEMENTS.modal) {
closeModal();
}
});
VOUCHER_ELEMENTS.redeemVoucherInput.addEventListener('input', function() {
const redeemVoucherInput = document.getElementById('inputReedemVoucher');
VOUCHER_ELEMENTS.redeemVoucherButton.disabled = false;
VOUCHER_ELEMENTS.applyButton.style.display = 'none';
const cards = document.querySelectorAll('.voucher-card');
cards.forEach(c => c.classList.remove('selected'));
VOUCHER_ELEMENTS.selectedVoucher = null;
})
// Apply voucher
VOUCHER_ELEMENTS.applyButton.addEventListener('click', function() {
MODALS.loadingModal.style.display = 'flex';
try {
if (VOUCHER_ELEMENTS.selectedVoucher) {
closeModal();
VOUCHER_ELEMENTS.voucherRedeemedSection.style.display = 'flex';
VOUCHER_ELEMENTS.showModalButton.querySelector('.voucher-text').textContent =
`Applied ${VOUCHER_ELEMENTS.selectedVoucher.name} (-${VOUCHER_ELEMENTS.selectedVoucher.value}%)`;
}
} catch (error) {
console.error('Error calculating shipping fee:', error);
} finally {
MODALS.loadingModal.style.display = 'none';
updateTotalPrice();
const event = new CustomEvent('voucherSelected', {
detail: VOUCHER_ELEMENTS.selectedVoucher
});
document.dispatchEvent(event);
}
});
}
// ======== End of Voucher Redeem =========
// ======== Helper =========
async function handlePaymentSubmission() {
const loadingText = MODALS.loadingModal.querySelector('p');
let paymentData = null;
try {
// Tampilkan loading modal
MODALS.loadingModal.style.display = 'flex';
PAYMENT_METHOD_ELEMENTS.payButton.disabled = true;
PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = `
<span class="checkout-pay-icon">⏳</span>
Memproses...
`;
// Ambil data pengiriman
const recipientName = SHIPPING_DATA.recipient_name;
const address = SHIPPING_DATA.full_address;
const post_code = SHIPPING_DATA.postcode;
const subdistrict = SHIPPING_DATA.subdistrict;
const district = SHIPPING_DATA.district;
const province = SHIPPING_DATA.province;
const phone = SHIPPING_DATA.recipient_phone;
// Validasi untuk memastikan data tidak kosong
let errorMessages = [];
let missingFields = [];
if (!recipientName.trim()) missingFields.push("Nama penerima");
if (!address.trim()) missingFields.push("alamat");
if (!subdistrict.trim()) missingFields.push("kecamatan");
if (!district.trim()) missingFields.push("kabupaten/kota");
if (!province.trim()) missingFields.push("provinsi");
if (!phone.trim()) missingFields.push("nomor telepon");
if (missingFields.length > 0) {
errorMessages.push(`${missingFields.join(", ")} masih kosong. Yuk, lengkapi dulu!`);
}
// Jika ada error, tampilkan pesan dan hentikan proses pembayaran
if (errorMessages.length > 0) {
errorMessages.forEach(msg => notyf.error(msg));
PAYMENT_METHOD_ELEMENTS.payButton.disabled = false;
PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = `Bayar Sekarang`;
MODALS.loadingModal.style.display = 'none';
return;
}
// ============= LANGKAH 1: CEK STOK DARI DATA FRONTEND =============
loadingText.textContent = "Memeriksa ketersediaan stok...";
const productItems = document.querySelectorAll('.checkout-product-details');
const insufficientStockItems = [];
productItems.forEach(item => {
const qty = parseInt(item.dataset.qty);
const availableStock = parseInt(item.dataset.stock);
const productName = item.dataset.productName;
const sku = item.dataset.sku;
// Cek apakah qty yang diminta melebihi stok yang tersedia
if (qty > availableStock) {
insufficientStockItems.push({
product_name: productName,
sku: sku,
requested: qty,
available: availableStock
});
}
});
// Jika ada item dengan stok tidak mencukupi, hentikan proses
if (insufficientStockItems.length > 0) {
let stockErrorMessage = "Stok tidak mencukupi untuk produk: ";
const itemNames = insufficientStockItems.map(item => {
return `${item.product_name} (diminta: ${item.requested}, tersedia: ${item.available})`;
});
stockErrorMessage += itemNames.join(", ");
notyf.error(stockErrorMessage);
MODALS.loadingModal.style.display = 'none';
PAYMENT_METHOD_ELEMENTS.payButton.disabled = false;
PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = `Bayar Sekarang`;
throw new Error(stockErrorMessage);
}
// ============= LANGKAH 2: BUAT ORDER (SETELAH STOK TERKONFIRMASI) =============
loadingText.textContent = "Menyiapkan pesanan Anda...";
const ordersFormData = {
customerId: <?= (int) $id_customer ?>,
totalAmount: TOTAL_PRICE_SUMMARY.subtotal,
orderDate: "<?= date('Y-m-d H:i:s') ?>",
recipientName: SHIPPING_DATA.recipient_name,
address: SHIPPING_DATA.full_address,
subdistrict: SHIPPING_DATA.subdistrict,
district: SHIPPING_DATA.district,
province: SHIPPING_DATA.province,
postcode: SHIPPING_DATA.postcode,
address_note: SHIPPING_DATA.notes,
phone: SHIPPING_DATA.recipient_phone,
email: "<?= $customer_data->email ?>",
first: <?= $customer_data->is_first ?> + 1,
country: "Indonesia",
shippingFee: SHIPPING_DATA.totalShippingFee,
isFreeShipping: SHIPPING_DATA.isFreeShipping,
createdBy: "System",
customerNote: SHIPPING_NOTES.notesInput.value,
giftRecipientName: SHIPPING_NOTES.receiverName.value,
giftRecipientPhone: SHIPPING_NOTES.receiverPhone.value,
insuranceStatus: SHIPPING_INSURANCE.insuranceCheckbox.checked ? "Yes" : "No",
insuranceFee: SHIPPING_INSURANCE.insuranceFee,
referral: "<?= !empty($customer_data->refferal) ? $customer_data->refferal : $this->session->userdata('referral') ?>",
$source: "<?= $this->session->userdata('initial_visitor_tracking')['source'] ?>",
$medium: "<?= $this->session->userdata('initial_visitor_tracking')['medium'] ?>",
$campaign: "<?= $this->session->userdata('initial_visitor_tracking')['campaign'] ?>",
orderLanguage: "<?= $this->session->userdata('site_lang') == 'english' ? 'english' : 'indonesian' ?>",
redeemedVoucherCode: VOUCHER_ELEMENTS.voucherRedeemedCode,
voucherRedeemedValue: VOUCHER_ELEMENTS.voucherRedeemedValue,
voucherRedeemedType: VOUCHER_ELEMENTS.voucherRedeemedType,
voucherRedeemedAmount: VOUCHER_ELEMENTS.voucherDiscount,
plusPointReward: "<?= !$id_reseller ? $this->cart->total() / 10000 : 0 ?>",
minusPointReward: parseInt(POINT_ELEMENTS.pointRedeemed),
pointRedeemedAmount: window.pointDiscount ? window.pointDiscount : 0,
grandTotalAmount: TOTAL_PRICE_SUMMARY.totalPrice,
paymentType: PAYMENT_METHOD_ELEMENTS.selectedPaymentMethod
};
// Insert ke table orders
loadingText.textContent = "Membuat pesanan...";
const orderData = await makeRequest(BASE_URLS.INSERT_ORDER, ordersFormData);
if (!orderData || orderData.error) {
MODALS.loadingModal.style.display = 'none';
PAYMENT_METHOD_ELEMENTS.payButton.disabled = false;
PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = `Bayar Sekarang`;
throw new Error(orderData.error || 'Gagal membuat pesanan');
}
// ============= LANGKAH 3: BUAT ORDER DETAIL =============
loadingText.textContent = "Menyiapkan detail produk...";
const orderDetailData = [];
productItems.forEach(item => {
const rowId = item.dataset.rowid;
const qty = parseInt(item.dataset.qty);
const price = parseInt(item.dataset.price);
const productId = item.dataset.productId;
const detailId = item.dataset.detailId;
const productName = item.dataset.productName;
const attributeDetailId = item.dataset.attributeDetailId;
const variant = item.dataset.attribute;
const sku = item.dataset.sku;
// Cari metode pengiriman yang dipilih
const selectedShipping = document.querySelector('input[name="shipping_method"]:checked');
const shippingMethodId = selectedShipping ? selectedShipping.value : null;
if (shippingMethodId) {
orderDetailData.push({
orderId: parseInt(orderData.order_id), // ID dari pesanan utama
productName: productName,
productId: parseInt(productId),
detailId: parseInt(detailId),
quantity: parseInt(qty),
itemPrice: parseInt(price),
shippingMethodId: parseInt(shippingMethodId),
warehouseId: 1,
shippingFee: parseInt(TOTAL_PRICE_SUMMARY.totalShippingFee),
attributeDetailId: attributeDetailId,
attribute: variant,
subtotal: price * qty,
sku: sku,
});
} else {
console.warn(`Tidak ada metode pengiriman dipilih untuk item ${rowId}`);
}
});
// Validasi orderDetailData sebelum dikirim
if (!Array.isArray(orderDetailData) || orderDetailData.length === 0) {
MODALS.loadingModal.style.display = 'none';
PAYMENT_METHOD_ELEMENTS.payButton.disabled = false;
PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = `Bayar Sekarang`;
throw new Error('Tidak ada produk yang valid untuk diproses. Pastikan metode pengiriman sudah dipilih.');
}
const payload = {
orderDetailData: orderDetailData // Pastikan key sesuai dengan yang di-expect backend
};
// Setup order timer untuk timeout
let orderTimeout = setTimeout(() => {
loadingText.textContent = "Proses membutuhkan waktu lebih lama dari biasanya...";
}, 10000);
try {
loadingText.textContent = "Menyimpan detail pesanan...";
const orderDetail = await makeRequest(BASE_URLS.INSERT_ORDER_DETAIL, payload);
clearTimeout(orderTimeout);
if (!orderDetail.status) {
throw new Error(orderDetail.message || 'Gagal membuat detail pesanan');
}
// ============= LANGKAH 4: PROSES PEMBAYARAN =============
loadingText.textContent = "Menghubungi gateway pembayaran...";
const paymentFormData = {
orderId: parseInt(orderData.order_id),
paymentType: PAYMENT_METHOD_ELEMENTS.selectedPaymentMethod
};
// Setup payment timer
let paymentTimeout = setTimeout(() => {
loadingText.textContent = "Menunggu gateway pembayaran merespons...";
}, 8000);
const paymentData = await makeRequest(BASE_URLS.PAYMENT_PROCESSING, paymentFormData);
clearTimeout(paymentTimeout);
if (paymentData.status === 'success' && paymentData.payment_type.toLowerCase() === 'doku') {
loadingText.textContent = "Mengarahkan ke halaman pembayaran DOKU...";
const form = document.createElement('form');
form.method = 'POST';
form.action = paymentData.redirect_url;
form.id = 'MerchatPaymentPage';
form.name = 'MerchatPaymentPage';
Object.entries(paymentData.response).forEach(([key, value]) => {
if (value !== null) {
const hiddenField = document.createElement('input');
hiddenField.type = 'hidden';
hiddenField.name = key;
hiddenField.id = key;
hiddenField.value = value;
form.appendChild(hiddenField);
}
});
document.body.appendChild(form);
setTimeout(() => {
form.submit();
}, 500);
return;
} else if (paymentData.status === 'success') {
loadingText.textContent = "Mengarahkan ke halaman pembayaran...";
setTimeout(() => {
window.location.href = paymentData.redirect_url;
}, 500);
return;
} else {
notyf.error('Error: ' + paymentData.message);
throw new Error(paymentData.message || 'Gagal memproses pembayaran');
}
} catch (orderDetailError) {
loadingText.textContent = "Terjadi kesalahan. Membersihkan pesanan...";
try {
const deleteResult = await makeRequest(BASE_URLS.DELETE_ORDER, {
orderId: orderData.order_id
});
if (deleteResult.status) {
loadingText.textContent = "Pesanan dibersihkan. Silakan coba lagi.";
} else {
loadingText.textContent = "Gagal membersihkan pesanan. Hubungi customer service.";
}
} catch (deleteError) {
loadingText.textContent = "Gagal membersihkan pesanan. Hubungi customer service.";
}
setTimeout(() => {
MODALS.loadingModal.style.display = 'none';
PAYMENT_METHOD_ELEMENTS.payButton.disabled = false;
PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = `Bayar Sekarang`;
}, 2000);
throw orderDetailError;
}
} catch (error) {
let errorMessage = error.message || 'Terjadi kesalahan saat memproses pembayaran';
notyf.error(errorMessage);
console.error('Payment error:', error);
setTimeout(() => {
MODALS.loadingModal.style.display = 'none';
PAYMENT_METHOD_ELEMENTS.payButton.disabled = false;
PAYMENT_METHOD_ELEMENTS.payButton.innerHTML = `Bayar Sekarang`;
}, 2000);
}
}
// Initialize voucher discount
if (!VOUCHER_ELEMENTS.voucherDiscount) {
VOUCHER_ELEMENTS.voucherDiscount = 0;
}
// Utility function for consistent number parsing
const parsePrice = (text) => {
if (!text) return 0;
const cleaned = text.replace(/[^\d.-]/g, '').replace(/\./g, '');
const parsed = parseFloat(cleaned);
return isNaN(parsed) ? 0 : Math.abs(parsed);
};
const updateTotalDiscount = () => {
// Get fresh text content from elements
const productDiscountElement = document.getElementById('total-product-discount');
const voucherDiscountElement = document.getElementById('voucher-redeem-value');
const shippingDiscountElement = document.getElementById('shipping-fee');
// Parse discount values
DISCOUNT_SUMMARY_ELEMENTS.productDiscount = productDiscountElement ?
parsePrice(productDiscountElement.textContent.trim()) : 0;
DISCOUNT_SUMMARY_ELEMENTS.voucherDiscount = voucherDiscountElement ?
parsePrice(voucherDiscountElement.textContent.trim()) : 0;
DISCOUNT_SUMMARY_ELEMENTS.shippingDiscount = shippingDiscountElement ?
parsePrice(shippingDiscountElement.textContent.trim()) : 0;
// Calculate total discount
DISCOUNT_SUMMARY_ELEMENTS.totalDiscount =
DISCOUNT_SUMMARY_ELEMENTS.productDiscount +
DISCOUNT_SUMMARY_ELEMENTS.voucherDiscount +
DISCOUNT_SUMMARY_ELEMENTS.shippingDiscount;
// Format and display total discount
const formattedTotalDiscount = DISCOUNT_SUMMARY_ELEMENTS.totalDiscount > 0 ?
`-IDR ${DISCOUNT_SUMMARY_ELEMENTS.totalDiscount.toLocaleString('id-ID')}` : 'IDR 0';
// Update total discount display
if (DISCOUNT_SUMMARY_ELEMENTS.totalDiscountDisplay) {
DISCOUNT_SUMMARY_ELEMENTS.totalDiscountDisplay.textContent = formattedTotalDiscount;
}
};
const updateTotalPrice = () => {
// Update discount summary first
updateTotalDiscount();
// Get fresh text content from elements
const subtotalElement = document.getElementById('subtotal_product');
const totalProductDiscountElement = document.getElementById('total-product-discount');
const shippingFeeElement = document.getElementById('original-shipping-fee');
const shippingFeeDiscountElement = document.getElementById('shipping-fee');
// Parse price values
TOTAL_PRICE_SUMMARY.subtotal = subtotalElement ?
parsePrice(subtotalElement.textContent.trim()) : 0;
TOTAL_PRICE_SUMMARY.totalProductDiscount = totalProductDiscountElement ?
parsePrice(totalProductDiscountElement.textContent.trim()) : 0;
TOTAL_PRICE_SUMMARY.shippingFee = shippingFeeElement ?
parsePrice(shippingFeeElement.textContent.trim()) : 0;
TOTAL_PRICE_SUMMARY.shippingFeeDiscount = shippingFeeDiscountElement ?
parsePrice(shippingFeeDiscountElement.textContent.trim()) : 0;
// Validate subtotal
if (TOTAL_PRICE_SUMMARY.subtotal === 0) {
console.warn('Subtotal is 0 or invalid');
return;
}
// Initialize voucher discount
if (!VOUCHER_ELEMENTS.voucherDiscount) {
VOUCHER_ELEMENTS.voucherDiscount = 0;
}
// Handle voucher calculations
if (VOUCHER_ELEMENTS.selectedVoucher) {
VOUCHER_ELEMENTS.voucherNameElement.textContent = `(${VOUCHER_ELEMENTS.selectedVoucher.code})`;
if (VOUCHER_ELEMENTS.selectedVoucher.type === "percentage") {
VOUCHER_ELEMENTS.voucherDiscount = (TOTAL_PRICE_SUMMARY.subtotal * VOUCHER_ELEMENTS.selectedVoucher.value) / 100;
VOUCHER_ELEMENTS.voucherValueElement.textContent = `-IDR ${VOUCHER_ELEMENTS.voucherDiscount.toLocaleString('id-ID')}`;
} else if (VOUCHER_ELEMENTS.selectedVoucher.type === "amount") {
VOUCHER_ELEMENTS.voucherDiscount = VOUCHER_ELEMENTS.selectedVoucher.value;
VOUCHER_ELEMENTS.voucherValueElement.textContent = `-IDR ${VOUCHER_ELEMENTS.selectedVoucher.value.toLocaleString('id-ID')}`;
}
// Store voucher redemption data
VOUCHER_ELEMENTS.voucherRedeemedValue = VOUCHER_ELEMENTS.selectedVoucher.value;
VOUCHER_ELEMENTS.voucherRedeemedType = VOUCHER_ELEMENTS.selectedVoucher.type;
VOUCHER_ELEMENTS.voucherRedeemedName = VOUCHER_ELEMENTS.selectedVoucher.name;
VOUCHER_ELEMENTS.voucherRedeemedCode = VOUCHER_ELEMENTS.selectedVoucher.code;
VOUCHER_ELEMENTS.voucherRedeemedAmount = VOUCHER_ELEMENTS.voucherDiscount;
} else if (VOUCHER_ELEMENTS.isVoucherRedeemed) {
VOUCHER_ELEMENTS.voucherNameElement.textContent = `(${VOUCHER_ELEMENTS.voucherRedeemedCode})`;
if (VOUCHER_ELEMENTS.voucherRedeemedType === "percentage") {
VOUCHER_ELEMENTS.voucherDiscount = (TOTAL_PRICE_SUMMARY.subtotal * VOUCHER_ELEMENTS.voucherRedeemedValue) / 100;
VOUCHER_ELEMENTS.voucherValueElement.textContent = `-IDR ${VOUCHER_ELEMENTS.voucherDiscount.toLocaleString('id-ID')}`;
} else if (VOUCHER_ELEMENTS.voucherRedeemedType === "amount") {
VOUCHER_ELEMENTS.voucherDiscount = VOUCHER_ELEMENTS.voucherRedeemedValue;
VOUCHER_ELEMENTS.voucherValueElement.textContent = `-IDR ${VOUCHER_ELEMENTS.voucherRedeemedValue.toLocaleString('id-ID')}`;
}
}
// Calculate total price
TOTAL_PRICE_SUMMARY.totalPrice = TOTAL_PRICE_SUMMARY.subtotal +
TOTAL_PRICE_SUMMARY.shippingFee -
TOTAL_PRICE_SUMMARY.shippingFeeDiscount -
VOUCHER_ELEMENTS.voucherDiscount;
// Calculate insurance fee
SHIPPING_INSURANCE.insuranceFee = Math.ceil((TOTAL_PRICE_SUMMARY.totalPrice * 0.2 / 100) + 5000);
const formattedInsuranceFee = `IDR ${SHIPPING_INSURANCE.insuranceFee.toLocaleString('id-ID')}`;
const useInsuranceElement = document.getElementById('use-insurance');
if (useInsuranceElement) {
useInsuranceElement.textContent = `(${formattedInsuranceFee})`;
}
// Apply point discount if available
if (window.pointDiscount) {
TOTAL_PRICE_SUMMARY.totalPrice -= window.pointDiscount;
}
// Handle insurance checkbox
if (SHIPPING_INSURANCE.insuranceCheckbox && SHIPPING_INSURANCE.insuranceCheckbox.checked) {
const insuranceFeeElement = document.getElementById('insurance-fee');
if (insuranceFeeElement) {
insuranceFeeElement.textContent = formattedInsuranceFee;
}
if (useInsuranceElement) {
useInsuranceElement.textContent = `(${formattedInsuranceFee})`;
}
TOTAL_PRICE_SUMMARY.totalPrice += SHIPPING_INSURANCE.insuranceFee;
} else {
if (SHIPPING_INSURANCE.insuranceFeeElement) {
SHIPPING_INSURANCE.insuranceFeeElement.textContent = '';
}
if (SHIPPING_INSURANCE.insuranceSection) {
SHIPPING_INSURANCE.insuranceSection.style.display = 'none';
}
}
// Update total price display
const totalDisplay = document.querySelector('.checkout-summary-total #final_grand_total');
if (totalDisplay) {
totalDisplay.textContent = `${TOTAL_PRICE_SUMMARY.totalPrice.toLocaleString('id-ID')}`;
}
const savingsRow = document.querySelector('.checkout-summary-row.savings-row');
const savingsDisplay = document.getElementById('checkout-savings');
const savingsPointDiscount = window.pointDiscount ?? 0;
TOTAL_PRICE_SUMMARY.totalSavings =
DISCOUNT_SUMMARY_ELEMENTS.totalDiscount +
savingsPointDiscount +
VOUCHER_ELEMENTS.voucherDiscount;
if (savingsDisplay && savingsRow) {
if (TOTAL_PRICE_SUMMARY.totalSavings > 0) {
savingsDisplay.textContent = `IDR ${TOTAL_PRICE_SUMMARY.totalSavings.toLocaleString('id-ID')}!`;
savingsRow.style.display = 'flex';
} else {
savingsRow.style.display = 'none';
}
}
};
const selectVoucher = (voucher, card) => {
// Remove selection from other cards
const cards = document.querySelectorAll('.voucher-card');
cards.forEach(c => c.classList.remove('selected'));
// Add selection to clicked card
card.classList.add('selected');
// Enable apply button
VOUCHER_ELEMENTS.applyButton.style.display = 'block';
VOUCHER_ELEMENTS.applyButton.disabled = false;
VOUCHER_ELEMENTS.redeemVoucherButton.disabled = true;
VOUCHER_ELEMENTS.selectedVoucher = voucher;
}
// Render vouchers
function renderVouchers(vouchers) {
if (!vouchers || vouchers.length === 0) {
VOUCHER_ELEMENTS.voucherList.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">📝</div>
<p>Belum ada voucher yang tersedia</p>
</div>
`;
return;
}
VOUCHER_ELEMENTS.voucherList.innerHTML = vouchers.map((voucher, index) => `
<div class="voucher-card" data-index="${index}">
<div class="voucher-info">
<div class="voucher-name">${voucher.name}</div>
<div class="voucher-value">Diskon ${voucher.value}%</div>
</div>
<div class="radio-button"></div>
</div>
`).join('');
// Add click handlers to voucher cards
const voucherCards = VOUCHER_ELEMENTS.voucherList.querySelectorAll('.voucher-card');
voucherCards.forEach(card => {
card.addEventListener('click', function() {
const index = this.getAttribute('data-index');
selectVoucher(vouchers[index], this);
});
});
}
function closeModal() {
VOUCHER_ELEMENTS.modal.style.display = 'none';
document.body.style.overflow = 'auto';
}
const populateSelectOptions = (selectEl, items, valueKey, textKey) => {
items.forEach(item => {
const option = document.createElement('option');
option.value = item[valueKey];
option.textContent = item[textKey];
selectEl.appendChild(option);
});
};
const createFormData = (data) => {
const formData = new FormData();
formData.append(CSRF_TOKEN, CSRF_HASH);
Object.keys(data).forEach(key => {
if (key === 'orderDetailData') {
formData.append(key, JSON.stringify(data[key]));
} else {
formData.append(key, data[key]);
}
});
return formData;
};
const makeRequest = async (url, data) => {
const formData = createFormData(data);
const response = await fetch(url, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
body: formData
});
return response.json();
};
function toggleDiscount() {
const content = document.getElementById('discount-content');
const arrow = document.getElementById('discount-arrow');
if (content.classList.contains('active')) {
content.classList.remove('active');
arrow.style.transform = 'rotate(0deg)';
} else {
content.classList.add('active');
arrow.style.transform = 'rotate(180deg)';
}
}
function showTooltip(event) {
const tooltip = document.createElement('div');
tooltip.className = 'checkout-tooltip';
tooltip.textContent = event.target.dataset.tooltip;
document.body.appendChild(tooltip);
const triggerRect = event.target.getBoundingClientRect();
tooltip.style.top = `${triggerRect.bottom + 5}px`;
tooltip.style.left = `${triggerRect.left}px`;
}
function hideTooltip() {
const tooltip = document.querySelector('.checkout-tooltip');
if (tooltip) {
tooltip.remove();
}
}
function showFeedback(message, type) {
let iconClass = '';
if (type === 'success') {
iconClass = 'fa fa-check-circle';
} else if (type === 'error') {
iconClass = 'fa fa-times-circle';
}
POINT_ELEMENTS.feedbackMessage.innerHTML = `<i class="${iconClass}"></i> ${message}`;
POINT_ELEMENTS.feedbackMessage.className = `feedback-message ${type}`;
POINT_ELEMENTS.feedbackMessage.style.display = 'flex';
}
function hideFeedback() {
POINT_ELEMENTS.feedbackMessage.style.display = 'none';
}
function setLoadingState(loading) {
if (loading) {
POINT_ELEMENTS.redeemButton.classList.add('loading');
POINT_ELEMENTS.redeemButton.disabled = true;
POINT_ELEMENTS.redeemButton.textContent = '';
} else {
POINT_ELEMENTS.redeemButton.classList.remove('loading');
POINT_ELEMENTS.redeemButton.disabled = false;
POINT_ELEMENTS.redeemButton.textContent = 'Tukar Poin';
}
}
function getCurrentSelectedShippingMethod() {
return selectedShippingMethod;
}
// Helper function untuk mendapatkan semua shipping methods yang tersedia
function getAllShippingMethods() {
return currentShippingMethods;
}
// Function untuk memvalidasi apakah ada shipping method yang terpilih
function validateShippingSelection() {
const checkedMethod = document.querySelector('input[name="shipping_method"]:checked');
return checkedMethod && selectedShippingMethod;
}
function updateHiddenShippingInputs(shippingMethod) {
let actualShippingFeeElement = document.getElementById('actual-shipping-fee');
if (!actualShippingFeeElement) {
actualShippingFeeElement = document.createElement('input');
actualShippingFeeElement.type = 'hidden';
actualShippingFeeElement.id = 'actual-shipping-fee';
actualShippingFeeElement.name = 'actual_shipping_fee';
document.body.appendChild(actualShippingFeeElement);
}
actualShippingFeeElement.value = shippingMethod.final_price;
let selectedMethodElement = document.getElementById('selected-shipping-method');
if (!selectedMethodElement) {
selectedMethodElement = document.createElement('input');
selectedMethodElement.type = 'hidden';
selectedMethodElement.id = 'selected-shipping-method';
selectedMethodElement.name = 'selected_shipping_method';
document.body.appendChild(selectedMethodElement);
}
selectedMethodElement.value = shippingMethod.id;
}
// Helper function untuk clear hidden inputs
function clearHiddenShippingInputs() {
const actualShippingFeeElement = document.getElementById('actual-shipping-fee');
if (actualShippingFeeElement) {
actualShippingFeeElement.value = 0;
}
const selectedMethodElement = document.getElementById('selected-shipping-method');
if (selectedMethodElement) {
selectedMethodElement.value = '';
}
}
// ======== End of Helper =========
</script>