Implement high priority features: filters, property type boxes, email routing

- Expanded search filters: MLS property types dropdown, MLS cities (50+ listings), zip code text input
- Property type showcase boxes: 5-category grid on homepage with icons, descriptions, counts
- Multi-recipient email: Primary to office@, CC to info@, sender confirmation receipt enabled
- Added helper functions: homeproz_get_mls_property_types(), homeproz_get_mls_cities()
- Updated FEATURES_PENDING with completion status

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Hanson.xyz Dev
2025-12-16 13:21:23 -06:00
parent ecdd75c307
commit 0487bd1dcf
13 changed files with 366 additions and 18 deletions
+3
View File
@@ -1,6 +1,7 @@
{"id":"html-117","title":"Filter MLS queries to MN and IA only","description":"","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-16T11:53:08.470255883-06:00","updated_at":"2025-12-16T12:45:04.808441903-06:00","closed_at":"2025-12-16T12:45:04.808441903-06:00","close_reason":"State filter implemented for all MLS query methods (get_properties, get_distinct_cities, get_distinct_counties, get_count, has_data, get_property_types, get_price_range). Added MLS_ALLOWED_STATES constant."}
{"id":"html-2fg","title":"Update office hours in footer","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-16T11:53:03.244743372-06:00","updated_at":"2025-12-16T11:54:52.108263115-06:00","closed_at":"2025-12-16T11:54:52.108263115-06:00","close_reason":"Updated office hours to Mon-Fri 9am-4pm, Sat/Sun By Appointment"}
{"id":"html-2fp","title":"Separate Residential and Commercial listings on homepage","description":"","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-30T02:24:09.984594683-06:00","updated_at":"2025-11-30T02:33:12.32537052-06:00","closed_at":"2025-11-30T02:33:12.32537052-06:00","close_reason":"Separated Featured Homes and Commercial/Land into distinct homepage sections"}
{"id":"html-2kf","title":"Multi-recipient email routing for contact form","description":"","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-16T13:11:50.988722809-06:00","updated_at":"2025-12-16T13:19:40.448106043-06:00","closed_at":"2025-12-16T13:19:40.448106043-06:00","close_reason":"Configured multi-recipient email: Primary mail to office@ with CC to info@. Enabled Mail 2 confirmation receipt to sender. For agent-specific routing, office staff forwards as needed."}
{"id":"html-3fb","title":"MLS by HansonXyz Plugin - Phase 5: Public API","description":"Query class with filter support, global helper functions (mls_get_properties, etc), integration hooks for themes","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-14T21:02:45.05131653-06:00","updated_at":"2025-12-14T21:21:46.940975819-06:00","closed_at":"2025-12-14T21:21:46.940975819-06:00","dependencies":[{"issue_id":"html-3fb","depends_on_id":"html-5j7","type":"blocks","created_at":"2025-12-14T21:04:05.308661828-06:00","created_by":"unknown"}]}
{"id":"html-3nq","title":"Enhance footer with office hours, professional logos, license numbers","description":"","status":"closed","priority":3,"issue_type":"task","created_at":"2025-11-30T02:24:30.889106857-06:00","updated_at":"2025-11-30T02:46:52.35661921-06:00","closed_at":"2025-11-30T02:46:52.35661921-06:00","close_reason":"Enhanced footer with office hours, professional logos (REALTOR, Equal Housing), and license number"}
{"id":"html-4q8","title":"MLS by HansonXyz Plugin - Phase 6: Admin Interface","description":"Settings page under Settings menu, API token configuration, sync status display, manual sync triggers","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-14T21:02:50.276941526-06:00","updated_at":"2025-12-14T21:21:56.543615901-06:00","closed_at":"2025-12-14T21:21:56.543615901-06:00","dependencies":[{"issue_id":"html-4q8","depends_on_id":"html-3fb","type":"blocks","created_at":"2025-12-14T21:04:10.388690618-06:00","created_by":"unknown"}]}
@@ -9,6 +10,7 @@
{"id":"html-5j7","title":"MLS by HansonXyz Plugin - Phase 3: Sync Engine","description":"Sync Engine class, full sync with pagination, incremental sync with ModificationTimestamp, sync state tracking for resume, CLI sync commands","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-14T21:02:34.581442915-06:00","updated_at":"2025-12-14T21:21:46.933578124-06:00","closed_at":"2025-12-14T21:21:46.933578124-06:00","dependencies":[{"issue_id":"html-5j7","depends_on_id":"html-4za","type":"blocks","created_at":"2025-12-14T21:03:55.163778736-06:00","created_by":"unknown"}]}
{"id":"html-76m","title":"Remove Bridge Realty text from site","description":"","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-16T11:52:42.455604014-06:00","updated_at":"2025-12-16T11:53:51.821637756-06:00","closed_at":"2025-12-16T11:53:51.821637756-06:00","close_reason":"Removed Bridge Realty text from page-about.php, replaced with correct LandProz DBA info"}
{"id":"html-7jz","title":"Add Communities section to navigation and create community pages structure","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-30T02:24:15.204568226-06:00","updated_at":"2025-11-30T02:43:22.075934867-06:00","closed_at":"2025-11-30T02:43:22.075934867-06:00","close_reason":"Created Communities landing page, community page template, 3 community pages, and added to navigation"}
{"id":"html-7lr","title":"Property type showcase boxes on homepage","description":"","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-16T13:11:45.764664597-06:00","updated_at":"2025-12-16T13:18:27.291310612-06:00","closed_at":"2025-12-16T13:18:27.291310612-06:00","close_reason":"Created property type showcase boxes section on homepage with 5 categories: Residential, Land, Commercial, Farm, Multi-Family. Each box shows icon, description, count, and links to filtered property archive."}
{"id":"html-98b","title":"Add location search dropdown to homepage hero","description":"","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-30T02:23:59.555310037-06:00","updated_at":"2025-11-30T02:30:59.92891882-06:00","closed_at":"2025-11-30T02:30:59.92891882-06:00","close_reason":"Added location search dropdown to hero section with community taxonomy"}
{"id":"html-bfd","title":"Update DESIGN-DOCUMENT.md and IMPLEMENTATION-PLAN.md with RHR structural changes","description":"","status":"closed","priority":0,"issue_type":"task","created_at":"2025-11-30T02:24:40.504170573-06:00","updated_at":"2025-11-30T02:28:50.551587345-06:00","closed_at":"2025-11-30T02:28:50.551587345-06:00","close_reason":"Updated DESIGN-DOCUMENT.md and IMPLEMENTATION-PLAN.md with RHR structural changes"}
{"id":"html-clv","title":"Analyze Robert Hoffman Realty site structure for HomeProz redesign","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-30T02:11:43.511290155-06:00","updated_at":"2025-11-30T02:21:47.665340956-06:00","closed_at":"2025-11-30T02:21:47.665340956-06:00","close_reason":"Completed site analysis comparing RHR to HomeProz design"}
@@ -21,4 +23,5 @@
{"id":"html-sbh","title":"MLS by HansonXyz Plugin - Phase 4: Media Handler","description":"Media Handler class, download and organize media files, PhotosChangeTimestamp detection, CLI media commands","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-14T21:02:39.793324508-06:00","updated_at":"2025-12-14T21:21:46.94012466-06:00","closed_at":"2025-12-14T21:21:46.94012466-06:00","dependencies":[{"issue_id":"html-sbh","depends_on_id":"html-5j7","type":"blocks","created_at":"2025-12-14T21:04:00.24472923-06:00","created_by":"unknown"}]}
{"id":"html-t8u","title":"Add Resources section to navigation and create resource pages","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-30T02:24:25.662824938-06:00","updated_at":"2025-11-30T02:45:31.13972652-06:00","closed_at":"2025-11-30T02:45:31.13972652-06:00","close_reason":"Created Resources landing page, resource page template, Buyer's Guide, Seller's Guide, and added to navigation"}
{"id":"html-x03","title":"MLS by HansonXyz Plugin - Phase 7: Cron \u0026 Automation","description":"WP Cron scheduling (configurable interval), standalone cron script for Unix cron","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-14T21:02:55.483012095-06:00","updated_at":"2025-12-14T21:22:06.519310981-06:00","closed_at":"2025-12-14T21:22:06.519310981-06:00","dependencies":[{"issue_id":"html-x03","depends_on_id":"html-4q8","type":"blocks","created_at":"2025-12-14T21:04:15.476790917-06:00","created_by":"unknown"}]}
{"id":"html-xub","title":"Expanded search filters - property type and zip code","description":"","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-16T13:11:40.568406605-06:00","updated_at":"2025-12-16T13:16:29.492622442-06:00","closed_at":"2025-12-16T13:16:29.492622442-06:00","close_reason":"Implemented expanded search filters: MLS property types dropdown (Residential, Land, Commercial Sale, Residential Lease, Residential Income, Farm), city dropdown (107 cities with 50+ listings), and zip code text input field"}
{"id":"html-yxd","title":"Update broker footer legal text","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-16T11:52:52.82531755-06:00","updated_at":"2025-12-16T11:54:46.914199743-06:00","closed_at":"2025-12-16T11:54:46.914199743-06:00","close_reason":"Updated broker footer to 'HomeProz Real Estate LLC DBA LandProz Real Estate, LLC'"}
+3 -3
View File
@@ -747,9 +747,9 @@ Also: "Broker Brian Haugen - MN | Broker/Auctioneer Greg Jensen - MN, IA - 24-21
### High Priority (Implement)
1. Co-listing agent support (1.1)
2. Service area MN + IA (5.3) - COMPLETED 2025-12-16: Updated footer tagline to "Minnesota and Iowa"
3. Expanded search filters (1.5)
4. Property type showcase boxes (1.6)
5. Multi-recipient email routing (4.2)
3. Expanded search filters (1.5) - COMPLETED 2025-12-16: MLS property types, MLS cities (50+ listings), zip code input
4. Property type showcase boxes (1.6) - COMPLETED 2025-12-16: 5-box grid on homepage (Residential, Land, Commercial, Farm, Multi-Family)
5. Multi-recipient email routing (4.2) - COMPLETED 2025-12-16: Primary to office@, CC to info@, sender confirmation receipt
6. Agent page redesign (3.1)
7. Geolocation search (1.4)
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -110,6 +110,9 @@ $featured_commercial = new WP_Query(array(
<?php
// Service Cards Section (Buy/Rent/Sell)
get_template_part('template-parts/components/service-cards');
// Property Type Showcase Boxes
get_template_part('template-parts/components/property-type-boxes');
?>
<!-- Featured Homes Section (MLS Listings) -->
@@ -28,6 +28,7 @@ function homeproz_ajax_filter_properties() {
$property_type = isset($_POST['property_type']) ? sanitize_text_field($_POST['property_type']) : '';
$property_status = isset($_POST['property_status']) ? sanitize_text_field($_POST['property_status']) : '';
$property_location = isset($_POST['property_location']) ? sanitize_text_field($_POST['property_location']) : '';
$zip = isset($_POST['zip']) ? sanitize_text_field($_POST['zip']) : '';
$min_price = isset($_POST['min_price']) ? intval($_POST['min_price']) : '';
$max_price = isset($_POST['max_price']) ? intval($_POST['max_price']) : '';
$beds = isset($_POST['beds']) ? intval($_POST['beds']) : '';
@@ -60,6 +61,9 @@ function homeproz_ajax_filter_properties() {
if ($property_location) {
$filter_args['city'] = $property_location;
}
if ($zip) {
$filter_args['postal_code'] = $zip;
}
if ($min_price) {
$filter_args['min_price'] = $min_price;
}
@@ -151,6 +155,7 @@ function homeproz_ajax_filter_properties() {
$filter_args = array();
if ($property_type) $filter_args['property_type'] = $property_type;
if ($property_location) $filter_args['property_location'] = $property_location;
if ($zip) $filter_args['zip'] = $zip;
if ($min_price) $filter_args['min_price'] = $min_price;
if ($max_price) $filter_args['max_price'] = $max_price;
if ($beds) $filter_args['beds'] = $beds;
@@ -388,6 +388,71 @@ function homeproz_format_mls_listing_for_json($listing, $is_homeproz = false) {
);
}
/**
* Get MLS property types for filter dropdowns
*
* Returns property types with listing counts from MLS database.
* Filters to only show types with active listings.
*
* @return array Array of objects with property_type and count
*/
function homeproz_get_mls_property_types() {
if (!function_exists('mls_plugin')) {
return array();
}
$plugin = mls_plugin();
$query = $plugin->get_query();
if (!$query) {
return array();
}
return $query->get_property_types('Active');
}
/**
* Get MLS cities for filter dropdowns
*
* Returns distinct cities from MLS database with active listings.
* Limited to cities with significant listing counts for usability.
*
* @param int $min_listings Minimum listings to include city (default 5)
* @return array Array of city names
*/
function homeproz_get_mls_cities($min_listings = 5) {
global $wpdb;
if (!function_exists('mls_plugin')) {
return array();
}
$plugin = mls_plugin();
$db = $plugin->get_db();
if (!$db) {
return array();
}
$table = $db->properties_table();
// Get cities with at least $min_listings active properties
// Also filter to MN and IA only
$cities = $wpdb->get_col($wpdb->prepare(
"SELECT city FROM {$table}
WHERE mlg_can_view = 1
AND standard_status = 'Active'
AND city IS NOT NULL
AND state_or_province IN ('MN', 'IA')
GROUP BY city
HAVING COUNT(*) >= %d
ORDER BY city ASC",
$min_listings
));
return $cities ?: array();
}
/**
* Get property locations that have active or pending properties
*
+1
View File
@@ -37,6 +37,7 @@
@import '../template-parts/components/testimonial.scss';
@import '../template-parts/components/feature-block.scss';
@import '../template-parts/components/service-cards.scss';
@import '../template-parts/components/property-type-boxes.scss';
// ============================================
// CSS Custom Properties (Design Tokens)
@@ -0,0 +1,92 @@
<?php
/**
* Property Type Showcase Boxes
*
* Displays category boxes for browsing properties by type.
* Links to the property archive with type filter applied.
*
* @package HomeProz
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Get property types with counts from MLS
$property_types = homeproz_get_mls_property_types();
if (empty($property_types)) {
return;
}
// Define display configuration for each type
// Icons use Feather Icons (already in use throughout the site)
$type_config = array(
'Residential' => array(
'icon' => '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>',
'description' => 'Single family homes, condos & townhomes',
),
'Land' => array(
'icon' => '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 22h20"/><path d="M6.36 17.4L4 22h16l-4.24-7.94a.5.5 0 0 0-.88 0L12.5 18.1l-2.5-4.5a.5.5 0 0 0-.88 0z"/><circle cx="13.5" cy="5.5" r="2.5"/></svg>',
'description' => 'Vacant lots & acreages',
),
'Commercial Sale' => array(
'icon' => '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="4" y="2" width="16" height="20" rx="2" ry="2"/><path d="M9 22v-4h6v4"/><path d="M8 6h.01M12 6h.01M16 6h.01M8 10h.01M12 10h.01M16 10h.01M8 14h.01M12 14h.01M16 14h.01"/></svg>',
'label' => 'Commercial',
'description' => 'Office, retail & industrial',
),
'Farm' => array(
'icon' => '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 21h18"/><path d="M5 21V7l7-4 7 4v14"/><path d="M9 21v-6h6v6"/><path d="M10 9h4"/></svg>',
'description' => 'Agricultural & hobby farms',
),
'Residential Income' => array(
'icon' => '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 12h6"/><path d="M9 16h6"/></svg>',
'label' => 'Multi-Family',
'description' => 'Duplexes, triplexes & apartments',
),
);
// Filter to only show configured types and sort by count
$display_types = array();
foreach ($property_types as $type) {
if (isset($type_config[$type->property_type])) {
$display_types[] = $type;
}
}
?>
<section class="property-type-boxes">
<div class="container">
<header class="section-header">
<h2 class="section-title">Browse by Property Type</h2>
<p class="section-subtitle">Find the perfect property for your needs</p>
</header>
<div class="type-boxes-grid">
<?php foreach ($display_types as $type) :
$type_name = $type->property_type;
$config = $type_config[$type_name];
$label = isset($config['label']) ? $config['label'] : $type_name;
$count = number_format($type->count);
$url = add_query_arg('property_type', urlencode($type_name), home_url('/properties/'));
?>
<a href="<?php echo esc_url($url); ?>" class="type-box">
<div class="type-box-icon">
<?php echo $config['icon']; ?>
</div>
<div class="type-box-content">
<h3 class="type-box-title"><?php echo esc_html($label); ?></h3>
<p class="type-box-description"><?php echo esc_html($config['description']); ?></p>
<span class="type-box-count"><?php echo esc_html($count); ?> listings</span>
</div>
<div class="type-box-arrow">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</div>
</a>
<?php endforeach; ?>
</div>
</div>
</section>
@@ -0,0 +1,133 @@
/**
* Property Type Boxes Styles
*
* Grid of clickable category boxes for browsing by property type.
*
* @package HomeProz
*/
.property-type-boxes {
padding: 4rem 0;
background-color: var(--color-bg-card);
@media (max-width: 768px) {
padding: 3rem 0;
}
}
.type-boxes-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 1.5rem;
@media (max-width: 1200px) {
grid-template-columns: repeat(3, 1fr);
}
@media (max-width: 768px) {
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
@media (max-width: 480px) {
grid-template-columns: 1fr;
}
}
.type-box {
display: flex;
flex-direction: column;
padding: 1.5rem;
background-color: var(--color-bg-dark);
border: 1px solid var(--color-border);
border-radius: 0.5rem;
text-decoration: none;
transition: all 0.2s ease;
&:hover {
border-color: var(--color-accent);
transform: translateY(-2px);
.type-box-icon {
color: var(--color-accent);
}
.type-box-arrow {
opacity: 1;
transform: translateX(0);
}
}
@media (max-width: 768px) {
padding: 1.25rem;
}
}
.type-box-icon {
color: var(--color-text-muted);
margin-bottom: 1rem;
transition: color 0.2s ease;
svg {
display: block;
}
@media (max-width: 768px) {
margin-bottom: 0.75rem;
svg {
width: 28px;
height: 28px;
}
}
}
.type-box-content {
flex: 1;
}
.type-box-title {
font-family: var(--font-display);
font-size: 1.125rem;
font-weight: 600;
color: var(--color-text);
margin-bottom: 0.25rem;
@media (max-width: 768px) {
font-size: 1rem;
}
}
.type-box-description {
font-size: 0.8125rem;
color: var(--color-text-muted);
line-height: 1.4;
margin-bottom: 0.75rem;
@media (max-width: 768px) {
font-size: 0.75rem;
margin-bottom: 0.5rem;
}
}
.type-box-count {
display: inline-block;
font-size: 0.75rem;
font-weight: 500;
color: var(--color-accent-light);
text-transform: uppercase;
letter-spacing: 0.03em;
}
.type-box-arrow {
align-self: flex-end;
color: var(--color-accent);
opacity: 0;
transform: translateX(-8px);
transition: all 0.2s ease;
margin-top: 0.75rem;
@media (max-width: 768px) {
display: none;
}
}
@@ -599,6 +599,15 @@
}
});
// Also handle text inputs (like zip)
this.$form.find('input[type="text"]').each(function() {
var name = $(this).attr('name');
if (params.has(name)) {
$(this).val(params.get(name));
hasFilters = true;
}
});
// Check for page in hash
var page = this.getPageFromHash();
@@ -664,6 +673,7 @@
nonce: homeprozAjax.nonce,
property_type: formData.property_type,
property_location: formData.property_location,
zip: formData.zip,
min_price: formData.min_price,
max_price: formData.max_price,
beds: formData.beds,
@@ -737,6 +747,7 @@
return {
property_type: this.$form.find('[name="property_type"]').val() || '',
property_location: this.$form.find('[name="property_location"]').val() || '',
zip: this.$form.find('[name="zip"]').val() || '',
min_price: this.$form.find('[name="min_price"]').val() || '',
max_price: this.$form.find('[name="max_price"]').val() || '',
beds: this.$form.find('[name="beds"]').val() || ''
@@ -1239,6 +1250,7 @@
nonce: homeprozAjax.nonce,
property_type: formData.property_type,
property_location: formData.property_location,
zip: formData.zip,
min_price: formData.min_price,
max_price: formData.max_price,
beds: formData.beds,
@@ -2,6 +2,8 @@
/**
* Property Filters Template Part
*
* Uses MLS database for property types and cities.
*
* @package HomeProz
*/
@@ -12,17 +14,15 @@ if (!defined('ABSPATH')) {
// Get current filter values from URL
$current_type = isset($_GET['property_type']) ? sanitize_text_field($_GET['property_type']) : '';
$current_status = isset($_GET['property_status']) ? sanitize_text_field($_GET['property_status']) : '';
$current_location = isset($_GET['property_location']) ? sanitize_text_field($_GET['property_location']) : '';
$current_zip = isset($_GET['zip']) ? sanitize_text_field($_GET['zip']) : '';
$current_min_price = isset($_GET['min_price']) ? intval($_GET['min_price']) : '';
$current_max_price = isset($_GET['max_price']) ? intval($_GET['max_price']) : '';
$current_beds = isset($_GET['beds']) ? intval($_GET['beds']) : '';
$current_sort = isset($_GET['sort']) ? sanitize_text_field($_GET['sort']) : 'newest';
// Get taxonomy terms
$property_types = get_terms(array('taxonomy' => 'property_type', 'hide_empty' => false));
$property_statuses = get_terms(array('taxonomy' => 'property_status', 'hide_empty' => false));
$property_locations = homeproz_get_active_locations();
// Get MLS property types and cities
$property_types = homeproz_get_mls_property_types();
$mls_cities = homeproz_get_mls_cities(50); // Cities with 50+ listings
?>
<div class="property-filters" id="property-filters" data-ajax-url="<?php echo esc_url(admin_url('admin-ajax.php')); ?>">
@@ -33,25 +33,30 @@ $property_locations = homeproz_get_active_locations();
<select name="property_type" id="filter-type" class="filter-select">
<option value="">All Types</option>
<?php foreach ($property_types as $type) : ?>
<option value="<?php echo esc_attr($type->slug); ?>" <?php selected($current_type, $type->slug); ?>>
<?php echo esc_html($type->name); ?>
<option value="<?php echo esc_attr($type->property_type); ?>" <?php selected($current_type, $type->property_type); ?>>
<?php echo esc_html($type->property_type); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="filter-item">
<label for="filter-location" class="filter-label">Location</label>
<label for="filter-location" class="filter-label">City</label>
<select name="property_location" id="filter-location" class="filter-select">
<option value="">All</option>
<?php foreach ($property_locations as $location) : ?>
<option value="<?php echo esc_attr($location->slug); ?>" <?php selected($current_location, $location->slug); ?>>
<?php echo esc_html($location->name); ?>
<option value="">All Cities</option>
<?php foreach ($mls_cities as $city) : ?>
<option value="<?php echo esc_attr($city); ?>" <?php selected($current_location, $city); ?>>
<?php echo esc_html($city); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="filter-item filter-item-zip">
<label for="filter-zip" class="filter-label">Zip Code</label>
<input type="text" name="zip" id="filter-zip" class="filter-input" placeholder="e.g. 55401" value="<?php echo esc_attr($current_zip); ?>" maxlength="10" pattern="[0-9\-]*">
</div>
<div class="filter-item">
<label for="filter-beds" class="filter-label">Beds</label>
<select name="beds" id="filter-beds" class="filter-select">
@@ -485,6 +485,35 @@
}
}
.filter-input {
width: 100%;
padding: 0.625rem 0.75rem;
background-color: var(--color-bg-dark);
border: 1px solid var(--color-border);
border-radius: 0.25rem;
color: var(--color-text);
font-size: 0.9375rem;
&::placeholder {
color: var(--color-text-muted);
opacity: 0.7;
}
&:focus {
outline: none;
border-color: var(--color-accent);
}
}
// Zip code filter - narrower width
.filter-item-zip {
max-width: 120px;
@media (max-width: 768px) {
max-width: none;
}
}
// Loading State
.property-filters.is-loading {