Compare commits

...
2 Commits
Author SHA1 Message Date
westfarn a817b9caf7 Update public About bio, Wheaton address, and headshot.
CI / test (pull_request) Successful in 13s
Closes #1 — refresh About copy and photo, surface the Butterfield Rd office on Home/Contact/nav, and align service-area defaults with Chicago's western suburbs.
2026-08-20 03:35:12 -07:00
westfarn 46abaa6917 Add public PageView tracking for portal analytics (#4)
Deploy Beta / unit-tests (push) Successful in 12s
Deploy Beta / docker (push) Successful in 22s
Deploy Beta / deploy-beta (push) Successful in 5m40s
## Summary
Closes #3.

- Add `PageView` model + migration for successful public marketing GET hits
- Wire `PublicPageViewMiddleware` (skips portal/admin/accounts/api/static/health/etc.; fails soft on DB errors)
- Portal analytics report: last-30-day views + top pages table
- Admin registration and tests

## Test plan
- [ ] Apply migration `analytics.0002_pageview`
- [ ] Hit `/` and `/about/` → rows in `PageView` / admin
- [ ] Hit `/healthz/`, `/portal/`, `/admin/` → no new pageviews
- [ ] Open portal Analytics → views count + Top pages look right
- [ ] `uv run python site/manage.py test analytics.tests`

Reviewed-on: #4
2026-08-20 03:34:56 -07:00
16 changed files with 269 additions and 25 deletions
+3 -2
View File
@@ -15,7 +15,8 @@ SITE_TAGLINE=MKDRealtor.com · EXIT Realty
PUBLIC_SITE_URL=http://127.0.0.1:8000 PUBLIC_SITE_URL=http://127.0.0.1:8000
CONTACT_PHONE=(630) 452-4443 CONTACT_PHONE=(630) 452-4443
CONTACT_EMAIL=moni.dhill@gmail.com CONTACT_EMAIL=moni.dhill@gmail.com
CONTACT_SERVICE_AREA=Serving Chicagoland CONTACT_ADDRESS=1245 Butterfield Rd. #100, Wheaton, IL 60189
CONTACT_SERVICE_AREA=Serving Chicago's western suburbs
CREDIT_NAME=AI ML Operations, LLC CREDIT_NAME=AI ML Operations, LLC
CREDIT_URL=https://aimloperations.com CREDIT_URL=https://aimloperations.com
@@ -59,7 +60,7 @@ PCM_WEBHOOK_SECRETS=
# PCM_WEBHOOK_SECRET= # PCM_WEBHOOK_SECRET=
# Required return address on every postcard order. # Required return address on every postcard order.
# Quote the JSON (compose/shell break on bare {…, …}). # Quote the JSON (compose/shell break on bare {…, …}).
# PCM_RETURN_ADDRESS='{"firstName":"Monica","lastName":"Dhillon","address":"123 Main St","city":"Naperville","state":"IL","zipCode":"60540"}' # PCM_RETURN_ADDRESS='{"firstName":"Monica","lastName":"Dhillon","address":"1245 Butterfield Rd.","address2":"#100","city":"Wheaton","state":"IL","zipCode":"60189"}'
PCM_RETURN_ADDRESS= PCM_RETURN_ADDRESS=
# Or set fields individually if JSON is empty: # Or set fields individually if JSON is empty:
# PCM_RETURN_LINE1= # PCM_RETURN_LINE1=
+10 -1
View File
@@ -1,6 +1,15 @@
from django.contrib import admin from django.contrib import admin
from analytics.models import Attribution, UTMVisit from analytics.models import Attribution, PageView, UTMVisit
@admin.register(PageView)
class PageViewAdmin(admin.ModelAdmin):
list_display = ("path", "created_at")
list_filter = ("created_at",)
search_fields = ("path",)
date_hierarchy = "created_at"
readonly_fields = ("id", "path", "created_at", "updated_at")
@admin.register(UTMVisit) @admin.register(UTMVisit)
+45 -1
View File
@@ -1,10 +1,54 @@
import logging
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from analytics.models import Attribution, UTMVisit from analytics.models import Attribution, PageView, UTMVisit
logger = logging.getLogger(__name__)
CORRELATION_COOKIE = "ms_cid" CORRELATION_COOKIE = "ms_cid"
# Portal, auth, health, assets, and transactional public endpoints.
_SKIP_PREFIXES = (
"/portal/",
"/admin/",
"/accounts/",
"/api/",
"/healthz",
"/static/",
"/media/",
"/unsubscribe/",
)
_SKIP_PATHS = frozenset({"/robots.txt", "/sitemap.xml", "/favicon.ico"})
class PublicPageViewMiddleware:
"""Record successful GET hits on public marketing pages."""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if _should_record_page_view(request, response):
try:
PageView.objects.create(path=request.path[:512])
except Exception:
logger.exception("Failed to record page view for %s", request.path)
return response
def _should_record_page_view(request, response) -> bool:
if request.method != "GET":
return False
if getattr(response, "status_code", 0) != 200:
return False
path = request.path
if path in _SKIP_PATHS:
return False
return not any(path.startswith(prefix) for prefix in _SKIP_PREFIXES)
class UTMTrackingMiddleware: class UTMTrackingMiddleware:
"""Capture UTM params into UTMVisit and stash a correlation id cookie.""" """Capture UTM params into UTMVisit and stash a correlation id cookie."""
@@ -0,0 +1,27 @@
# Generated by Django 6.1 on 2026-08-13 12:38
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('analytics', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='PageView',
fields=[
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('path', models.CharField(db_index=True, max_length=512)),
],
options={
'ordering': ['-created_at'],
'indexes': [models.Index(fields=['created_at', 'path'], name='analytics_p_created_9e8b64_idx')],
},
),
]
+15
View File
@@ -4,6 +4,21 @@ from core.models import TimeStampedModel, UUIDPrimaryKeyModel
from leads.models import Lead from leads.models import Lead
class PageView(UUIDPrimaryKeyModel, TimeStampedModel):
"""One successful GET of a public marketing page."""
path = models.CharField(max_length=512, db_index=True)
class Meta:
ordering = ["-created_at"]
indexes = [
models.Index(fields=["created_at", "path"]),
]
def __str__(self) -> str:
return self.path or "/"
class UTMVisit(UUIDPrimaryKeyModel, TimeStampedModel): class UTMVisit(UUIDPrimaryKeyModel, TimeStampedModel):
correlation_id = models.CharField(max_length=64, db_index=True) correlation_id = models.CharField(max_length=64, db_index=True)
path = models.CharField(max_length=512, blank=True) path = models.CharField(max_length=512, blank=True)
+28 -2
View File
@@ -2,11 +2,37 @@
{% block title %}Analytics · Portal{% endblock %} {% block title %}Analytics · Portal{% endblock %}
{% block topbar_title %}Analytics{% endblock %} {% block topbar_title %}Analytics{% endblock %}
{% block portal_content %} {% block portal_content %}
<div class="stat-row"> <div class="analytics-overview">
<div class="stat-card"> <div class="stat-card">
<div class="label">Views (last 30 days)</div> <div class="label">Views (last 30 days)</div>
<div class="value">{{ visits_last_30_days }}</div> <div class="value">{{ pageviews_last_30_days }}</div>
</div> </div>
<div class="panel">
<div class="panel-h"><h2>Top pages</h2><span class="muted" style="font-size:12px">Public visits, last 30 days</span></div>
<div class="panel-b" style="padding:0">
<table class="table">
<thead>
<tr>
<th>Page</th>
<th>Visits</th>
</tr>
</thead>
<tbody>
{% for row in top_pages %}
<tr>
<td>{{ row.path }}</td>
<td>{{ row.count }}</td>
</tr>
{% empty %}
<tr><td colspan="2" class="empty-state">No public page views in the last 30 days.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="stat-row">
<div class="stat-card"> <div class="stat-card">
<div class="label">UTM landings</div> <div class="label">UTM landings</div>
<div class="value">{{ total_visits }}</div> <div class="value">{{ total_visits }}</div>
+84
View File
@@ -0,0 +1,84 @@
from datetime import timedelta
from django.contrib.auth import get_user_model
from django.test import Client, TestCase
from django.urls import reverse
from django.utils import timezone
from analytics.models import PageView, UTMVisit
class PublicPageViewTests(TestCase):
def setUp(self):
self.client = Client()
def test_home_records_page_view(self):
response = self.client.get("/")
self.assertEqual(response.status_code, 200)
self.assertEqual(PageView.objects.count(), 1)
self.assertEqual(PageView.objects.get().path, "/")
def test_about_records_page_view(self):
self.client.get(reverse("public:about"))
self.assertEqual(PageView.objects.filter(path="/about/").count(), 1)
def test_plain_visit_does_not_create_utm_visit(self):
self.client.get("/")
self.assertEqual(PageView.objects.count(), 1)
self.assertEqual(UTMVisit.objects.count(), 0)
def test_utm_hit_records_both(self):
self.client.get("/?utm_source=test&utm_campaign=demo")
self.assertEqual(PageView.objects.count(), 1)
self.assertEqual(UTMVisit.objects.count(), 1)
def test_healthz_not_recorded(self):
self.client.get("/healthz/")
self.assertEqual(PageView.objects.count(), 0)
def test_portal_and_admin_not_recorded(self):
self.client.get("/portal/")
self.client.get("/admin/")
self.assertEqual(PageView.objects.count(), 0)
def test_robots_and_sitemap_not_recorded(self):
self.client.get("/robots.txt")
self.client.get("/sitemap.xml")
self.assertEqual(PageView.objects.count(), 0)
def test_missing_page_not_recorded(self):
response = self.client.get("/not-a-real-page/")
self.assertEqual(response.status_code, 404)
self.assertEqual(PageView.objects.count(), 0)
def test_contact_post_not_recorded(self):
self.client.post(reverse("public:contact"), {})
self.assertEqual(PageView.objects.count(), 0)
class AnalyticsReportTests(TestCase):
def setUp(self):
self.client = Client()
user = get_user_model().objects.create_user(
username="monica", password="pass-word-1"
)
self.client.force_login(user)
def test_views_card_uses_public_pageviews(self):
self.client.get("/")
self.client.get(reverse("public:about"))
self.client.get(reverse("public:about"))
stale = PageView.objects.create(path="/terms/")
PageView.objects.filter(pk=stale.pk).update(
created_at=timezone.now() - timedelta(days=31)
)
response = self.client.get(reverse("analytics:report"))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context["pageviews_last_30_days"], 3)
top = {row["path"]: row["count"] for row in response.context["top_pages"]}
self.assertEqual(top["/about/"], 2)
self.assertEqual(top["/"], 1)
self.assertNotIn("/terms/", top)
self.assertContains(response, "Top pages")
self.assertContains(response, "/about/")
+8 -3
View File
@@ -5,7 +5,7 @@ from django.db.models import Count
from django.shortcuts import render from django.shortcuts import render
from django.utils import timezone from django.utils import timezone
from analytics.models import Attribution, UTMVisit from analytics.models import Attribution, PageView, UTMVisit
def _bar_pct(rows, key="count"): def _bar_pct(rows, key="count"):
@@ -18,7 +18,11 @@ def _bar_pct(rows, key="count"):
@login_required @login_required
def report(request): def report(request):
since_30d = timezone.now() - timedelta(days=30) since_30d = timezone.now() - timedelta(days=30)
visits_last_30_days = UTMVisit.objects.filter(created_at__gte=since_30d).count() pageviews_qs = PageView.objects.filter(created_at__gte=since_30d)
pageviews_last_30_days = pageviews_qs.count()
top_pages = list(
pageviews_qs.values("path").annotate(count=Count("id")).order_by("-count")[:20]
)
visits_by_source = _bar_pct( visits_by_source = _bar_pct(
list( list(
@@ -60,7 +64,8 @@ def report(request):
request, request,
"analytics/report.html", "analytics/report.html",
{ {
"visits_last_30_days": visits_last_30_days, "pageviews_last_30_days": pageviews_last_30_days,
"top_pages": top_pages,
"total_visits": UTMVisit.objects.count(), "total_visits": UTMVisit.objects.count(),
"total_attributed": Attribution.objects.count(), "total_attributed": Attribution.objects.count(),
"top_source": (top_visit or {}).get("utm_source") or "(direct)", "top_source": (top_visit or {}).get("utm_source") or "(direct)",
+7 -1
View File
@@ -134,6 +134,7 @@ MIDDLEWARE = [
"django.contrib.messages.middleware.MessageMiddleware", "django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
"analytics.middleware.UTMTrackingMiddleware", "analytics.middleware.UTMTrackingMiddleware",
"analytics.middleware.PublicPageViewMiddleware",
"public.middleware.UnderConstructionMiddleware", "public.middleware.UnderConstructionMiddleware",
] ]
@@ -215,7 +216,12 @@ SITE_TAGLINE = env("SITE_TAGLINE", "MKDRealtor.com · EXIT Realty Redefined")
PUBLIC_SITE_URL = env("PUBLIC_SITE_URL", "") PUBLIC_SITE_URL = env("PUBLIC_SITE_URL", "")
CONTACT_PHONE = env("CONTACT_PHONE", "(555) 123-4567") CONTACT_PHONE = env("CONTACT_PHONE", "(555) 123-4567")
CONTACT_EMAIL = env("CONTACT_EMAIL", "monica@example.com") CONTACT_EMAIL = env("CONTACT_EMAIL", "monica@example.com")
CONTACT_SERVICE_AREA = env("CONTACT_SERVICE_AREA", "Serving Greater Metro Area") CONTACT_ADDRESS = env(
"CONTACT_ADDRESS", "1245 Butterfield Rd. #100, Wheaton, IL 60189"
)
CONTACT_SERVICE_AREA = env(
"CONTACT_SERVICE_AREA", "Serving Chicago's western suburbs"
)
CREDIT_NAME = env("CREDIT_NAME", "AI ML Operations, LLC") CREDIT_NAME = env("CREDIT_NAME", "AI ML Operations, LLC")
CREDIT_URL = env("CREDIT_URL", "https://aimloperations.com") CREDIT_URL = env("CREDIT_URL", "https://aimloperations.com")
+16
View File
@@ -83,6 +83,22 @@ body.portal {
/* Portal widgets */ /* Portal widgets */
.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 16px; margin-bottom: 24px; } .stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 16px; margin-bottom: 24px; }
.analytics-overview {
display: grid;
grid-template-columns: minmax(140px, 1fr) 4fr;
gap: 16px;
margin-bottom: 24px;
align-items: stretch;
}
.analytics-overview .stat-card {
display: flex;
flex-direction: column;
justify-content: center;
}
.analytics-overview .panel { margin-bottom: 0; }
@media (max-width: 800px) {
.analytics-overview { grid-template-columns: 1fr; }
}
.stat-card { .stat-card {
background: #fff; background: #fff;
border: 1px solid var(--monica-border); border: 1px solid var(--monica-border);
Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

+2 -2
View File
@@ -65,7 +65,7 @@
{% endif %} {% endif %}
<article class="unit align-items-center"> <article class="unit align-items-center">
<div class="unit-left"><span class="icon icon-md icon-modern mdi mdi-map-marker"></span></div> <div class="unit-left"><span class="icon icon-md icon-modern mdi mdi-map-marker"></span></div>
<div class="unit-body"><span class="link-default">{{ CONTACT_SERVICE_AREA }}</span></div> <div class="unit-body"><span class="link-default">{% if CONTACT_ADDRESS %}{{ CONTACT_ADDRESS }}{% else %}{{ CONTACT_SERVICE_AREA }}{% endif %}</span></div>
</article> </article>
<a class="button button-gray-bordered button-winona" href="{% url 'public:contact' %}" data-tianji-event="nav_get_in_touch">Get in touch</a> <a class="button button-gray-bordered button-winona" href="{% url 'public:contact' %}" data-tianji-event="nav_get_in_touch">Get in touch</a>
</div> </div>
@@ -117,7 +117,7 @@
<span style="color:#7dd3da;font-size:12px">{{ SITE_TAGLINE }}</span> <span style="color:#7dd3da;font-size:12px">{{ SITE_TAGLINE }}</span>
</div> </div>
</div> </div>
<p class="footer-advanced-text">Local expertise, clear communication, and a marketing platform built to bring serious buyers and sellers to you — not to a generic campaign factory.</p> <p class="footer-advanced-text">Local expertise, clear communication, and a marketing platform built to bring serious buyers and sellers to you — not to a generic campaign factory.{% if CONTACT_ADDRESS %} {{ CONTACT_ADDRESS }}.{% endif %}</p>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<h5 class="font-weight-bold text-uppercase text-white">Navigate</h5> <h5 class="font-weight-bold text-uppercase text-white">Navigate</h5>
+4 -1
View File
@@ -20,8 +20,11 @@ def site_branding(request):
"CONTACT_PHONE": phone, "CONTACT_PHONE": phone,
"CONTACT_PHONE_TEL": _phone_tel(phone), "CONTACT_PHONE_TEL": _phone_tel(phone),
"CONTACT_EMAIL": settings.CONTACT_EMAIL or "", "CONTACT_EMAIL": settings.CONTACT_EMAIL or "",
"CONTACT_ADDRESS": getattr(settings, "CONTACT_ADDRESS", "") or "",
"CONTACT_SERVICE_AREA": getattr( "CONTACT_SERVICE_AREA": getattr(
settings, "CONTACT_SERVICE_AREA", "Serving Greater Metro Area" settings,
"CONTACT_SERVICE_AREA",
"Serving Chicago's western suburbs",
), ),
"CREDIT_NAME": getattr(settings, "CREDIT_NAME", "AI ML Operations, LLC"), "CREDIT_NAME": getattr(settings, "CREDIT_NAME", "AI ML Operations, LLC"),
"CREDIT_URL": getattr( "CREDIT_URL": getattr(
+14 -10
View File
@@ -3,12 +3,12 @@
{% block title %}About {{ SITE_NAME }} · EXIT Realty Realtor{% endblock %} {% block title %}About {{ SITE_NAME }} · EXIT Realty Realtor{% endblock %}
{% block og_title %}About {{ SITE_NAME }} · EXIT Realty Realtor{% endblock %} {% block og_title %}About {{ SITE_NAME }} · EXIT Realty Realtor{% endblock %}
{% block twitter_title %}About {{ SITE_NAME }} · EXIT Realty Realtor{% endblock %} {% block twitter_title %}About {{ SITE_NAME }} · EXIT Realty Realtor{% endblock %}
{% block meta_description %}Meet {{ SITE_NAME }}, a local EXIT Realty realtor helping families buy and sell homes with clear communication and marketing that reaches people. {{ CONTACT_SERVICE_AREA }}.{% endblock %} {% block meta_description %}Meet {{ SITE_NAME }}, your real estate partner in Chicago's western suburbs with EXIT Realty Redefined — buyers, sellers, and first-time homebuyers welcome. {{ CONTACT_SERVICE_AREA }}.{% endblock %}
{% block og_description %}Meet {{ SITE_NAME }}, a local EXIT Realty realtor helping families buy and sell homes with clear communication and marketing that reaches people. {{ CONTACT_SERVICE_AREA }}.{% endblock %} {% block og_description %}Meet {{ SITE_NAME }}, your real estate partner in Chicago's western suburbs with EXIT Realty Redefined — buyers, sellers, and first-time homebuyers welcome. {{ CONTACT_SERVICE_AREA }}.{% endblock %}
{% block twitter_description %}Meet {{ SITE_NAME }}, a local EXIT Realty realtor helping families buy and sell homes with clear communication and marketing that reaches people. {{ CONTACT_SERVICE_AREA }}.{% endblock %} {% block twitter_description %}Meet {{ SITE_NAME }}, your real estate partner in Chicago's western suburbs with EXIT Realty Redefined — buyers, sellers, and first-time homebuyers welcome. {{ CONTACT_SERVICE_AREA }}.{% endblock %}
{% block og_image %} {% block og_image %}
<meta property="og:image" content="{{ PUBLIC_SITE_URL }}{% static 'images/careers-1-570x388.jpg' %}"> <meta property="og:image" content="{{ PUBLIC_SITE_URL }}{% static 'images/monica-dhillon-headshot.jpg' %}">
<meta property="og:image:alt" content="{{ SITE_NAME }}"> <meta property="og:image:alt" content="Professional headshot of {{ SITE_NAME }}, Real Estate Agent">
{% endblock %} {% endblock %}
{% block structured_data %} {% block structured_data %}
<script type="application/ld+json"> <script type="application/ld+json">
@@ -17,13 +17,15 @@
"@type": "AboutPage", "@type": "AboutPage",
"name": "About {{ SITE_NAME|escapejs }}", "name": "About {{ SITE_NAME|escapejs }}",
"url": "{{ PUBLIC_SITE_URL }}{% url 'public:about' %}", "url": "{{ PUBLIC_SITE_URL }}{% url 'public:about' %}",
"description": "Meet {{ SITE_NAME|escapejs }}, a local EXIT Realty realtor helping families buy and sell homes with clear communication and marketing that reaches people.", "description": "Meet {{ SITE_NAME|escapejs }}, your real estate partner in Chicago's western suburbs with EXIT Realty Redefined.",
"mainEntity": { "mainEntity": {
"@type": "RealEstateAgent", "@type": "RealEstateAgent",
"name": "{{ SITE_NAME|escapejs }}", "name": "{{ SITE_NAME|escapejs }}",
"url": "{{ PUBLIC_SITE_URL }}/", "url": "{{ PUBLIC_SITE_URL }}/",
"image": "{{ PUBLIC_SITE_URL }}{% static 'images/monica-dhillon-headshot.jpg' %}",
"telephone": "{{ CONTACT_PHONE|escapejs }}", "telephone": "{{ CONTACT_PHONE|escapejs }}",
"email": "{{ CONTACT_EMAIL|escapejs }}", "email": "{{ CONTACT_EMAIL|escapejs }}",
{% if CONTACT_ADDRESS %}"address": "{{ CONTACT_ADDRESS|escapejs }}",{% endif %}
"areaServed": "{{ CONTACT_SERVICE_AREA|escapejs }}", "areaServed": "{{ CONTACT_SERVICE_AREA|escapejs }}",
"worksFor": { "worksFor": {
"@type": "Organization", "@type": "Organization",
@@ -52,13 +54,15 @@
<div class="container"> <div class="container">
<div class="row row-50 justify-content-center justify-content-lg-between flex-lg-row-reverse"> <div class="row row-50 justify-content-center justify-content-lg-between flex-lg-row-reverse">
<div class="col-md-10 col-lg-6 col-xl-5"> <div class="col-md-10 col-lg-6 col-xl-5">
<h2 class="text-uppercase">Local realtor,<br>personal service</h2> <h2>Meet Monica Dhillon — Your Real Estate Partner in the Western Suburbs</h2>
<p class="about-subtitle">I help families buy and sell homes with clear communication and marketing that actually reaches people — not just another postcard blast.</p> <p class="about-subtitle">Mandeep "Monica" Dhillon is a real estate agent serving Chicago's western suburbs, just forty minutes from downtown. At Exit Realty Redefined, Monica and her team bring years of hands-on experience helping buyers and sellers reach their goals — with a special passion for guiding first-time homebuyers through a process that can feel overwhelming, from mortgages to market values to all the fine print in between.</p>
<p>This site replaces a costly third-party campaign platform. Visitors reach me through a protected contact form; every inquiry lands in my portal with source tracking so I know which campaigns work.</p> <p>Family values aren't just a tagline here — they're how we treat every client who walks through our door.</p>
<p>Monica has been through the homebuying process herself, more than once, so she gets the nerves and the "wait, is this normal?" moments. Her experience spans both sides of the table — buyers, sellers, investors — and properties ranging from $200K starter homes to multi-million dollar deals.</p>
<p>Whatever your real estate goals, Monica and the Exit Realty team are ready to help you navigate the market's ups and downs. Questions? Don't be shy — she's always just a call or message away.</p>
<a class="button button-lg button-primary button-winona" href="{% url 'public:contact' %}" data-tianji-event="about_cta" data-tianji-event-destination="contact">Work with me</a> <a class="button button-lg button-primary button-winona" href="{% url 'public:contact' %}" data-tianji-event="about_cta" data-tianji-event-destination="contact">Work with me</a>
</div> </div>
<div class="col-md-10 col-lg-6 col-xl-6"> <div class="col-md-10 col-lg-6 col-xl-6">
<img class="img-responsive" src="{% static 'images/careers-1-570x388.jpg' %}" alt="{{ SITE_NAME }}, EXIT Realty realtor" width="570" height="388"/> <img class="img-responsive" src="{% static 'images/monica-dhillon-headshot.jpg' %}" alt="Professional headshot of Mandeep &quot;Monica&quot; Dhillon, Real Estate Agent, wearing a blue cardigan against a black background." width="816" height="1024"/>
</div> </div>
</div> </div>
</div> </div>
+4 -1
View File
@@ -20,6 +20,7 @@
"url": "{{ PUBLIC_SITE_URL }}/", "url": "{{ PUBLIC_SITE_URL }}/",
"telephone": "{{ CONTACT_PHONE|escapejs }}", "telephone": "{{ CONTACT_PHONE|escapejs }}",
"email": "{{ CONTACT_EMAIL|escapejs }}", "email": "{{ CONTACT_EMAIL|escapejs }}",
{% if CONTACT_ADDRESS %}"address": "{{ CONTACT_ADDRESS|escapejs }}",{% endif %}
"areaServed": "{{ CONTACT_SERVICE_AREA|escapejs }}", "areaServed": "{{ CONTACT_SERVICE_AREA|escapejs }}",
"contactPoint": { "contactPoint": {
"@type": "ContactPoint", "@type": "ContactPoint",
@@ -67,12 +68,14 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if CONTACT_ADDRESS or CONTACT_SERVICE_AREA %}
<div class="layout-bordered-item wow-outer"> <div class="layout-bordered-item wow-outer">
<div class="layout-bordered-item-inner wow slideInUp"> <div class="layout-bordered-item-inner wow slideInUp">
<div class="icon icon-lg mdi mdi-map-marker text-primary"></div> <div class="icon icon-lg mdi mdi-map-marker text-primary"></div>
<span class="link-default">{{ CONTACT_SERVICE_AREA }}</span> <span class="link-default">{% if CONTACT_ADDRESS %}{{ CONTACT_ADDRESS }}{% else %}{{ CONTACT_SERVICE_AREA }}{% endif %}</span>
</div> </div>
</div> </div>
{% endif %}
</div> </div>
</div> </div>
</section> </section>
+2 -1
View File
@@ -16,6 +16,7 @@
"image": "{{ PUBLIC_SITE_URL }}{% static 'images/slider-minimal-slide-1-1920x968.jpg' %}", "image": "{{ PUBLIC_SITE_URL }}{% static 'images/slider-minimal-slide-1-1920x968.jpg' %}",
"telephone": "{{ CONTACT_PHONE|escapejs }}", "telephone": "{{ CONTACT_PHONE|escapejs }}",
"email": "{{ CONTACT_EMAIL|escapejs }}", "email": "{{ CONTACT_EMAIL|escapejs }}",
{% if CONTACT_ADDRESS %}"address": "{{ CONTACT_ADDRESS|escapejs }}",{% endif %}
"description": "{{ SITE_NAME|escapejs }}, EXIT Realty — buying and selling homes with clear advice and multi-channel marketing. {{ CONTACT_SERVICE_AREA|escapejs }}.", "description": "{{ SITE_NAME|escapejs }}, EXIT Realty — buying and selling homes with clear advice and multi-channel marketing. {{ CONTACT_SERVICE_AREA|escapejs }}.",
"areaServed": "{{ CONTACT_SERVICE_AREA|escapejs }}", "areaServed": "{{ CONTACT_SERVICE_AREA|escapejs }}",
"worksFor": { "worksFor": {
@@ -35,7 +36,7 @@
<div class="title-docor-text font-weight-bold title-decorated text-uppercase wow slideInLeft text-white">{{ SITE_NAME }} · MKDRealtor.com</div> <div class="title-docor-text font-weight-bold title-decorated text-uppercase wow slideInLeft text-white">{{ SITE_NAME }} · MKDRealtor.com</div>
</div> </div>
<h1 class="text-uppercase text-white font-weight-bold wow-outer"><span class="wow slideInDown" data-wow-delay=".2s">Your Home,<br>Guided Well</span></h1> <h1 class="text-uppercase text-white font-weight-bold wow-outer"><span class="wow slideInDown" data-wow-delay=".2s">Your Home,<br>Guided Well</span></h1>
<p class="text-white wow-outer"><span class="wow slideInDown" data-wow-delay=".35s">EXIT Realty Redefined — buying or selling in the metro area with clear advice, strong marketing, and a team that answers the phone.</span></p> <p class="text-white wow-outer"><span class="wow slideInDown" data-wow-delay=".35s">EXIT Realty Redefined — buying or selling in Chicago's western suburbs with clear advice, strong marketing, and a team that answers the phone.</span></p>
<div class="wow-outer button-outer"> <div class="wow-outer button-outer">
<a class="button button-md button-primary button-winona wow slideInDown" href="{% url 'public:contact' %}" data-wow-delay=".4s" data-tianji-event="hero_cta" data-tianji-event-destination="contact">Start a conversation</a> <a class="button button-md button-primary button-winona wow slideInDown" href="{% url 'public:contact' %}" data-wow-delay=".4s" data-tianji-event="hero_cta" data-tianji-event-destination="contact">Start a conversation</a>
</div> </div>