diff --git a/src/app/candidate-contact/candidate-contact-client.tsx b/src/app/candidate-contact/candidate-contact-client.tsx index 6c3e667..f27f0d6 100644 --- a/src/app/candidate-contact/candidate-contact-client.tsx +++ b/src/app/candidate-contact/candidate-contact-client.tsx @@ -29,7 +29,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/finding-match/finding-match-client.tsx b/src/app/finding-match/finding-match-client.tsx index 77077b2..6ff52cb 100644 --- a/src/app/finding-match/finding-match-client.tsx +++ b/src/app/finding-match/finding-match-client.tsx @@ -23,7 +23,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/new-match/new-match-client.tsx b/src/app/new-match/new-match-client.tsx index b270c9c..d3c3515 100644 --- a/src/app/new-match/new-match-client.tsx +++ b/src/app/new-match/new-match-client.tsx @@ -43,7 +43,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" }, ]; @@ -623,67 +623,56 @@ export default function NewMatchClient() { return ( <> + +
-
- -
+ {/* 1. Header Section Skeleton */} +
+ + + +
-
- {/* 1. Header Section Skeleton */} -
- - - -
- - {/* 2. Match Card Skeleton */} -
- {/* Name line */} - - - {/* Subtitle / Details lines */} -
- - -
+ {/* 2. Match Card Skeleton */} +
+ {/* Name line */} + - {/* Button skeleton */} - -
- - {/* 3. Advisor Card Skeleton */} -
-
- -
- - -
+ {/* Subtitle / Details lines */} +
+ + +
-
-
- - - -
+ {/* Button skeleton */} + +
+ + {/* 3. Advisor Card Skeleton */} +
+
+ +
+ + +
- +
+
+ + +
+ +
-
-
+ +
); @@ -712,24 +701,14 @@ export default function NewMatchClient() { <> + +
-
- -
- -
{/* 1. Header Section */}
-
-
+ {profile?.can_edit_profile === false && (
@@ -612,7 +612,7 @@ export default function NewMatchProfilePage({
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 ( -