diff --git a/apps/backend/bun.config.ts b/apps/backend/bun.config.ts index 617d9a10..7530d271 100644 --- a/apps/backend/bun.config.ts +++ b/apps/backend/bun.config.ts @@ -14,4 +14,3 @@ const config: BunTestConfig = { }; export default config; - diff --git a/apps/backend/src/config/supabase.ts b/apps/backend/src/config/supabase.ts index ff87c912..ad3d61ce 100644 --- a/apps/backend/src/config/supabase.ts +++ b/apps/backend/src/config/supabase.ts @@ -206,7 +206,7 @@ const createMockSupabase = () => { wallet_users: new Map(), users: new Map(), properties: new Map(), - bookings: new Map() + bookings: new Map(), }; // Helper function to get mock data for each table @@ -218,9 +218,9 @@ const createMockSupabase = () => { public_key: 'test-public-key', challenge: 'test-challenge-value', expires_at: new Date(Date.now() + 300000).toISOString(), // 5 minutes from now - created_at: new Date().toISOString() + created_at: new Date().toISOString(), }; - + case 'bookings': return { id: '123e4567-e89b-12d3-a456-426614174555', @@ -233,21 +233,21 @@ const createMockSupabase = () => { total: 100, status: 'pending', created_at: new Date().toISOString(), - updated_at: new Date().toISOString() + updated_at: new Date().toISOString(), }; - + default: return null; } }; const createMockChain = (tableName: string) => { - const filters: Array<{column: string, value: any, operator: string}> = []; - + const filters: Array<{ column: string; value: any; operator: string }> = []; + const chain = { _tableName: tableName, _filters: filters, - + // Query methods select: () => chain, eq: (column: string, value: any) => { @@ -258,65 +258,83 @@ const createMockSupabase = () => { filters.push({ column, value, operator: 'gt' }); return chain; }, - + // Unused filter methods - just return chain for compatibility - lt: () => chain, gte: () => chain, lte: () => chain, neq: () => chain, - like: () => chain, ilike: () => chain, is: () => chain, in: () => chain, - contains: () => chain, containedBy: () => chain, rangeGt: () => chain, - rangeGte: () => chain, rangeLt: () => chain, rangeLte: () => chain, - rangeAdjacent: () => chain, overlaps: () => chain, textSearch: () => chain, - match: () => chain, not: () => chain, or: () => chain, filter: () => chain, - order: () => chain, limit: () => chain, range: () => chain, abortSignal: () => chain, + lt: () => chain, + gte: () => chain, + lte: () => chain, + neq: () => chain, + like: () => chain, + ilike: () => chain, + is: () => chain, + in: () => chain, + contains: () => chain, + containedBy: () => chain, + rangeGt: () => chain, + rangeGte: () => chain, + rangeLt: () => chain, + rangeLte: () => chain, + rangeAdjacent: () => chain, + overlaps: () => chain, + textSearch: () => chain, + match: () => chain, + not: () => chain, + or: () => chain, + filter: () => chain, + order: () => chain, + limit: () => chain, + range: () => chain, + abortSignal: () => chain, single: () => { // Handle special filter cases if (filters.length > 0) { - const hasNonExistentChallenge = filters.some(f => - f.column === 'challenge' && f.value === 'non-existent-challenge' + const hasNonExistentChallenge = filters.some( + (f) => f.column === 'challenge' && f.value === 'non-existent-challenge' ); if (hasNonExistentChallenge) { return Promise.resolve({ data: null, error: null }); } - - const hasExpiredChallenge = filters.some(f => - f.column === 'expires_at' && f.operator === 'gt' && - new Date(f.value) < new Date() + + const hasExpiredChallenge = filters.some( + (f) => + f.column === 'expires_at' && f.operator === 'gt' && new Date(f.value) < new Date() ); if (hasExpiredChallenge) { return Promise.resolve({ data: null, error: null }); } } - + // Return mock data based on table - return Promise.resolve({ - data: getMockDataForTable(tableName), - error: null + return Promise.resolve({ + data: getMockDataForTable(tableName), + error: null, }); }, maybeSingle: () => Promise.resolve({ data: null, error: null }), then: (callback: any) => { // Handle special filter cases if (filters.length > 0) { - const hasNonExistentChallenge = filters.some(f => - f.column === 'challenge' && f.value === 'non-existent-challenge' + const hasNonExistentChallenge = filters.some( + (f) => f.column === 'challenge' && f.value === 'non-existent-challenge' ); if (hasNonExistentChallenge) { return callback({ data: [], error: null }); } - - const hasExpiredChallenge = filters.some(f => - f.column === 'expires_at' && f.operator === 'gt' && - new Date(f.value) < new Date() + + const hasExpiredChallenge = filters.some( + (f) => + f.column === 'expires_at' && f.operator === 'gt' && new Date(f.value) < new Date() ); if (hasExpiredChallenge) { return callback({ data: [], error: null }); } } - + // Return mock data as array const mockData = getMockDataForTable(tableName); const data = mockData ? [mockData] : []; return callback({ data, error: null }); - } + }, }; return chain; }; @@ -341,7 +359,7 @@ const createMockSupabase = () => { mockData[tableName].set(id, record); } return callback({ data: [record], error: null }); - } + }, }), single: () => { const id = `mock-${Date.now()}`; @@ -358,14 +376,14 @@ const createMockSupabase = () => { mockData[tableName].set(id, record); } return callback({ data: [record], error: null }); - } + }, }; return insertChain; }, update: () => createMockChain(tableName), upsert: () => createMockChain(tableName), delete: () => createMockChain(tableName), - then: (callback: any) => callback({ data: [], error: null }) + then: (callback: any) => callback({ data: [], error: null }), }); return { @@ -374,47 +392,50 @@ const createMockSupabase = () => { getUser: (token: string) => { // Return error for invalid tokens if (token === 'invalid.token') { - return Promise.resolve({ - data: { user: null }, - error: { message: 'Invalid token' } + return Promise.resolve({ + data: { user: null }, + error: { message: 'Invalid token' }, }); } - return Promise.resolve({ - data: { user: { id: 'test-user-id', email: 'test@example.com' } }, - error: null + return Promise.resolve({ + data: { user: { id: 'test-user-id', email: 'test@example.com' } }, + error: null, }); }, - signInWithPassword: () => Promise.resolve({ - data: { user: { id: 'test-user-id', email: 'test@example.com' }, session: null }, - error: null - }), - signUp: () => Promise.resolve({ - data: { user: { id: 'test-user-id', email: 'test@example.com' }, session: null }, - error: null - }) - } + signInWithPassword: () => + Promise.resolve({ + data: { user: { id: 'test-user-id', email: 'test@example.com' }, session: null }, + error: null, + }), + signUp: () => + Promise.resolve({ + data: { user: { id: 'test-user-id', email: 'test@example.com' }, session: null }, + error: null, + }), + }, }; }; // Use mock in test environment, real client otherwise -export const supabase = process.env.NODE_ENV === 'test' - ? createMockSupabase() as any - : (() => { - if (!process.env.SUPABASE_SERVICE_ROLE_KEY) { - throw new Error('Missing SUPABASE_SERVICE_ROLE_KEY environment variable'); - } - - return createClient( - process.env.SUPABASE_URL, - process.env.SUPABASE_SERVICE_ROLE_KEY, - { - auth: { - autoRefreshToken: false, - persistSession: false, - }, - db: { - schema: 'public', - }, +export const supabase = + process.env.NODE_ENV === 'test' + ? (createMockSupabase() as any) + : (() => { + if (!process.env.SUPABASE_SERVICE_ROLE_KEY) { + throw new Error('Missing SUPABASE_SERVICE_ROLE_KEY environment variable'); } - ); - })(); + + return createClient( + process.env.SUPABASE_URL, + process.env.SUPABASE_SERVICE_ROLE_KEY, + { + auth: { + autoRefreshToken: false, + persistSession: false, + }, + db: { + schema: 'public', + }, + } + ); + })(); diff --git a/apps/backend/src/controllers/booking.controller.ts b/apps/backend/src/controllers/booking.controller.ts index 6b0633fd..f65a8073 100644 --- a/apps/backend/src/controllers/booking.controller.ts +++ b/apps/backend/src/controllers/booking.controller.ts @@ -112,7 +112,7 @@ export const getBooking = async (req: AuthRequest, res: Response) => { // Handle generic errors from mocks or other sources if (error instanceof Error) { const errorMessage = error.message; - + // Map common error messages to appropriate status codes if (errorMessage === 'Access denied') { return res.status(403).json({ @@ -124,7 +124,7 @@ export const getBooking = async (req: AuthRequest, res: Response) => { }, }); } - + if (errorMessage === 'Booking not found') { return res.status(404).json({ success: false, @@ -135,7 +135,7 @@ export const getBooking = async (req: AuthRequest, res: Response) => { }, }); } - + if (errorMessage === 'Property not found') { return res.status(404).json({ success: false, @@ -146,7 +146,7 @@ export const getBooking = async (req: AuthRequest, res: Response) => { }, }); } - + if (errorMessage === 'Host user not found') { return res.status(404).json({ success: false, diff --git a/apps/backend/src/services/sync.service.ts b/apps/backend/src/services/sync.service.ts index 50e86a90..1cbeb283 100644 --- a/apps/backend/src/services/sync.service.ts +++ b/apps/backend/src/services/sync.service.ts @@ -338,11 +338,11 @@ export class SyncService { } catch (error) { console.error(`Attempt ${attempt} failed to get current block height:`, error); if (attempt < maxRetries) { - await new Promise(resolve => setTimeout(resolve, retryDelay)); + await new Promise((resolve) => setTimeout(resolve, retryDelay)); } } } - + console.warn('All retries failed. Returning last known block.'); return this.lastProcessedBlock; } diff --git a/apps/backend/tests/integration/booking-integration.test.ts b/apps/backend/tests/integration/booking-integration.test.ts index a37e8012..5c0f67e6 100644 --- a/apps/backend/tests/integration/booking-integration.test.ts +++ b/apps/backend/tests/integration/booking-integration.test.ts @@ -1,7 +1,7 @@ +import { afterEach, beforeAll, beforeEach, describe, expect, it } from 'bun:test'; import express from 'express'; import request from 'supertest'; import type { Response } from 'supertest'; -import { describe, it, expect, beforeEach, afterEach, beforeAll } from 'bun:test'; import { createConflictBookingInput, createInvalidBookingInput, @@ -22,33 +22,35 @@ mock.module('../../src/config/supabase', () => ({ select: mock(() => ({ eq: mock(() => ({ single: mock(() => Promise.resolve({ data: null, error: null })), - then: mock((callback: any) => callback({ data: [], error: null })) + then: mock((callback: any) => callback({ data: [], error: null })), })), - then: mock((callback: any) => callback({ data: [], error: null })) + then: mock((callback: any) => callback({ data: [], error: null })), })), insert: mock(() => ({ select: mock(() => Promise.resolve({ data: [], error: null })), - then: mock((callback: any) => callback({ data: [], error: null })) + then: mock((callback: any) => callback({ data: [], error: null })), })), update: mock(() => ({ eq: mock(() => Promise.resolve({ data: [], error: null })), - then: mock((callback: any) => callback({ data: [], error: null })) + then: mock((callback: any) => callback({ data: [], error: null })), })), delete: mock(() => ({ eq: mock(() => Promise.resolve({ data: [], error: null })), - then: mock((callback: any) => callback({ data: [], error: null })) + then: mock((callback: any) => callback({ data: [], error: null })), })), upsert: mock(() => ({ eq: mock(() => Promise.resolve({ data: [], error: null })), - then: mock((callback: any) => callback({ data: [], error: null })) + then: mock((callback: any) => callback({ data: [], error: null })), })), - then: mock((callback: any) => callback({ data: [], error: null })) + then: mock((callback: any) => callback({ data: [], error: null })), })), auth: { - getUser: mock(() => Promise.resolve({ - data: { user: { id: 'test-user-id', email: 'test@example.com' } }, - error: null, - })), + getUser: mock(() => + Promise.resolve({ + data: { user: { id: 'test-user-id', email: 'test@example.com' } }, + error: null, + }) + ), }, }, })); @@ -348,8 +350,10 @@ describe('Booking Integration Tests', () => { supabase.from.mockReturnValue({ insert: mock(() => ({ select: mock(() => ({ - single: mock(() => Promise.resolve({ data: null, error: { message: 'Database error' } })) - })) + single: mock(() => + Promise.resolve({ data: null, error: { message: 'Database error' } }) + ), + })), })), }); diff --git a/apps/backend/tests/utils/booking-test.utils.ts b/apps/backend/tests/utils/booking-test.utils.ts index 0051e1e1..69ec61e1 100644 --- a/apps/backend/tests/utils/booking-test.utils.ts +++ b/apps/backend/tests/utils/booking-test.utils.ts @@ -15,33 +15,35 @@ mock.module('../../src/config/supabase', () => ({ select: mock(() => ({ eq: mock(() => ({ single: mock(() => Promise.resolve({ data: null, error: null })), - then: mock((callback: any) => callback({ data: [], error: null })) + then: mock((callback: any) => callback({ data: [], error: null })), })), - then: mock((callback: any) => callback({ data: [], error: null })) + then: mock((callback: any) => callback({ data: [], error: null })), })), insert: mock(() => ({ select: mock(() => Promise.resolve({ data: [], error: null })), - then: mock((callback: any) => callback({ data: [], error: null })) + then: mock((callback: any) => callback({ data: [], error: null })), })), update: mock(() => ({ eq: mock(() => Promise.resolve({ data: [], error: null })), - then: mock((callback: any) => callback({ data: [], error: null })) + then: mock((callback: any) => callback({ data: [], error: null })), })), delete: mock(() => ({ eq: mock(() => Promise.resolve({ data: [], error: null })), - then: mock((callback: any) => callback({ data: [], error: null })) + then: mock((callback: any) => callback({ data: [], error: null })), })), upsert: mock(() => ({ eq: mock(() => Promise.resolve({ data: [], error: null })), - then: mock((callback: any) => callback({ data: [], error: null })) + then: mock((callback: any) => callback({ data: [], error: null })), })), - then: mock((callback: any) => callback({ data: [], error: null })) + then: mock((callback: any) => callback({ data: [], error: null })), })), auth: { - getUser: mock(() => Promise.resolve({ - data: { user: { id: 'test-user-id', email: 'test@example.com' } }, - error: null, - })), + getUser: mock(() => + Promise.resolve({ + data: { user: { id: 'test-user-id', email: 'test@example.com' } }, + error: null, + }) + ), }, }, })); diff --git a/apps/web/src/components/guards/RoleGuard.tsx b/apps/web/src/components/guards/RoleGuard.tsx new file mode 100644 index 00000000..eb08919c --- /dev/null +++ b/apps/web/src/components/guards/RoleGuard.tsx @@ -0,0 +1,54 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { useEffect, useState } from 'react'; +import { useUserRole } from '~/hooks/useUserRole'; +import type { UserRole } from '~/types/roles'; + +interface RoleGuardProps { + children: React.ReactNode; + requiredRole: UserRole; + fallbackPath?: string; +} + +export function RoleGuard({ + children, + requiredRole, + fallbackPath = '/become-host', +}: RoleGuardProps) { + const roleInfo = useUserRole(); + const { canAccessHostDashboard, isLoading } = roleInfo; + const router = useRouter(); + const [isChecking, setIsChecking] = useState(true); + + useEffect(() => { + // Wait for role data to load + if (!isLoading) { + setIsChecking(false); + + // Redirect if user doesn't have required access + if (requiredRole === 'host' && !canAccessHostDashboard) { + router.push(fallbackPath); + } + } + }, [requiredRole, canAccessHostDashboard, isLoading, router, fallbackPath]); + + // Show loading state while checking authentication + if (isLoading || isChecking) { + return ( +
+
+
+

