<?php
|
/**
|
* WPCode Snippet 5/5 — Training Page Shortcode
|
* Name: IM - Training Page
|
* Type: PHP Snippet
|
* Location: Run everywhere
|
*
|
* Dependency: Snippet 1 must be enabled first
|
*
|
* Usage:
|
* [im_training] → Training page (embed in a WordPress page)
|
* Access via: /training/?im_training_token=xxxxxx
|
*/
|
|
defined('ABSPATH') || exit;
|
|
/* ============================================================
|
Disable cache for training pages
|
============================================================ */
|
add_action('template_redirect', function () {
|
if (!empty($_GET['im_training_token'])) {
|
nocache_headers();
|
}
|
}, 1);
|
|
/* ============================================================
|
Training CSS
|
============================================================ */
|
function im_enqueue_training_styles()
|
{
|
static $done = false;
|
if ($done)
|
return;
|
$done = true;
|
echo '<style>
|
.im-tr-wrap{max-width:860px;margin:0 auto;padding:24px 0 60px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC",sans-serif;color:#1f2937;font-size:16px;line-height:1.7}
|
.im-tr-wrap *,.im-tr-wrap *::before,.im-tr-wrap *::after{box-sizing:border-box}
|
.im-tr-page{background:#fff;border-radius:12px;box-shadow:0 2px 16px rgba(0,0,0,.07);overflow:hidden}
|
.im-tr-header{background:linear-gradient(135deg,#009dff,#3de1fe,#53eee0);padding:36px 40px 28px;color:#fff}
|
.im-tr-header h1{margin:0 0 8px;font-size:26px;font-weight:700}
|
.im-tr-header p{margin:0 0 8px;opacity:.9;font-size:15px}
|
.im-tr-progress-bar{background:rgba(255,255,255,.3);border-radius:20px;height:10px;margin-top:16px;overflow:hidden}
|
.im-tr-progress-fill{background:#fff;height:100%;border-radius:20px;transition:width .5s ease;width:0}
|
.im-tr-progress-text{font-size:14px;font-weight:600;margin-top:8px;opacity:.9}
|
.im-tr-section{padding:32px 40px;border-bottom:1px solid #f3f4f6}
|
.im-tr-section:last-child{border-bottom:none}
|
.im-tr-section h2{margin:0 0 20px;font-size:20px;font-weight:700;color:#111827;display:flex;align-items:center;gap:10px}
|
.im-tr-section h2 .im-tr-step{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;background:linear-gradient(135deg,#009dff,#3de1fe);color:#fff;border-radius:50%;font-size:14px;font-weight:700;flex-shrink:0}
|
.im-tr-video-wrap{position:relative;width:100%;padding-bottom:56.25%;border-radius:10px;overflow:hidden;background:#0f172a;margin-bottom:20px}
|
.im-tr-video-wrap iframe{position:absolute;top:0;left:0;width:100%;height:100%;border:none}
|
.im-tr-dl-btn{display:inline-flex;align-items:center;gap:8px;background:linear-gradient(135deg,#009dff,#3de1fe,#53eee0);color:#fff;padding:12px 24px;border-radius:8px;text-decoration:none;font-size:15px;font-weight:700;transition:all .3s;border:none;cursor:pointer;font-family:inherit}
|
.im-tr-dl-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,157,255,.3);color:#fff}
|
.im-tr-quiz{margin-top:28px;border-top:2px solid #e5e7eb;padding-top:24px}
|
.im-tr-quiz-title{font-size:16px;font-weight:700;color:#009dff;margin:0 0 20px;display:flex;align-items:center;gap:8px}
|
.im-tr-q{background:#f9fafb;border:1px solid #e5e7eb;border-radius:10px;padding:20px 24px;margin-bottom:16px;transition:border-color .3s}
|
.im-tr-q.correct{border-color:#10b981;background:#ecfdf5}
|
.im-tr-q.wrong{border-color:#ef4444;background:#fef2f2}
|
.im-tr-q-text{font-size:15px;font-weight:600;color:#111827;margin:0 0 14px}
|
.im-tr-opt{display:flex;align-items:center;gap:10px;padding:10px 14px;border:1.5px solid #d1d5db;border-radius:8px;margin-bottom:8px;cursor:pointer;transition:all .15s;position:relative;font-size:15px;color:#374151}
|
.im-tr-opt:hover{border-color:#009dff;background:#e6f9ff}
|
.im-tr-opt.selected{border-color:#009dff;background:#e6f9ff;color:#009dff;font-weight:600}
|
.im-tr-opt.locked{cursor:default;pointer-events:none;opacity:.85}
|
.im-tr-opt.locked.selected.correct-opt{border-color:#10b981;background:#d1fae5;color:#065f46}
|
.im-tr-opt.locked.selected.wrong-opt{border-color:#ef4444;background:#fee2e2;color:#991b1b}
|
.im-tr-opt input[type=radio]{position:absolute;opacity:0;width:0;height:0;pointer-events:none}
|
.im-tr-opt .im-tr-ck{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border:2px solid #d1d5db;border-radius:50%;flex-shrink:0;transition:all .2s;background:#fff;position:relative}
|
.im-tr-opt .im-tr-ck::after{content:"";width:10px;height:10px;border-radius:50%;background:#fff;opacity:0;transition:opacity .15s}
|
.im-tr-opt.selected .im-tr-ck{background:linear-gradient(135deg,#009dff,#3de1fe,#53eee0);border-color:transparent}
|
.im-tr-opt.selected .im-tr-ck::after{opacity:1}
|
.im-tr-opt .im-tr-letter{font-weight:700;min-width:16px}
|
.im-tr-q-feedback{margin-top:8px;font-size:14px;font-weight:600;display:none}
|
.im-tr-q-feedback.show{display:block}
|
.im-tr-q-feedback.err{color:#ef4444}
|
.im-tr-q-feedback.ok{color:#10b981}
|
.im-tr-btn-row{display:flex;gap:12px;margin-top:20px;flex-wrap:wrap}
|
.im-tr-submit{display:inline-flex;align-items:center;gap:8px;background:linear-gradient(135deg,#009dff,#3de1fe,#53eee0);color:#fff;border:none;padding:13px 32px;border-radius:8px;font-size:16px;font-weight:700;cursor:pointer;transition:all .3s;font-family:inherit}
|
.im-tr-submit:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,157,255,.3)}
|
.im-tr-submit:disabled{background:#94a3b8;cursor:not-allowed;transform:none;box-shadow:none}
|
.im-tr-retry{display:none;align-items:center;gap:8px;background:#f59e0b;color:#fff;border:none;padding:13px 32px;border-radius:8px;font-size:16px;font-weight:700;cursor:pointer;transition:all .3s;font-family:inherit}
|
.im-tr-retry:hover{background:#d97706;transform:translateY(-1px)}
|
.im-tr-locked-overlay{position:relative}
|
.im-tr-locked-overlay::after{content:"✓ Completed";position:absolute;top:12px;right:16px;background:#10b981;color:#fff;padding:4px 12px;border-radius:6px;font-size:12px;font-weight:700}
|
.im-tr-hidden{display:none}
|
.im-tr-center{text-align:center;padding:60px 40px}
|
.im-tr-icon{width:72px;height:72px;border-radius:50%;font-size:36px;line-height:72px;margin:0 auto 24px;font-weight:700}
|
.im-tr-icon.ok{background:#d1fae5;color:#065f46}
|
.im-tr-icon.err{background:#fee2e2;color:#991b1b}
|
.im-tr-center h1{font-size:24px;margin:0 0 12px}
|
.im-tr-center p{color:#6b7280;font-size:15px;margin-bottom:5px;}
|
.im-tr-notice{background:#e6f9ff;border-left:4px solid #009dff;padding:12px 16px;border-radius:0 8px 8px 0;color:#007acc;font-size:14px;margin-bottom:20px}
|
@media(max-width:600px){.im-tr-header,.im-tr-section{padding:24px 20px}.im-tr-q{padding:16px 18px}}
|
@keyframes imTrFadeIn{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}
|
.im-tr-fade-in{animation:imTrFadeIn .5s ease}
|
@keyframes imTrSpin{to{transform:rotate(360deg)}}
|
.im-tr-btn-loading{position:relative;color:transparent!important}
|
.im-tr-btn-loading::after{content:"";position:absolute;width:20px;height:20px;top:50%;left:50%;margin:-10px 0 0 -10px;border:3px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:imTrSpin .6s linear infinite}
|
</style>';
|
echo '<script>var imTrAjaxUrl="' . esc_url(admin_url('admin-ajax.php')) . '";</script>';
|
}
|
|
/* ============================================================
|
AJAX: Training Complete Handler
|
============================================================ */
|
add_action('wp_ajax_im_training_complete', 'im_ajax_training_complete');
|
add_action('wp_ajax_nopriv_im_training_complete', 'im_ajax_training_complete');
|
function im_ajax_training_complete()
|
{
|
$token = sanitize_text_field($_POST['im_training_token'] ?? '');
|
$candidate = $token ? IM_Candidate::get_by_training_token($token) : null;
|
if (!$candidate) {
|
wp_send_json_error(['message' => 'Invalid or expired training link.']);
|
}
|
if (!wp_verify_nonce($_POST['im_nonce'] ?? '', 'im_training_' . $token)) {
|
wp_send_json_error(['message' => 'Security verification failed. Please refresh and try again.']);
|
}
|
IM_Candidate::mark_training_completed($candidate->id);
|
IM_Mailer::send_training_complete($candidate->id);
|
$name = esc_html($candidate->preferred_name ?: $candidate->first_name);
|
wp_send_json_success(['message' => 'ok', 'name' => $name]);
|
}
|
|
/* ============================================================
|
Shortcode: [im_training]
|
============================================================ */
|
add_shortcode('im_training', function () {
|
im_enqueue_training_styles();
|
ob_start();
|
|
$token_str = sanitize_text_field($_GET['im_training_token'] ?? '');
|
|
// No token
|
if (!$token_str): ?>
|
<div class="im-tr-wrap">
|
<div class="im-tr-page im-tr-center">
|
<div class="im-tr-icon err">!</div>
|
<h1>Invalid Link</h1>
|
<p>This training link is invalid. Please check your email for the correct link.</p>
|
</div>
|
</div>
|
<?php return ob_get_clean();
|
endif;
|
|
// Check if already completed
|
$candidate = IM_Candidate::get_by_training_token($token_str);
|
if (!$candidate):
|
$any = IM_Candidate::get_by_training_token_any($token_str);
|
if ($any && $any->status === 'trained'): ?>
|
<div class="im-tr-wrap">
|
<div class="im-tr-page im-tr-center">
|
<div class="im-tr-icon ok">✓</div>
|
<h1>Training Already Completed</h1>
|
<p>You have already completed all training modules.</p>
|
<p>Your trial account information has been sent to your email. Thank you!</p>
|
</div>
|
</div>
|
<?php else: ?>
|
<div class="im-tr-wrap">
|
<div class="im-tr-page im-tr-center">
|
<div class="im-tr-icon err">!</div>
|
<h1>Invalid Link</h1>
|
<p>This training link is invalid or no longer active. Please contact us if you need assistance.</p>
|
</div>
|
</div>
|
<?php endif;
|
return ob_get_clean();
|
endif;
|
|
// Mark opened
|
if (empty($candidate->training_opened_at)) {
|
IM_Candidate::mark_training_opened($candidate->id);
|
}
|
|
// Fetch all training questions ordered by menu_order
|
$training_posts = get_posts([
|
'post_type' => 'training_questions',
|
'posts_per_page' => -1,
|
'orderby' => 'menu_order',
|
'order' => 'ASC',
|
'post_status' => 'publish',
|
]);
|
|
if (empty($training_posts)): ?>
|
<div class="im-tr-wrap">
|
<div class="im-tr-page im-tr-center">
|
<div class="im-tr-icon err">!</div>
|
<h1>No Training Available</h1>
|
<p>There are currently no training modules available. Please contact the recruitment team.</p>
|
</div>
|
</div>
|
<?php return ob_get_clean();
|
endif;
|
|
$name = esc_html($candidate->preferred_name ?: $candidate->first_name);
|
$nonce = wp_create_nonce('im_training_' . $token_str);
|
$total = count($training_posts);
|
|
// Build training data for JS
|
$training_data = [];
|
foreach ($training_posts as $idx => $post) {
|
$youtube_url = get_field('youtube_url', $post->ID) ?: '';
|
$download_url = get_field('download_url', $post->ID) ?: '';
|
$download_btn_text = get_field('download_btn_text', $post->ID) ?: 'Download Materials';
|
$test_questions = get_field('test_questions', $post->ID) ?: [];
|
|
$qs = [];
|
if (is_array($test_questions)) {
|
foreach ($test_questions as $qi => $q) {
|
$correct_raw = $q['correct_answer'] ?? '';
|
$correct_val = is_array($correct_raw) ? ($correct_raw['value'] ?? '') : $correct_raw;
|
$qs[] = [
|
'question' => $q['question'] ?? '',
|
'option_a' => $q['option_a'] ?? '',
|
'option_b' => $q['option_b'] ?? '',
|
'option_c' => $q['option_c'] ?? '',
|
'option_d' => $q['option_d'] ?? '',
|
'correct' => $correct_val,
|
];
|
}
|
}
|
|
$training_data[] = [
|
'id' => $post->ID,
|
'title' => $post->post_title,
|
'youtube_url' => $youtube_url,
|
'download_url' => $download_url,
|
'download_btn_text' => $download_btn_text,
|
'questions' => $qs,
|
];
|
}
|
?>
|
<div class="im-tr-wrap">
|
<div class="im-tr-page" id="im-tr-page">
|
<div class="im-tr-header">
|
<h1>Onboarding Training</h1>
|
<p>Welcome, <strong><?= $name ?></strong>! Please complete the training modules below to get started.</p>
|
<div class="im-tr-progress-bar">
|
<div class="im-tr-progress-fill" id="im-tr-progress-fill"></div>
|
</div>
|
<div class="im-tr-progress-text" id="im-tr-progress-text"> 0 / <?= $total ?> completed</div>
|
</div>
|
|
<?php foreach ($training_data as $idx => $td):
|
$youtube_id = '';
|
if ($td['youtube_url']) {
|
if (preg_match('/[?&]v=([^&]+)/', $td['youtube_url'], $m))
|
$youtube_id = $m[1];
|
elseif (preg_match('/youtu\.be\/([^?&]+)/', $td['youtube_url'], $m))
|
$youtube_id = $m[1];
|
elseif (preg_match('/embed\/([^?&]+)/', $td['youtube_url'], $m))
|
$youtube_id = $m[1];
|
}
|
?>
|
<div class="im-tr-section im-tr-module <?= $idx > 0 ? 'im-tr-hidden' : '' ?>" id="im-tr-module-<?= $idx ?>"
|
data-index="<?= $idx ?>" data-youtube-id="<?= esc_attr($youtube_id) ?>">
|
<h2>
|
<span class="im-tr-step"><?= $idx + 1 ?></span>
|
<?= esc_html($td['title']) ?>
|
</h2>
|
|
<!-- Video -->
|
<?php if ($youtube_id): ?>
|
<div class="im-tr-video-wrap">
|
<div id="im-tr-player-<?= $idx ?>"></div>
|
</div>
|
<?php endif; ?>
|
|
<!-- Download button (hidden until video ends) -->
|
<?php if ($td['download_url']): ?>
|
<div class="im-tr-dl-section im-tr-hidden" id="im-tr-dl-<?= $idx ?>">
|
<a href="<?= esc_url($td['download_url']) ?>" target="_blank" class="im-tr-dl-btn">
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3" />
|
</svg>
|
<?= esc_html($td['download_btn_text']) ?>
|
</a>
|
</div>
|
<?php endif; ?>
|
|
<!-- Quiz (hidden until video ends) -->
|
<?php if (!empty($td['questions'])): ?>
|
<div class="im-tr-quiz im-tr-hidden" id="im-tr-quiz-<?= $idx ?>">
|
<div class="im-tr-quiz-title">
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<path d="M9 11l3 3L22 4M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11" />
|
</svg>
|
Test Questions — Answer all correctly to proceed
|
</div>
|
|
<?php foreach ($td['questions'] as $qi => $q): ?>
|
<div class="im-tr-q" id="im-tr-q-<?= $idx ?>-<?= $qi ?>" data-correct="<?= esc_attr($q['correct']) ?>">
|
<div class="im-tr-q-text">Q<?= $qi + 1 ?>. <?= esc_html($q['question']) ?></div>
|
<?php foreach (['option_a' => 'A', 'option_b' => 'B', 'option_c' => 'C', 'option_d' => 'D'] as $key => $letter): ?>
|
<label class="im-tr-opt" data-val="<?= esc_attr($key) ?>">
|
<input type="radio" name="im_tr_q_<?= $idx ?>_<?= $qi ?>" value="<?= esc_attr($key) ?>">
|
<span class="im-tr-ck"></span>
|
<span class="im-tr-letter"><?= $letter ?>.</span>
|
<span><?= esc_html($q[$key]) ?></span>
|
</label>
|
<?php endforeach; ?>
|
<div class="im-tr-q-feedback" id="im-tr-fb-<?= $idx ?>-<?= $qi ?>"></div>
|
</div>
|
<?php endforeach; ?>
|
|
<div class="im-tr-btn-row">
|
<button type="button" class="im-tr-submit" id="im-tr-submit-<?= $idx ?>"
|
onclick="imTrSubmitQuiz(<?= $idx ?>)">Submit Answers</button>
|
<button type="button" class="im-tr-retry" id="im-tr-retry-<?= $idx ?>"
|
onclick="imTrRetryQuiz(<?= $idx ?>)">Retry Incorrect Questions</button>
|
</div>
|
</div>
|
<?php endif; ?>
|
</div>
|
<?php endforeach; ?>
|
</div>
|
</div>
|
|
<script>
|
(function () {
|
var TOTAL = <?= $total ?>;
|
var completed = 0;
|
var moduleState = {}; // index -> {videoEnded, quizPassed}
|
var players = {};
|
|
for (var i = 0; i < TOTAL; i++) {
|
moduleState[i] = { videoEnded: false, quizPassed: false };
|
}
|
|
// ===== YouTube IFrame API =====
|
var tag = document.createElement('script');
|
tag.src = 'https://www.youtube.com/iframe_api';
|
var firstScript = document.getElementsByTagName('script')[0];
|
firstScript.parentNode.insertBefore(tag, firstScript);
|
|
window.onYouTubeIframeAPIReady = function () {
|
// Only create player for the first visible module
|
createPlayerForModule(0);
|
};
|
|
function createPlayerForModule(idx) {
|
var mod = document.getElementById('im-tr-module-' + idx);
|
if (!mod) return;
|
var ytId = mod.getAttribute('data-youtube-id');
|
if (!ytId) {
|
// No video, show quiz and download immediately
|
onVideoEnd(idx);
|
return;
|
}
|
if (players[idx]) return; // Already created
|
players[idx] = new YT.Player('im-tr-player-' + idx, {
|
videoId: ytId,
|
playerVars: { rel: 0, modestbranding: 1 },
|
events: {
|
onStateChange: function (e) {
|
if (e.data === YT.PlayerState.ENDED) {
|
onVideoEnd(idx);
|
}
|
}
|
}
|
});
|
}
|
|
function onVideoEnd(idx) {
|
moduleState[idx].videoEnded = true;
|
var dl = document.getElementById('im-tr-dl-' + idx);
|
if (dl) { dl.classList.remove('im-tr-hidden'); dl.classList.add('im-tr-fade-in'); }
|
var quiz = document.getElementById('im-tr-quiz-' + idx);
|
if (quiz) {
|
quiz.classList.remove('im-tr-hidden');
|
quiz.classList.add('im-tr-fade-in');
|
} else {
|
// No quiz for this module, auto-pass
|
onQuizPass(idx);
|
}
|
}
|
|
// ===== Quiz Logic =====
|
// Option selection
|
document.addEventListener('click', function (e) {
|
var opt = e.target.closest('.im-tr-opt');
|
if (!opt || opt.classList.contains('locked')) return;
|
var q = opt.closest('.im-tr-q');
|
q.querySelectorAll('.im-tr-opt').forEach(function (o) { o.classList.remove('selected'); });
|
opt.classList.add('selected');
|
opt.querySelector('input[type=radio]').checked = true;
|
});
|
|
window.imTrSubmitQuiz = function (idx) {
|
var mod = document.getElementById('im-tr-module-' + idx);
|
var questions = mod.querySelectorAll('.im-tr-q');
|
var allCorrect = true;
|
var hasErrors = false;
|
var firstError = null;
|
|
questions.forEach(function (q, qi) {
|
// Skip already passed questions
|
if (q.classList.contains('correct')) return;
|
|
var fb = document.getElementById('im-tr-fb-' + idx + '-' + qi);
|
var correct = q.getAttribute('data-correct');
|
var selected = q.querySelector('input[type=radio]:checked');
|
|
if (!selected) {
|
fb.textContent = 'Please select an answer.';
|
fb.className = 'im-tr-q-feedback show err';
|
q.classList.remove('correct', 'wrong');
|
allCorrect = false;
|
hasErrors = true;
|
if (!firstError) firstError = q;
|
return;
|
}
|
|
var val = selected.value;
|
// Lock all options in this question
|
q.querySelectorAll('.im-tr-opt').forEach(function (o) { o.classList.add('locked'); });
|
|
if (val === correct) {
|
q.classList.remove('wrong');
|
q.classList.add('correct');
|
fb.textContent = '✓ Correct!';
|
fb.className = 'im-tr-q-feedback show ok';
|
// Mark correct option
|
var selOpt = q.querySelector('.im-tr-opt.selected');
|
if (selOpt) selOpt.classList.add('correct-opt');
|
} else {
|
q.classList.remove('correct');
|
q.classList.add('wrong');
|
fb.textContent = '✗ Incorrect. Please retry this question.';
|
fb.className = 'im-tr-q-feedback show err';
|
// Mark wrong option
|
var selOpt = q.querySelector('.im-tr-opt.selected');
|
if (selOpt) selOpt.classList.add('wrong-opt');
|
allCorrect = false;
|
hasErrors = true;
|
if (!firstError) firstError = q;
|
}
|
});
|
|
if (allCorrect) {
|
// All correct — hide submit, show completed
|
document.getElementById('im-tr-submit-' + idx).style.display = 'none';
|
document.getElementById('im-tr-retry-' + idx).style.display = 'none';
|
onQuizPass(idx);
|
} else if (hasErrors) {
|
// Show retry button
|
document.getElementById('im-tr-retry-' + idx).style.display = 'inline-flex';
|
if (firstError) firstError.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
}
|
};
|
|
window.imTrRetryQuiz = function (idx) {
|
var mod = document.getElementById('im-tr-module-' + idx);
|
var questions = mod.querySelectorAll('.im-tr-q.wrong');
|
questions.forEach(function (q) {
|
q.classList.remove('wrong');
|
var fb = q.querySelector('.im-tr-q-feedback');
|
if (fb) { fb.className = 'im-tr-q-feedback'; fb.textContent = ''; }
|
q.querySelectorAll('.im-tr-opt').forEach(function (o) {
|
o.classList.remove('locked', 'selected', 'wrong-opt', 'correct-opt');
|
});
|
q.querySelectorAll('input[type=radio]').forEach(function (r) { r.checked = false; });
|
});
|
document.getElementById('im-tr-retry-' + idx).style.display = 'none';
|
// Scroll to first retry question
|
if (questions.length) questions[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
};
|
|
function onQuizPass(idx) {
|
moduleState[idx].quizPassed = true;
|
completed++;
|
updateProgress();
|
|
// Lock the completed module
|
var mod = document.getElementById('im-tr-module-' + idx);
|
mod.classList.add('im-tr-locked-overlay');
|
|
if (completed >= TOTAL) {
|
// All modules done — submit completion
|
submitTrainingComplete();
|
} else {
|
// Show next module
|
var next = idx + 1;
|
var nextMod = document.getElementById('im-tr-module-' + next);
|
if (nextMod) {
|
nextMod.classList.remove('im-tr-hidden');
|
nextMod.classList.add('im-tr-fade-in');
|
// Scroll to next module
|
setTimeout(function () {
|
nextMod.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
}, 300);
|
// Create player for next module
|
createPlayerForModule(next);
|
}
|
}
|
}
|
|
function updateProgress() {
|
var pct = Math.round(completed / TOTAL * 100);
|
document.getElementById('im-tr-progress-fill').style.width = pct + '%';
|
document.getElementById('im-tr-progress-text').textContent = 'Progress: ' + completed + ' / ' + TOTAL;
|
}
|
|
function submitTrainingComplete() {
|
var fd = new FormData();
|
fd.append('action', 'im_training_complete');
|
fd.append('im_training_token', '<?= esc_js($token_str) ?>');
|
fd.append('im_nonce', '<?= esc_js($nonce) ?>');
|
|
fetch(imTrAjaxUrl, { method: 'POST', body: fd })
|
.then(function (r) { return r.json(); })
|
.then(function (res) {
|
if (res.success) {
|
var page = document.getElementById('im-tr-page');
|
page.innerHTML = '<div class="im-tr-center im-tr-fade-in">'
|
+ '<div class="im-tr-icon ok">✓</div>'
|
+ '<h1>Training Completed!</h1>'
|
+ '<p>Congratulations, <strong>' + (res.data.name || '') + '</strong>! You have successfully completed all training modules.</p>'
|
+ '<p style="margin-top:8px">Your trial account information has been sent to your email.</p>'
|
+ '<p style="margin-top:16px;color:#009dff;font-weight:600">Welcome to the YStar Edu team!</p>'
|
+ '</div>';
|
page.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
} else {
|
alert(res.data.message || 'An error occurred. Please try again.');
|
}
|
})
|
.catch(function () { alert('Network error. Please try again.'); });
|
}
|
})();
|
</script>
|
<?php
|
return ob_get_clean();
|
});
|