- 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.
241 lines
9.7 KiB
Twig
241 lines
9.7 KiB
Twig
<!-- ===================== 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>
|