9e9788ea22
- add show price preference(store in cookies)
255 lines
13 KiB
HTML
255 lines
13 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}List a Property - NexHome{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="max-w-3xl mx-auto px-4 py-10">
|
|
<h1 class="text-3xl font-bold text-primary mb-2">List a New Property</h1>
|
|
<p class="text-gray-500 mb-8">Fill in the details below to create your listing.</p>
|
|
|
|
{% if error %}
|
|
<div class="bg-red-100 border border-red-200 text-red-800 rounded-lg p-4 mb-6 text-sm">
|
|
{{ error }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<form method="POST" action="/properties/new" enctype="multipart/form-data" class="bg-white rounded-2xl shadow-lg p-8 space-y-6">
|
|
<!-- Title -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Property Title *</label>
|
|
<input type="text" name="title" required
|
|
value="{{ values.title|default('') }}"
|
|
placeholder="e.g. Beautiful Family Home in Austin"
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-accent outline-none transition">
|
|
</div>
|
|
|
|
<!-- Description -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Description *</label>
|
|
<textarea name="description" required rows="5"
|
|
placeholder="Describe your property..."
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-accent outline-none transition">{{ values.description|default('') }}</textarea>
|
|
</div>
|
|
|
|
<!-- Price & Type -->
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Price (USD) *</label>
|
|
<!-- Input mode toggle -->
|
|
<div class="flex gap-1 mb-2 bg-gray-100 rounded-lg p-0.5 text-xs">
|
|
<button type="button" id="priceModeTotal" onclick="setPriceMode('total')"
|
|
class="flex-1 py-1.5 rounded-md font-medium transition bg-white shadow text-primary">
|
|
Total Price
|
|
</button>
|
|
<button type="button" id="priceModeUnit" onclick="setPriceMode('unit')"
|
|
class="flex-1 py-1.5 rounded-md font-medium transition text-gray-500">
|
|
Per Sqft
|
|
</button>
|
|
</div>
|
|
<!-- Total price input (default visible) -->
|
|
<div class="relative" id="totalPriceGroup">
|
|
<span class="absolute left-3 top-3 text-gray-500 font-medium">$</span>
|
|
<input type="number" id="totalPriceInput" name="price" required min="0"
|
|
value="{{ values.price|default('') }}"
|
|
placeholder="450,000"
|
|
class="w-full pl-8 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-accent outline-none transition">
|
|
</div>
|
|
<!-- Unit price input (hidden by default) -->
|
|
<div class="relative hidden" id="unitPriceGroup">
|
|
<span class="absolute left-3 top-3 text-gray-500 font-medium">$</span>
|
|
<input type="number" id="unitPriceInput" min="0" step="0.01"
|
|
placeholder="225"
|
|
class="w-full pl-8 pr-12 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-accent outline-none transition">
|
|
<span class="absolute right-3 top-3 text-gray-400 text-sm">/sqft</span>
|
|
</div>
|
|
<!-- Reference total when in unit mode -->
|
|
<p class="text-xs text-gray-400 mt-1 hidden" id="priceRef">
|
|
Total: <span id="priceRefValue">$0</span>
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Property Type *</label>
|
|
<select name="property_type" required
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-accent outline-none transition">
|
|
<option value="">Select type</option>
|
|
<option value="house" {% if values.property_type == 'house' %}selected{% endif %}>House</option>
|
|
<option value="condo" {% if values.property_type == 'condo' %}selected{% endif %}>Condo</option>
|
|
<option value="townhouse" {% if values.property_type == 'townhouse' %}selected{% endif %}>Townhouse</option>
|
|
<option value="land" {% if values.property_type == 'land' %}selected{% endif %}>Land</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Beds / Baths / Sqft -->
|
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Bedrooms *</label>
|
|
<input type="number" name="bedrooms" required min="0" max="20"
|
|
value="{{ values.bedrooms|default('0') }}"
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-accent outline-none transition">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Bathrooms *</label>
|
|
<input type="number" name="bathrooms" required min="0" max="20" step="0.5"
|
|
value="{{ values.bathrooms|default('0') }}"
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-accent outline-none transition">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Area (sqft) *</label>
|
|
<input type="number" id="areaSqftInput" name="area_sqft" required min="0"
|
|
value="{{ values.area_sqft|default('') }}"
|
|
placeholder="2000"
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-accent outline-none transition">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Address -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Street Address *</label>
|
|
<input type="text" name="address" required
|
|
value="{{ values.address|default('') }}"
|
|
placeholder="123 Main Street"
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-accent outline-none transition">
|
|
</div>
|
|
|
|
<!-- City / State / Zip -->
|
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">City *</label>
|
|
<input type="text" name="city" required
|
|
value="{{ values.city|default('') }}"
|
|
placeholder="Austin"
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-accent outline-none transition">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">State *</label>
|
|
<input type="text" name="state" required maxlength="2"
|
|
value="{{ values.state|default('') }}"
|
|
placeholder="TX"
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-accent outline-none transition">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Zip Code *</label>
|
|
<input type="text" name="zip_code" required maxlength="10"
|
|
value="{{ values.zip_code|default('') }}"
|
|
placeholder="78701"
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-accent outline-none transition">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Year Built -->
|
|
<div class="sm:w-1/2">
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Year Built</label>
|
|
<input type="number" name="year_built" min="1800" max="2030"
|
|
value="{{ values.year_built|default('') }}"
|
|
placeholder="e.g. 2005"
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-accent outline-none transition">
|
|
</div>
|
|
|
|
<!-- Contact Info -->
|
|
<div class="border-t pt-6">
|
|
<h3 class="text-lg font-semibold text-primary mb-4">Contact Information</h3>
|
|
<p class="text-sm text-gray-500 mb-4">Provide at least one contact method (email or phone).</p>
|
|
|
|
<div class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Contact Email</label>
|
|
<input type="email" name="contact_email"
|
|
value="{{ values.contact_email|default('') }}"
|
|
placeholder="seller@example.com"
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-accent outline-none transition">
|
|
</div>
|
|
|
|
<div class="relative">
|
|
{% set phone_value = values.contact_phone|default("") %}
|
|
{% include "components/phone_input.html" %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Image Upload -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Property Image</label>
|
|
<div class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-accent transition">
|
|
<svg class="w-10 h-10 mx-auto text-gray-400 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
|
|
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
|
</svg>
|
|
<p class="text-sm text-gray-500 mb-2">Click or drag to upload an image</p>
|
|
<input type="file" name="image" accept="image/*"
|
|
class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-accent file:text-white hover:file:bg-blue-700 transition">
|
|
<p class="text-xs text-gray-400 mt-2">JPG, PNG or WebP. Max 5MB.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Submit -->
|
|
<div class="flex gap-4 pt-4">
|
|
<button type="submit"
|
|
class="flex-1 bg-accent hover:bg-blue-700 text-white font-semibold py-3.5 rounded-lg transition">
|
|
Publish Listing
|
|
</button>
|
|
<a href="/"
|
|
class="px-8 py-3.5 border border-gray-300 text-gray-700 font-medium rounded-lg hover:bg-gray-50 transition">
|
|
Cancel
|
|
</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
(function() {
|
|
var priceMode = 'total';
|
|
|
|
window.setPriceMode = function(mode) {
|
|
priceMode = mode;
|
|
var totalGroup = document.getElementById('totalPriceGroup');
|
|
var unitGroup = document.getElementById('unitPriceGroup');
|
|
var totalBtn = document.getElementById('priceModeTotal');
|
|
var unitBtn = document.getElementById('priceModeUnit');
|
|
var ref = document.getElementById('priceRef');
|
|
var priceInput = document.getElementById('totalPriceInput');
|
|
|
|
if (mode === 'total') {
|
|
totalGroup.classList.remove('hidden');
|
|
unitGroup.classList.add('hidden');
|
|
ref.classList.add('hidden');
|
|
totalBtn.classList.add('bg-white', 'shadow', 'text-primary');
|
|
totalBtn.classList.remove('text-gray-500');
|
|
unitBtn.classList.remove('bg-white', 'shadow', 'text-primary');
|
|
unitBtn.classList.add('text-gray-500');
|
|
} else {
|
|
totalGroup.classList.add('hidden');
|
|
unitGroup.classList.remove('hidden');
|
|
ref.classList.remove('hidden');
|
|
unitBtn.classList.add('bg-white', 'shadow', 'text-primary');
|
|
unitBtn.classList.remove('text-gray-500');
|
|
totalBtn.classList.remove('bg-white', 'shadow', 'text-primary');
|
|
totalBtn.classList.add('text-gray-500');
|
|
updateTotalFromUnit();
|
|
}
|
|
};
|
|
|
|
function getArea() {
|
|
return parseInt(document.getElementById('areaSqftInput').value) || 0;
|
|
}
|
|
|
|
function updateTotalFromUnit() {
|
|
var unitVal = parseFloat(document.getElementById('unitPriceInput').value) || 0;
|
|
var area = getArea();
|
|
var total = Math.round(unitVal * area);
|
|
document.getElementById('totalPriceInput').value = total || '';
|
|
document.getElementById('priceRefValue').textContent = '$' + total.toLocaleString();
|
|
}
|
|
|
|
document.getElementById('unitPriceInput').addEventListener('input', updateTotalFromUnit);
|
|
document.getElementById('areaSqftInput').addEventListener('input', function() {
|
|
if (priceMode === 'unit') updateTotalFromUnit();
|
|
});
|
|
|
|
// Ensure total price is synced before form submission
|
|
document.querySelector('form').addEventListener('submit', function() {
|
|
if (priceMode === 'unit') updateTotalFromUnit();
|
|
});
|
|
})();
|
|
</script>
|
|
{% endblock %}
|