Files
SweetHome/full-project-analysis.md
2026-08-06 23:51:50 +03:00

18 KiB

SweetHome — Full Project Analysis (Updated Aug 2026)

Project: SweetHome Next.js Real Estate Application Framework: Next.js 16.1.6 (App Router) + React 18.3.1 + React Compiler enabled Language: JavaScript (no TypeScript) Styling: Tailwind CSS v4, Framer Motion, lucide-react Backend: .NET REST API at https://45.93.137.91.nip.io/api (+ report API at http://45.93.137.91/api)


1. Project Structure

SweetHome/
├── middleware.js               # Root-level route protection (54 lines)
├── next.config.mjs             # reactCompiler + images remotePatterns
├── full-project-analysis.md    # This document
├── edit-property-flow.md       # Flutter edit-property investigation (backend bug doc)
├── security-audit-report.md    # Security findings
├── app/
│   ├── layout.js               # Root layout: fonts, metadata, providers
│   ├── ClientLayout.js         # Nav (desktop+mobile), language, user menu
│   ├── page.js                 # Home page (hero, map, features)
│   ├── components/             # 20+ reusable components
│   ├── contexts/               # Favorites, Notifications, Theme (+orphan Property)
│   ├── enums/                  # 24 enum files (index.js barrel)
│   ├── utils/
│   │   ├── api.js              # 70+ API functions + 4 fetch helpers
│   │   ├── ratings.js          # Rating API (own private apiFetch)
│   │   └── constants.js
│   ├── services/AuthService.js # JWT cookie storage, role detection
│   ├── i18n/config.js          # AR/EN translations (3173 lines)
│   ├── validations/            # OwnerValidatoin.js (email/phone/step validation)
│   ├── HelperFunction/         # ApiProperty.js mapper, UploadImage.js
│   ├── hooks/                  # useApplyFilters, useAuth, UseFilterOfHeroSearch
│   ├── auth/ blocked/ faq/ favorites/ login/ onboarding/ ...  # pages
│   └── owner/                  # Dashboard (7 sub-routes)
├── public/                     # APK, fonts, firebase-messaging-sw.js
└── package.json