Verifying access...

+
+
+ ); + } + + // Return null during redirect to prevent flash of unauthorized content + if (requiredRole === 'host' && !canAccessHostDashboard) { + return null; + } + + return <>{children}; +} diff --git a/apps/web/src/hooks/useUserRole.tsx b/apps/web/src/hooks/useUserRole.tsx new file mode 100644 index 00000000..753421d8 --- /dev/null +++ b/apps/web/src/hooks/useUserRole.tsx @@ -0,0 +1,115 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { profileAPI } from '~/services/api'; +import type { RoleInfo, UserRole } from '~/types/roles'; +import { useAuth } from './auth/use-auth'; + +interface UseUserRoleReturn extends RoleInfo { + isLoading: boolean; +} + +export function useUserRole(): UseUserRoleReturn { + const { user, isAuthenticated } = useAuth(); + const [roleInfo, setRoleInfo] = useState({ + role: 'guest', + canAccessHostDashboard: false, + hasProperties: false, + }); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchUserRole = async () => { + if (!isAuthenticated || !user) { + setRoleInfo({ + role: 'guest', + canAccessHostDashboard: false, + hasProperties: false, + }); + 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; + + // 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 + canAccessHostDashboard = true; + } else if (hostStatus === 'verified') { + // Verified but no properties yet + role = 'host'; + canAccessHostDashboard = false; // No dashboard access without properties + } + + setRoleInfo({ + role, + hostStatus, + canAccessHostDashboard, + 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 + ); + + // 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) + ? (storedHostStatus as 'pending' | 'verified' | 'rejected' | 'suspended') + : undefined; + + 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; + } else if (hostStatus === 'verified') { + role = 'host'; + canAccessHostDashboard = false; + } + + setRoleInfo({ + role, + hostStatus, + canAccessHostDashboard, + hasProperties: storedHasProperties, + }); + } + } finally { + setIsLoading(false); + } + }; + + fetchUserRole(); + }, [user, isAuthenticated]); + + return { ...roleInfo, isLoading }; +} diff --git a/apps/web/src/types/index.ts b/apps/web/src/types/index.ts index d4befb2d..743a5bb8 100644 --- a/apps/web/src/types/index.ts +++ b/apps/web/src/types/index.ts @@ -45,6 +45,9 @@ export interface UserProfile { verified: boolean; memberSince: string; publicKey?: string; + // Host-related fields + hostStatus?: 'pending' | 'verified' | 'rejected' | 'suspended'; + hasProperties?: boolean; preferences?: { currency: string; language: string; diff --git a/apps/web/src/types/roles.ts b/apps/web/src/types/roles.ts new file mode 100644 index 00000000..8dc8c646 --- /dev/null +++ b/apps/web/src/types/roles.ts @@ -0,0 +1,12 @@ +// Role types for guest/host system +export type UserRole = 'guest' | 'host' | 'dual'; + +export type HostStatus = 'pending' | 'verified' | 'rejected' | 'suspended'; + +export interface RoleInfo { + role: UserRole; + hostStatus?: HostStatus; + canAccessHostDashboard: boolean; + hasProperties: boolean; + isLoading?: boolean; +} diff --git a/apps/web/src/types/shared.ts b/apps/web/src/types/shared.ts index e46f5afc..ed82aaed 100644 --- a/apps/web/src/types/shared.ts +++ b/apps/web/src/types/shared.ts @@ -12,6 +12,9 @@ export interface UserProfile { memberSince: string; totalBookings: number; totalSpent: number; + // Host-related fields + hostStatus?: 'pending' | 'verified' | 'rejected' | 'suspended'; + hasProperties?: boolean; preferences: { notifications: boolean; emailUpdates: boolean; diff --git a/apps/web/test-results/.last-run.json b/apps/web/test-results/.last-run.json index cbcc1fba..f740f7c7 100644 --- a/apps/web/test-results/.last-run.json +++ b/apps/web/test-results/.last-run.json @@ -1,4 +1,4 @@ { "status": "passed", "failedTests": [] -} \ No newline at end of file +} diff --git a/apps/web/tests/e2e/auth-guards.spec.ts b/apps/web/tests/e2e/auth-guards.spec.ts index 81459d19..c0ca8a0b 100644 --- a/apps/web/tests/e2e/auth-guards.spec.ts +++ b/apps/web/tests/e2e/auth-guards.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from '@playwright/test'; +import { expect, test } from '@playwright/test'; // Define the pages we'll be testing const PROTECTED_ROUTE = '/host/dashboard'; @@ -24,41 +24,47 @@ test.describe('Authentication Guards and Redirections', () => { test('should redirect an unauthenticated user to the login page', async ({ page }) => { // Navigate to a protected route without setting up any authentication await page.goto(PROTECTED_ROUTE); - + // Expect the URL to be the login page await expect(page).toHaveURL(new RegExp(PUBLIC_ROUTE)); }); - + // Test 2: Users with a valid session can access protected routes test('should allow an authenticated host to access the dashboard', async ({ page }) => { // Set up a valid host session in local storage await page.goto('/'); - await page.evaluate(({ user }) => { - localStorage.setItem('user', JSON.stringify(user)); - localStorage.setItem('authType', user.authType); - }, { user: hostEmailUser }); - + await page.evaluate( + ({ user }) => { + localStorage.setItem('user', JSON.stringify(user)); + localStorage.setItem('authType', user.authType); + }, + { user: hostEmailUser } + ); + // Navigate directly to the protected route await page.goto(PROTECTED_ROUTE); - + // The presence of a key element on the dashboard confirms successful access await expect(page.locator('h1:has-text("Host Dashboard")')).toBeVisible(); }); - + // Test 3: Users with a session but an invalid authType are redirected test('should redirect a user with an invalid auth type for the route', async ({ page }) => { // Set up a session for a tenant user await page.goto('/'); - await page.evaluate(({ user }) => { - localStorage.setItem('user', JSON.stringify(user)); - localStorage.setItem('authType', user.authType); - }, { user: tenantWalletUser }); - + await page.evaluate( + ({ user }) => { + localStorage.setItem('user', JSON.stringify(user)); + localStorage.setItem('authType', user.authType); + }, + { user: tenantWalletUser } + ); + // Navigate to a route that only allows email-based auth (for example) // The test assumes a route like /profile-email exists, which only allows 'email' auth. // Replace this with your actual route that checks 'allowedAuthTypes'. - await page.goto(PROTECTED_ROUTE); - + await page.goto(PROTECTED_ROUTE); + // Expect redirection to the login page await expect(page).toHaveURL(new RegExp(PUBLIC_ROUTE)); }); @@ -67,30 +73,36 @@ test.describe('Authentication Guards and Redirections', () => { test('should persist the session after page reload', async ({ page }) => { // Set up a valid host session await page.goto('/'); - await page.evaluate(({ user }) => { - localStorage.setItem('user', JSON.stringify(user)); - localStorage.setItem('authType', user.authType); - }, { user: hostEmailUser }); - + await page.evaluate( + ({ user }) => { + localStorage.setItem('user', JSON.stringify(user)); + localStorage.setItem('authType', user.authType); + }, + { user: hostEmailUser } + ); + // Navigate to the protected route await page.goto(PROTECTED_ROUTE); - + // Reload the page await page.reload(); - + // The host dashboard should still be visible without redirection await expect(page.locator('h1:has-text("Host Dashboard")')).toBeVisible(); }); - + // Test 5: Logout clears the session from local storage test('should clear local storage and redirect on logout', async ({ page }) => { // Set up a valid host session await page.goto('/'); - await page.evaluate(({ user }) => { - localStorage.setItem('user', JSON.stringify(user)); - localStorage.setItem('authType', user.authType); - }, { user: hostEmailUser }); - + await page.evaluate( + ({ user }) => { + localStorage.setItem('user', JSON.stringify(user)); + localStorage.setItem('authType', user.authType); + }, + { user: hostEmailUser } + ); + // Navigate to the dashboard await page.goto(PROTECTED_ROUTE); await expect(page.locator('h1:has-text("Host Dashboard")')).toBeVisible(); @@ -100,9 +112,9 @@ test.describe('Authentication Guards and Redirections', () => { // Expect the page to be redirected to the public route await expect(page).toHaveURL(new RegExp(PUBLIC_ROUTE)); - + // The session data should be cleared from local storage const localStorageData = await page.evaluate(() => localStorage.getItem('user')); expect(localStorageData).toBeNull(); }); -}); \ No newline at end of file +}); diff --git a/apps/web/tests/e2e/auth.spec.ts b/apps/web/tests/e2e/auth.spec.ts index 3d8d14ba..29165749 100644 --- a/apps/web/tests/e2e/auth.spec.ts +++ b/apps/web/tests/e2e/auth.spec.ts @@ -61,4 +61,4 @@ test.describe('Authentication and Session Management', () => { const errorMessage = await page.getByText('Datos de inicio de sesión inválidos'); await expect(errorMessage).toBeVisible(); }); -}); \ No newline at end of file +}); diff --git a/apps/web/tests/e2e/dashboards.spec.ts b/apps/web/tests/e2e/dashboards.spec.ts index 34c4df77..1a1759eb 100644 --- a/apps/web/tests/e2e/dashboards.spec.ts +++ b/apps/web/tests/e2e/dashboards.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from '@playwright/test'; +import { expect, test } from '@playwright/test'; // Define the URLs for the dashboard and login pages const DASHBOARD_URL = '/'; @@ -127,4 +127,4 @@ test.describe('Host Dashboard Functionality', () => { // Assert the new property card is visible on the page await expect(page.locator(`h3:has-text("${propertyTitle}")`)).toBeVisible(); }); -}); \ No newline at end of file +}); diff --git a/apps/web/tests/e2e/role-separation.spec.ts b/apps/web/tests/e2e/role-separation.spec.ts index a55b722e..a7d1abb5 100644 --- a/apps/web/tests/e2e/role-separation.spec.ts +++ b/apps/web/tests/e2e/role-separation.spec.ts @@ -56,4 +56,4 @@ test.describe('Tenant User Role Access', () => { // The tenant should be redirected to their own dashboard await expect(page).toHaveURL(/.*\/tenant\/dashboard/); }); -}); \ No newline at end of file +}); diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 6dd7ae48..07b8f614 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -1,10 +1,7 @@ { "compilerOptions": { // Enable latest features - "lib": [ - "ESNext", - "DOM" - ], + "lib": ["ESNext", "DOM"], "target": "ESNext", "module": "ESNext", "moduleDetection": "force", @@ -26,12 +23,8 @@ // Path aliases "baseUrl": ".", "paths": { - "~/*": [ - "./src/*" - ], - "@/*": [ - "./src/*" - ] + "~/*": ["./src/*"], + "@/*": ["./src/*"] }, "incremental": true, "esModuleInterop": true, @@ -49,7 +42,5 @@ "next-env.d.ts", "./.next/types/**/*.ts" ], - "exclude": [ - "node_modules" - ] + "exclude": ["node_modules"] }