Files
hamed 68b8b05630 feat: add landing page templates and registration modal
- Implemented header and hero section in _header.html.twig and _hero_art.html.twig.
- Created a pre-registration modal in _reg_modal.html.twig with form fields and validation.
- Added page scripts for dynamic behavior and interaction in _page_scripts.html.twig.
- Developed landing page structure in landing.html.twig, integrating header, footer, and modal.
- Introduced tests for landing page rendering and registry validation in LandingPageTest.php and LandingRegistryTest.php.
2026-08-10 14:38:27 +03:30

241 lines
9.7 KiB
Twig
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- ===================== Pre-Registration Modal ===================== -->
<div id="regOverlay" style="display:none;position:fixed;inset:0;background:oklch(0.2 0.05 285 / 0.55);z-index:900;backdrop-filter:blur(4px);overflow-y:auto;padding:24px 16px" onclick="if(event.target===this)closeRegModal()">
<div style="background:#fff;border-radius:20px;max-width:520px;margin:auto;padding:32px 28px;position:relative;box-shadow:0 24px 60px oklch(0.3 0.1 285 / 0.22)">
<button onclick="closeRegModal()" aria-label="بستن" style="position:absolute;top:16px;left:20px;background:none;border:none;cursor:pointer;font-size:22px;color:var(--text-2);line-height:1">×</button>
<h2 style="margin:0 0 6px;font-size:20px;font-weight:800;color:var(--ink)">ثبت نام در کلینیک پرو</h2>
<p style="margin:0 0 24px;font-size:13px;color:var(--text-2)">نوع حساب خود را انتخاب کنید</p>
<!-- Type Cards -->
<div id="typeCards" style="display:flex;flex-direction:column;gap:10px;margin-bottom:22px">
<label class="reg-card" data-type="independent_doctor" style="display:flex;align-items:center;gap:14px;padding:14px 16px;border:2px solid var(--border);border-radius:14px;cursor:pointer;transition:all .2s">
<input type="radio" name="regType" value="independent_doctor" style="display:none">
<span style="font-size:26px;flex-shrink:0">🩺</span>
<div>
<div style="font-weight:700;font-size:14px;color:var(--ink)">دکتر هستم</div>
<div style="font-size:12px;color:var(--text-2);margin-top:2px">مطب شخصی دارم، به تنهایی کار می‌کنم</div>
</div>
</label>
<label class="reg-card" data-type="doctor_with_clinic" style="display:flex;align-items:center;gap:14px;padding:14px 16px;border:2px solid var(--border);border-radius:14px;cursor:pointer;transition:all .2s">
<input type="radio" name="regType" value="doctor_with_clinic" style="display:none">
<span style="font-size:26px;flex-shrink:0">🏥</span>
<div>
<div style="font-weight:700;font-size:14px;color:var(--ink)">دکتر هستم و کلینیک دارم</div>
<div style="font-size:12px;color:var(--text-2);margin-top:2px">با چند دکتر در یک کلینیک همکاری می‌کنم</div>
</div>
</label>
<label class="reg-card" data-type="clinic_manager" style="display:flex;align-items:center;gap:14px;padding:14px 16px;border:2px solid var(--border);border-radius:14px;cursor:pointer;transition:all .2s">
<input type="radio" name="regType" value="clinic_manager" style="display:none">
<span style="font-size:26px;flex-shrink:0">🏢</span>
<div>
<div style="font-weight:700;font-size:14px;color:var(--ink)">مدیر کلینیک هستم</div>
<div style="font-size:12px;color:var(--text-2);margin-top:2px">مدیریت کلینیک را دارم، خودم دکتر نیستم</div>
</div>
</label>
</div>
<!-- Form Fields -->
<div id="regFields" style="display:flex;flex-direction:column;gap:14px">
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:5px;color:var(--ink)">نام کامل
<span style="color:var(--coral)">*</span>
</label>
<input id="regName" type="text" placeholder="مثال: دکتر علی احمدی" style="width:100%;border:1.5px solid var(--border);border-radius:10px;padding:10px 14px;font-size:14px;font-family:inherit;outline:none;box-sizing:border-box;color:var(--ink)"/>
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:5px;color:var(--ink)">شماره موبایل
<span style="color:var(--coral)">*</span>
</label>
<input id="regMobile" type="tel" placeholder="09xxxxxxxxx" dir="ltr" style="width:100%;border:1.5px solid var(--border);border-radius:10px;padding:10px 14px;font-size:14px;font-family:inherit;outline:none;box-sizing:border-box;color:var(--ink);text-align:right"/>
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:5px;color:var(--ink)">توضیحات
<span style="font-weight:400;color:var(--text-2)">(اختیاری)</span>
</label>
<textarea id="regInfo" rows="3" placeholder="تخصص، آدرس، سابقه کاری، ..." style="width:100%;border:1.5px solid var(--border);border-radius:10px;padding:10px 14px;font-size:14px;font-family:inherit;outline:none;box-sizing:border-box;resize:vertical;color:var(--ink)"></textarea>
</div>
</div>
{% if altcha_enabled %}
<!-- ALTCHA captcha — proof-of-work در پس‌زمینه (بدون تعامل کاربر) -->
{# widget v3: attribute «challenge» (نه challengeurl قدیمی) #}
<altcha-widget id="regAltcha" challenge="/api/v1/altcha/challenge" auto="onload" language="fa" style="display:block;margin-top:16px"></altcha-widget>
{% endif %}
<!-- Error / Success -->
<div id="regMsg" style="display:none;margin-top:14px;padding:12px 16px;border-radius:10px;font-size:13px"></div>
<button id="regSubmitBtn" disabled onclick="submitReg()" style="margin-top:20px;width:100%;background:var(--blue);color:#fff;border:none;border-radius:12px;padding:13px;font-size:15px;font-weight:700;cursor:pointer;font-family:inherit;opacity:.45;transition:opacity .2s">
ارسال درخواست
</button>
<p style="text-align:center;font-size:12px;color:var(--text-2);margin:12px 0 0">پس از بررسی، اطلاعات ورود از طریق SMS ارسال می‌شود</p>
</div>
</div>
<style>
.reg-card:hover {
border-color: var(--blue) !important;
background: oklch(0.97 0.02 277);
}
.reg-card.selected {
border-color: var(--blue) !important;
background: oklch(0.96 0.03 277);
}
.reg-card.selected div > div:first-child {
color: var(--blue);
}
#regName:focus,
#regMobile:focus,
#regInfo:focus {
border-color: var(--blue);
box-shadow: 0 0 0 3px oklch(0.58 0.185 277 / 0.12);
}
</style>
<script>
(function () {
var overlay = document.getElementById('regOverlay');
var submitBtn = document.getElementById('regSubmitBtn');
var selectedType = null;
// payload حل‌شده‌ی ALTCHA (یک‌بارمصرف؛ در prod الزامی است)
var regAltchaPayload = '';
var regAltchaEl = document.getElementById('regAltcha');
if (regAltchaEl)
regAltchaEl.addEventListener('statechange', function (e) {
if (e.detail && e.detail.state === 'verified')
regAltchaPayload = e.detail.payload;
});
function openRegModal() {
overlay.style.display = 'block';
document.body.style.overflow = 'hidden';
}
window.closeRegModal = function () {
overlay.style.display = 'none';
document.body.style.overflow = '';
};
var btn1 = document.getElementById('openRegModal');
var btn2 = document.getElementById('openRegModal2');
var btn3 = document.getElementById('openRegModalNav');
if (btn1)
btn1.addEventListener('click', openRegModal);
if (btn2)
btn2.addEventListener('click', openRegModal);
if (btn3)
btn3.addEventListener('click', function (e) {
e.preventDefault();
openRegModal();
});
document.querySelectorAll('.reg-card').forEach(function (card) {
card.addEventListener('click', function () {
document.querySelectorAll('.reg-card').forEach(function (c) {
c.classList.remove('selected');
});
card.classList.add('selected');
var radio = card.querySelector('input[type=radio]');
if (radio)
radio.checked = true;
selectedType = card.dataset.type;
submitBtn.disabled = false;
submitBtn.style.opacity = '1';
});
});
window.submitReg = function () {
var name = document.getElementById('regName').value.trim();
var mobile = document.getElementById('regMobile').value.trim();
var info = document.getElementById('regInfo').value.trim();
var msg = document.getElementById('regMsg');
if (! selectedType)
return;
if (! name) {
showMsg('نام را وارد کنید', 'error');
return;
}
if (! mobile || mobile.length < 10) {
showMsg('شماره موبایل معتبر نیست', 'error');
return;
}
submitBtn.disabled = true;
submitBtn.textContent = 'در حال ارسال...';
fetch('/api/v1/pre-registration', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(
{
type: selectedType,
name: name,
mobile: mobile,
info: info || null,
altcha: regAltchaPayload
}
)
}).then(function (r) {
return r.json();
}).then(function (data) {
if (data.success) {
showMsg('درخواست شما ثبت شد. پس از بررسی، اطلاعات ورود از طریق SMS ارسال می‌شود.', 'success');
submitBtn.textContent = 'ارسال شد ✓';
setTimeout(function () {
closeRegModal();
}, 2200);
} else {
var errMsg = (data.errors && data.errors[0]) ? data.errors[0].message : 'خطایی رخ داد';
showMsg(errMsg, 'error');
submitBtn.disabled = false;
submitBtn.style.opacity = '1';
submitBtn.textContent = 'ارسال درخواست';
resetRegAltcha();
}
}).catch(function () {
showMsg('خطا در اتصال به سرور', 'error');
submitBtn.disabled = false;
submitBtn.style.opacity = '1';
submitBtn.textContent = 'ارسال درخواست';
resetRegAltcha();
});
};
// challenge یک‌بارمصرف است؛ بعد از خطا یک challenge تازه بگیر.
function resetRegAltcha() {
regAltchaPayload = '';
if (regAltchaEl && typeof regAltchaEl.reset === 'function')
regAltchaEl.reset();
}
function showMsg(text, type) {
var msg = document.getElementById('regMsg');
msg.textContent = text;
msg.style.display = 'block';
msg.style.background = type === 'success' ? '#dcfce7' : '#fef2f2';
msg.style.color = type === 'success' ? '#16a34a' : '#ef4444';
msg.style.border = type === 'success' ? '1px solid #bbf7d0' : '1px solid #fecaca';
}
})();
</script>