forked from UKSOURCE/cms.hailearning.edu.vn
first commit
This commit is contained in:
638
views/admin/insurance/index.ejs
Normal file
638
views/admin/insurance/index.ejs
Normal file
@@ -0,0 +1,638 @@
|
||||
<div class="container">
|
||||
<div class="d-flex justify-content-between align-items-center mt-4 mb-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-2" style="color: var(--primary-dark);">
|
||||
<%= title %>
|
||||
</h1>
|
||||
<p class="text-muted mb-0">
|
||||
Manage insurance page content with hero section, page information, and rich content editor
|
||||
</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-outline-primary preview-btn">
|
||||
<i class="fas fa-eye me-2"></i>Preview
|
||||
</button>
|
||||
<button type="submit" form="insuranceForm" class="btn btn-primary" id="saveBtn">
|
||||
<i class="fas fa-save me-2"></i>Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="insuranceForm" action="/admin/insurance/update" method="POST" class="needs-validation" novalidate>
|
||||
<!-- Hidden inputs -->
|
||||
<input type="hidden" name="hero" id="heroJson">
|
||||
<input type="hidden" name="page" id="pageJson">
|
||||
<input type="hidden" name="content" id="contentJson">
|
||||
|
||||
<div class="row">
|
||||
<!-- Main Content Column -->
|
||||
<div class="col-lg-8">
|
||||
<!-- Page Information Card -->
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="card-title mb-0">Page Information</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Page Title <span class="text-danger">*</span></label>
|
||||
<textarea class="form-control" id="pageTitle" rows="2" required><%= data.page?.title || 'Insurance & Travel Information' %></textarea>
|
||||
<div class="invalid-feedback">Please enter a page title</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hero Section Card -->
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="card-title mb-0">Hero Section</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Background Image</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="heroBackgroundImage"
|
||||
value="<%= data.hero?.backgroundImage || '/uploads/banner/b13.jpg' %>" readonly>
|
||||
<button type="button" class="btn btn-outline-primary btn-upload-image"
|
||||
data-target-input="heroBackgroundImage"
|
||||
data-image-type="insurance">
|
||||
<i class="fas fa-upload"></i>
|
||||
</button>
|
||||
</div>
|
||||
<small class="form-text text-muted">Recommended size: 1920x1080px</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-7">
|
||||
<div id="heroImagePreview" style="height: 200px; width: 100%;">
|
||||
<% if (data.hero?.backgroundImage) { %>
|
||||
<%
|
||||
const heroImgSrc = data.hero.backgroundImage.startsWith('http')
|
||||
? data.hero.backgroundImage
|
||||
: (frontendUrl ? frontendUrl + '/' + data.hero.backgroundImage : data.hero.backgroundImage);
|
||||
%>
|
||||
<img src="<%= heroImgSrc %>" class="img-thumbnail"
|
||||
style="height: 200px; width: 100%; object-fit: cover;"
|
||||
alt="Background image preview"
|
||||
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
|
||||
<div class="border rounded p-5 text-center text-muted"
|
||||
style="height: 200px; display: none; align-items: center; justify-content: center; flex-direction: column;">
|
||||
<i class="fas fa-image fa-3x mb-2"></i><p class="mb-0">Image preview not available</p>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div class="border rounded p-5 text-center text-muted"
|
||||
style="height: 200px; display: flex; align-items: center; justify-content: center; flex-direction: column;">
|
||||
<i class="fas fa-image fa-3x mb-2"></i><p class="mb-0">No image selected</p>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Hero Title</label>
|
||||
<input type="text" class="form-control" id="heroTitle"
|
||||
value="<%= data.hero?.title || 'Insurance & Travel Cancellation Guarantee' %>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Hero Subtitle</label>
|
||||
<input type="text" class="form-control" id="heroSubtitle"
|
||||
value="<%= data.hero?.subtitle || 'Comprehensive coverage for your peace of mind' %>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Editor Section -->
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="card-title mb-0">Content Editor</h5>
|
||||
<p class="text-muted mb-0 small">Write your insurance page content just like a blog post</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="editorjs" class="border rounded p-3" style="min-height: 500px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar Column -->
|
||||
<div class="col-lg-4">
|
||||
<!-- SEO Settings Card -->
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="card-title mb-0">SEO Settings</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Meta Title</label>
|
||||
<input type="text" class="form-control" id="metadataTitle"
|
||||
value="<%= data.page?.metadata?.title || '' %>">
|
||||
<small class="text-muted">Title for search engines (max 60 characters)</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Meta Description</label>
|
||||
<textarea class="form-control" id="metadataDescription" rows="3"
|
||||
placeholder="Meta description for SEO"><%= data.page?.metadata?.description || '' %></textarea>
|
||||
<small class="text-muted">Description for search engines (max 160 characters)</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Page Settings Card -->
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="card-title mb-0">Page Settings</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="pageDivider"
|
||||
<%= data.page?.divider !== false ? 'checked' : '' %>>
|
||||
<label class="form-check-label" for="pageDivider">Show page divider</label>
|
||||
</div>
|
||||
<small class="text-muted">Display divider line below page title</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="enableScrollspy"
|
||||
<%= data.hero?.enableScrollspy ? 'checked' : '' %>>
|
||||
<label class="form-check-label" for="enableScrollspy">Enable Scrollspy Navigation</label>
|
||||
</div>
|
||||
<small class="text-muted">Creates table of contents from headers</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Tips Card -->
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="card-title mb-0">Content Tips</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-info">
|
||||
<h6><i class="fas fa-lightbulb me-2"></i>Tips for Insurance Content:</h6>
|
||||
<ul class="mb-0 small">
|
||||
<li>Use <strong>Header 2</strong> for main sections</li>
|
||||
<li>Use <strong>Header 3</strong> for subsections</li>
|
||||
<li>Use <strong>Lists</strong> for coverage items</li>
|
||||
<li>Use <strong>Quote</strong> for important notes</li>
|
||||
<li>Use <strong>Embed</strong> for video explanations</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Preview Modal -->
|
||||
<div class="modal fade" id="previewModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Insurance Page Preview</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body p-0">
|
||||
<iframe id="previewFrame" style="width: 100%; height: 80vh; border: none;"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden inputs for file upload -->
|
||||
<input type="file" id="directImageUpload" style="display: none;">
|
||||
<input type="hidden" id="currentImageType">
|
||||
<input type="hidden" id="currentTargetInput">
|
||||
|
||||
<!-- Editor.js Dependencies -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@2.28.2"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/header@2.7.0"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/paragraph@2.11.3"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/list@1.8.0"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/image@2.8.1"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/quote@2.5.0"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/marker@1.3.0"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/embed@2.5.3"></script>
|
||||
|
||||
<script>
|
||||
// Insurance Content Manager
|
||||
class InsuranceContentManager {
|
||||
// Convert Editor.js data to Insurance content structure
|
||||
convertEditorToInsurance(editorData) {
|
||||
const contentItems = [];
|
||||
|
||||
editorData.blocks.forEach(block => {
|
||||
switch (block.type) {
|
||||
case 'header':
|
||||
contentItems.push({
|
||||
type: 'header',
|
||||
level: block.data.level || 2,
|
||||
text: block.data.text || ''
|
||||
});
|
||||
break;
|
||||
|
||||
case 'paragraph':
|
||||
contentItems.push({
|
||||
type: 'paragraph',
|
||||
text: block.data.text || ''
|
||||
});
|
||||
break;
|
||||
|
||||
case 'list':
|
||||
contentItems.push({
|
||||
type: 'list',
|
||||
level: block.data.style === 'ordered' ? 1 : 0, // 1 for ordered, 0 for unordered
|
||||
items: block.data.items || []
|
||||
});
|
||||
break;
|
||||
|
||||
case 'quote':
|
||||
contentItems.push({
|
||||
type: 'note',
|
||||
text: block.data.text || '',
|
||||
caption: block.data.caption || ''
|
||||
});
|
||||
break;
|
||||
|
||||
case 'embed':
|
||||
contentItems.push({
|
||||
type: 'embed',
|
||||
source: block.data.service || 'youtube',
|
||||
url: block.data.embed || '',
|
||||
embed: block.data.embed || '',
|
||||
caption: block.data.caption || '',
|
||||
width: block.data.width || 560,
|
||||
height: block.data.height || 315
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return contentItems;
|
||||
}
|
||||
|
||||
// Convert Insurance content to Editor.js data
|
||||
convertInsuranceToEditor(insuranceContent) {
|
||||
const blocks = [];
|
||||
|
||||
insuranceContent.forEach(item => {
|
||||
switch (item.type) {
|
||||
case 'header':
|
||||
blocks.push({
|
||||
type: 'header',
|
||||
data: {
|
||||
text: item.text || '',
|
||||
level: item.level || 2
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case 'paragraph':
|
||||
blocks.push({
|
||||
type: 'paragraph',
|
||||
data: {
|
||||
text: item.text || ''
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case 'section':
|
||||
if (item.title) {
|
||||
blocks.push({
|
||||
type: 'header',
|
||||
data: {
|
||||
text: item.title,
|
||||
level: 3
|
||||
}
|
||||
});
|
||||
}
|
||||
if (item.content) {
|
||||
blocks.push({
|
||||
type: 'paragraph',
|
||||
data: {
|
||||
text: item.content
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'list':
|
||||
blocks.push({
|
||||
type: 'list',
|
||||
data: {
|
||||
style: item.level === 1 ? 'ordered' : 'unordered', // Check level to determine style
|
||||
items: item.items || []
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case 'note':
|
||||
blocks.push({
|
||||
type: 'quote',
|
||||
data: {
|
||||
text: item.text || '',
|
||||
caption: item.caption || ''
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case 'embed':
|
||||
blocks.push({
|
||||
type: 'embed',
|
||||
data: {
|
||||
service: item.source || 'youtube',
|
||||
embed: item.url || item.embed || '',
|
||||
caption: item.caption || '',
|
||||
width: item.width || 560,
|
||||
height: item.height || 315
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
time: Date.now(),
|
||||
blocks: blocks,
|
||||
version: "2.28.2"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize Editor
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
// Initialize content manager
|
||||
window.insuranceContentManager = new InsuranceContentManager();
|
||||
|
||||
// Load existing content if available
|
||||
let initialEditorData = {
|
||||
time: Date.now(),
|
||||
blocks: [],
|
||||
version: "2.28.2"
|
||||
};
|
||||
|
||||
if (window.INSURANCE_DATA?.content?.content) {
|
||||
try {
|
||||
const insuranceContent = window.INSURANCE_DATA.content.content;
|
||||
initialEditorData = window.insuranceContentManager.convertInsuranceToEditor(insuranceContent);
|
||||
} catch (error) {
|
||||
console.error('Error loading insurance content:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize Editor via BlogEditor module (adds uploader + autosave)
|
||||
let blogEditorInstance = null;
|
||||
try {
|
||||
if (window.BlogEditor) {
|
||||
const initialCustom = window.INSURANCE_DATA?.content?.content ? { blocks: window.INSURANCE_DATA.content.content } : { blocks: [] };
|
||||
blogEditorInstance = new window.BlogEditor('editorjs', initialCustom);
|
||||
window.blogEditorInstance = blogEditorInstance;
|
||||
console.log('BlogEditor instance created for insurance');
|
||||
} else {
|
||||
console.warn('BlogEditor not available. Falling back to EditorJS init.');
|
||||
window.insuranceEditor = new EditorJS({
|
||||
holder: 'editorjs',
|
||||
data: initialEditorData,
|
||||
tools: {
|
||||
header: Header,
|
||||
paragraph: {
|
||||
class: Paragraph,
|
||||
inlineToolbar: true
|
||||
},
|
||||
list: List,
|
||||
quote: Quote,
|
||||
marker: Marker,
|
||||
embed: Embed
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error initializing editor module:', err);
|
||||
window.insuranceEditor = new EditorJS({ holder: 'editorjs', data: initialEditorData });
|
||||
}
|
||||
|
||||
// Form submission handler
|
||||
const form = document.getElementById('insuranceForm');
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const saveBtn = document.getElementById('saveBtn');
|
||||
saveBtn.disabled = true;
|
||||
saveBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Saving...';
|
||||
|
||||
try {
|
||||
// Save editor content
|
||||
let editorData = null;
|
||||
if (window.blogEditorInstance?.editor) {
|
||||
editorData = await window.blogEditorInstance.editor.save();
|
||||
} else if (window.insuranceEditor) {
|
||||
editorData = await window.insuranceEditor.save();
|
||||
} else {
|
||||
throw new Error('Editor not initialized');
|
||||
}
|
||||
|
||||
// Convert to Insurance content structure
|
||||
const insuranceContent = window.insuranceContentManager.convertEditorToInsurance(editorData);
|
||||
|
||||
// Prepare JSON data
|
||||
const heroData = {
|
||||
title: document.getElementById('heroTitle').value,
|
||||
subtitle: document.getElementById('heroSubtitle').value,
|
||||
backgroundImage: document.getElementById('heroBackgroundImage').value,
|
||||
sectionClass: "uk-section-default uk-section-overlap uk-preserve-color uk-light uk-position-relative",
|
||||
backgroundClasses: "uk-background-norepeat uk-background-cover uk-background-top-center uk-section uk-section-xlarge",
|
||||
overlayStyle: { backgroundColor: "rgba(0, 0, 0, 0)" },
|
||||
titleClass: "text-white text-[5vw] uk-text-center",
|
||||
subtitleClass: "uk-panel font-[Raleway] italic text-[1.5vw] uk-margin uk-text-center",
|
||||
enableScrollspy: true
|
||||
};
|
||||
|
||||
const pageData = {
|
||||
title: document.getElementById('pageTitle').value,
|
||||
divider: document.getElementById('pageDivider').checked,
|
||||
sectionClass: "uk-section-default uk-section-overlap uk-section",
|
||||
titleClass: "text-[2.5vw] text-[#292c3d] uk-text-left@m uk-text-center",
|
||||
dividerClass: "uk-divider-small uk-text-left@m uk-text-center"
|
||||
};
|
||||
|
||||
const contentData = {
|
||||
sectionClass: "uk-section-muted uk-section-overlap uk-section",
|
||||
textClass: "uk-panel uk-margin text-[1vw]",
|
||||
content: insuranceContent
|
||||
};
|
||||
|
||||
// Set hidden inputs
|
||||
document.getElementById('heroJson').value = JSON.stringify(heroData);
|
||||
document.getElementById('pageJson').value = JSON.stringify(pageData);
|
||||
document.getElementById('contentJson').value = JSON.stringify(contentData);
|
||||
|
||||
// Submit form
|
||||
form.submit();
|
||||
} catch (error) {
|
||||
console.error('Error saving insurance content:', error);
|
||||
alert('Error saving content. Please check console for details.');
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.innerHTML = '<i class="fas fa-save me-2"></i>Save Changes';
|
||||
}
|
||||
});
|
||||
|
||||
// Preview functionality
|
||||
const previewBtn = document.querySelector('.preview-btn');
|
||||
const previewModal = new bootstrap.Modal(document.getElementById('previewModal'));
|
||||
|
||||
previewBtn.addEventListener('click', async function () {
|
||||
try {
|
||||
// Save editor content first
|
||||
let editorData = null;
|
||||
if (window.blogEditorInstance?.editor) {
|
||||
editorData = await window.blogEditorInstance.editor.save();
|
||||
} else if (window.insuranceEditor) {
|
||||
editorData = await window.insuranceEditor.save();
|
||||
} else {
|
||||
throw new Error('Editor not initialized');
|
||||
}
|
||||
const insuranceContent = window.insuranceContentManager.convertEditorToInsurance(editorData);
|
||||
|
||||
// Prepare data for preview
|
||||
const heroData = {
|
||||
title: document.getElementById('heroTitle').value,
|
||||
subtitle: document.getElementById('heroSubtitle').value,
|
||||
backgroundImage: document.getElementById('heroBackgroundImage').value
|
||||
};
|
||||
|
||||
const pageData = {
|
||||
title: document.getElementById('pageTitle').value,
|
||||
divider: document.getElementById('pageDivider').checked
|
||||
};
|
||||
|
||||
const contentData = {
|
||||
content: insuranceContent
|
||||
};
|
||||
|
||||
const response = await fetch('/admin/insurance/preview', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
hero: JSON.stringify(heroData),
|
||||
page: JSON.stringify(pageData),
|
||||
content: JSON.stringify(contentData)
|
||||
})
|
||||
});
|
||||
|
||||
const html = await response.text();
|
||||
const previewFrame = document.getElementById('previewFrame');
|
||||
previewFrame.srcdoc = html;
|
||||
|
||||
previewModal.show();
|
||||
} catch (error) {
|
||||
console.error('Error generating preview:', error);
|
||||
alert('Error generating preview. Please try again.');
|
||||
}
|
||||
});
|
||||
|
||||
// Image upload handling
|
||||
document.querySelectorAll('.btn-upload-image').forEach(btn => {
|
||||
btn.addEventListener('click', function () {
|
||||
const targetInput = this.getAttribute('data-target-input');
|
||||
const imageType = this.getAttribute('data-image-type');
|
||||
document.getElementById('currentImageType').value = imageType;
|
||||
document.getElementById('currentTargetInput').value = targetInput;
|
||||
document.getElementById('directImageUpload').click();
|
||||
});
|
||||
});
|
||||
|
||||
// Handle file selection
|
||||
document.getElementById('directImageUpload').addEventListener('change', async function (e) {
|
||||
if (!this.files || !this.files[0]) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('image', this.files[0]);
|
||||
const imageType = document.getElementById('currentImageType').value;
|
||||
const targetInput = document.getElementById('currentTargetInput').value;
|
||||
|
||||
try {
|
||||
showToast('Uploading image...', 'info');
|
||||
|
||||
const uploadResponse = await fetch(`/admin/upload/image?imageType=${imageType}`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const uploadResult = await uploadResponse.json();
|
||||
|
||||
if (uploadResult.success && uploadResult.path) {
|
||||
const inputElement = document.getElementById(targetInput);
|
||||
if (inputElement) {
|
||||
inputElement.value = uploadResult.path;
|
||||
|
||||
// Update preview
|
||||
if (targetInput === 'heroBackgroundImage') {
|
||||
updateHeroImagePreview(uploadResult.path);
|
||||
}
|
||||
|
||||
// Show success message
|
||||
showToast('Image uploaded successfully', 'success');
|
||||
}
|
||||
} else {
|
||||
showToast(uploadResult.error || 'Error uploading image', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Upload error:', error);
|
||||
showToast('Error uploading image', 'error');
|
||||
}
|
||||
|
||||
this.value = '';
|
||||
});
|
||||
|
||||
function updateHeroImagePreview(imageUrl) {
|
||||
const previewDiv = document.getElementById('heroImagePreview');
|
||||
let img = previewDiv.querySelector('img');
|
||||
let fallback = previewDiv.querySelector('.border');
|
||||
|
||||
if (!img) {
|
||||
img = document.createElement('img');
|
||||
img.className = 'img-thumbnail';
|
||||
img.style.height = '200px';
|
||||
img.style.width = '100%';
|
||||
img.style.objectFit = 'cover';
|
||||
img.alt = 'Background image preview';
|
||||
img.onerror = function() {
|
||||
this.style.display = 'none';
|
||||
if (fallback) fallback.style.display = 'flex';
|
||||
};
|
||||
previewDiv.insertBefore(img, fallback);
|
||||
}
|
||||
|
||||
img.src = imageUrl;
|
||||
img.style.display = 'block';
|
||||
if (fallback) fallback.style.display = 'none';
|
||||
}
|
||||
|
||||
function showToast(message, type = 'info') {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast align-items-center text-bg-${type} border-0 position-fixed bottom-0 end-0 m-3`;
|
||||
toast.setAttribute('role', 'alert');
|
||||
toast.innerHTML = `
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">${message}</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
const bsToast = new bootstrap.Toast(toast, { delay: 3000 });
|
||||
bsToast.show();
|
||||
|
||||
toast.addEventListener('hidden.bs.toast', () => {
|
||||
toast.remove();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Store insurance data in global variable
|
||||
window.INSURANCE_DATA = <%- JSON.stringify(data) %>;
|
||||
</script>
|
||||
Reference in New Issue
Block a user