Property_Listingindex.html19 matches
64}
65
66.property-image {
67height: 200px;
68background-size: cover;
120
121/* Property detail page */
122.property-detail-image {
123height: 400px;
124background-size: cover;
308${properties.length > 0 ? properties.map(property => `
309<div class="property-card">
310<div class="property-image" style="background-image: url('${property.images && property.images.length > 0 ? property.images[0].image_url : 'https://via.placeholder.com/400x300?text=No+Image'}')"></div>
311<div class="p-4">
312<div class="flex justify-between items-start mb-2">
372const propertiesGrid = document.getElementById('properties-grid');
373
374// Fetch all properties with images
375const response = await fetch(`${API_BASE_URL}/properties/with-images`);
376const result = await response.json();
377
385propertiesGrid.innerHTML = properties.length > 0 ? properties.map(property => `
386<div class="property-card">
387<div class="property-image" style="background-image: url('${property.images && property.images.length > 0 ? property.images[0].image_url : 'https://via.placeholder.com/400x300?text=No+Image'}')"></div>
388<div class="p-4">
389<div class="flex justify-between items-start mb-2">
457propertiesGrid.innerHTML = properties.length > 0 ? properties.map(property => `
458<div class="property-card">
459<div class="property-image" style="background-image: url('${property.images && property.images.length > 0 ? property.images[0].image_url : 'https://via.placeholder.com/400x300?text=No+Image'}')"></div>
460<div class="p-4">
461<div class="flex justify-between items-start mb-2">
494try {
495// Fetch property details
496const response = await fetch(`${API_BASE_URL}/properties/${propertyId}/with-images`);
497const result = await response.json();
498
507}
508
509// Get primary image or first image
510const primaryImage = property.images.find(img => img.is_primary) ||
511(property.images.length > 0 ? property.images[0] : null);
512
513// Render property detail page
520
521<div class="bg-white rounded-lg shadow-md overflow-hidden mb-8">
522<div class="property-detail-image" id="main-image" style="background-image: url('${primaryImage ? primaryImage.image_url : 'https://via.placeholder.com/800x600?text=No+Image'}')"></div>
523
524${property.images.length > 1 ? `
525<div class="p-4 bg-gray-100 flex space-x-2 overflow-x-auto">
526${property.images.map((image, index) => `
527<div class="thumbnail ${primaryImage && primaryImage.id === image.id ? 'active' : ''}"
528style="background-image: url('${image.image_url}')"
529data-image="${image.image_url}"
530onclick="document.getElementById('main-image').style.backgroundImage = 'url(${image.image_url})';
531document.querySelectorAll('.thumbnail').forEach(t => t.classList.remove('active'));
532this.classList.add('active')"></div>
626similarPropertiesContainer.innerHTML = similarProperties.length > 0 ? similarProperties.map(property => `
627<div class="property-card">
628<div class="property-image" style="background-image: url('${property.images && property.images.length > 0 ? property.images[0].image_url : 'https://via.placeholder.com/400x300?text=No+Image'}')"></div>
629<div class="p-4">
630<h3 class="text-lg font-bold mb-2">${property.title}</h3>
Property_Listingadmin.ts18 matches
4updateProperty,
5deleteProperty,
6addPropertyImage,
7deletePropertyImage,
8verifyAdminCredentials
9} from "../database/queries.ts";
193});
194195// Add property image
196app.post("/properties/:id/images", async (c) => {
197try {
198const id = parseInt(c.req.param("id"));
205}
206
207const { imageUrl, isPrimary } = await c.req.json();
208
209if (!imageUrl) {
210return c.json<ApiResponse<any>>({
211success: false,
212error: "Image URL is required"
213}, 400);
214}
215
216const imageId = await addPropertyImage(id, imageUrl, isPrimary);
217
218return c.json<ApiResponse<{ id: number }>>({
219success: true,
220data: { id: imageId }
221}, 201);
222} catch (error) {
223console.error("Error adding property image:", error);
224return c.json<ApiResponse<any>>({
225success: false,
226error: "Failed to add property image"
227}, 500);
228}
229});
230231// Delete property image
232app.delete("/images/:id", async (c) => {
233try {
234const id = parseInt(c.req.param("id"));
237return c.json<ApiResponse<any>>({
238success: false,
239error: "Invalid image ID"
240}, 400);
241}
242
243const deleted = await deletePropertyImage(id);
244
245if (!deleted) {
246return c.json<ApiResponse<any>>({
247success: false,
248error: "Image not found"
249}, 404);
250}
255});
256} catch (error) {
257console.error("Error deleting property image:", error);
258return c.json<ApiResponse<any>>({
259success: false,
260error: "Failed to delete property image"
261}, 500);
262}
Property_Listingproperties.ts15 matches
5getPropertiesByCategory,
6searchProperties,
7getPropertiesWithImages,
8getPropertyWithImages
9} from "../database/queries.ts";
10import { ApiResponse, Property, PropertyWithImages } from "../../shared/types.ts";
1112const app = new Hono();
29});
3031// Get all properties with images
32app.get("/with-images", async (c) => {
33try {
34const properties = await getPropertiesWithImages();
35return c.json<ApiResponse<PropertyWithImages[]>>({
36success: true,
37data: properties
38});
39} catch (error) {
40console.error("Error fetching properties with images:", error);
41return c.json<ApiResponse<any>>({
42success: false,
43error: "Failed to fetch properties with images"
44}, 500);
45}
80});
8182// Get property by ID with images
83app.get("/:id/with-images", async (c) => {
84try {
85const id = parseInt(c.req.param("id"));
92}
93
94const property = await getPropertyWithImages(id);
95
96if (!property) {
101}
102
103return c.json<ApiResponse<PropertyWithImages>>({
104success: true,
105data: property
106});
107} catch (error) {
108console.error("Error fetching property with images:", error);
109return c.json<ApiResponse<any>>({
110success: false,
111error: "Failed to fetch property with images"
112}, 500);
113}
Property_Listingtypes.ts4 matches
15}
1617export interface PropertyImage {
18id: number;
19property_id: number;
20image_url: string;
21is_primary: boolean;
22created_at: string;
23}
2425export interface PropertyWithImages extends Property {
26images: PropertyImage[];
27}
28
Property_Listingqueries.ts23 matches
1import { sqlite } from "https://esm.town/v/stevekrouse/sqlite";
2import { PROPERTIES_TABLE, PROPERTY_IMAGES_TABLE, ADMIN_USERS_TABLE } from "./migrations.ts";
3import { Property, PropertyImage, PropertyWithImages } from "../../shared/types.ts";
45// Property queries
131}
132133// Property images queries
134export async function getPropertyImages(propertyId: number): Promise<PropertyImage[]> {
135const result = await sqlite.execute(
136`SELECT * FROM ${PROPERTY_IMAGES_TABLE} WHERE property_id = ? ORDER BY is_primary DESC`,
137[propertyId]
138);
139
140return result.rows as PropertyImage[];
141}
142143export async function addPropertyImage(
144propertyId: number,
145imageUrl: string,
146isPrimary: boolean = false
147): Promise<number> {
148// If this is a primary image, reset other primary images
149if (isPrimary) {
150await sqlite.execute(
151`UPDATE ${PROPERTY_IMAGES_TABLE} SET is_primary = 0 WHERE property_id = ?`,
152[propertyId]
153);
155
156const result = await sqlite.execute(
157`INSERT INTO ${PROPERTY_IMAGES_TABLE} (property_id, image_url, is_primary)
158VALUES (?, ?, ?)
159RETURNING id`,
160[propertyId, imageUrl, isPrimary ? 1 : 0]
161);
162
164}
165166export async function deletePropertyImage(id: number): Promise<boolean> {
167const result = await sqlite.execute(
168`DELETE FROM ${PROPERTY_IMAGES_TABLE} WHERE id = ?`,
169[id]
170);
173}
174175export async function getPropertiesWithImages(): Promise<PropertyWithImages[]> {
176const properties = await getAllProperties();
177const propertiesWithImages: PropertyWithImages[] = [];
178
179for (const property of properties) {
180const images = await getPropertyImages(property.id);
181propertiesWithImages.push({
182...property,
183images
184});
185}
186
187return propertiesWithImages;
188}
189190export async function getPropertyWithImages(id: number): Promise<PropertyWithImages | null> {
191const property = await getPropertyById(id);
192
195}
196
197const images = await getPropertyImages(property.id);
198
199return {
200...property,
201images
202};
203}
Property_Listingmigrations.ts4 matches
3// Table names - use these constants throughout the app
4export const PROPERTIES_TABLE = "properties_v1";
5export const PROPERTY_IMAGES_TABLE = "property_images_v1";
6export const ADMIN_USERS_TABLE = "admin_users_v1";
726`);
2728// Property images table
29await sqlite.execute(`
30CREATE TABLE IF NOT EXISTS ${PROPERTY_IMAGES_TABLE} (
31id INTEGER PRIMARY KEY AUTOINCREMENT,
32property_id INTEGER NOT NULL,
33image_url TEXT NOT NULL,
34is_primary BOOLEAN DEFAULT 0,
35created_at TEXT DEFAULT (datetime('now')),
Property_ListingREADME.md1 match
67- Property listings with filtering by category, price, and location
8- Detailed property pages with images, descriptions, and contact options
9- WhatsApp integration for inquiries
10- Admin panel for managing property listings
testeindex.html1 match
5<meta name="viewport" content="width=device-width, initial-scale=1.0">
6<title>Val Town To-Do App</title>
7<link rel="icon" href="/frontend/favicon.svg" type="image/svg+xml">
8<!-- TailwindCSS -->
9<script src="https://cdn.twind.style" crossorigin></script>
301>
302<img
303src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSIGEJA8-xL8Aa9oC7Y7gFqBoICxvhebGig6ADUJYSFVEzWRD0Y5GmuhnjW6B6e11C07iE&usqp=CAU"
304alt="EEP Portal Logo"
305style={{
1841<meta charset="UTF-8">
1842<style>${css}</style>
1843<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📰</text></svg>">
1844</head>
1845<body>
ExperimentalMain.tsx2 matches
610{
611"content": "The generated content as a string. For 'Blog Ideas', this should be a list of titles/concepts separated by newlines.",
612"suggestions": "Optional brief suggestions for improving or using the content (e.g., 'Consider adding an image/video.', 'Research relevant keywords for blog posts.') as a string."
613}
614Do not include any explanatory text before or after the JSON object.`,
956.fCA .fc:focus{color:#495057;background:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}
957.fCA textarea.fc{min-height:80px}
958.fCA select.fc{height:38px;appearance:none;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;padding-right:2.25rem}
959.fCA .aB{display:inline-block;font-weight:500;text-align:center;white-space:nowrap;vertical-align:middle;user-select:none;border:1px solid transparent;padding:.5rem 1rem;font-size:.9rem;line-height:1.5;border-radius:.25rem;transition:all .15s ease-in-out;cursor:pointer;font-family:inherit;background:#00aa65;color:#fff;border-color:#00aa65;margin-top:1rem} /* Abbreviated action-button */
960.fCA .aB:hover{background:#008751;border-color:#007a49}