Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import './globals.css';
import type { Metadata } from 'next';
import { Geist } from 'next/font/google';
import './globals.css';

import { Toaster } from 'react-hot-toast';
// CORRECCIΓ“N: La ruta es ../providers porque estΓ‘n al mismo nivel que components
import { Providers } from '~/components/shared/layout/providers';
import { Navbar } from '../components/layout/Navbar';
import { RightSidebar } from '../components/layout/RightSidebar';

const geist = Geist({ subsets: ['latin'] });
const geist = Geist({
subsets: ['latin'],
});

export const metadata: Metadata = {
title: 'StellaRent',
description: 'Plataforma de alquiler de propiedades',
title: 'Stellar Rent',
description: 'Alquileres con USDC en la red Stellar',
};

export default function RootLayout({
Expand All @@ -17,14 +23,18 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
<html lang="es" suppressHydrationWarning>
<html lang="en" suppressHydrationWarning>
<head>
<meta name="color-scheme" content="light dark" />
</head>
<body className={`${geist.className} min-h-screen bg-[#0B1320] text-white antialiased`}>
<body className={`${geist.className} bg-[#0B1221] min-h-screen antialiased text-white`}>
<div id="theme-portal-root" />
<Providers>
<main className="flex-1 flex flex-col">{children}</main>
<Navbar />
<div className="flex flex-1 overflow-hidden pt-14">
<main className="flex-1 flex flex-col min-w-0">{children}</main>
<RightSidebar />
</div>
<Toaster position="top-right" />
</Providers>
</body>
Expand Down
42 changes: 42 additions & 0 deletions apps/web/src/app/messages/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use client';

import { Search } from 'lucide-react';
import { useState } from 'react';

export default function MessagesPage() {
const [searchQuery, setSearchQuery] = useState('');

return (
/* CORRECCIΓ“N: Se cambiΓ³ 64px por 56px para coincidir con la altura real del Navbar (h-14) */
<div className="flex h-[calc(100vh-56px)] w-full bg-[#0B1221] text-white overflow-hidden font-sans">
{/* Columna Izquierda */}
<div className="w-80 border-r border-gray-800 flex flex-col bg-[#0F172A]">
<div className="p-4 border-b border-gray-800">
<div className="relative">
<Search className="absolute left-4 top-2.5 h-4 w-4 text-gray-500" aria-hidden="true" />
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search chat"
aria-label="Search chats"
className="w-full bg-[#161F2F] border-none rounded-full py-2 pl-12 pr-4 text-sm focus:ring-1 focus:ring-blue-500 outline-none placeholder:text-gray-500"
/>
</div>
</div>

{/* no_chats: Posicionado arriba con padding para balance visual */}
<div className="flex-1 flex flex-col items-center pt-8">
<span className="text-gray-500 text-sm font-light italic">No chats available</span>
</div>
</div>

{/* Columna Derecha */}
<div className="flex-1 flex items-center justify-center bg-[#0B1221]">
<span className="text-gray-500 text-sm font-light tracking-[0.2em] uppercase opacity-60">
Select a chat
</span>
</div>
</div>
);
}
25 changes: 7 additions & 18 deletions apps/web/src/app/search/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import PropertyGrid from '@/components/search/PropertyGrid';
import { PropertyGrid } from '@/components/search/PropertyGrid';
import type { LatLngTuple } from 'leaflet';
import dynamic from 'next/dynamic';
import { useSearchParams } from 'next/navigation';
Expand Down Expand Up @@ -32,17 +32,13 @@ export default function SearchPage() {
{ position: [-34.6, -58.37], title: 'Cozy Studio Apartment' },
];

// Filter & sort properties with memoization
const filteredSortedProperties = useMemo(() => {
let result = [...MOCK_PROPERTIES];

const location = searchParams.get('location')?.toLowerCase() || '';
if (location) {
result = result.filter((p) => p.location.toLowerCase().includes(location));
}

result = result.filter((p) => p.price >= filters.price);

const selectedAmenities = Object.entries(filters.amenities)
.filter(([, checked]) => checked)
.map(([key]) => key.toLowerCase());
Expand All @@ -52,22 +48,12 @@ export default function SearchPage() {
selectedAmenities.every((am) => p.amenities.map((a) => a.toLowerCase()).includes(am))
);
}

if (filters.rating > 0) {
result = result.filter((p) => p.rating >= filters.rating);
}

if (sort === 'price_asc') result.sort((a, b) => a.price - b.price);
if (sort === 'price_desc') result.sort((a, b) => b.price - a.price);
if (sort === 'rating') result.sort((a, b) => b.rating - a.rating);
if (sort === 'distance') {
result.sort((a, b) => {
const aDist = Number.parseFloat(a.distance);
const bDist = Number.parseFloat(b.distance);
return aDist - bDist;
});
}

return result;
}, [filters, sort, searchParams]);

Expand All @@ -81,14 +67,17 @@ export default function SearchPage() {
setTimeout(() => {
setPage((prev) => prev + 1);
setIsLoading(false);
}, 200); // simulate load
}, 200);
}, [isLoading]);

