Найден корректный robots.txt
Файл управления сканированием сайта роботами
?
Robots.txt указывает поисковым роботам, какие страницы сканировать, а какие — нет. Ошибки в файле могут случайно закрыть важные разделы от индексации.
Robots.txt настроен корректно. Размер файла: 34392 байт. Загружен за: 0сек.
Проверяемая страница не запрещена в robots.txt.
Показать содержимое robots.txt
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"/>
<meta name="description" content="مرکز یادگیری سیناوب — مدیریت دورهها، Canvas و پشتیبانی."/>
<title>مرکز یادگیری سیناوب</title>
<link href="https://sinaweb.org/favicon.ico" rel="icon" type="image/png"/>
<link rel="preconnect" href="https://fonts.googleapis.com"/>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;600;700;800&display=swap" rel="stylesheet"/>
<link href="/portal-theme.css" rel="stylesheet"/>
<link href="/hub-pages.css?v=14" rel="stylesheet"/>
<link href="/portal-dashboard.css?v=7" rel="stylesheet"/>
<script src="/hub-common.js?v=12"></script>
<style>
.profile-form{display:grid;gap:12px}.profile-form label{display:grid;gap:5px;font-size:12px;font-weight:700;color:var(--ink)}.profile-form input,.profile-form select{width:100%;padding:10px 11px;border:1px solid var(--line);border-radius:8px;font:inherit;background:#fff}.profile-form .form-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px}
</style>
</head>
<body>
<div id="loadingView" class="loading">
<div class="spinner" aria-hidden="true"></div>
<span>در حال بارگذاری…</span>
</div>
<!-- Signed-out gate -->
<div id="authView" class="auth-shell hidden">
<div class="auth-panel">
<div class="auth-panel-brand">
<a href="https://sinaweb.org/"><img src="/sinaweb-logo.jpg" alt="سیناوب" width="160" height="40"/></a>
<span>پرتال دانشجو</span>
</div>
<h1>یادگیری و پشتیبانی — در یک جا</h1>
<p class="lead" id="authMessage">برای درخواست ثبتنام در دورهها وارد شوید؛ دسترسی پس از تأیید پشتیبانی فعال میشود.</p>
<div id="authPaymentBanner" class="alert alert-success hidden" style="margin-bottom:20px"></div>
<ul class="auth-features">
<li>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 10v6M2 10l10-5 10 5-10 5z"/><path d="M6 12v5c0 2 2 3 6 3s6-1 6-3v-5"/></svg>
درخواست ثبتنام و پیگیری دسترسی
</li>
<li>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
فهرست مسیرهای گردشکار نقشمحور
</li>
<li>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
ارتباط با پشتیبانی در هر زمان
</li>
</ul>
<div class="auth-actions">
<a class="btn" href="/login.html">ورود به پرتال</a>
<a class="btn btn-outline" href="/catalog-v1.html">فهرست دورهها</a>
</div>
<div class="auth-links">
تازهوارد هستید؟ <a href="/register.html">ثبتنام</a><br/>
نیاز به راهنما؟ <a href="mailto:support@sinaweb.cc">پشتیبانی</a>
</div>
</div>
<div class="auth-visual" aria-hidden="true">
<div class="auth-visual-inner">
<div class="eyebrow">مرکز یادگیری سیناوب</div>
<h2>مسیرهای ساختارمند روی Canvas LMS</h2>
<p>از ثبتنام تا یادگیری — وضعیت هر مرحله را ببینید و مستقیم به محیط آموزشی دسترسی داشته باشید.</p>
<div class="auth-stats">
<div class="auth-stat"><strong>Canvas</strong><span>دسترسی LMS</span></div>
<div class="auth-stat"><strong>AI</strong><span>دستیار هوشمند</span></div>
<div class="auth-stat"><strong>۲۴/۷</strong><span>پشتیبانی</span></div>
</div>
</div>
</div>
</div>
<!-- Signed-in portal -->
<div id="portalView" class="layout hidden">
<aside class="sidebar">
<div class="sidebar-head">
<a class="sidebar-logo" href="/"><img src="/sinaweb-logo.jpg" alt="سیناوب" width="160" height="40"/></a>
<button type="button" class="sidebar-toggle hub-sidebar-hide-btn" aria-label="Hide sidebar" title="Hide sidebar">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v18"/></svg>
</button>
</div>
<div class="sidebar-label">مرکز یادگیری سیناوب</div>
<div class="sidebar-title">سیناوب</div>
<nav data-hub-nav-page="home" aria-label="Student portal"></nav>
<div class="sidebar-user hub-sidebar-user hidden" id="hubSidebarUser"></div>
</aside>
<div class="main">
<header class="page-banner">
<div class="page-banner-inner">
<div class="eyebrow">مرکز یادگیری سیناوب · پرتال دانشجو</div>
<h1 id="welcomeTitle">خوش آمدید</h1>
<p>درخواست ثبتنام دورهها را ثبت کنید و پس از تأیید پشتیبانی، دسترسی Canvas و پرتال یادگیری فعال میشود.</p>
</div>
</header>
<div class="page-body">
<div id="nextStepGuide" class="next-step-guide hidden" role="status" aria-live="polite">
<div class="next-step-guide__pulse" aria-hidden="true"></div>
<div class="next-step-guide__content">
<p class="next-step-guide__label">گام بعدی</p>
<p class="next-step-guide__text" id="nextStepMessage"></p>
</div>
<a class="btn btn-sm" id="nextStepGo" href="#">نمایش</a>
<button type="button" class="next-step-guide__dismiss" id="nextStepDismiss" aria-label="Dismiss guide">×</button>
</div>
<div id="verifiedBanner" class="alert alert-success hidden">ایمیل تأیید شد — اکنون میتوانید در دوره ثبتنام کنید.</div>
<div id="statusBanner" class="alert hidden"></div>
<div class="stats-row" id="statsRow">
<div class="stat-card">
<div class="label">دورههای فعال</div>
<div class="value" id="statCourses">—</div>
<div class="hint">فعال</div>
</div>
<div class="stat-card">
<div class="label">اقدامات معلق</div>
<div class="value" id="statPending">—</div>
<div class="hint">در انتظار فعالسازی</div>
</div>
<div class="stat-card">
<div class="label">دسترسی یادگیری</div>
<div class="value" id="statCanvas">—</div>
<div class="hint">پرتال و درسها</div>
</div>
</div>
<p class="section-title">دسترسی سریع</p>
<div class="quick-grid">
<a class="quick" href="#courses">
<div class="quick-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 10v6M2 10l10-5 10 5-10 5z"/></svg></div>
<div><strong>دورههای من</strong><span>درخواست ثبتنام</span></div>
</a>
<a class="quick" href="https://canvas.sinaweb.org/login" target="_blank" rel="noopener noreferrer">
<div class="quick-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg></div>
<div><strong>Canvas LMS</strong><span>ماژولها و نمرات</span></div>
</a>
<a class="quick" href="/catalog-v1.html">
<div class="quick-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg></div>
<div><strong>فهرست دورهها</strong><span>انتخاب مسیر گردشکار</span></div>
</a>
<a class="quick" href="/tickets.html?new=1">
<div class="quick-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></div>
<div><strong>پشتیبانی</strong><span>ثبت درخواست</span></div>
</a>
</div>
<div class="card" id="gettingStarted">
<div class="card-head">
<div>
<h2>شروع کار</h2>
<p class="card-sub">از درخواست ثبتنام تا تأیید و ورود به Canvas.</p>
</div>
</div>
<div class="steps" id="stepsList">
<div class="step done" data-step="register">
<div class="step-num">✓</div>
<div><h3>ایجاد حساب</h3><p>ثبتنام و تأیید ایمیل.</p></div>
</div>
<div class="step" data-step="enroll">
<div class="step-num">۲</div>
<div><h3>انتخاب نقش</h3><p>از فهرست دورهها مسیر گردشکار خود را انتخاب کنید.</p></div>
</div>
<div class="step" data-step="pay">
<div class="step-num">۳</div>
<div><h3>تکمیل ثبتنام</h3><p>پس از فعالسازی دوره در Canvas ثبتنام کنید.</p></div>
</div>
<div class="step" data-step="canvas">
<div class="step-num">۴</div>
<div><h3>شروع یادگیری</h3><p><a href="https://canvas.sinaweb.org/login" target="_blank" rel="noopener noreferrer">Canvas LMS</a> را باز کنید.</p></div>
</div>
</div>
</div>
<div class="card hidden" id="profileCompleteCard">
<div class="card-head">
<div>
<h2>تکمیل پروفایل</h2>
<p class="card-sub">قبل از درخواست دسترسی به دوره، اطلاعات پروفایل را کامل کنید.</p>
</div>
</div>
<div id="profileCompleteError" class="alert alert-error hidden"></div>
<form id="profileCompleteForm" class="profile-form" novalidate>
<div class="form-row">
<label>عنوان
<select id="pcTitle" name="title">
<option value="Mr">آقا</option>
<option value="Mrs">خانم</option>
<option value="Ms">خانم</option>
<option value="Dr">دکتر</option>
<option value="Prof">استاد</option>
</select>
</label>
<label>نام
<input id="pcFirstName" name="firstName" autocomplete="given-name" required/>
</label>
<label>نام خانوادگی
<input id="pcLastName" name="lastName" autocomplete="family-name" required/>
</label>
</div>
<div class="form-row">
<label>کشور
<input id="pcCountry" name="country" value="Iran" autocomplete="country-name" required/>
</label>
<label>موبایل
<input id="pcPhone" name="phone" dir="ltr" style="text-align:left" placeholder="9123456789" autocomplete="tel-national"/>
</label>
</div>
<label>آدرس سایت نشریه
<input id="pcJournalUrl" name="journalUrl" type="url" dir="ltr" style="text-align:left" placeholder="https://example.ac.ir" autocomplete="url" required/>
</label>
<label>عنوان نشریه
<input id="pcJournalTitle" name="journalTitle" placeholder="در صورت خالی بودن از سایت خوانده میشود"/>
</label>
<button class="btn" type="submit" id="profileCompleteBtn">ذخیره پروفایل</button>
</form>
</div>
<div class="card" id="courses">
<div class="card-head">
<div>
<h2>دورههای من</h2>
<p class="card-sub">درخواستها و لینک Canvas پس از تأیید.</p>
</div>
<a class="btn btn-sm btn-outline" href="/catalog-v1.html">+ فهرست دورهها</a>
</div>
<div id="enrollList"></div>
<div id="coursesLoading" class="courses-loading hidden">
<div class="skeleton skeleton-block"></div>
<div class="skeleton skeleton-block"></div>
</div>
<div id="emptyCourses" class="empty hidden">
<div class="empty-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg></div>
<h3>هنوز دورهای ندارید</h3>
<p>فهرست گردشکار را ببینید و پس از فعالسازی ثبتنام کنید.</p>
<a class="btn" href="/catalog-v1.html">مشاهده فهرست دورهها ←</a>
</div>
</div>
<div class="card hidden" id="courseEnrollCard">
<div class="card-head">
<div>
<h2 id="courseEnrollTitle">Enroll</h2>
<p class="card-sub" id="courseEnrollLead"></p>
</div>
</div>
<button class="btn" id="enrollBtn" type="button">Confirm enrollment →</button>
<p class="meta" id="courseEnrollNote"></p>
</div>
<div class="grid-2">
<div class="help-panel">
<h3>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--purple)" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
Student resources
</h3>
<ul>
<li><a href="https://edu.sinaweb.org/catalog-v1.html">فهرست دورهها</a> — مسیرهای گردشکار نقشمحور</li>
<li><a href="https://canvas.sinaweb.org/login" target="_blank" rel="noopener noreferrer">Canvas LMS</a> — ماژولها، تکالیف و نمرات</li>
<li><a href="/ai-assistant">دستیار هوشمند</a> — کمک مطالعه مرتبط با دوره</li>
<li><a href="https://notebooklm.google.com/notebook/8470c8e3-d6c7-4049-99f5-110a18977a91" target="_blank" rel="noopener noreferrer">NotebookLM</a> — دستیار مطالعه سیناوب</li>
<li><a href="/tickets.html">درخواستهای من</a> — تماس با مشاور</li>
</ul>
</div>
<div class="help-panel">
<h3>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--purple)" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
دسترسی پس از تأیید
</h3>
<p>همه دورهها رایگان هستند، اما دسترسی هر کاربر بعد از بررسی و تأیید پشتیبانی فعال میشود. وضعیت درخواست را همینجا دنبال کنید یا <a href="/tickets.html?new=1">درخواست پشتیبانی</a> ثبت کنید.</p>
</div>
</div>
<div class="card" id="introVideoCard">
<div class="card-head">
<div>
<h2>خودتان را معرفی کنید</h2>
<p class="card-sub">یک ویدیوی کوتاه ۱ تا ۲ دقیقهای ضبط کنید تا مشاوران و همدورهایها با شما آشنا شوند. پیش از ذخیره در پروفایل، ویدیو را بازبینی کنید.</p>
</div>
</div>
<div id="introError" class="alert alert-error hidden" role="alert"></div>
<div class="intro-video-wrap">
<div class="intro-video-stage">
<video id="introLive" class="intro-video hidden" playsinline muted aria-hidden="true"></video>
<video id="introPlayback" class="intro-video hidden" playsinline controls aria-label="Preview your recording"></video>
<video id="introSavedVideo" class="intro-video hidden" playsinline controls aria-label="Your saved introduction video"></video>
<div class="intro-video-placeholder" data-intro-phase="idle">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M15 10l4.553-2.276A1 1 0 0 1 21 8.618v6.764a1 1 0 0 1-1.447.894L15 14M5 18h8a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2z"/></svg>
<p>پس از شروع ضبط، دوربین و میکروفون فعال میشوند.</p>
</div>
<div class="intro-recording-badge hidden" data-intro-phase="recording" aria-live="polite">
<span class="intro-rec-dot" aria-hidden="true"></span> Recording
<span id="introTimer" class="intro-timer">0:00 / 2:00</span>
</div>
</div>
<div id="introConfirmBlock" class="intro-confirm-block hidden" role="group" aria-labelledby="introConfirmHeading">
<p id="introConfirmHeading" class="intro-confirm-heading">Before saving</p>
<label class="intro-confirm-label" for="introProfileConfirm">
<input type="checkbox" id="introProfileConfirm" name="introProfileConfirm" class="intro-confirm-checkbox"/>
<span class="intro-confirm-text">I confirm that I am the person in this video, that it accurately represents me, and that it is correct to save it to my student profile.</span>
</label>
</div>
<div class="intro-video-actions">
<div data-intro-phase="idle" class="intro-action-row">
<button type="button" class="btn" id="introStartBtn">Start recording</button>
</div>
<div data-intro-phase="recording" class="intro-action-row hidden">
<button type="button" class="btn btn-outline" id="introStopBtn">Stop recording</button>
<span class="intro-hint">Aim for 1–2 minutes. Recording stops automatically at 2:00.</span>
</div>
<div data-intro-phase="preview" class="intro-action-row hidden">
<button type="button" class="btn" id="introConfirmBtn" disabled>Save to my Profile</button>
<button type="button" class="btn btn-outline" id="introRedoBtn">Record again</button>
</div>
<div data-intro-phase="uploading" class="intro-action-row hidden">
<p class="intro-hint" id="introUploadStatus">Uploading…</p>
</div>
<div data-intro-phase="saved" class="intro-action-row hidden">
<p class="intro-hint" id="introSavedMeta"></p>
<button type="button" class="btn btn-outline" id="introReplaceBtn">Record a new video</button>
</div>
</div>
<p class="intro-privacy meta">فقط شما میتوانید این ویدیو را بهروزرسانی کنید. ویدیو بهصورت امن روی سرورهای سیناوب ذخیره میشود و به پروفایل دانشجویی شما متصل است.</p>
</div>
</div>
<div class="card">
<div class="card-head">
<div>
<h2>Account</h2>
<p class="card-sub">Your registered profile details.</p>
</div>
</div>
<div class="profile-grid" id="profileMeta"></div>
</div>
<footer class="page-footer">
<span>© SinaWeb · SinaWeb Learning</span>
<span>
<a href="https://sinaweb.org/">Main site</a> ·
<a href="https://edu.sinaweb.org">Support</a> ·
<a href="mailto:support@sinaweb.cc">Contact support</a>
</span>
</footer>
</div>
</div>
</div>
<script>
const params = new URLSearchParams(location.search);
const courseSlug = params.get('course');
const scholarship = params.get('scholarship') === '1';
const SUPPORT_NEW = '/tickets.html?new=1';
const CANVAS_LMS_URL = 'https://canvas.sinaweb.org/login';
function canvasUrl(slug) {
return CANVAS_LMS_URL;
}
function statusInfo(s) {
return {
comp_pending: { label: 'بررسی بورسیه', badge: 'badge-review', hint: 'درخواست شما در حال بررسی است.' },
invoice_sent: { label: 'در انتظار ثبتنام', badge: 'badge-review', hint: 'درخواست ثبتنام را تکمیل کنید تا Canvas فعال شود.' },
payment_pending: { label: 'در انتظار ثبتنام', badge: 'badge-review', hint: 'درخواست ثبتنام را تکمیل کنید تا Canvas فعال شود.' },
paid: { label: 'در حال فعالسازی', badge: 'badge-active', hint: 'دسترسی Canvas در حال آمادهسازی است.' },
enrolled: { label: 'فعال', badge: 'badge-active', hint: 'دسترسی کامل — Canvas را باز کنید.' },
}[s] || { label: (s||'').replace(/_/g,' '), badge: 'badge-review', hint: '' };
}
function banner(type, text) {
const el = document.getElementById('statusBanner');
el.className = 'alert alert-' + type;
el.textContent = text;
el.classList.remove('hidden');
}
function updateStats(rows) {
const active = rows.filter(r => r.status !== 'archived');
const enrolled = active.filter(r => r.status === 'enrolled');
const pending = active.filter(r => ['approval_pending','comp_pending','invoice_sent','payment_pending','paid'].includes(r.status));
document.getElementById('statCourses').textContent = enrolled.length || active.length || '0';
document.getElementById('statPending').textContent = pending.length;
document.getElementById('statCanvas').textContent = enrolled.length > 0 ? 'آماده' : (pending.length ? 'معلق' : '—');
}
function updateSteps(rows) {
const active = rows.filter(r => r.status !== 'archived');
const hasEnroll = active.length > 0;
const hasPaid = active.some(r => ['approval_pending','paid','enrolled','invoice_sent','payment_pending'].includes(r.status));
const hasCanvas = active.some(r => r.status === 'enrolled');
const steps = document.querySelectorAll('#stepsList .step');
const mark = (el, done, num) => {
el.classList.toggle('done', done);
el.querySelector('.step-num').textContent = done ? '✓' : String(num);
};
mark(steps[0], true, 1);
mark(steps[1], hasEnroll, 2);
mark(steps[2], hasPaid, 3);
mark(steps[3], hasCanvas, 4);
}
function renderCourses(rows) {
showCoursesLoading(false);
const list = document.getElementById('enrollList');
const empty = document.getElementById('emptyCourses');
const active = rows.filter(r => r.status !== 'archived');
updateStats(active);
updateSteps(active);
if (!active.length) {
list.innerHTML = '';
empty.classList.remove('hidden');
if (window.PortalNextStep) {
PortalNextStep.update({
enrollments: active,
user: window.__portalUser || null,
enrollCardVisible: !document.getElementById('courseEnrollCard').classList.contains('hidden'),
});
}
return;
}
empty.classList.add('hidden');
list.innerHTML = active.map(row => {
const info = statusInfo(row.status);
let actions = '';
if (row.status === 'invoice_sent' || row.status === 'payment_pending') {
actions = '<button type="button" class="btn btn-sm enroll-activate" data-course="'+row.course+'">تکمیل ثبتنام ←</button>';
} else if (row.status === 'approval_pending' || row.status === 'comp_pending') {
actions = '<a class="btn btn-sm btn-outline" href="'+SUPPORT_NEW+'">پرسش از پشتیبانی</a>';
} else if (row.status === 'enrolled') {
actions = '<a class="btn btn-sm" href="'+canvasUrl(row.course)+'">باز کردن Canvas ←</a>';
}
return '<div class="course-card" id="course-card-'+row.course+'" data-course="'+row.course+'" data-status="'+row.status+'"><div class="course-head"><div class="course-title">'+row.courseTitle
+'</div><span class="badge '+info.badge+'">'+info.label+'</span></div>'
+ '<div class="course-meta"><strong>هزینه:</strong> رایگان</div>'
+ (info.hint ? '<div class="course-meta">'+info.hint+'</div>' : '')
+ '<div class="course-actions">'+actions+'</div></div>';
}).join('');
list.querySelectorAll('.enroll-activate').forEach(btn => {
btn.onclick = () => enroll(btn.dataset.course);
});
if (window.Hub) Hub.bindCanvasLaunch();
if (window.PortalNextStep) {
PortalNextStep.update({
enrollments: active,
user: window.__portalUser || null,
enrollCardVisible: !document.getElementById('courseEnrollCard').classList.contains('hidden'),
});
}
}
function renderProfile(u) {
const fields = [
{ label: 'نام', val: [u.title, u.firstName, u.lastName].filter(Boolean).join(' ') },
{ label: 'ایمیل', val: u.email },
{ label: 'کشور', val: u.country },
{ label: 'ویدیوی معرفی', val: u.hasIntroVideo ? 'در پروفایل ذخیره شده' : 'هنوز ضبط نشده' },
].filter(f => f.val);
document.getElementById('profileMeta').innerHTML = fields.map(f =>
'<div class="profile-item"><div class="label">'+f.label+'</div><div class="val">'+f.val+'</div></div>'
).join('');
}
function profileComplete(u) {
return !!(u && u.profileComplete);
}
function fillProfileForm(u) {
const set = (id, val) => { const el = document.getElementById(id); if (el) el.value = val || ''; };
set('pcTitle', u.title || 'Mr');
set('pcFirstName', u.firstName || '');
set('pcLastName', u.lastName || '');
set('pcCountry', u.country || 'Iran');
set('pcPhone', (u.phone || '').replace(/^\+?98/, '').replace(/\D/g, ''));
set('pcJournalUrl', u.journalUrl || '');
set('pcJournalTitle', u.journalTitle || '');
}
function updateProfileGate(u) {
const card = document.getElementById('profileCompleteCard');
if (!card) return;
if (profileComplete(u)) {
card.classList.add('hidden');
} else {
fillProfileForm(u || {});
card.classList.remove('hidden');
banner('info', 'برای درخواست دسترسی به دوره، ابتدا پروفایل خود را کامل کنید.');
}
}
function profileError(text) {
const el = document.getElementById('profileCompleteError');
if (!el) return;
el.textContent = text || '';
el.classList.toggle('hidden', !text);
}
async function saveProfile(e) {
if (e) e.preventDefault();
profileError('');
const btn = document.getElementById('profileCompleteBtn');
if (btn) { btn.disabled = true; btn.textContent = 'در حال ذخیره…'; }
const payload = {
title: document.getElementById('pcTitle').value || 'Mr',
firstName: document.getElementById('pcFirstName').value.trim(),
lastName: document.getElementById('pcLastName').value.trim(),
country: document.getElementById('pcCountry').value.trim() || 'Iran',
phone: document.getElementById('pcPhone').value.trim(),
phoneDial: '98',
journalUrl: document.getElementById('pcJournalUrl').value.trim(),
journalTitle: document.getElementById('pcJournalTitle').value.trim(),
};
if (!payload.firstName || !payload.lastName || !payload.journalUrl) {
profileError('نام، نام خانوادگی و آدرس سایت نشریه الزامی است.');
if (btn) { btn.disabled = false; btn.textContent = 'ذخیره پروفایل'; }
return;
}
try {
const r = await fetch('/api/v1/profile', {
method: 'POST', credentials: 'include',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify(payload),
});
const data = await r.json().catch(() => ({}));
if (!r.ok) throw new Error(typeof data.detail === 'string' ? data.detail : 'ذخیره پروفایل ناموفق بود.');
const p = data.profile || {};
Object.assign(window.__portalUser, {
title: p.title, firstName: p.firstName, lastName: p.lastName, country: p.country, phone: p.phone,
journalUrl: p.journalUrl, journalTitle: p.journalTitle, profileComplete: p.complete, profileMissing: p.missing || [],
});
renderProfile(window.__portalUser);
updateProfileGate(window.__portalUser);
banner('success', 'پروفایل ذخیره شد. اکنون میتوانید درخواست دوره را ثبت کنید.');
} catch (err) {
profileError(err.message || 'ذخیره پروفایل ناموفق بود.');
} finally {
if (btn) { btn.disabled = false; btn.textContent = 'ذخیره پروفایل'; }
}
}
function hideLoading() {
document.getElementById('loadingView').classList.add('hidden');
}
function fetchWithTimeout(url, ms) {
return Promise.race([
fetch(url, { credentials: 'include' }),
new Promise(function(_, reject) {
setTimeout(function() { reject(new Error('timeout')); }, ms);
}),
]);
}
function portalNextQuery() {
if (courseSlug) return 'course=' + encodeURIComponent(courseSlug);
return '';
}
function showAuth(message) {
hideLoading();
document.getElementById('portalView').classList.add('hidden');
document.getElementById('authView').classList.remove('hidden');
document.body.classList.remove('portal-active');
document.getElementById('authPaymentBanner').classList.add('hidden');
if (message) document.getElementById('authMessage').textContent = message;
var loginHref = '/login.html';
var registerHref = '/register.html';
var nextQ = portalNextQuery();
if (nextQ) {
loginHref += '?next=' + encodeURIComponent(nextQ);
if (courseSlug) registerHref += '?course=' + encodeURIComponent(courseSlug);
}
var authActions = document.querySelector('#authView .auth-actions');
if (authActions) {
var btnLabel = 'ورود به پرتال';
authActions.innerHTML = '<a class="btn" href="' + loginHref + '">' + btnLabel + '</a>'
+ '<a class="btn btn-outline" href="/catalog-v1.html">فهرست دورهها</a>';
}
var regLink = document.querySelector('#authView .auth-links a[href*="register"]');
if (regLink) regLink.href = registerHref;
}
function showPortal() {
hideLoading();
document.getElementById('authView').classList.add('hidden');
document.getElementById('portalView').classList.remove('hidden');
document.body.classList.add('portal-active');
}
function showCoursesLoading(on) {
document.getElementById('coursesLoading').classList.toggle('hidden', !on);
if (on) {
document.getElementById('enrollList').innerHTML = '';
document.getElementById('emptyCourses').classList.add('hidden');
}
}
async function loadEnrollments() {
const r = await fetch('/api/v1/enrollments', { credentials: 'include' });
if (!r.ok) return [];
return (await r.json()).enrollments || [];
}
async function enroll(slug, opts) {
opts = opts || {};
if (!profileComplete(window.__portalUser)) {
updateProfileGate(window.__portalUser || {});
document.getElementById('profileCompleteCard')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
return;
}
const btn = document.getElementById('enrollBtn');
if (btn) { btn.disabled = true; btn.textContent = 'Processing…'; }
const body = { course: slug };
if (scholarship && !opts.switchToPaid) body.scholarshipCode = 'apply';
if (opts.switchToPaid) body.switchToPaid = true;
const r = await fetch('/api/v1/enrollments', {
method: 'POST', credentials: 'include',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify(body),
});
const data = await r.json().catch(() => ({}));
if (btn) { btn.disabled = false; btn.textContent = 'ارسال درخواست ثبتنام ←'; }
if (!r.ok) { banner('error', data.detail || data.message || 'ثبتنام ناموفق بود'); return; }
if (data.enrollment && data.enrollment.status === 'enrolled') {
banner('success', data.message || 'ثبتنام انجام شد! در حال باز کردن دوره…');
setTimeout(function() { location.href = canvasUrl(slug); }, 800);
return;
}
banner('success', data.message || 'درخواست ثبت شد و پس از تأیید فعال میشود.');
document.getElementById('courseEnrollCard').classList.add('hidden');
renderCourses(await loadEnrollments());
}
async function checkSession() {
const retrySigned = params.get('signed') === '1';
const attempts = retrySigned ? 5 : 1;
for (let i = 0; i < attempts; i++) {
if (i > 0) await new Promise(function(r) { setTimeout(r, 350 * i); });
try {
const resp = await fetchWithTimeout('/api/v1/auth/me', 6000);
if (resp.status === 401 && i < attempts - 1) continue;
return resp;
} catch (err) {
if (i === attempts - 1) throw err;
}
}
throw new Error('timeout');
}
async function init() {
let resp;
try {
resp = await checkSession();
} catch (_) {
showAuth();
return;
}
if (resp.status === 401) {
showAuth();
return;
}
if (resp.status === 403) {
showAuth('Please confirm your email first. Check your inbox for the verification link, then return here to sign in.');
return;
}
if (!resp.ok) {
showAuth();
return;
}
let u;
try {
u = (await resp.json()).user;
} catch (_) {
showAuth();
return;
}
if (!u || !u.email) {
showAuth();
return;
}
showPortal();
window.__portalUser = u;
updateProfileGate(u);
const profileForm = document.getElementById('profileCompleteForm');
if (profileForm) profileForm.onsubmit = saveProfile;
if (Hub.setSidebarUser) Hub.setSidebarUser(u.email);
Hub.bindCanvasLaunch();
if (params.get('signed') === '1') {
history.replaceState(null, '', location.pathname + location.hash);
}
const first = u.firstName || u.email.split('@')[0];
document.getElementById('welcomeTitle').textContent = first + ' عزیز، خوش آمدید';
renderProfile(u);
if (window.IntroVideo) {
IntroVideo.onSaved = function() { renderProfile(u); fetch('/api/v1/auth/me', { credentials: 'include' }).then(function(r) { return r.ok ? r.json() : null; }).then(function(d) { if (d && d.user) { Object.assign(u, d.user); renderProfile(u); } }); };
IntroVideo.init().catch(function() {});
}
showCoursesLoading(true);
if (params.get('verified') === '1') document.getElementById('verifiedBanner').classList.remove('hidden');
if (params.get('canvas') === 'locked') banner('info', 'ابتدا درخواست ثبتنام را تکمیل کنید — سپس از پرتال دورهها را باز کنید.');
if (params.get('canvas') === 'pending') banner('info', 'دسترسی Canvas در حال آمادهسازی است. چند دقیقه دیگر دوباره تلاش کنید.');
const enrollmentsPromise = loadEnrollments();
let enrollments = [];
try {
enrollments = await enrollmentsPromise;
} catch (_) {}
renderCourses(enrollments);
if (courseSlug) {
const ex = enrollments.find(e => e.course === courseSlug && e.status !== 'archived');
const card = document.getElementById('courseEnrollCard');
if (ex && ex.status === 'enrolled') {
banner('success', 'ثبتنام شما انجام شد! Canvas را از پایین باز کنید.');
} else if (ex && ['approval_pending','comp_pending','invoice_sent','payment_pending','paid'].includes(ex.status)) {
banner('info', 'درخواست این دوره ثبت شده و در انتظار تأیید است.');
} else if (!ex) {
const cr = await fetch('/api/v1/courses');
const course = ((await cr.json()).courses || []).find(c => c.slug === courseSlug);
if (course) {
if (!profileComplete(u)) { updateProfileGate(u); return; }
card.classList.remove('hidden');
document.getElementById('courseEnrollTitle').textContent = 'ثبتنام: ' + course.title;
document.getElementById('courseEnrollLead').textContent = 'درخواست ثبتنام — دسترسی پس از تأیید پشتیبانی فعال میشود.';
document.getElementById('courseEnrollNote').textContent = 'همه دورهها رایگان هستند، اما فعالسازی نیاز به تأیید دارد.';
document.getElementById('enrollBtn').textContent = 'درخواست ثبتنام ←';
document.getElementById('enrollBtn').onclick = () => enroll(courseSlug);
}
}
}
if (window.PortalNextStep) {
PortalNextStep.update({
enrollments: enrollments,
user: u,
enrollCardVisible: !document.getElementById('courseEnrollCard').classList.contains('hidden'),
});
}
}
init().catch(function() {
if (!document.getElementById('portalView').classList.contains('hidden')) return;
showAuth();
});
</script>
<script src="/intro-video.js?v=3"></script>
<script src="/portal-next-step.js?v=3"></script>
</body>
</html>