3 Commits

Author SHA1 Message Date
fc932dbc7e fix(security): resolve audit findings — logging, endpoints, contact details
- Stop logging submitted password forms to the browser console
- Drive analytics from NEXT_PUBLIC_UMAMI_* instead of a hardcoded vendor
  script URL and site ID; renders nothing when unset
- Replace the wildcard image remote host "**" with an explicit allowlist
  (adds DigitalOcean Spaces)
- Fix the socket URL fallback to the API port (:3001, was :4000)
- Replace placeholder and personal contact emails with support@re-quest.com,
  including the privacy policy and terms pages

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 11:30:46 +05:30
9c0e7457e5 Merge pull request 'Added home page with dashboard content and SEO metadata' (#2) from fix/profile-contact-improvements into main
Reviewed-on: #2
2026-06-23 11:44:37 +00:00
pradeepkumar
a07484af7e refactor: decouple /home from cross-route page/layout imports
Move /home into the (user) route group so it inherits UserLayout via the
router instead of importing (user)/layout.tsx manually, and render shared
HomeDashboard content in both /home and /user/dashboard instead of importing
the dashboard route's default page export. Removes the fragile cross-route
coupling while keeping the same URL, SEO metadata, and rendered output.
2026-06-23 17:13:55 +05:30
14 changed files with 94 additions and 95 deletions

View File

@@ -7,18 +7,11 @@ const nextConfig: NextConfig = {
// Image optimization // Image optimization
images: { images: {
remotePatterns: [ remotePatterns: [
{ { protocol: "https", hostname: "*.contabostorage.com" },
protocol: "https", { protocol: "https", hostname: "*.amazonaws.com" },
hostname: "**", { protocol: "https", hostname: "*.digitaloceanspaces.com" },
}, { protocol: "http", hostname: "localhost" },
{ { protocol: "http", hostname: "127.0.0.1" },
protocol: "http",
hostname: "localhost",
},
{
protocol: "http",
hostname: "127.0.0.1",
},
], ],
// Don't proxy external images through Next.js server // Don't proxy external images through Next.js server
// Avoids SSL cert issues with Contabo S3 (sin1.contabostorage.com) // Avoids SSL cert issues with Contabo S3 (sin1.contabostorage.com)

View File

@@ -3,8 +3,8 @@
import { SettingsSidebar, PasswordSecurityForm } from '@/components/settings'; import { SettingsSidebar, PasswordSecurityForm } from '@/components/settings';
export default function PasswordSecurityPage() { export default function PasswordSecurityPage() {
const handleSave = (data: { currentPassword: string; newPassword: string }) => { const handleSave = (_data: { currentPassword: string; newPassword: string }) => {
console.log('Updating agent password:', data); // TODO: call the change-password API. Do not log password payloads.
}; };
return ( return (

View File

@@ -0,0 +1,11 @@
import { HomeDashboard } from '@/components/home/HomeDashboard';
export const metadata = {
title: 'RE-Quest - Connect with Trusted Real Estate Professionals',
description:
'Discover verified real estate professionals for buying, selling, renting, and investing. Search by location, specialization, and expertise to connect with trusted agents on RE-Quest.',
};
export default function HomePage() {
return <HomeDashboard />;
}

View File

@@ -1,53 +1,7 @@
'use client'; 'use client';
import { useState, useEffect } from 'react'; import { HomeDashboard } from '@/components/home/HomeDashboard';
import { HeroSection } from '@/components/home/HeroSection';
import { FeaturesSection } from '@/components/home/FeaturesSection';
import { TopProfessionals } from '@/components/home/TopProfessionals';
import { TestimonialsSection } from '@/components/home/TestimonialsSection';
import { cmsService, resolveImageUrl } from '@/services/cms.service';
import type { HeroContent, FeaturesContent, TopProfessionalsContent, TestimonialsContent, CmsContentRecord } from '@/types/cms';
export default function UserDashboard() { export default function UserDashboard() {
const [cmsData, setCmsData] = useState<Record<string, unknown>>({}); return <HomeDashboard />;
const [cmsLoaded, setCmsLoaded] = useState(false);
useEffect(() => {
const fetchCms = async () => {
try {
const sections = await cmsService.getPageContent('landing');
const data: Record<string, unknown> = {};
for (const s of sections) {
const content = s.content as Record<string, unknown>;
// Resolve S3 keys in image fields
if (s.sectionKey === 'features' && Array.isArray(content.features)) {
for (const feat of content.features as { iconPath?: string }[]) {
if (feat.iconPath) feat.iconPath = await resolveImageUrl(feat.iconPath);
}
}
if (s.sectionKey === 'testimonials' && Array.isArray(content.stats)) {
for (const stat of content.stats as { iconPath?: string }[]) {
if (stat.iconPath) stat.iconPath = await resolveImageUrl(stat.iconPath);
}
}
data[s.sectionKey] = content;
}
setCmsData(data);
} catch {
// Use default content on error
} finally {
setCmsLoaded(true);
}
};
fetchCms();
}, []);
return (
<div>
<HeroSection content={cmsData.hero as HeroContent | undefined} />
{cmsLoaded && <FeaturesSection content={cmsData.features as FeaturesContent | undefined} />}
<TopProfessionals content={cmsData.topProfessionals as TopProfessionalsContent | undefined} />
<TestimonialsSection content={cmsData.testimonials as TestimonialsContent | undefined} />
</div>
);
} }

View File

@@ -3,8 +3,8 @@
import { SettingsSidebar, PasswordSecurityForm } from '@/components/settings'; import { SettingsSidebar, PasswordSecurityForm } from '@/components/settings';
export default function UserPasswordSecurityPage() { export default function UserPasswordSecurityPage() {
const handleSave = (data: { currentPassword: string; newPassword: string }) => { const handleSave = (_data: { currentPassword: string; newPassword: string }) => {
console.log('Updating user password:', data); // TODO: call the change-password API. Do not log password payloads.
}; };
return ( return (

View File

@@ -31,7 +31,7 @@ interface ContactCta {
const defaultContactDetails: ContactDetails = { const defaultContactDetails: ContactDetails = {
title: 'Get In Touch', title: 'Get In Touch',
description: 'Have a question about a property or need assistance? Fill out the form below and our team will get back to you shortly.', description: 'Have a question about a property or need assistance? Fill out the form below and our team will get back to you shortly.',
email: '123support@gmail.com', email: 'support@re-quest.com',
phone: '1234567890', phone: '1234567890',
phoneHours: 'Mon-Fri 9am-6pm', phoneHours: 'Mon-Fri 9am-6pm',
officeAddress: '123 Market Street', officeAddress: '123 Market Street',

View File

@@ -262,7 +262,7 @@ export default function FAQPage() {
Start Live Chat Start Live Chat
</Link> </Link>
<a <a
href="mailto:support@requesn.com" href="mailto:support@re-quest.com"
className="flex items-center justify-center gap-2 w-[174px] h-[51px] border border-[#00293d] rounded-[7px] font-fractul text-[16px] text-[#00293d] hover:bg-gray-50 transition-colors" className="flex items-center justify-center gap-2 w-[174px] h-[51px] border border-[#00293d] rounded-[7px] font-fractul text-[16px] text-[#00293d] hover:bg-gray-50 transition-colors"
> >
<Image <Image

View File

@@ -1,16 +0,0 @@
import UserLayout from "../(user)/layout";
import DashboardPage from "../(user)/user/dashboard/page";
export const metadata = {
title: "RE-Quest - Connect with Trusted Real Estate Professionals",
description:
"Discover verified real estate professionals for buying, selling, renting, and investing. Search by location, specialization, and expertise to connect with trusted agents on RE-Quest.",
};
export default function HomePage() {
return (
<UserLayout>
<DashboardPage />
</UserLayout>
);
}

View File

@@ -158,12 +158,15 @@ export default function RootLayout({
<NotificationProvider /> <NotificationProvider />
{children} {children}
</SessionProvider> </SessionProvider>
{/* Umami analytics — loaded after page becomes interactive */} {/* Umami analytics — set NEXT_PUBLIC_UMAMI_URL and NEXT_PUBLIC_UMAMI_WEBSITE_ID to enable */}
<Script {process.env.NEXT_PUBLIC_UMAMI_URL &&
src="https://analytics.superlabs.co/script.js" process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID && (
data-website-id="00e1ce31-e174-4519-8b59-63e8d4556b01" <Script
strategy="afterInteractive" src={process.env.NEXT_PUBLIC_UMAMI_URL}
/> data-website-id={process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID}
strategy="afterInteractive"
/>
)}
{/* Microsoft Clarity */} {/* Microsoft Clarity */}
{process.env.NEXT_PUBLIC_CLARITY_ID && ( {process.env.NEXT_PUBLIC_CLARITY_ID && (
<Script id="ms-clarity" strategy="afterInteractive"> <Script id="ms-clarity" strategy="afterInteractive">

View File

@@ -477,10 +477,10 @@ export default function PrivacyPolicyPage() {
<p className="mb-1"> <p className="mb-1">
Email:{' '} Email:{' '}
<a <a
href="mailto:request.sha@gmail.com" href="mailto:support@re-quest.com"
className="text-[#e58625] underline hover:opacity-80" className="text-[#e58625] underline hover:opacity-80"
> >
request.sha@gmail.com support@re-quest.com
</a> </a>
</p> </p>
<p>Address: 1975 Peralta Point, Colorado Springs, CO 80910</p> <p>Address: 1975 Peralta Point, Colorado Springs, CO 80910</p>

View File

@@ -163,7 +163,7 @@ export default function TermsOfServicePage() {
<p className="font-serif text-[15px] leading-[24px] text-[#00293d] mb-4"> <p className="font-serif text-[15px] leading-[24px] text-[#00293d] mb-4">
If you experience any threatening, abusive, or suspicious behavior from another user, please report the If you experience any threatening, abusive, or suspicious behavior from another user, please report the
interaction immediately using the in-app reporting feature or by contacting us at interaction immediately using the in-app reporting feature or by contacting us at
officialteam.request@gmail.com. RE-Quest will investigate reported incidents and take appropriate action, support@re-quest.com. RE-Quest will investigate reported incidents and take appropriate action,
which may include account suspension or referral to law enforcement. which may include account suspension or referral to law enforcement.
</p> </p>
@@ -464,7 +464,7 @@ export default function TermsOfServicePage() {
If you have any questions or concerns about these Terms, please contact us: If you have any questions or concerns about these Terms, please contact us:
</p> </p>
<p className="font-serif text-[15px] leading-[24px] text-[#00293d] mb-1"> <p className="font-serif text-[15px] leading-[24px] text-[#00293d] mb-1">
<span className="font-bold">Email:</span> officialteam.request@gmail.com <span className="font-bold">Email:</span> support@re-quest.com
</p> </p>
<p className="font-serif text-[15px] leading-[24px] text-[#00293d]"> <p className="font-serif text-[15px] leading-[24px] text-[#00293d]">
<span className="font-bold">Address:</span> 1975 Peralta Point, Colorado Springs, CO 80910 <span className="font-bold">Address:</span> 1975 Peralta Point, Colorado Springs, CO 80910

View File

@@ -0,0 +1,54 @@
'use client';
import { useState, useEffect } from 'react';
import { HeroSection } from '@/components/home/HeroSection';
import { FeaturesSection } from '@/components/home/FeaturesSection';
import { TopProfessionals } from '@/components/home/TopProfessionals';
import { TestimonialsSection } from '@/components/home/TestimonialsSection';
import { cmsService, resolveImageUrl } from '@/services/cms.service';
import type { HeroContent, FeaturesContent, TopProfessionalsContent, TestimonialsContent } from '@/types/cms';
// Shared landing/dashboard content rendered by both /home and /user/dashboard.
export function HomeDashboard() {
const [cmsData, setCmsData] = useState<Record<string, unknown>>({});
const [cmsLoaded, setCmsLoaded] = useState(false);
useEffect(() => {
const fetchCms = async () => {
try {
const sections = await cmsService.getPageContent('landing');
const data: Record<string, unknown> = {};
for (const s of sections) {
const content = s.content as Record<string, unknown>;
// Resolve S3 keys in image fields
if (s.sectionKey === 'features' && Array.isArray(content.features)) {
for (const feat of content.features as { iconPath?: string }[]) {
if (feat.iconPath) feat.iconPath = await resolveImageUrl(feat.iconPath);
}
}
if (s.sectionKey === 'testimonials' && Array.isArray(content.stats)) {
for (const stat of content.stats as { iconPath?: string }[]) {
if (stat.iconPath) stat.iconPath = await resolveImageUrl(stat.iconPath);
}
}
data[s.sectionKey] = content;
}
setCmsData(data);
} catch {
// Use default content on error
} finally {
setCmsLoaded(true);
}
};
fetchCms();
}, []);
return (
<div>
<HeroSection content={cmsData.hero as HeroContent | undefined} />
{cmsLoaded && <FeaturesSection content={cmsData.features as FeaturesContent | undefined} />}
<TopProfessionals content={cmsData.topProfessionals as TopProfessionalsContent | undefined} />
<TestimonialsSection content={cmsData.testimonials as TestimonialsContent | undefined} />
</div>
);
}

View File

@@ -270,10 +270,10 @@ export function SubscriptionForm() {
{/* Support Email */} {/* Support Email */}
<Link <Link
href="mailto:support@example.com" href="mailto:support@re-quest.com"
className="font-serif font-bold text-[14px] text-[#e58625] underline hover:text-[#d47920] transition-colors" className="font-serif font-bold text-[14px] text-[#e58625] underline hover:text-[#d47920] transition-colors"
> >
support@example.com support@re-quest.com
</Link> </Link>
</div> </div>

View File

@@ -59,7 +59,7 @@ class SocketService {
} }
// Extract base URL without /api/v1 path for Socket.io connection // Extract base URL without /api/v1 path for Socket.io connection
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000'; const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api/v1';
const baseUrl = apiUrl.replace(/\/api\/v1\/?$/, ''); const baseUrl = apiUrl.replace(/\/api\/v1\/?$/, '');
this.socket = io(baseUrl, { this.socket = io(baseUrl, {