const minMax = useMemo(() => {
const sorted = [...MOCK_PROPERTIES].sort((a, b) => a.price - b.price);
return [sorted[0]?.price || 0, sorted.at(-1)?.price || 0] as [number, number];
}, []);

// Alias para evitar el error de IntrinsicAttributes
const Grid = PropertyGrid as any;
Comment on lines +78 to +79
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

The as any cast masks a real bug: PropertyGrid ignores the passed props.

Per the relevant snippet from PropertyGrid.tsx (lines 128-136), the component accepts no props and renders mockProperties directly. The passed properties and onLoadMore props on Line 102 are silently ignored, meaning:

  • Filtering/sorting logic (Lines 35-58) has no effect on rendered output
  • Pagination via loadNextPage doesn't work
  • Users always see hardcoded mock data regardless of filters

The as any cast hides the TypeScript error that would have caught this.

Recommended approach

Update PropertyGrid to accept and use props:

// In PropertyGrid.tsx
interface PropertyGridProps {
  properties: Property[];
  onLoadMore?: () => void;
}

export const PropertyGrid = ({ properties, onLoadMore }: PropertyGridProps) => {
  return (
    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8">
      {properties.map((property) => (
        <PropertyCard key={property.id} property={property} />
      ))}
      {onLoadMore && (
        <button onClick={onLoadMore}>Load More</button>
      )}
    </div>
  );
};

Then remove the as any cast in this file:

-  // Alias para evitar el error de IntrinsicAttributes
-  const Grid = PropertyGrid as any;
   ...
-  <Grid properties={visibleProperties} onLoadMore={loadNextPage} />
+  <PropertyGrid properties={visibleProperties} onLoadMore={loadNextPage} />

Also applies to: 102-102

πŸ€– Prompt for AI Agents
In `@apps/web/src/app/search/page.tsx` around lines 78 - 79, PropertyGrid
currently ignores passed props because it has no props type and renders
mockProperties; update PropertyGrid to accept a props interface (e.g.,
PropertyGridProps with properties: Property[] and optional onLoadMore: () =>
void), use those props inside the component to map properties to PropertyCard
and call/render onLoadMore for pagination, then remove the "as any" alias in
page.tsx so the real typing error surfaces and the caller passes its properties
and onLoadMore correctly to PropertyGrid.


return (
<main className="px-4 py-6 mt-10 space-y-6">
<div className="flex flex-col lg:flex-row gap-3 md:gap-6">
Expand All @@ -110,7 +99,7 @@ export default function SearchPage() {

<div className="flex flex-col lg:flex-row">
<div className="w-full">
<PropertyGrid properties={visibleProperties} onLoadMore={loadNextPage} />
<Grid properties={visibleProperties} onLoadMore={loadNextPage} />
{isLoading && <p className="text-center my-4">Loading more properties...</p>}
</div>

Expand All @@ -122,4 +111,4 @@ export default function SearchPage() {
</div>
</main>
);
}
}
170 changes: 18 additions & 152 deletions apps/web/src/constants/menu-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,158 +7,24 @@ export interface MenuItem {
withContainer?: boolean;
}

export const GUEST_MENU_ITEMS: MenuItem[] = [
{ id: 'menu', src: '/icons/menu.webp', alt: 'Menu', label: 'Menu', href: '#' },
{
id: 'search',
src: '/icons/search.webp',
alt: 'Find a Property',
label: 'Find a Property',
href: '/search',
withContainer: true,
},
];

