Contact reveal modal: click phone/contact on cards and detail pages
CI/CD / CI · dotnet build (push) Successful in 2m26s
CI/CD / Deploy · hamkadr (push) Successful in 58s

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:
soroush.asadi
2026-06-20 09:04:08 +03:30
parent 0cf5b30dd8
commit 4c0b29addf
10 changed files with 144 additions and 97 deletions
@@ -13,16 +13,6 @@
comp = JalaliDate.Toman(pa) + " مدنظر";
else
comp = "توافقی";
string? telHref = null;
if (!string.IsNullOrWhiteSpace(t.Phone))
{
var digits = new string(t.Phone.Where(char.IsDigit).ToArray());
if (digits.Length >= 7) telHref = "tel:" + digits;
}
// Only Divar is surfaced as a fallback source (and only when no number was extracted).
// We never name other crawl sources (medjobs/telegram/…) publicly.
bool isDivar = !string.IsNullOrWhiteSpace(t.SourceUrl)
&& System.Uri.TryCreate(t.SourceUrl, UriKind.Absolute, out var su) && su.Host.Contains("divar");
}
<div class="page-head">
@@ -67,41 +57,9 @@
<aside>
<div class="card card-pad">
<h3 style="margin-top:0;">راه‌های ارتباطی</h3>
@{ var contacts = (t.Contacts ?? new List<JobsMedical.Web.Models.ContactMethod>()).OrderBy(c => c.SortOrder).ToList(); }
@if (contacts.Count > 0)
{
<div class="contact-reveal">
@foreach (var c in contacts)
{
var href = JobsMedical.Web.Services.ContactInfo.Href(c.Type, c.Value);
var label = JobsMedical.Web.Services.ContactInfo.Label(c.Type);
var icon = JobsMedical.Web.Services.ContactInfo.Icon(c.Type);
var cls = c.Type is JobsMedical.Web.Models.ContactType.Mobile or JobsMedical.Web.Models.ContactType.Phone ? "btn-accent" : "btn-outline";
<div class="contact-row">
<span class="c-meta"><span class="c-type">@icon @label</span><span class="c-val" dir="ltr">@c.Value</span></span>
@if (href is not null)
{
<a class="btn @cls" href="@href" target="_blank" rel="nofollow noopener">باز کردن</a>
}
</div>
}
</div>
}
else if (telHref is not null)
{
<a href="@telHref" class="btn btn-accent btn-block btn-lg" dir="ltr">📞 @t.Phone</a>
}
else if (isDivar)
{
@* Divar hides the number behind a login-gated reveal — point to the original ad. *@
<p class="muted" style="margin-top:0;">مشاهده شماره در وبسایت دیوار</p>
<a href="@t.SourceUrl" target="_blank" rel="nofollow noopener" class="btn btn-accent btn-block btn-lg">مشاهده شماره در دیوار ↗</a>
<p class="muted" style="font-size:12px; margin:10px 0 0;">برای دریافت شماره به آگهی اصلی در دیوار مراجعه کن.</p>
}
else
{
<p class="muted">شماره تماس ثبت نشده است.</p>
}
<button type="button" class="btn btn-accent btn-block btn-lg contact-trigger"
data-contact-type="talent" data-contact-id="@t.Id">📞 مشاهده راه‌های ارتباطی</button>
<p class="muted" style="font-size:12px; margin:10px 0 0;">با کلیک، شماره تماس و راه‌های ارتباطی نمایش داده می‌شود.</p>
</div>
</aside>
</div>