Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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',
Copy link
Contributor

Choose a reason for hiding this comment

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

variables in english

description: 'Alquileres con USDC en la red Stellar',
};

export default function RootLayout({
Expand All @@ -17,7 +23,7 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
<html lang="es" suppressHydrationWarning>
<html lang="en" suppressHydrationWarning>
<head>
<meta name="color-scheme" content="light dark" />
<meta
Expand All @@ -26,10 +32,14 @@ export default function RootLayout({
/>
<script src="https://accounts.google.com/gsi/client" async defer />
</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) */
Copy link
Contributor

Choose a reason for hiding this comment

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

delete this comment

<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
Copy link
Contributor

Choose a reason for hiding this comment

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

could be in english please

const Grid = PropertyGrid as any;

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>
);
}
}
49 changes: 16 additions & 33 deletions apps/web/src/components/shared/layout/providers.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,31 @@
'use client';

import { useTheme } from 'next-themes';
import dynamic from 'next/dynamic';
import { ThemeProvider, useTheme } from 'next-themes';
import React from 'react';
import { AuthProvider } from '~/hooks/auth/use-auth';
import { StellarProvider } from '~/hooks/stellar/stellar-context';
import { TrustlessWorkProvider } from '~/providers/TrustlessWorkProvider';

const ThemeProvider = dynamic(
() => import('next-themes').then((mod) => ({ default: mod.ThemeProvider })),
{
ssr: false,
loading: () => (
<div className="min-h-screen bg-background text-foreground">
<div className="container mx-auto p-4">Cargando...</div>
</div>
),
}
);
/**
* Syncs resolved theme to theme-portal-root. Must live inside ThemeProvider.
*/
function ThemePortalSync() {
const { resolvedTheme } = useTheme();
React.useEffect(() => {
const portal =
typeof window !== 'undefined' ? document.getElementById('theme-portal-root') : null;
if (portal && resolvedTheme) portal.className = resolvedTheme;
}, [resolvedTheme]);
return null;
}

interface ProvidersProps {
children: React.ReactNode;
}

export function Providers({ children }: ProvidersProps) {
const { resolvedTheme } = useTheme();
const [mounted, setMounted] = React.useState(false);

React.useEffect(() => {
setMounted(true);
}, []);

React.useEffect(() => {
const portal =
typeof window !== 'undefined' ? document.getElementById('theme-portal-root') : null;
if (portal && resolvedTheme) {
portal.className = resolvedTheme;
}
}, [resolvedTheme]);
React.useEffect(() => setMounted(true), []);

if (!mounted) {
return (
Expand All @@ -51,17 +39,12 @@ export function Providers({ children }: ProvidersProps) {
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem={true}
enableSystem
disableTransitionOnChange={false}
storageKey="stellar-rent-theme"
value={{
light: 'light',
dark: 'dark',
system: 'system',
}}
>
<ThemePortalSync />
<StellarProvider>
{/* You can envolve a tanstak provider one layer up of TW in order to use mutations or whatever you need */}
<TrustlessWorkProvider>
<AuthProvider>{children}</AuthProvider>
</TrustlessWorkProvider>
Expand Down
65 changes: 56 additions & 9 deletions apps/web/src/constants/menu-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,64 @@ export interface MenuItem {
withContainer?: boolean;
}

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',
};
Comment on lines +21 to +27
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 | 🟑 Minor

Semantic mismatch: "Favorites" label points to bookings tab.

ICON_FAVORITES has label "Favorites" but navigates to /dashboard/guest?tab=bookings. This mirrors the previously flagged Settings/invitations mismatch. Consider either:

  • Renaming the label to reflect the destination (e.g., "Bookings")
  • Updating the href to an actual favorites page (e.g., /dashboard/guest?tab=favorites)
πŸ€– Prompt for AI Agents
In `@apps/web/src/constants/menu-items.ts` around lines 19 - 25, ICON_FAVORITES
currently has a semantic mismatch: its label "Favorites" points to
/dashboard/guest?tab=bookings; update the constant (ICON_FAVORITES) so label and
href matchβ€”either change label to "Bookings" to reflect href or change href to
/dashboard/guest?tab=favorites to match the "Favorites" label; ensure the alt
and id remain consistent with the chosen meaning.

const ICON_MESSAGES = {
id: 'messages',
src: '/icons/send.webp',
alt: 'Messages',
label: 'Messages',
href: '/messages',
withContainer: true,
};

// CORRECCIΓ“N: Renombramos 'Settings' a 'Invitations' para que coincida con la ruta /invitations
const ICON_INVITATIONS = {
id: 'invitations',
src: '/icons/settings.webp',
alt: 'Invitations',
label: 'Invitations',
href: '/invitations',
};
Comment on lines +39 to +45
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 | 🟑 Minor

Icon asset doesn't match the "Invitations" semantics.

While the label/href mismatch has been corrected, the icon source still references /icons/settings.webp. For visual consistency, consider using an invitation-themed icon (e.g., envelope or bell icon) instead of the settings gear.

πŸ€– Prompt for AI Agents
In `@apps/web/src/constants/menu-items.ts` around lines 36 - 42, ICON_INVITATIONS
currently points to the settings icon; update the asset and alt text to match
the "Invitations" semantics by changing ICON_INVITATIONS.src to an
invitation-themed file (for example '/icons/invitations.webp' or
'/icons/envelope.webp') and adjust ICON_INVITATIONS.alt to "Invitations" or
"Envelope" as appropriate so the icon, alt and label/href all align.


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 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,
},
ICON_MENU,
ICON_SEARCH,
ICON_FAVORITES,
ICON_MESSAGES,
ICON_INVITATIONS, // Ahora el nombre es semΓ‘nticamente correcto
ICON_LOCK,
ICON_APPLICATIONS,
];

<<<<<<< HEAD
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];
=======
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

Critical: Unresolved merge conflict markers in code.

The file contains merge conflict markers (<<<<<<< HEAD, =======, >>>>>>> origin/main) that will cause syntax errors. These must be resolved before merging.

Based on the structure, the HEAD version (lines 64-66) consolidates all role menus to share GUEST_MENU_ITEMS, while origin/main (lines 68-210) has distinct role-specific menus. Determine the intended behavior and remove the conflict markers.

πŸ€– Prompt for AI Agents
In `@apps/web/src/constants/menu-items.ts` around lines 63 - 67, The file contains
unresolved Git conflict markers around the menu exports; remove the conflict
markers and resolve to the intended variant for TENANT_MENU_ITEMS,
HOST_MENU_ITEMS and DUAL_MENU_ITEMS (either keep the HEAD shorthand exports that
set them to [...GUEST_MENU_ITEMS] or restore the role-specific arrays from
origin/main), then ensure the final code exports valid MenuItem[] constants and
that GUEST_MENU_ITEMS is imported/defined; also remove any leftover <<<<<<<,
=======, or >>>>>>> tokens so the file compiles cleanly.

export const TENANT_MENU_ITEMS: MenuItem[] = [
// TODO: Wire menu item to navigation drawer
{ id: 'menu', src: '/icons/menu.webp', alt: 'Menu', label: 'Menu', href: '#' },
Expand Down Expand Up @@ -162,3 +208,4 @@ export const DUAL_MENU_ITEMS: MenuItem[] = [
href: '/dashboard/tenant-dashboard?tab=calendar',
},
];
>>>>>>> origin/main
Loading
Loading