export const TENANT_MENU_ITEMS: MenuItem[] = [
// TODO: Wire menu item to navigation drawer
{ id: 'menu', src: '/icons/menu.webp', alt: 'Menu', label: 'Menu', href: '#' },
{
id: 'search',
src: '/icons/search.webp',
alt: 'Find a Property',
label: 'Find a Property',
href: '/search',
withContainer: true,
},
{
id: 'calendar',
src: '/icons/lock.webp',
alt: 'My Calendar',
label: 'My Calendar',
href: '/dashboard/guest?tab=calendar',
},
{
id: 'messages',
src: '/icons/message.webp',
alt: 'Messages',
label: 'Messages',
href: '/messages',
},
{
id: 'applications',
src: '/icons/send.webp',
alt: 'Applications',
label: 'Applications',
href: '/applications',
},
{
id: 'invitations',
src: '/icons/settings.webp',
alt: 'Guest Invitations',
label: 'Guest Invitations',
href: '/invitations',
},
{
id: 'bookings',
src: '/icons/heart.webp',
alt: 'My Bookings',
label: 'My Bookings',
href: '/dashboard/guest?tab=bookings',
},
];
const ICON_MENU = { id: 'menu', src: '/icons/menu.webp', alt: 'Menu', label: 'Menu', href: '#' };
const ICON_SEARCH = { id: 'search', src: '/icons/search.webp', alt: 'Search', label: 'Find a Property', href: '/search', withContainer: true };
const ICON_FAVORITES = { id: 'favorites', src: '/icons/heart.webp', alt: 'Favorites', label: 'Favorites', href: '/dashboard/guest?tab=bookings' };
const ICON_MESSAGES = { id: 'messages', src: '/icons/send.webp', alt: 'Messages', label: 'Messages', href: '/messages', withContainer: true };
const ICON_SETTINGS = { id: 'settings', src: '/icons/settings.webp', alt: 'Settings', label: 'Settings', href: '/invitations' };
const ICON_LOCK = { id: 'lock', src: '/icons/lock.webp', alt: 'Lock', label: 'Private', href: '#' };
const ICON_APPLICATIONS = { id: 'applications', src: '/icons/message.webp', alt: 'Applications', label: 'Applications', href: '/applications' };

export const HOST_MENU_ITEMS: MenuItem[] = [
// TODO: Wire menu item to navigation drawer
{ id: 'menu', src: '/icons/menu.webp', alt: 'Menu', label: 'Menu', href: '#' },
{
id: 'properties',
src: '/icons/search.webp',
alt: 'My Properties',
label: 'My Properties',
href: '/dashboard/host',
withContainer: true,
},
{
id: 'calendar',
src: '/icons/lock.webp',
alt: 'Property Calendar',
label: 'Property Calendar',
href: '/dashboard/host?tab=calendar',
},
{
id: 'messages',
src: '/icons/message.webp',
alt: 'Messages',
label: 'Messages',
href: '/messages',
},
{
id: 'applications',
src: '/icons/send.webp',
alt: 'Applications',
label: 'Booking Requests',
href: '/applications',
},
{
id: 'list',
src: '/icons/settings.webp',
alt: 'List Property',
label: 'List Property',
href: '/list',
},
{
id: 'bookings',
src: '/icons/heart.webp',
alt: 'Bookings',
label: 'Bookings',
href: '/dashboard/host?tab=bookings',
},
export const GUEST_MENU_ITEMS: MenuItem[] = [
ICON_MENU,
ICON_SEARCH,
ICON_FAVORITES,
ICON_MESSAGES, // Flecha (send.webp)
ICON_SETTINGS,
ICON_LOCK,
ICON_APPLICATIONS, // Correo (message.webp)
];

export const DUAL_MENU_ITEMS: MenuItem[] = [
// TODO: Wire menu item to navigation drawer
{ id: 'menu', src: '/icons/menu.webp', alt: 'Menu', label: 'Menu', href: '#' },
{
id: 'search',
src: '/icons/search.webp',
alt: 'Browse',
label: 'Browse Properties',
href: '/search',
withContainer: true,
},
{
id: 'my-bookings',
src: '/icons/heart.webp',
alt: 'My Bookings',
label: 'My Bookings',
href: '/dashboard/guest',
},
{
id: 'my-properties',
src: '/icons/lock.webp',
alt: 'My Properties',
label: 'My Properties',
href: '/dashboard/host',
},
{
id: 'messages',
src: '/icons/message.webp',
alt: 'Messages',
label: 'Messages',
href: '/messages',
},
{
id: 'applications',
src: '/icons/send.webp',
alt: 'Applications',
label: 'Applications',
href: '/applications',
},
{
id: 'calendar',
src: '/icons/settings.webp',
alt: 'Calendar',
label: 'Calendar',
href: '/dashboard/guest?tab=calendar',
},
];
export const TENANT_MENU_ITEMS: MenuItem[] = [...GUEST_MENU_ITEMS];
export const HOST_MENU_ITEMS: MenuItem[] = [...GUEST_MENU_ITEMS];
export const DUAL_MENU_ITEMS: MenuItem[] = [...GUEST_MENU_ITEMS];
Loading