diff --git a/src/app/request-accepted/request-accepted-client.tsx b/src/app/request-accepted/request-accepted-client.tsx
index 02ddae0..03b9307 100644
--- a/src/app/request-accepted/request-accepted-client.tsx
+++ b/src/app/request-accepted/request-accepted-client.tsx
@@ -44,7 +44,7 @@ import { useI18n } from "@/translations/provider";
const advisorAvatars = [
{ id: "advisor-primary", src: "/assets/images/Avatar Image.png" },
- { id: "advisor-secondary", src: "/assets/images/Ellipse 370.png" },
+ { id: "advisor-secondary", src: "/assets/images/Avatar Image.png" },
{ id: "advisor-tertiary", src: "/assets/images/Avatar Image.png" },
];
diff --git a/src/app/request-sent/request-sent-client.tsx b/src/app/request-sent/request-sent-client.tsx
index 01fa028..b6fffa0 100644
--- a/src/app/request-sent/request-sent-client.tsx
+++ b/src/app/request-sent/request-sent-client.tsx
@@ -22,7 +22,7 @@ import { useI18n } from "@/translations/provider";
const advisorAvatars = [
{ id: "advisor-primary", src: "/assets/images/Avatar Image.png" },
- { id: "advisor-secondary", src: "/assets/images/Ellipse 370.png" },
+ { id: "advisor-secondary", src: "/assets/images/Avatar Image.png" },
{ id: "advisor-tertiary", src: "/assets/images/Avatar Image.png" },
];
diff --git a/src/components/Componentes/advisor-actions-card.test.tsx b/src/components/Componentes/advisor-actions-card.test.tsx
new file mode 100644
index 0000000..c981d25
--- /dev/null
+++ b/src/components/Componentes/advisor-actions-card.test.tsx
@@ -0,0 +1,127 @@
+import { cleanup, render, screen } from "@testing-library/react";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { I18nProvider } from "@/translations/provider";
+import {
+ AdvisorActionsCard,
+ type AdvisorAvatar,
+} from "./advisor-actions-card";
+
+let mockAdvisorsData: any = { results: [] };
+let mockIsLoading = false;
+
+vi.mock("@/hooks/marriage/use-marriage-advisors", () => ({
+ useMarriageAdvisorsQuery: () => ({
+ data: mockAdvisorsData,
+ isLoading: mockIsLoading,
+ }),
+}));
+
+const fallbackAvatars: AdvisorAvatar[] = [
+ { id: "advisor-1", src: "/assets/images/Avatar Image.png" },
+ { id: "advisor-2", src: "/assets/images/Ellipse 370.png" },
+ { id: "advisor-3", src: "/assets/images/Avatar Image.png" },
+];
+
+function createWrapper() {
+ const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: { retry: false },
+ },
+ });
+
+ return function Wrapper({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ );
+ };
+}
+
+describe("AdvisorActionsCard", () => {
+ beforeEach(() => {
+ mockAdvisorsData = { results: [] };
+ mockIsLoading = false;
+ });
+
+ afterEach(() => {
+ cleanup();
+ vi.restoreAllMocks();
+ });
+
+ it("renders up to 3 fallback avatars and +N badge when API returns empty", () => {
+ const wrapper = createWrapper();
+ const { container } = render(
+
,
+ { wrapper },
+ );
+
+ // 3 avatar images rendered
+ const images = container.querySelectorAll("img");
+ expect(images.length).toBe(3);
+
+ // +7 badge rendered
+ expect(screen.getByText("+7")).toBeDefined();
+ });
+
+ it("renders up to 3 avatars and calculates +N badge when API returns 10 advisors", () => {
+ mockAdvisorsData = {
+ results: Array.from({ length: 10 }).map((_, i) => ({
+ username: `consultant_${i}`,
+ fullname: `مشاور ${i}`,
+ avatar_url: i === 0 ? "https://example.com/avatar0.jpg" : null,
+ })),
+ };
+
+ const wrapper = createWrapper();
+ const { container } = render(
+
,
+ { wrapper },
+ );
+
+ const images = container.querySelectorAll("img");
+ expect(images.length).toBe(3);
+
+ // 10 - 3 = 7
+ expect(screen.getByText("+7")).toBeDefined();
+ });
+
+ it("renders exact count and no +N badge when API returns 2 advisors", () => {
+ mockAdvisorsData = {
+ results: [
+ { username: "c1", fullname: "مشاور 1", avatar_url: "https://example.com/1.jpg" },
+ { username: "c2", fullname: "مشاور 2", avatar_url: "https://example.com/2.jpg" },
+ ],
+ };
+
+ const wrapper = createWrapper();
+ const { container } = render(
+
,
+ { wrapper },
+ );
+
+ const images = container.querySelectorAll("img");
+ expect(images.length).toBe(2);
+
+ expect(screen.queryByText(/^\+/)).toBeNull();
+ });
+});
diff --git a/src/components/Componentes/advisor-actions-card.tsx b/src/components/Componentes/advisor-actions-card.tsx
index 1fde0ac..ed00b0f 100644
--- a/src/components/Componentes/advisor-actions-card.tsx
+++ b/src/components/Componentes/advisor-actions-card.tsx
@@ -39,27 +39,25 @@ export function AdvisorActionsCard({
const { data, isLoading } = useMarriageAdvisorsQuery();
const advisors = data?.results ?? [];
- // Derive real avatars from API response (pick up to 4 with an avatar).
- const realAvatars: AdvisorAvatar[] = advisors
- .filter((a) => a.avatar_url)
- .slice(0, 4)
- .map((a) => ({
- id: a.username,
- src: a.avatar_url ?? FALLBACK_AVATAR,
- }));
+ // Derive up to 3 avatars from API response (or fallback if empty).
+ const realAvatars: AdvisorAvatar[] = advisors.slice(0, 3).map((a, idx) => ({
+ id: a.username || `advisor-${idx}`,
+ src: a.avatar_url || (fallbackAvatars[idx]?.src ?? FALLBACK_AVATAR),
+ }));
const displayAvatars =
- realAvatars.length > 0 ? realAvatars : fallbackAvatars;
+ realAvatars.length > 0 ? realAvatars : fallbackAvatars.slice(0, 3);
// Show total remaining advisors (capped at advisor count minus shown avatars).
- const displayExtraCount =
+ const totalCount =
advisors.length > 0
- ? Math.max(0, advisors.length - displayAvatars.length)
- : fallbackExtraCount;
+ ? advisors.length
+ : fallbackAvatars.length + fallbackExtraCount;
+ const displayExtraCount = Math.max(0, totalCount - displayAvatars.length);
return (
-
+
{title}
@@ -74,13 +72,13 @@ export function AdvisorActionsCard({
Array.from({ length: 3 }).map((_, i) => (
))
- : displayAvatars.map((avatar) => (
+ : displayAvatars.map((avatar, idx) => (
))}
{!isLoading && displayExtraCount > 0 && (
-
+
+{displayExtraCount}
)}
diff --git a/src/components/Componentes/information-sheet.tsx b/src/components/Componentes/information-sheet.tsx
index 6308367..bb590d3 100644
--- a/src/components/Componentes/information-sheet.tsx
+++ b/src/components/Componentes/information-sheet.tsx
@@ -5,7 +5,6 @@ import type { HTMLAttributes, ReactNode } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import Button from "./button";
-import { LoadingSkeleton } from "./loading-skeleton";
import { LoadingThreeDot } from "./loading-three-dot";
import { useHardwareBackHandler } from "@/hooks/use-hardware-back-handler";
import { useI18n } from "@/translations/provider";
@@ -356,37 +355,8 @@ export function InformationSheet({
{isLoading ? (
-
- {/* Icon Skeleton */}
-
-
- {/* Title Skeleton */}
-
-
- {/* Description Lines Skeleton */}
-
-
-
-
-
-
- {/* Middle Box Skeleton (e.g. Plan / Info) */}
-
-
-
-
-
- {/* Subtext Skeleton */}
-
-
-
-
-
- {/* Action / Swipe Buttons Skeleton */}
-
-
-
-
+
+
) : (
<>
diff --git a/src/components/Componentes/navigation-button.tsx b/src/components/Componentes/navigation-button.tsx
index c4e6333..d7faeec 100644
--- a/src/components/Componentes/navigation-button.tsx
+++ b/src/components/Componentes/navigation-button.tsx
@@ -18,6 +18,7 @@ type NavigationButtonIcon =
| "back"
| "support"
| "close"
+ | "exit"
| "info"
| "document"
| "subscription"
@@ -198,12 +199,24 @@ export function NavigationButton({
className={`size-6 ${variant === "transparent" ? "text-white" : "text-[#111111]"}`}
/>
);
+ case "exit":
+ return (
+
+ );
case "support":
case "consultation":
return (
-
);
@@ -304,10 +317,9 @@ export function NavigationButton({
}}
className="flex w-full items-center gap-3 px-4 py-3 text-start text-sm font-semibold text-gray-700 hover:bg-gray-50 cursor-pointer border-0 bg-transparent"
>
-
{t["Support"]}
@@ -353,9 +365,11 @@ export function NavigationButton({
iconLabel ??
(icon === "back"
? t["Back"]
- : icon === "consultation"
- ? (t as any)["Consultation"]
- : icon)
+ : icon === "exit"
+ ? (t["Exit"] || t["Back"] || "Exit")
+ : icon === "consultation"
+ ? (t as any)["Consultation"]
+ : icon)
}
onClick={(event) => {
props.onClick?.(event);
@@ -372,7 +386,7 @@ export function NavigationButton({
} else {
router.back();
}
- } else if (icon === "close") {
+ } else if (icon === "close" || icon === "exit") {
router.back();
} else if (icon === "support") {
setIsSupportOpen(true);
diff --git a/src/components/Componentes/page-header.tsx b/src/components/Componentes/page-header.tsx
index 6d9603a..0d9a643 100644
--- a/src/components/Componentes/page-header.tsx
+++ b/src/components/Componentes/page-header.tsx
@@ -22,6 +22,11 @@ type PageHeaderProps = {
*/
enableProfileQuery?: boolean;
profile?: any;
+ /**
+ * When true, header stays fixed/sticky at the top with Flutter native toolbar height (56px),
+ * safe area top padding, and frosted backdrop blur during scroll.
+ */
+ sticky?: boolean;
};
export function PageHeader({
@@ -30,6 +35,7 @@ export function PageHeader({
rightButton,
enableProfileQuery = true,
profile: profileProp,
+ sticky = false,
}: PageHeaderProps) {
const { dictionary: t } = useI18n();
@@ -57,48 +63,56 @@ export function PageHeader({
return (
);
}
diff --git a/src/components/Componentes/slider-page.tsx b/src/components/Componentes/slider-page.tsx
index 471a505..8965023 100644
--- a/src/components/Componentes/slider-page.tsx
+++ b/src/components/Componentes/slider-page.tsx
@@ -133,8 +133,8 @@ export default function SliderPage({ onClose }: SliderPageProps = {}) {
onClose={() => setSubmitError(null)}
/>
)}
-