Contact reveal modal: click phone/contact on cards and detail pages
Adds a lazy-loaded contact modal. Any element with data-contact-type + data-contact-id (the «📞 تماس» button on shift/job/talent/recommendation cards, and the contact CTA on the three detail pages) opens a modal that fetches the listing's numbers from a new GET /contact endpoint and renders them with click- to-call links. Numbers are loaded only on click, so they never sit in list-page HTML (privacy / anti-scrape). The endpoint logs the same Apply interest signal for shift/job that the old inline-reveal POST did, and falls back to the facility phone (or Divar source link for talent) when an ad has no own contacts. Verified locally: GET /contact?type=shift&id=1 → {title, contacts:[{value: '021-82032000', href:'tel:...'}]}, and the modal opens and renders on the shift detail page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,6 @@
|
||||
}
|
||||
<div class="foot">
|
||||
<span class="pay">@salary</span>
|
||||
<span class="btn btn-outline" style="padding: 6px 14px;">جزئیات</span>
|
||||
<span class="btn btn-accent contact-trigger" style="padding: 6px 14px;" data-contact-type="job" data-contact-id="@Model.Id">📞 تماس</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -275,6 +275,57 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
@* Contact modal — any element with data-contact-type + data-contact-id opens it; numbers are
|
||||
fetched from /contact on click (so they never sit in list HTML and bots can't scrape them). *@
|
||||
<div id="contactModal" class="contact-modal" aria-hidden="true">
|
||||
<div class="contact-modal-box" role="dialog" aria-modal="true" aria-labelledby="contactModalTitle">
|
||||
<div class="contact-modal-head">
|
||||
<h3 id="contactModalTitle">راههای ارتباطی</h3>
|
||||
<button type="button" class="contact-modal-x" data-contact-close aria-label="بستن">✕</button>
|
||||
</div>
|
||||
<div id="contactModalBody" class="contact-modal-body"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var modal = document.getElementById('contactModal');
|
||||
var box = document.getElementById('contactModalBody');
|
||||
var titleEl = document.getElementById('contactModalTitle');
|
||||
function esc(s) { var d = document.createElement('div'); d.textContent = s == null ? '' : s; return d.innerHTML; }
|
||||
function open() { modal.classList.add('show'); modal.setAttribute('aria-hidden', 'false'); }
|
||||
function close() { modal.classList.remove('show'); modal.setAttribute('aria-hidden', 'true'); box.innerHTML = ''; }
|
||||
function render(d) {
|
||||
titleEl.textContent = d.title || 'راههای ارتباطی';
|
||||
var html = '';
|
||||
(d.contacts || []).forEach(function (c) {
|
||||
html += '<div class="contact-row"><span class="c-meta"><span class="c-type">' + esc(c.icon + ' ' + c.label) +
|
||||
'</span><span class="c-val" dir="ltr">' + esc(c.value) + '</span></span>' +
|
||||
(c.href ? '<a class="btn btn-accent" href="' + esc(c.href) + '" target="_blank" rel="nofollow noopener">تماس</a>' : '') + '</div>';
|
||||
});
|
||||
if (d.fallbackUrl) html += '<a class="btn btn-accent btn-block" href="' + esc(d.fallbackUrl) +
|
||||
'" target="_blank" rel="nofollow noopener">' + esc(d.fallbackLabel || 'مشاهده') + '</a>';
|
||||
box.innerHTML = html || '<p class="muted" style="margin:0;">شمارهای ثبت نشده است.</p>';
|
||||
}
|
||||
document.addEventListener('click', function (e) {
|
||||
var trigger = e.target.closest('[data-contact-type]');
|
||||
if (trigger) {
|
||||
e.preventDefault(); e.stopPropagation(); // don't follow the card link
|
||||
titleEl.textContent = 'راههای ارتباطی';
|
||||
box.innerHTML = '<p class="muted" style="margin:0;">در حال دریافت…</p>';
|
||||
open();
|
||||
fetch('/contact?type=' + encodeURIComponent(trigger.getAttribute('data-contact-type')) +
|
||||
'&id=' + encodeURIComponent(trigger.getAttribute('data-contact-id')))
|
||||
.then(function (r) { return r.ok ? r.json() : Promise.reject(); })
|
||||
.then(render)
|
||||
.catch(function () { box.innerHTML = '<p class="muted" style="margin:0;">خطا در دریافت اطلاعات تماس.</p>'; });
|
||||
return;
|
||||
}
|
||||
if (e.target.closest('[data-contact-close]') || e.target === modal) close();
|
||||
});
|
||||
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') close(); });
|
||||
})();
|
||||
</script>
|
||||
|
||||
@* Live in-app notifications over SSE (our own origin — works in Iran, no Google push).
|
||||
Updates the bell badge, shows a toast, and fires a local OS notification when allowed. *@
|
||||
@if (User.Identity?.IsAuthenticated == true)
|
||||
|
||||
@@ -35,6 +35,6 @@
|
||||
|
||||
<div class="foot">
|
||||
<span class="pay">@JalaliDate.PayLabel(s.PayType, s.PayAmount, s.SharePercent)</span>
|
||||
<span class="btn btn-outline" style="padding: 6px 14px;">جزئیات</span>
|
||||
<span class="btn btn-accent contact-trigger" style="padding: 6px 14px;" data-contact-type="shift" data-contact-id="@s.Id">📞 تماس</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -40,6 +40,6 @@
|
||||
<partial name="_HourBar" model="Model" />
|
||||
<div class="foot">
|
||||
<span class="pay">@JalaliDate.PayLabel(Model.PayType, Model.PayAmount, Model.SharePercent)</span>
|
||||
<span class="btn btn-outline" style="padding: 6px 14px;">جزئیات</span>
|
||||
<span class="btn btn-accent contact-trigger" style="padding: 6px 14px;" data-contact-type="shift" data-contact-id="@Model.Id">📞 تماس</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -56,6 +56,6 @@
|
||||
}
|
||||
<div class="foot">
|
||||
<span class="pay">@comp</span>
|
||||
<span class="btn btn-outline" style="padding: 6px 14px;">مشاهده و تماس</span>
|
||||
<span class="btn btn-accent contact-trigger" style="padding: 6px 14px;" data-contact-type="talent" data-contact-id="@Model.Id">📞 مشاهده و تماس</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user