@@ -110,7 +99,7 @@ export default function SearchPage() {
-
+
{isLoading &&
Loading more properties...
}
@@ -122,4 +111,4 @@ export default function SearchPage() {
);
-}
+}
\ No newline at end of file
diff --git a/apps/web/src/constants/menu-items.ts b/apps/web/src/constants/menu-items.ts
index 6f616f86..65b67e83 100644
--- a/apps/web/src/constants/menu-items.ts
+++ b/apps/web/src/constants/menu-items.ts
@@ -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];
\ No newline at end of file
diff --git a/apps/web/src/hooks/useUserRole.tsx b/apps/web/src/hooks/useUserRole.tsx
index 0205a095..36314d80 100644
--- a/apps/web/src/hooks/useUserRole.tsx
+++ b/apps/web/src/hooks/useUserRole.tsx
@@ -1,8 +1,9 @@
'use client';
import { useEffect, useState } from 'react';
-<<<<<<< HEAD
+// @ts-ignore: Alias resolution issue
import { profileAPI } from '~/services/api';
+// @ts-ignore: Alias resolution issue
import type { RoleInfo, UserRole } from '~/types/roles';
import { useAuth } from './auth/use-auth';
@@ -11,23 +12,17 @@ interface UseUserRoleReturn extends RoleInfo {
}
export function useUserRole(): UseUserRoleReturn {
-=======
-import type { RoleInfo, UserRole } from '~/types/roles';
-import { useAuth } from './auth/use-auth';
-
-export function useUserRole(): RoleInfo {
->>>>>>> 60310ea (feat: add stellar contract dependencies and integration setup)
const { user, isAuthenticated } = useAuth();
const [roleInfo, setRoleInfo] = useState
({
role: 'guest',
canAccessHostDashboard: false,
hasProperties: false,
});
-<<<<<<< HEAD
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchUserRole = async () => {
+ // 1. Si no está autenticado o no hay usuario, retornamos guest de inmediato
if (!isAuthenticated || !user) {
setRoleInfo({
role: 'guest',
@@ -38,29 +33,35 @@ export function useUserRole(): RoleInfo {
return;
}
+ // 2. Extraemos el ID. Si no existe, no llamamos a la API
+ const userId = user.publicKey || user.id;
+ if (!userId) {
+ // CORRECCIÓN: Tipamos 'prev' como RoleInfo para eliminar el error 7006
+ setRoleInfo((prev: RoleInfo) => ({ ...prev, role: 'guest' }));
+ setIsLoading(false);
+ return;
+ }
+
try {
setIsLoading(true);
- // Try to fetch profile from API first
try {
- const response = await profileAPI.getUserProfile(user.id);
- const profile = response.data;
+ const response = await profileAPI.getUserProfile(userId);
+ // biome-ignore lint/suspicious/noExplicitAny: API data handling
+ const profile = (response.data as any) || {};
- // Extract host information from profile
const hostStatus = profile.hostStatus;
const hasProperties = profile.hasProperties || false;
let role: UserRole = 'guest';
let canAccessHostDashboard = false;
- // User is a host if they have verified host status and properties
if (hostStatus === 'verified' && hasProperties) {
- role = 'dual'; // Can be both guest and host
+ role = 'dual';
canAccessHostDashboard = true;
} else if (hostStatus === 'verified') {
- // Verified but no properties yet
role = 'host';
- canAccessHostDashboard = false; // No dashboard access without properties
+ canAccessHostDashboard = false;
}
setRoleInfo({
@@ -70,22 +71,15 @@ export function useUserRole(): RoleInfo {
hasProperties,
});
- // Cache in localStorage for faster subsequent loads
if (hostStatus) {
localStorage.setItem('hostStatus', hostStatus);
}
localStorage.setItem('hasProperties', String(hasProperties));
- } catch (apiError) {
- console.warn(
- 'Failed to fetch user profile from API, falling back to localStorage',
- apiError
- );
-
+ } catch (_apiError) {
// Fallback to localStorage if API fails
const storedHostStatus = localStorage.getItem('hostStatus');
const storedHasProperties = localStorage.getItem('hasProperties') === 'true';
- // Validate hostStatus
const validHostStatuses = ['pending', 'verified', 'rejected', 'suspended'];
const hostStatus =
storedHostStatus && validHostStatuses.includes(storedHostStatus)
@@ -95,7 +89,6 @@ export function useUserRole(): RoleInfo {
let role: UserRole = 'guest';
let canAccessHostDashboard = false;
- // User is a host if they have verified host status and properties
if (hostStatus === 'verified' && storedHasProperties) {
role = 'dual';
canAccessHostDashboard = true;
@@ -120,43 +113,4 @@ export function useUserRole(): RoleInfo {
}, [user, isAuthenticated]);
return { ...roleInfo, isLoading };
-=======
-
- useEffect(() => {
- if (!isAuthenticated || !user) {
- setRoleInfo({
- role: 'guest',
- canAccessHostDashboard: false,
- hasProperties: false,
- });
- return;
- }
-
- // Check if user has host status in localStorage or from API
- const storedHostStatus = localStorage.getItem('hostStatus');
- const storedHasProperties = localStorage.getItem('hasProperties') === 'true';
-
- let role: UserRole = 'guest';
- let canAccessHostDashboard = false;
-
- // User is a host if they have verified host status and properties
- if (storedHostStatus === 'verified' && storedHasProperties) {
- role = 'dual'; // Can be both guest and host
- canAccessHostDashboard = true;
- } else if (storedHostStatus === 'verified') {
- // Verified but no properties yet
- role = 'host';
- canAccessHostDashboard = false; // No dashboard access without properties
- }
-
- setRoleInfo({
- role,
- hostStatus: storedHostStatus as 'pending' | 'verified' | 'rejected' | 'suspended' | undefined,
- canAccessHostDashboard,
- hasProperties: storedHasProperties,
- });
- }, [user, isAuthenticated]);
-
- return roleInfo;
->>>>>>> 60310ea (feat: add stellar contract dependencies and integration setup)
-}
+}
\ No newline at end of file
diff --git a/apps/web/src/lib/config/config.ts b/apps/web/src/lib/config/config.ts
index 3689cde1..247d30b6 100644
--- a/apps/web/src/lib/config/config.ts
+++ b/apps/web/src/lib/config/config.ts
@@ -10,13 +10,23 @@ export const HORIZON_URL =
export const NETWORK_PASSPHRASE =
STELLAR_NETWORK === 'mainnet' ? Networks.PUBLIC : Networks.TESTNET;
-export const USDC_ISSUER =
+// 1. Definimos el emisor real de la Testnet (Circle) como respaldo.
+const TESTNET_USDC_ISSUER = 'GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5';
+
+// 2. Intentamos usar la variable de entorno según la red.
+const envIssuer =
STELLAR_NETWORK === 'mainnet'
? process.env.NEXT_PUBLIC_USDC_ISSUER_MAINNET
: process.env.NEXT_PUBLIC_USDC_ISSUER_TESTNET;
-if (!USDC_ISSUER) {
- throw new Error(
- `USDC_ISSUER for ${STELLAR_NETWORK} is not defined. Please check your environment variables.`
- );
+export const USDC_ISSUER = envIssuer || TESTNET_USDC_ISSUER;
+
+// 3. CORRECCIÓN MINOR: Mensaje con el nombre exacto de la variable de entorno
+if (!envIssuer) {
+ const varName =
+ STELLAR_NETWORK === 'mainnet'
+ ? 'NEXT_PUBLIC_USDC_ISSUER_MAINNET'
+ : 'NEXT_PUBLIC_USDC_ISSUER_TESTNET';
+
+ console.warn(`⚠️ ${varName} no está definida. Usando fallback: ${TESTNET_USDC_ISSUER}`);
}
diff --git a/apps/web/src/lib/stellar.ts b/apps/web/src/lib/stellar.ts
index 162d7d05..52c35d5b 100644
--- a/apps/web/src/lib/stellar.ts
+++ b/apps/web/src/lib/stellar.ts
@@ -1,8 +1,22 @@
-import { Asset, Horizon, Operation, TransactionBuilder } from 'stellar-sdk';
-import Server from 'stellar-sdk';
+import { Asset, Horizon, Operation, TransactionBuilder, Transaction } from 'stellar-sdk';
import { HORIZON_URL, NETWORK_PASSPHRASE, USDC_ISSUER } from './config/config';
-const USDC_ASSET = new Asset('USDC', USDC_ISSUER);
+/**
+ * Función auxiliar para obtener el Asset de forma segura.
+ * Lanza un error si el emisor no es válido para evitar pagos accidentales en XLM.
+ */
+const getUSDCAsset = () => {
+ // 1. Validamos que el issuer tenga un formato coherente de Stellar
+ if (USDC_ISSUER && USDC_ISSUER.startsWith('G') && USDC_ISSUER.length === 56) {
+ return new Asset('USDC', USDC_ISSUER);
+ }
+
+ // 2. Si estamos en desarrollo/testnet y no hay issuer, podrías usar el de Circle,
+ // pero lo más seguro es lanzar un error si la configuración está rota.
+ throw new Error(
+ `Invalid USDC_ISSUER configuration. Check your environment variables. Value: ${USDC_ISSUER}`
+ );
+};
export async function createPaymentTransaction(
sourcePublicKey: string,
@@ -12,6 +26,9 @@ export async function createPaymentTransaction(
try {
const server = new Horizon.Server(HORIZON_URL);
const sourceAccount = await server.loadAccount(sourcePublicKey);
+
+ // Aquí se lanzará el error si el asset no es válido
+ const asset = getUSDCAsset();
const transaction = new TransactionBuilder(sourceAccount, {
fee: '100',
@@ -20,7 +37,7 @@ export async function createPaymentTransaction(
.addOperation(
Operation.payment({
destination: destinationPublicKey,
- asset: USDC_ASSET,
+ asset: asset,
amount: amount,
})
)
@@ -34,10 +51,17 @@ export async function createPaymentTransaction(
}
}
-export async function submitTransaction(signedTransaction: string) {
+/**
+ * Envía una transacción firmada a la red.
+ */
+export async function submitTransaction(signedTransactionXDR: string) {
try {
- const server = new Server(HORIZON_URL);
- const result = await server.submitTransaction(signedTransaction);
+ const server = new Horizon.Server(HORIZON_URL);
+
+ // Reconstruimos el objeto Transaction desde el string XDR
+ const transactionToSubmit = new Transaction(signedTransactionXDR, NETWORK_PASSPHRASE);
+
+ const result = await server.submitTransaction(transactionToSubmit);
return result.hash;
} catch (error) {
console.error('Error submitting transaction:', error);
@@ -45,26 +69,29 @@ export async function submitTransaction(signedTransaction: string) {
}
}
+/**
+ * Procesa el pago completo: Crea la transacción, solicita firma a Freighter y la envía.
+ */
export async function processPayment(
sourcePublicKey: string,
destinationPublicKey: string,
amount: string
) {
try {
- // Create the transaction
const transactionXDR = await createPaymentTransaction(
sourcePublicKey,
destinationPublicKey,
amount
);
- // Sign the transaction with Freighter
+ // @ts-ignore: Freighter API global access
if (typeof window === 'undefined' || !window.freighterApi) {
throw new Error('Freighter wallet not found');
}
+
+ // @ts-ignore: Freighter API global access
const signedTransaction = await window.freighterApi.signTransaction(transactionXDR);
- // Submit the signed transaction
const transactionHash = await submitTransaction(signedTransaction);
return transactionHash;
} catch (error) {
@@ -73,20 +100,25 @@ export async function processPayment(
}
}
-/**
- * Fetches the USDC balance for a given Stellar public key on the client-side.
- * @param publicKey The Stellar public key of the account.
- * @returns The USDC balance as a string, or '0' if not found.
- */
export async function getUSDCBalance(publicKey: string): Promise {
try {
const server = new Horizon.Server(HORIZON_URL);
const account = await server.loadAccount(publicKey);
+
+ // Para el balance, si falla el asset, simplemente retornamos '0'
+ // pero logueamos el error de configuración.
+ let asset: Asset;
+ try {
+ asset = getUSDCAsset();
+ } catch (e) {
+ console.error("Cannot fetch balance: USDC Asset not configured.");
+ return '0';
+ }
- // Filter for asset balances and then find USDC
const usdcBalance = account.balances.find((balance) => {
if (balance.asset_type === 'credit_alphanum4' || balance.asset_type === 'credit_alphanum12') {
- return balance.asset_code === USDC_ASSET.code && balance.asset_issuer === USDC_ASSET.issuer;
+ const b = balance as any;
+ return b.asset_code === asset.code && b.asset_issuer === asset.issuer;
}
return false;
});
@@ -96,4 +128,4 @@ export async function getUSDCBalance(publicKey: string): Promise {
console.error(`Error fetching USDC balance for ${publicKey}:`, error);
return '0';
}
-}
+}
\ No newline at end of file