diff --git a/public/assets/icons/copy-icon.svg b/public/assets/icons/copy-icon.svg
new file mode 100644
index 0000000..7bd31cc
--- /dev/null
+++ b/public/assets/icons/copy-icon.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/public/assets/icons/dashboard-white-icon.svg b/public/assets/icons/dashboard-white-icon.svg
new file mode 100644
index 0000000..8967d9c
--- /dev/null
+++ b/public/assets/icons/dashboard-white-icon.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/public/assets/icons/download-white-icon.svg b/public/assets/icons/download-white-icon.svg
new file mode 100644
index 0000000..ca7a49d
--- /dev/null
+++ b/public/assets/icons/download-white-icon.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/public/assets/icons/payment-success-icon.svg b/public/assets/icons/payment-success-icon.svg
new file mode 100644
index 0000000..3b1058f
--- /dev/null
+++ b/public/assets/icons/payment-success-icon.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/public/assets/icons/sort-arrow-down-icon.svg b/public/assets/icons/sort-arrow-down-icon.svg
new file mode 100644
index 0000000..342fb98
--- /dev/null
+++ b/public/assets/icons/sort-arrow-down-icon.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/public/assets/icons/verified-shield-green-icon.svg b/public/assets/icons/verified-shield-green-icon.svg
new file mode 100644
index 0000000..42544e4
--- /dev/null
+++ b/public/assets/icons/verified-shield-green-icon.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/app/(agent)/agent/settings/billings/confirmation/page.tsx b/src/app/(agent)/agent/settings/billings/confirmation/page.tsx
new file mode 100644
index 0000000..44c2fbd
--- /dev/null
+++ b/src/app/(agent)/agent/settings/billings/confirmation/page.tsx
@@ -0,0 +1,18 @@
+'use client';
+
+import { SettingsSidebar } from '@/components/settings';
+import { PaymentSuccessForm } from '@/components/settings/PaymentSuccessForm';
+
+export default function PaymentConfirmationPage() {
+ return (
+
+ {/* Left Sidebar */}
+
+
+ {/* Main Content */}
+
+
+ );
+}
diff --git a/src/app/(agent)/agent/settings/testimonials/page.tsx b/src/app/(agent)/agent/settings/testimonials/page.tsx
index 86e37e4..0c58a7d 100644
--- a/src/app/(agent)/agent/settings/testimonials/page.tsx
+++ b/src/app/(agent)/agent/settings/testimonials/page.tsx
@@ -1,6 +1,7 @@
'use client';
import { SettingsSidebar } from '@/components/settings';
+import { TestimonialsForm } from '@/components/settings/TestimonialsForm';
export default function TestimonialsPage() {
return (
@@ -10,15 +11,7 @@ export default function TestimonialsPage() {
{/* Main Content */}
-
-
- Add Testimonials
-
-
-
- Testimonials management coming soon.
-
-
+
);
diff --git a/src/components/settings/PaymentCheckoutForm.tsx b/src/components/settings/PaymentCheckoutForm.tsx
index 76b3c21..91bb792 100644
--- a/src/components/settings/PaymentCheckoutForm.tsx
+++ b/src/components/settings/PaymentCheckoutForm.tsx
@@ -2,6 +2,7 @@
import { useState } from 'react';
import Image from 'next/image';
+import { useRouter } from 'next/navigation';
interface CheckoutFeature {
label: string;
@@ -15,11 +16,13 @@ const CHECKOUT_FEATURES: CheckoutFeature[] = [
export function PaymentCheckoutForm() {
const [isProcessing, setIsProcessing] = useState(false);
+ const router = useRouter();
const handlePayNow = async () => {
setIsProcessing(true);
// TODO: Integrate with payment gateway
- setTimeout(() => setIsProcessing(false), 2000);
+ // For now, navigate to confirmation page
+ router.push('/agent/settings/billings/confirmation');
};
return (
diff --git a/src/components/settings/PaymentSuccessForm.tsx b/src/components/settings/PaymentSuccessForm.tsx
new file mode 100644
index 0000000..29eba57
--- /dev/null
+++ b/src/components/settings/PaymentSuccessForm.tsx
@@ -0,0 +1,158 @@
+'use client';
+
+import Image from 'next/image';
+import { useRouter } from 'next/navigation';
+
+interface TransactionDetail {
+ label: string;
+ value: string;
+ bold?: boolean;
+}
+
+const TRANSACTION_DETAILS: TransactionDetail[] = [
+ { label: 'Transaction ID', value: 'REQ-89234475', bold: true },
+ { label: 'Amount Paid', value: '$499.00', bold: true },
+ { label: 'Date', value: 'Oct 24, 2024', bold: true },
+];
+
+export function PaymentSuccessForm() {
+ const router = useRouter();
+
+ return (
+ <>
+ {/* Page Title */}
+
+
+ Payments
+
+
+ Manage Your billing cycles , upgrade your plan , or boost individual listings
+
+
+
+ {/* Payment Success Card */}
+
+ {/* Success Icon */}
+
+
+
+
+ {/* Success Title */}
+
+ Payment Successful !
+
+
+ {/* Success Description */}
+
+ Your annual membership is now active. You have full access to all premium features
+
+
+ {/* Transaction Details Section */}
+
+ {/* Header Row */}
+
+
+ Transaction Details
+
+
+
+
+ Verified User
+
+
+
+
+ {/* Detail Rows */}
+
+ {TRANSACTION_DETAILS.map((detail) => (
+
+
+ {detail.label}
+
+
+ {detail.value}
+
+
+ ))}
+
+
+ {/* Divider */}
+
+
+ {/* Next Renewal Date */}
+
+
+ Next Renewal Date
+
+
+ Oct 24, 2025
+
+
+
+ {/* Action Buttons */}
+
+ router.push('/agent/dashboard')}
+ className="flex-1 bg-[#e58625] border border-[#e58625] text-white font-fractul font-bold text-[14px] py-4 rounded-[15px] hover:bg-[#d47920] transition-colors flex items-center justify-center gap-2"
+ >
+
+ Go To Dashboard
+
+
+
+ Download Receipt
+
+
+
+ {/* Trust Badges */}
+
+
+
+
+ Verified User
+
+
+
+
+
+ Secure Checkout
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/settings/TestimonialsForm.tsx b/src/components/settings/TestimonialsForm.tsx
new file mode 100644
index 0000000..550b5a5
--- /dev/null
+++ b/src/components/settings/TestimonialsForm.tsx
@@ -0,0 +1,227 @@
+'use client';
+
+import { useState } from 'react';
+import Image from 'next/image';
+
+interface Testimonial {
+ id: number;
+ rating: number;
+ text: string;
+ authorName: string;
+ authorType: string;
+ date: string;
+}
+
+const LATEST_TESTIMONIALS: Testimonial[] = [
+ {
+ id: 1,
+ rating: 5,
+ text: 'Brian was amazing to work with. He found us the perfect home quickly and made the entire process smooth and stress-free.',
+ authorName: 'Neha R',
+ authorType: 'Home Buyer',
+ date: '2 days ago',
+ },
+ {
+ id: 2,
+ rating: 5,
+ text: 'Brian was amazing to work with. He found us the perfect home quickly and made the entire process smooth and stress-free.',
+ authorName: 'Neha R',
+ authorType: 'Home Buyer',
+ date: 'April,23,2023',
+ },
+ {
+ id: 3,
+ rating: 5,
+ text: 'Brian was amazing to work with. He found us the perfect home quickly and made the entire process smooth and stress-free.',
+ authorName: 'Neha R',
+ authorType: 'Home Buyer',
+ date: '2 days ago',
+ },
+];
+
+const OLDEST_TESTIMONIALS: Testimonial[] = [
+ {
+ id: 4,
+ rating: 5,
+ text: 'Brian was amazing to work with. He found us the perfect home quickly and made the entire process smooth and stress-free.',
+ authorName: 'Neha R',
+ authorType: 'Investor',
+ date: '5 Months ago',
+ },
+ {
+ id: 5,
+ rating: 5,
+ text: 'Brian was amazing to work with. He found us the perfect home quickly and made the entire process smooth and stress-free.',
+ authorName: 'Mark R',
+ authorType: 'Home Seller',
+ date: '6 Months ago',
+ },
+ {
+ id: 6,
+ rating: 5,
+ text: 'Brian was amazing to work with. He found us the perfect home quickly and made the entire process smooth and stress-free.',
+ authorName: 'Alex P',
+ authorType: 'Home Buyer',
+ date: '8 Months ago',
+ },
+];
+
+function StarRating({ rating }: { rating: number }) {
+ return (
+
+ {Array.from({ length: rating }).map((_, i) => (
+
+ ))}
+
+ );
+}
+
+function TestimonialCard({ testimonial }: { testimonial: Testimonial }) {
+ return (
+
+
+
+ {testimonial.text}
+
+
+ {testimonial.authorName} ,
+ {testimonial.authorType} ยท {testimonial.date}
+
+
+ );
+}
+
+export function TestimonialsForm() {
+ const [copied, setCopied] = useState(false);
+ const testimonialLink = 'https://www.re-quest1.com/testimonials/9A528z';
+
+ const handleCopyLink = async () => {
+ try {
+ await navigator.clipboard.writeText(testimonialLink);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch {
+ // Fallback for older browsers
+ }
+ };
+
+ return (
+ <>
+ {/* Page Title */}
+
+
+ Add Testimonials
+
+
+ What people are saying about you
+
+
+
+ {/* Create Testimonials Section */}
+
+
+ Create Testimonials
+
+
+
+ {/* Generate Link Button */}
+
+ Generate Link
+
+
+ {/* Link Input with Copy */}
+
+
+ {testimonialLink}
+
+
+
+
+
+ {copied ? 'Copied!' : 'Copy'}
+
+
+
+
+
+
+ {/* Testimonials Columns */}
+
+ {/* Latest Column */}
+
+ {/* Sort Header */}
+
+
+ Sort By : Latest
+
+
+
+
+ {/* Cards */}
+
+ {LATEST_TESTIMONIALS.map((testimonial) => (
+
+ ))}
+
+
+
+ {/* Oldest Column */}
+
+ {/* Sort Header */}
+
+
+ Sort By : Oldest
+
+
+
+
+ {/* Cards */}
+
+ {OLDEST_TESTIMONIALS.map((testimonial) => (
+
+ ))}
+
+
+
+
+ {/* Custom scrollbar styles */}
+
+ >
+ );
+}
diff --git a/src/components/settings/index.ts b/src/components/settings/index.ts
index 19a3271..ec48a76 100644
--- a/src/components/settings/index.ts
+++ b/src/components/settings/index.ts
@@ -7,3 +7,5 @@ export { PrivacyForm } from './PrivacyForm';
export { TwoFactorSettings } from './TwoFactorSettings';
export { SubscriptionForm } from './SubscriptionForm';
export { PaymentCheckoutForm } from './PaymentCheckoutForm';
+export { PaymentSuccessForm } from './PaymentSuccessForm';
+export { TestimonialsForm } from './TestimonialsForm';
diff --git a/src/services/api.ts b/src/services/api.ts
index 366a9ac..4a4b179 100644
--- a/src/services/api.ts
+++ b/src/services/api.ts
@@ -21,12 +21,25 @@ export interface ApiErrorResponse {
// Token refresh state
let isRefreshing = false;
+let isLoggingOut = false;
let failedQueue: Array<{
resolve: (value: AxiosResponse) => void;
reject: (error: AxiosError) => void;
config: InternalAxiosRequestConfig;
}> = [];
+// Force logout: clear all tokens and redirect to login
+const forceLogout = () => {
+ if (isLoggingOut) return; // Prevent multiple redirects
+ isLoggingOut = true;
+ if (typeof window !== 'undefined') {
+ localStorage.removeItem('accessToken');
+ localStorage.removeItem('refreshToken');
+ localStorage.removeItem('user');
+ window.location.href = '/api/auth/signout?callbackUrl=/login';
+ }
+};
+
const processQueue = (error: AxiosError | null, newToken?: string) => {
failedQueue.forEach((prom) => {
if (error) {
@@ -111,14 +124,7 @@ api.interceptors.response.use(
// Don't try to refresh if this was the refresh token request itself
if (originalRequest.url?.includes('/auth/refresh')) {
- // Refresh token is also invalid, clear everything and sign out properly
- if (typeof window !== 'undefined') {
- localStorage.removeItem('accessToken');
- localStorage.removeItem('refreshToken');
- localStorage.removeItem('user');
- // Use NextAuth's signout endpoint to properly clear the session
- window.location.href = '/api/auth/signout?callbackUrl=/login';
- }
+ forceLogout();
return Promise.reject(error);
}
@@ -135,13 +141,10 @@ api.interceptors.response.use(
const refreshToken = typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null;
if (!refreshToken) {
- // No refresh token available, just clear tokens but don't redirect for better UX
- if (typeof window !== 'undefined') {
- localStorage.removeItem('accessToken');
- localStorage.removeItem('refreshToken');
- localStorage.removeItem('user');
- }
+ // No refresh token available, force logout
isRefreshing = false;
+ processQueue(error);
+ forceLogout();
return Promise.reject(error);
}
@@ -173,14 +176,10 @@ api.interceptors.response.use(
// Retry original request
return api(originalRequest);
} catch (refreshError) {
- // Refresh failed, clear tokens but don't redirect
- if (typeof window !== 'undefined') {
- localStorage.removeItem('accessToken');
- localStorage.removeItem('refreshToken');
- localStorage.removeItem('user');
- }
+ // Refresh failed, force logout
processQueue(refreshError as AxiosError);
isRefreshing = false;
+ forceLogout();
return Promise.reject(refreshError);
}
}