Routes (~33 pages): Public (/, /login, /register/*, /properties, /property/[id], /terms, /privacy, /faq, /support, /onboarding) | Auth-protected (/profile, /reservations, /payments, /favorites, /booked-properties, /my-rates, /notifications, /reports, /settings, /change-password) | Owner-only (/owner/properties, /owner/properties/add, /owner/reservations, /owner/bookings, /owner/calendar, /owner/profits, /owner/account-book)


2. Architecture

Layout hierarchy

app/layout.js (server) — fonts (Geist + Madani Arabic), metadata, <html lang="ar" dir="rtl">
  └── <ThemeProvider>
      └── <NotificationsProvider>
          └── <FavoritesProvider>
              └── <ClientLayout>        # nav, user menu, language switcher
                  └── <main>{children}</main>
                  ├── <BottomNav />     # mobile (authenticated)
                  └── <NotificationHandler />  # Firebase FCM

Route protection (root middleware.js)

// middleware.js — reads cookies: auth_token + cached_user
if (!token && !isPublicRoute && !isAuthRoute) return NextResponse.redirect(new URL("/login", request.url));
if (token && isAuthRoute) return NextResponse.redirect(new URL("/", request.url));
if (IsOwner && !isOwner) return NextResponse.redirect(new URL("/", request.url));  // owner routes
if (IsPrivateRoute && !token) return NextResponse.redirect(new URL("/login", request.url));
  • Owner detection relies on cached_user cookie's roles array (not the JWT).
  • Static assets excluded via matcher regex.

Data flow

Page (useEffect) → apiFetch(endpoint, options) → AuthService.getToken() → Bearer JWT
  → fetch → unwrap { data } envelope → useState → render Tailwind UI

3. API Layer (app/utils/api.js, 783 lines)

Fetch helpers

Helper Lines Behavior
apiFetch 38-87 Auto Bearer, auto JSON-stringify (skips FormData), unwraps {data}, 206 tolerated, 451 → /blocked redirect, Arabic network error
authFetch 89-134 POST only, returns {status,data,ok,message} (no throw)
reportFetch 136-172 POST to REPORT_API_BASE (no auth)
multipartAuthFetch 418-448 POST + FormData + Bearer
async function apiFetch(endpoint, options = {}) {
  const token = AuthService.getToken();
  const headers = { ...(token && { Authorization: `Bearer ${token}` }), ...(options.headers || {}) };
  // ... FormData passthrough, JSON.stringify for objects
  // unwraps: if (json && "data" in json) return json.data;
}

Key API functions (endpoints)

Function Endpoint Method
getRentProperties /RentProperties/GetRentProperties GET
getRentProperty /RentProperties/GetRentPropertyById/{id} GET
getMyRentListings / getMySaleListings /RentProperties/GetMyRentListings, /SaleProperties/GetMySaleListings GET
addRentProperty /RentProperties/AddRentProperty (uses buildRentPropertyPayload) POST
editRentProperty /RentProperties/EditRentProperty/{id} — strict camelCase schema PUT
editSaleProperty /SaleProperties/EditSaleProperty/{id} PUT
updateRentPropertyStatus /RentProperties/UpdateStatus/{id} {status} PUT
uploadPicture /Files/UploadPicture (FormData image) → {value: "/Pictures/xxx.jpg"} POST
bookReservation /Reservations/BookReservation/book POST
getUserReservations /Reservations/GetUserResevations GET
getOwnerReservationRequests /Reservations/GetOwnerResevationRequests GET
ownerConfirmReservation /Reservations/OwnerConfirmReservation/owner-confirm/{id} PUT
payDeposit /Reservations/PayDeposit/pay-deposit (FormData) POST
getPaymentTypes /PaymentType/GetAll GET
getMyTransaction /Customer/GetMyTransaction GET
getOwnerStatistics /Statistics/GetOwnerStatistics GET
getOwnerContactInformation /Owner/GetOwnerContactInformation?propertyInformationId= GET
loginWithEmail /Auth/LogInWithEmail {credential,password,device:0} POST
loginWithPhone /Auth/LogInWithPhoneNumber POST
getCurrencies /Currency/GetAll GET
filterRentProperties /RentProperties/FilterRentProperties?{params} GET
getUserNotifications /Notifications/GetUserNotifications GET
setFCMToken /User/SetFCMToken {token, deviceType:2} POST
submitReservationReport /ReservationReports POST

buildRentPropertyPayload — city/governorate/documentType normalizer

export function buildRentPropertyPayload(data = {}) {
  const rawCityValue = data?.city ?? data?.governorate ?? propertyInformation?.city ?? 1;
  const cityInt = parseInt(rawCityValue, 10) || 1;
  return { ...data, city: cityInt, governorate: cityInt, documentType: data?.documentType ?? 1,
    propertyInformation: { ...propertyInformation, city: cityInt, governorate: cityInt, documentType: ... } };
}

editRentProperty — strict swagger-contract payload (current, works)

export async function editRentProperty(id, data) {
  const pi = data?.propertyInformation || {};
  const images = Array.isArray(pi.images) ? pi.images : [];
  const body = {
    propertyInformation: {
      activityStatus: 1, images: images.length > 0 ? images : [""],
      cordsX: pi.cordsX ?? null, cordsY: pi.cordsY ?? null,
      address: pi.address ?? "", description: pi.description ?? null,
      numberOfBathRooms: pi.numberOfBathRooms ?? 0, numberOfRooms: pi.numberOfRooms ?? 0,
      numberOfBedRooms: pi.numberOfBedRooms ?? 0, space: pi.space ?? 0,
      detailsJSON: pi.detailsJSON ?? "", buildingType: pi.buildingType ?? 0,
      status: pi.status ?? 0, propertyType: pi.propertyType ?? 0, city: data.city ?? pi.city ?? 1,
    },
    deposit: data.deposit ?? 0, acceptedCertificate: data.acceptedCertificate ?? 0,
    monthlyRent: data.monthlyRent ?? 0, dailyRent: data.dailyRent ?? 0,
    rating: data.rating ?? 1, currencyId: data.currencyId ?? 1, rentType: data.rentType ?? 0,
    isSmokeAllow: data.isSmokeAllow ?? false, specializedFor: data.specializedFor ?? false,
    isVisitorAllow: data.isVisitorAllow ?? false, allowedPaymentPeriod: data.allowedPaymentPeriod ?? "",
    type: data.type ?? 0,
  };
  return apiFetch(`/RentProperties/EditRentProperty/${id}`, { method: "PUT", body });
}

4. Auth (app/services/AuthService.js, 219 lines)

  • Storage: js-cookieauth_token (7d, secure, sameSite=lax), cached_user.
  • Role detection: JWT claims via atob(payload); roles from http://schemas.microsoft.com/ws/2008/06/identity/claims/role (string or array).
  • getUser() merges cookie profile (name/email/phone) + JWT (id from nameidentifier/sub).
  • login() flow: loginWithEmail/Phone → 200 = SUCCESS + cacheCurrentUser(); 206 = OTP_REQUIRED.
getRoles() {
  const payload = this.decodeToken();
  const roles = payload["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"];
  return Array.isArray(roles) ? roles : typeof roles === "string" ? [roles] : [];
}
isOwner() { return this.getRoles().includes("Owner"); }
isAgent() { return this.getRoles().includes("RealEstateAgent"); }

Bug: login() calls sendEmailOTP()/sendPhoneOTP() (lines 203-204) but they are not imported → ReferenceError on OTP-required login.


5. Owner Dashboard

Page Lines Data source Endpoints
/owner/reservations 1537 (active from 646) Real API GET GetOwnerResevationRequests + batch GetRentProperties enrichment; PUT OwnerConfirmReservation/owner-confirm/{id}; POST ChangeReservationStatus?id=&newStatus=5; POST ReservationReports/ReportReservation
/owner/properties 2270 Real API + localStorage GET GetMyRentListings/GetMySaleListings; PUT EditRentProperty/{id}; PUT EditSaleProperty/{id}; PUT UpdateStatus/{id}; POST UploadPicture. Delete is local-only
/owner/properties/add 1712 Real API POST AddRentProperty/AddSaleProperty; POST UploadPicture; GET Currency/GetAll; Nominatim geocoding
/owner/bookings 600 Real API GET GetOwnerResevationRequests
/owner/calendar 743 Mock/localStorage None (seeds fake properties; calls undefined loadCalendar())
/owner/profits 594 Mock/localStorage None (hard-coded sampleData, 5% commission, XLSX export)
/owner/account-book 142 Real API GET Statistics/GetOwnerStatistics

Owner reservations page — key code

// loadReservations (1259-1314): parallel fetch + batch enrich
const [resResult, rentProps] = await Promise.all([
  fetch(`${API_BASE}/Reservations/GetOwnerResevationRequests`, { headers: { Authorization: `Bearer ${token}` } })
    .then(async (res) => { ... return list; }),
  getRentProperties().catch(() => []),
]);
const propMap = {};
propsList.forEach(rp => {
  const info = rp?.propertyInformation ?? {};
  propMap[rp.propertyInformationId] = info;
  if (rp?.propertyInformation?.id) propMap[rp.propertyInformation.id] = info;
});
const enriched = resResult.map(r => { if (r.propertyId && propMap[r.propertyId]) r._prop = propMap[r.propertyId]; return r; });

Owner properties — edit modal

  • formData: propertyType, furnished, description, bedrooms/bathrooms/floor/salons/balconies/livingRooms/area, services {}, serviceDetails {}, terms {}, customTerms[], 6 nearby distances, purpose, currencyId, daily/monthlyPrice, deposit, rentType, allowedPaymentPeriod, salePrice.
  • Services: 13 enum-based (PropertyService) checkboxes with detail inputs.
  • Terms: 3 enum-based (PropertyTerm) + custom terms chips.
  • Images: existingImages (API) + newImages (upload via uploadPicture, preview via URL.createObjectURL, max 10, 5MB each) with delete/reorder.
  • Validation: description, bedrooms≥1, bathrooms≥1, area>0, price required.
  • Save: builds detailsJSONpropInfoeditRentProperty(property.id, payload).

6. Customer Pages

Page Lines Data source
/reservations 1826 (active from 391) getUserReservations + getRentProperties enrich + getPaymentTypes
/payments 600 getMyTransaction + getPaymentTypes
/property/[id] 1231 getRentPropertygetSalePropertyByIdgetSaleProperty cascade
/properties 851 FilterRentProperties + getSaleProperties hybrid
/ home HomeClient 71 + hooks getRentProperties + getSaleProperties client-side filter
/booked-properties 220 getUserReservations
/favorites 128 FavoritesContext
/my-rates 148 getCustomerRatings
/notifications 159 getUserNotifications (mark-read client-only)

Customer reservations — PaymentDialog (Haram receipt flow)

const selectedMethod = paymentMethods?.find((m) => String(m?.id ?? m?.name) === String(selectedPayment));
const isHaram = selectedMethod?.name?.toLowerCase?.() === "haram";
const canPay = payingId !== reservation.id && hasSelectedMethod && (!isHaram || !!receiptImage);
// handleConfirmPay: payDeposit({ reservationId, paymentTypeId: selectedPayment, comment, paymentImage: receiptImage })
// payDeposit → FormData: ReservationId, PaymentTypeId, Comment, paymentImage (multipart)
  • CountdownTimer: deadline = ownerApprovalDate + allowedPaymentPeriod (parseTimeSpan handles d.hh:mm:ss).
  • Inline rating form (4 categories) for depositPaid/completed.

Property detail

  • Image gallery (chevron prev/next, thumbnails, counter), Leaflet map, availability calendar (availableDatesSet from GetAvailableDates/available/{id}), booking flow (daily/monthly toggle, calendar month grid, summary, bookReservation).
  • Unused: owner-contact feature (getOwnerContactInformation defined but no UI), handleBookNow/bookingDates dead code.

7. i18n (app/i18n/config.js, 3173 lines)

  • 2 languages: en (1-1662), ar (1663-3155); flat dot-namespaced keys; fallbackLng: "en".
  • Init: i18n.use(LanguageDetector).use(initReactI18next).init({ resources, fallbackLng: "en", detection: { order: ["localStorage", "navigator"], caches: ["localStorage"] } }).
  • ClientLayout overrides: localStorage("language") default "ar", flips document.documentElement.dir (rtl/ltr).
  • Active language toggle: mobile header + authenticated sticky bar (desktop navbar one commented out).

8. State Management

Context Status
FavoritesProvider (app/contexts/FavoritesContext.js) Mounted in layout.js; favorites list, add/remove (optimistic w/ rollback), refetch
NotificationsProvider (app/contexts/NotificationsContext.js) Mounted; unreadCount = full array length; markAsRead is client-only
ThemeProvider Mounted
PropertyContext (contexts/ + utils/ duplicate) Orphaned — never mounted/imported; pure in-memory mock

9. Enums (24 files in app/enums/)

Key ones: BuildingType (0-9 numeric), PropertyService (13 string), PropertyTerm (3 string), PropertyStatus (0/1/2), BookingStatus (string, ⚠ mixed casing PENDING vs ownerConfirmed), RentType (MONTHLY 0, DAILY 1), Currency (SYP 1, USD 2, EUR 3, TRY 4), Governorate (13), DocumentType (Passport/IDCard/Both), UserRole, City (Arabic values + CityEnum 0-12), RentPropertyCondition, RentPropertyType, TransactionType, CancellationReason.


10. Known Bugs & Issues

# Issue Location
1 sendEmailOTP/sendPhoneOTP used but not imported → ReferenceError on 206 AuthService.js 203-204
2 Edit endpoint ignores images + detailsJSON (backend bug) Backend PUT handler
3 Delete property is local-only (no API call) owner/properties/page.js 1657-1666
4 /owner/calendar calls undefined loadCalendar() (line 499) owner/calendar/page.js
5 /owner/calendar + /owner/profits are mock/localStorage only
6 useApplyFilters.js:62 price bucket "2000-3000" compares > 300 (typo, should be 3000) hooks
7 HeroSearch propertyType onChange writes to filters.city (line 69) components/home/HeroSearch.js
8 addOwner appends both back-ID + license images under same key RearIdCarImagePath api.js 470-471
9 getSaleProperty fetches whole list and filters client-side api.js 194
10 Notifications markAsRead client-only (no persistence) contexts + page
11 BookingCalendar.js date click is a no-op components/property
12 BookingStatus enum mixed casing enums/BookingStatus.js
13 Ratings API has own private apiFetch without 451 handling utils/ratings.js
14 home/PropertyMap.js:162 quick-book uses alert() stub components/home
15 Hardcoded phone +963567823411 in payments + reservations multiple
16 Firebase config hard-coded in NotificationHandler.js components
17 Owner-contact feature dead on property detail PropertyDetail.js 269-361

11. Dead Code

  • app/contexts/PropertyContext.js + app/utils/PropertyContext.js (both orphaned)
  • reservations/page.js lines 1-390, profits/page.js lines 1-291, StarRating.js 1-94, PropertyRatingList.js 1-151, PropertyRatingForm.js 1-219 (commented legacy)
  • handleBookNow/bookingDates in PropertyDetail.js (383-403)
  • Desktop language switcher (ClientLayout 259-268, commented)

12. Dependencies

Prod (20): next 16.1.6, react 18.3.1, firebase, leaflet/react-leaflet, framer-motion, lucide-react, react-hot-toast, i18next/react-i18next/browser-languagedetector, jspdf, html2canvas, xlsx, react-intersection-observer, flowbite/flowbite-react (unused), @pbe/react-yandex-maps (unused), js-cookie.

Dev (7): tailwindcss v4, @tailwindcss/postcss, postcss, autoprefixer, daisyui (unused), babel-plugin-react-compiler.

Missing: TypeScript, ESLint, tests, state library, data-fetching library, form library, HTTP client, JWT lib.

13. Security (summary — see security-audit-report.md)

# Finding Severity
1 JWT in cookie (secure flag OK) but roles in client-decodable cookie High
2 Client-side role gating (cached_user cookie editable) High
3 Passwords passed via URL query params (changePassword, resetPassword) Critical
4 Stored XSS via Leaflet HTML popups Critical
5 OTP codes logged to console Critical
6 IDOR on property edit/status APIs (no server ownership check) Critical
7 Firebase config public Medium
8 Hardcoded IPs/credentials across files Medium