Browse Source

fix page header

master
mortezaei 12 hours ago
parent
commit
360c537543
  1. 2
      src/app/candidate-contact/candidate-contact-client.tsx
  2. 2
      src/app/finding-match/finding-match-client.tsx
  3. 122
      src/app/new-match/new-match-client.tsx
  4. 4
      src/app/new-match/profile/page.tsx
  5. 2
      src/app/request-accepted/request-accepted-client.tsx
  6. 2
      src/app/request-sent/request-sent-client.tsx
  7. 127
      src/components/Componentes/advisor-actions-card.test.tsx
  8. 34
      src/components/Componentes/advisor-actions-card.tsx
  9. 34
      src/components/Componentes/information-sheet.tsx
  10. 32
      src/components/Componentes/navigation-button.tsx
  11. 72
      src/components/Componentes/page-header.tsx
  12. 6
      src/components/Componentes/slider-page.tsx
  13. 2
      src/components/Componentes/ui-icon.tsx

2
src/app/candidate-contact/candidate-contact-client.tsx

@ -29,7 +29,7 @@ import { useI18n } from "@/translations/provider";
const advisorAvatars = [ const advisorAvatars = [
{ id: "advisor-primary", src: "/assets/images/Avatar Image.png" }, { 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" }, { id: "advisor-tertiary", src: "/assets/images/Avatar Image.png" },
]; ];

2
src/app/finding-match/finding-match-client.tsx

@ -23,7 +23,7 @@ import { useI18n } from "@/translations/provider";
const advisorAvatars = [ const advisorAvatars = [
{ id: "advisor-primary", src: "/assets/images/Avatar Image.png" }, { 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" }, { id: "advisor-tertiary", src: "/assets/images/Avatar Image.png" },
]; ];

122
src/app/new-match/new-match-client.tsx

@ -43,7 +43,7 @@ import { useI18n } from "@/translations/provider";
const advisorAvatars = [ const advisorAvatars = [
{ id: "advisor-primary", src: "/assets/images/Avatar Image.png" }, { 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" }, { id: "advisor-tertiary", src: "/assets/images/Avatar Image.png" },
]; ];
@ -623,67 +623,56 @@ export default function NewMatchClient() {
return ( return (
<> <>
<PageBackground /> <PageBackground />
<PageHeader profile={profile} sticky />
<main <main
className="-mx-[17px] flex min-h-dvh flex-col px-[18px] text-center"
style={{
paddingBottom: `${profile?.can_edit_profile === false ? 84 + bottom : 20 + bottom}px`,
}}
className="min-h-0 flex-1 flex flex-col gap-3.5 pt-4 pb-1 text-center"
> >
<div
style={{
paddingTop: `${Math.max(12, top + 4)}px`,
}}
className="sticky top-0 z-30 -mx-[18px] px-[18px] pb-2 bg-[#F7F1F0]/90 backdrop-blur-md"
>
<PageHeader profile={profile} />
</div>
{/* 1. Header Section Skeleton */}
<section className="flex flex-col items-center shrink-0">
<LoadingSkeleton className="h-[56px] w-[56px] rounded-full" />
<LoadingSkeleton className="h-6 w-[200px] mt-2.5" />
<LoadingSkeleton className="h-3.5 w-[260px] mt-1.5" />
</section>
<div
style={{
paddingBottom: `${profile?.can_edit_profile === false ? 84 + bottom : 20 + bottom}px`,
}}
className="min-h-0 flex-1 flex flex-col gap-3.5 py-1"
>
{/* 1. Header Section Skeleton */}
<section className="flex flex-col items-center shrink-0">
<LoadingSkeleton className="h-[56px] w-[56px] rounded-full" />
<LoadingSkeleton className="h-6 w-[200px] mt-2.5" />
<LoadingSkeleton className="h-3.5 w-[260px] mt-1.5" />
</section>
{/* 2. Match Card Skeleton */}
<section className="w-full rounded-[16px] border border-white/80 bg-white px-4 py-4 shadow-none flex flex-col items-center shrink-0">
{/* Name line */}
<LoadingSkeleton className="h-4 w-[130px]" />
{/* Subtitle / Details lines */}
<div className="mt-2.5 w-full flex flex-col items-center gap-2 min-h-[40px]">
<LoadingSkeleton className="h-3 w-[180px]" />
<LoadingSkeleton className="h-3 w-[150px]" />
</div>
{/* 2. Match Card Skeleton */}
<section className="w-full rounded-[16px] border border-white/80 bg-white px-4 py-4 shadow-none flex flex-col items-center shrink-0">
{/* Name line */}
<LoadingSkeleton className="h-4 w-[130px]" />
{/* Button skeleton */}
<LoadingSkeleton className="mt-3.5 h-[40px] w-full rounded-[10px]" />
</section>
{/* 3. Advisor Card Skeleton */}
<section className="w-full shrink-0">
<div className="rounded-[14px] border border-white/80 bg-white/75 px-4 py-3.5 text-left shadow-none backdrop-blur-sm">
<LoadingSkeleton className="h-4 w-[120px]" />
<div className="mt-1.5 space-y-1">
<LoadingSkeleton className="h-2.5 w-[240px]" />
<LoadingSkeleton className="h-2.5 w-[160px]" />
</div>
{/* Subtitle / Details lines */}
<div className="mt-2.5 w-full flex flex-col items-center gap-2 min-h-[40px]">
<LoadingSkeleton className="h-3 w-[180px]" />
<LoadingSkeleton className="h-3 w-[150px]" />
</div>
<div className="mt-3.5 flex items-center justify-between gap-3">
<div className="flex items-center pl-1">
<LoadingSkeleton className="h-[28px] w-[28px] rounded-full border-2 border-white" />
<LoadingSkeleton className="-ml-1.5 h-[28px] w-[28px] rounded-full border-2 border-white" />
<LoadingSkeleton className="-ml-1.5 h-[28px] w-[28px] rounded-full border-2 border-white" />
</div>
{/* Button skeleton */}
<LoadingSkeleton className="mt-3.5 h-[40px] w-full rounded-[10px]" />
</section>
{/* 3. Advisor Card Skeleton */}
<section className="w-full shrink-0">
<div className="rounded-[14px] border border-white/80 bg-white/75 px-4 py-3.5 text-left shadow-none backdrop-blur-sm">
<LoadingSkeleton className="h-4 w-[120px]" />
<div className="mt-1.5 space-y-1">
<LoadingSkeleton className="h-2.5 w-[240px]" />
<LoadingSkeleton className="h-2.5 w-[160px]" />
</div>
<LoadingSkeleton className="h-[36px] w-[100px] rounded-[9px]" />
<div className="mt-3.5 flex items-center justify-between gap-3">
<div className="flex items-center pl-1">
<LoadingSkeleton className="h-[28px] w-[28px] rounded-full border-2 border-white" />
<LoadingSkeleton className="-ml-1.5 h-[28px] w-[28px] rounded-full border-2 border-white" />
<LoadingSkeleton className="-ml-1.5 h-[28px] w-[28px] rounded-full border-2 border-white" />
</div> </div>
<LoadingSkeleton className="h-[36px] w-[100px] rounded-[9px]" />
</div> </div>
</section>
</div>
</div>
</section>
</main> </main>
</> </>
); );
@ -712,24 +701,14 @@ export default function NewMatchClient() {
<> <>
<PageBackground /> <PageBackground />
<PageHeader profile={profile} sticky />
<main <main
className="-mx-[17px] flex min-h-dvh flex-col px-[18px] text-center"
style={{
paddingBottom: `${profile?.can_edit_profile === false ? 90 + bottom : 28 + bottom}px`,
}}
className="min-h-0 flex-1 flex flex-col gap-4 pt-4 py-2 text-center"
> >
<div
style={{
paddingTop: `${Math.max(12, top + 4)}px`,
}}
className="sticky top-0 z-30 -mx-[18px] px-[18px] pb-2 bg-[#F7F1F0]/90 backdrop-blur-md"
>
<PageHeader profile={profile} />
</div>
<div
style={{
paddingBottom: `${profile?.can_edit_profile === false ? 90 + bottom : 28 + bottom}px`,
}}
className="min-h-0 flex-1 flex flex-col gap-4 py-2"
>
{/* 1. Header Section */} {/* 1. Header Section */}
<section className="flex flex-col items-center shrink-0"> <section className="flex flex-col items-center shrink-0">
<div <div
@ -943,8 +922,7 @@ export default function NewMatchClient() {
onGetAdvisor={openAdvisors} onGetAdvisor={openAdvisors}
/> />
</div> </div>
</div>
</main>
</main>
{profile?.can_edit_profile === false && ( {profile?.can_edit_profile === false && (
<div <div

4
src/app/new-match/profile/page.tsx

@ -185,7 +185,7 @@ function NewMatchProfileSkeleton({
{!hideBackButton ? ( {!hideBackButton ? (
<NavigationButton <NavigationButton
variant="transparent" variant="transparent"
icon="close"
icon="exit"
iconLabel={t["Go back"]} iconLabel={t["Go back"]}
onClick={onClose} onClick={onClose}
/> />
@ -612,7 +612,7 @@ export default function NewMatchProfilePage({
<div className="flex items-center justify-between gap-3"> <div className="flex items-center justify-between gap-3">
<NavigationButton <NavigationButton
variant="transparent" variant="transparent"
icon="close"
icon="exit"
iconLabel={t["Go back"]} iconLabel={t["Go back"]}
onClick={onClose} onClick={onClose}
/> />

2
src/app/request-accepted/request-accepted-client.tsx

@ -44,7 +44,7 @@ import { useI18n } from "@/translations/provider";
const advisorAvatars = [ const advisorAvatars = [
{ id: "advisor-primary", src: "/assets/images/Avatar Image.png" }, { 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" }, { id: "advisor-tertiary", src: "/assets/images/Avatar Image.png" },
]; ];

2
src/app/request-sent/request-sent-client.tsx

@ -22,7 +22,7 @@ import { useI18n } from "@/translations/provider";
const advisorAvatars = [ const advisorAvatars = [
{ id: "advisor-primary", src: "/assets/images/Avatar Image.png" }, { 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" }, { id: "advisor-tertiary", src: "/assets/images/Avatar Image.png" },
]; ];

127
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 (
<QueryClientProvider client={queryClient}>
<I18nProvider locale="fa">{children}</I18nProvider>
</QueryClientProvider>
);
};
}
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(
<AdvisorActionsCard
title="دریافت مشاور"
description="توضیحات"
avatars={fallbackAvatars}
extraCount={7}
getAdvisorLabel="دریافت مشاور"
/>,
{ 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(
<AdvisorActionsCard
title="دریافت مشاور"
description="توضیحات"
avatars={fallbackAvatars}
extraCount={7}
getAdvisorLabel="دریافت مشاور"
/>,
{ 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(
<AdvisorActionsCard
title="دریافت مشاور"
description="توضیحات"
avatars={fallbackAvatars}
extraCount={7}
getAdvisorLabel="دریافت مشاور"
/>,
{ wrapper },
);
const images = container.querySelectorAll("img");
expect(images.length).toBe(2);
expect(screen.queryByText(/^\+/)).toBeNull();
});
});

34
src/components/Componentes/advisor-actions-card.tsx

@ -39,27 +39,25 @@ export function AdvisorActionsCard({
const { data, isLoading } = useMarriageAdvisorsQuery(); const { data, isLoading } = useMarriageAdvisorsQuery();
const advisors = data?.results ?? []; 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 = 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). // Show total remaining advisors (capped at advisor count minus shown avatars).
const displayExtraCount =
const totalCount =
advisors.length > 0 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 ( return (
<section className={["w-full", className].filter(Boolean).join(" ")}> <section className={["w-full", className].filter(Boolean).join(" ")}>
<div className="rounded-[14px] border border-white/80 bg-white/75 px-4 py-3.5 text-left shadow-none backdrop-blur-sm">
<div className="rounded-[14px] border border-white/80 bg-white/75 px-4 py-3.5 text-left rtl:text-right shadow-none backdrop-blur-sm">
<h2 className="text-[13.5px] leading-tight font-bold text-[#1C1C1C]"> <h2 className="text-[13.5px] leading-tight font-bold text-[#1C1C1C]">
{title} {title}
</h2> </h2>
@ -74,13 +72,13 @@ export function AdvisorActionsCard({
Array.from({ length: 3 }).map((_, i) => ( Array.from({ length: 3 }).map((_, i) => (
<span <span
key={`shimmer-${i}`} key={`shimmer-${i}`}
className="-ml-1.5 flex h-[28px] w-[28px] overflow-hidden rounded-full border-2 border-white first:ml-0 shimmer-bg"
className="-ms-1.5 flex h-[28px] w-[28px] overflow-hidden rounded-full border-2 border-white first:ms-0 shimmer-bg shrink-0"
/> />
)) ))
: displayAvatars.map((avatar) => (
: displayAvatars.map((avatar, idx) => (
<span <span
key={avatar.id}
className="-ml-1.5 flex h-[28px] w-[28px] overflow-hidden rounded-full border-2 border-white bg-[#E7E7E7] first:ml-0"
key={avatar.id || idx}
className="-ms-1.5 flex h-[28px] w-[28px] overflow-hidden rounded-full border-2 border-white bg-[#E7E7E7] first:ms-0 shrink-0"
> >
<NetworkImage <NetworkImage
src={avatar.src} src={avatar.src}
@ -93,7 +91,7 @@ export function AdvisorActionsCard({
</span> </span>
))} ))}
{!isLoading && displayExtraCount > 0 && ( {!isLoading && displayExtraCount > 0 && (
<span className="-ml-1.5 flex h-[28px] w-[28px] items-center justify-center rounded-full border-2 border-white bg-[#EDEEF1] text-[10px] font-bold text-[#1C1C1C]">
<span className="-ms-1.5 flex h-[28px] w-[28px] items-center justify-center rounded-full border-2 border-white bg-[#EDEEF1] text-[10px] font-bold text-[#1C1C1C] shrink-0">
+{displayExtraCount} +{displayExtraCount}
</span> </span>
)} )}

34
src/components/Componentes/information-sheet.tsx

@ -5,7 +5,6 @@ import type { HTMLAttributes, ReactNode } from "react";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import Button from "./button"; import Button from "./button";
import { LoadingSkeleton } from "./loading-skeleton";
import { LoadingThreeDot } from "./loading-three-dot"; import { LoadingThreeDot } from "./loading-three-dot";
import { useHardwareBackHandler } from "@/hooks/use-hardware-back-handler"; import { useHardwareBackHandler } from "@/hooks/use-hardware-back-handler";
import { useI18n } from "@/translations/provider"; import { useI18n } from "@/translations/provider";
@ -356,37 +355,8 @@ export function InformationSheet({
<div className="mx-auto flex flex-col items-center w-full"> <div className="mx-auto flex flex-col items-center w-full">
{isLoading ? ( {isLoading ? (
<div className="flex flex-col items-center w-full animate-in fade-in duration-200">
{/* Icon Skeleton */}
<LoadingSkeleton className="h-[56px] w-[56px] rounded-full" />
{/* Title Skeleton */}
<LoadingSkeleton className="mt-3.5 h-[22px] w-[65%] rounded-md" />
{/* Description Lines Skeleton */}
<div className="mt-3 flex flex-col items-center gap-2 w-full">
<LoadingSkeleton className="h-[14px] w-[90%] rounded-md" />
<LoadingSkeleton className="h-[14px] w-[80%] rounded-md" />
<LoadingSkeleton className="h-[14px] w-[60%] rounded-md" />
</div>
{/* Middle Box Skeleton (e.g. Plan / Info) */}
<div className="mt-4 w-full rounded-[11px] bg-white p-3 border border-gray-100 flex flex-col items-center gap-2">
<LoadingSkeleton className="h-[12px] w-[35%] rounded-md" />
<LoadingSkeleton className="h-[20px] w-[50%] rounded-md" />
</div>
{/* Subtext Skeleton */}
<div className="mt-3.5 flex flex-col items-center gap-1.5 w-full">
<LoadingSkeleton className="h-[11px] w-[85%] rounded-md" />
<LoadingSkeleton className="h-[11px] w-[70%] rounded-md" />
</div>
{/* Action / Swipe Buttons Skeleton */}
<div className="mt-5 grid grid-cols-[33fr_67fr] w-full gap-3">
<LoadingSkeleton className="h-[52px] w-full rounded-[11px]" />
<LoadingSkeleton className="h-[52px] w-full rounded-[11px]" />
</div>
<div className="flex h-[240px] w-full items-center justify-center animate-in fade-in duration-200">
<LoadingThreeDot className="text-[#F0445B]" />
</div> </div>
) : ( ) : (
<> <>

32
src/components/Componentes/navigation-button.tsx

@ -18,6 +18,7 @@ type NavigationButtonIcon =
| "back" | "back"
| "support" | "support"
| "close" | "close"
| "exit"
| "info" | "info"
| "document" | "document"
| "subscription" | "subscription"
@ -198,12 +199,24 @@ export function NavigationButton({
className={`size-6 ${variant === "transparent" ? "text-white" : "text-[#111111]"}`} className={`size-6 ${variant === "transparent" ? "text-white" : "text-[#111111]"}`}
/> />
); );
case "exit":
return (
<svg
viewBox="0 0 24 24"
fill="none"
className={`size-6 rtl:rotate-180 ${variant === "transparent" ? "text-white" : "text-[#111111]"}`}
>
<path
d="M8 4C6.93913 4 5.92172 4.42143 5.17157 5.17157C4.42143 5.92172 4 6.93913 4 8V16C4 17.0609 4.42143 18.0783 5.17157 18.8284C5.92172 19.5786 6.93913 20 8 20H15C15.2652 20 15.5196 19.8946 15.7071 19.7071C15.8946 19.5196 16 19.2652 16 19C16 18.7348 15.8946 18.4804 15.7071 18.2929C15.5196 18.1054 15.2652 18 15 18H8C7.46957 18 6.96086 17.7893 6.58579 17.4142C6.21071 17.0391 6 16.5304 6 16V8C6 7.46957 6.21071 6.96086 6.58579 6.58579C6.96086 6.21071 7.46957 6 8 6H15C15.2652 6 15.5196 5.89464 15.7071 5.70711C15.8946 5.51957 16 5.26522 16 5C16 4.73478 15.8946 4.48043 15.7071 4.29289C15.5196 4.10536 15.2652 4 15 4H8ZM17.708 7.292C17.615 7.19902 17.5046 7.12527 17.3832 7.07495C17.2617 7.02464 17.1315 6.99874 17 6.99874C16.8685 6.99874 16.7383 7.02464 16.6168 7.07495C16.4954 7.12527 16.385 7.19902 16.292 7.292C16.199 7.38498 16.1253 7.49535 16.075 7.61683C16.0246 7.73831 15.9987 7.86851 15.9987 8C15.9987 8.13149 16.0246 8.26169 16.075 8.38317C16.1253 8.50465 16.199 8.61502 16.292 8.708L18.586 11H10C9.73478 11 9.48043 11.1054 9.29289 11.2929C9.10536 11.4804 9 11.7348 9 12C9 12.2652 9.10536 12.5196 9.29289 12.7071C9.48043 12.8946 9.73478 13 10 13H18.586L16.292 15.292C16.199 15.385 16.1253 15.4954 16.075 15.6168C16.0246 15.7383 15.9987 15.8685 15.9987 16C15.9987 16.1315 16.0246 16.2617 16.075 16.3832C16.1253 16.5046 16.199 16.615 16.292 16.708C16.385 16.801 16.4954 16.8747 16.6168 16.925C16.7383 16.9754 16.8685 17.0013 17 17.0013C17.1315 17.0013 17.2617 16.9754 17.3832 16.925C17.5046 16.8747 17.615 16.801 17.708 16.708L21.708 12.708C21.8011 12.6151 21.875 12.5048 21.9254 12.3833C21.9758 12.2618 22.0018 12.1315 22.0018 12C22.0018 11.8685 21.9758 11.7382 21.9254 11.6167C21.875 11.4952 21.8011 11.3849 21.708 11.292L17.708 7.292Z"
fill="currentColor"
/>
</svg>
);
case "support": case "support":
case "consultation": case "consultation":
return ( return (
<Ic
<UiIcon
name="support" name="support"
aria-hidden="true"
className={`size-6 ${variant === "transparent" ? "text-white" : "text-[#111111]"}`} className={`size-6 ${variant === "transparent" ? "text-white" : "text-[#111111]"}`}
/> />
); );
@ -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" 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"
> >
<Ic
<UiIcon
name="support" name="support"
aria-hidden="true"
className="size-5 shrink-0"
className="size-5 shrink-0 text-gray-700"
/> />
<span>{t["Support"]}</span> <span>{t["Support"]}</span>
</button> </button>
@ -353,9 +365,11 @@ export function NavigationButton({
iconLabel ?? iconLabel ??
(icon === "back" (icon === "back"
? t["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) => { onClick={(event) => {
props.onClick?.(event); props.onClick?.(event);
@ -372,7 +386,7 @@ export function NavigationButton({
} else { } else {
router.back(); router.back();
} }
} else if (icon === "close") {
} else if (icon === "close" || icon === "exit") {
router.back(); router.back();
} else if (icon === "support") { } else if (icon === "support") {
setIsSupportOpen(true); setIsSupportOpen(true);

72
src/components/Componentes/page-header.tsx

@ -22,6 +22,11 @@ type PageHeaderProps = {
*/ */
enableProfileQuery?: boolean; enableProfileQuery?: boolean;
profile?: any; 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({ export function PageHeader({
@ -30,6 +35,7 @@ export function PageHeader({
rightButton, rightButton,
enableProfileQuery = true, enableProfileQuery = true,
profile: profileProp, profile: profileProp,
sticky = false,
}: PageHeaderProps) { }: PageHeaderProps) {
const { dictionary: t } = useI18n(); const { dictionary: t } = useI18n();
@ -57,48 +63,56 @@ export function PageHeader({
return ( return (
<header <header
className={["flex items-center justify-between pb-3", className]
className={[
sticky
? "sticky top-0 z-30 -mx-[17px] px-[17px] bg-[#F5F5F5] bg-[url('/assets/images/home-Checkups-List.svg')] bg-top bg-no-repeat bg-cover pt-[max(12px,calc(var(--safe-top)+4px))]"
: "pb-3",
className,
]
.filter(Boolean) .filter(Boolean)
.join(" ")} .join(" ")}
> >
{leftButton?.className?.includes("hidden") ? (
<div className="size-10 shrink-0" />
) : (
<NavigationButton icon="back" profile={profile} {...leftButton} />
)}
<h1 className="font-faminela text-[20px] font-normal text-foreground">
{t["Habib Marriage"]}
</h1>
<div className="relative flex h-[56px] items-center justify-between">
{leftButton?.className?.includes("hidden") ? (
<div className="size-10 shrink-0" />
) : (
<NavigationButton icon="back" profile={profile} {...leftButton} />
)}
{isCustomRightButton ? (
<NavigationButton
icon={iconFromProp || "support"}
profile={profile}
{...(iconFromProp === "support" ? { iconLabel: t["Support"] } : {})}
{...rightButtonRest}
/>
) : isMale ? (
<div className="flex items-center gap-2">
<h1 className="pointer-events-none absolute left-1/2 -translate-x-1/2 font-faminela text-[20px] font-normal text-foreground text-center truncate max-w-[60%]">
{t["Habib Marriage"]}
</h1>
{isCustomRightButton ? (
<NavigationButton <NavigationButton
icon="subscription"
icon={iconFromProp || "support"}
profile={profile} profile={profile}
iconLabel={t["Subscription Status"] || "Subscription"}
{...(iconFromProp === "support" ? { iconLabel: t["Support"] } : {})}
{...rightButtonRest}
/> />
) : isMale ? (
<div className="flex items-center gap-2">
<NavigationButton
icon="subscription"
profile={profile}
iconLabel={t["Subscription Status"] || "Subscription"}
/>
<NavigationButton
icon="support"
profile={profile}
iconLabel={t["Support"]}
{...rightButtonRest}
/>
</div>
) : (
<NavigationButton <NavigationButton
icon="support" icon="support"
profile={profile} profile={profile}
iconLabel={t["Support"]} iconLabel={t["Support"]}
{...rightButtonRest} {...rightButtonRest}
/> />
</div>
) : (
<NavigationButton
icon="support"
profile={profile}
iconLabel={t["Support"]}
{...rightButtonRest}
/>
)}
)}
</div>
</header> </header>
); );
} }

6
src/components/Componentes/slider-page.tsx

@ -133,8 +133,8 @@ export default function SliderPage({ onClose }: SliderPageProps = {}) {
onClose={() => setSubmitError(null)} onClose={() => setSubmitError(null)}
/> />
)} )}
<header className="relative pt-[max(12px,calc(var(--safe-top)+4px))] pb-3">
<div className="relative flex items-center justify-between">
<header className="relative shrink-0 pt-[max(12px,calc(var(--safe-top)+4px))]">
<div className="relative flex h-[56px] items-center justify-between">
<NavigationButton <NavigationButton
icon="back" icon="back"
iconLabel="Close slider" iconLabel="Close slider"
@ -185,7 +185,7 @@ export default function SliderPage({ onClose }: SliderPageProps = {}) {
</div> </div>
</header> </header>
<main className="flex min-h-0 flex-1 overflow-hidden pt-3">
<main className="flex min-h-0 flex-1 overflow-hidden pt-2">
<div <div
className="flex h-full w-full transition-transform duration-300 ease-out" className="flex h-full w-full transition-transform duration-300 ease-out"
style={{ transform: `translateX(-${activeSlide * 100}%)` }} style={{ transform: `translateX(-${activeSlide * 100}%)` }}

2
src/components/Componentes/ui-icon.tsx

@ -56,7 +56,7 @@ export function UiIcon({ name, ...props }: UiIconProps) {
<svg viewBox="0 0 25 25" fill="none" aria-hidden="true" {...props}> <svg viewBox="0 0 25 25" fill="none" aria-hidden="true" {...props}>
<path <path
d="M22.0473 12.4463C22.0536 10.9147 21.0054 9.62813 19.5981 9.29067C19.3513 5.27489 16.0816 2.06455 12.0894 2.04836C8.09724 2.03217 4.80167 5.20521 4.52225 9.22954C3.11229 9.55458 2.05372 10.8336 2.04751 12.3652L2.03945 14.3517C2.03223 16.1316 3.44442 17.5962 5.19784 17.6033L6.61595 17.609C7.60852 17.6131 8.41256 16.7986 8.41667 15.785L8.4361 10.9943C8.44021 9.98069 7.6428 9.15975 6.65023 9.15572L6.04233 9.15326C6.35895 6.03022 8.95241 3.5983 12.0831 3.61099C15.2138 3.62369 17.7874 6.07557 18.0787 9.20207L17.4708 9.1996C16.4782 9.19558 15.6742 10.01 15.6701 11.0236L15.6507 15.8143C15.6465 16.8279 16.444 17.6489 17.4365 17.6529L17.9228 17.6549C17.6267 18.3055 16.9866 18.7578 16.2466 18.7548L14.7573 18.7488C14.5989 17.7549 13.7614 16.9862 12.7479 16.9821L11.3193 16.9763C10.1843 16.9717 9.25913 17.9098 9.25443 19.0679L9.25091 19.937C9.24621 21.0961 10.1647 22.0407 11.2988 22.0453L12.7273 22.0511C13.7408 22.0552 14.5845 21.2933 14.751 20.3007L16.2403 20.3068C17.8617 20.3134 19.2238 19.1394 19.5446 17.578C20.964 17.2627 22.033 15.9741 22.0393 14.4328L22.0473 12.4463ZM6.91635 10.9881L6.89693 15.7788C6.8963 15.934 6.77422 16.0577 6.62224 16.0571L5.20414 16.0513C4.29229 16.0476 3.55542 15.289 3.5592 14.3578L3.56725 12.3713C3.57103 11.4402 4.31403 10.6876 5.22587 10.6913L6.64398 10.697C6.79595 10.6976 6.91703 10.8223 6.9164 10.9775L6.91635 10.9881ZM13.2631 19.9436C13.2619 20.2433 13.0177 20.4906 12.7242 20.4894L11.2956 20.4836C11.0021 20.4825 10.76 20.2332 10.7612 19.9334L10.7647 19.0643C10.7659 18.7646 11.0101 18.5173 11.3036 18.5185L12.7322 18.5243C13.0257 18.5255 13.2678 18.7748 13.2666 19.0745L13.2631 19.9436ZM20.5195 14.4266C20.5158 15.3578 19.7728 16.1104 18.8609 16.1067L17.4428 16.101C17.2908 16.1003 17.1698 15.9757 17.1704 15.8205L17.1898 11.0298C17.1905 10.8746 17.3125 10.7509 17.4645 10.7516L18.8826 10.7573C19.7945 10.761 20.5313 11.5196 20.5276 12.4508L20.5195 14.4373L20.5195 14.4266Z" d="M22.0473 12.4463C22.0536 10.9147 21.0054 9.62813 19.5981 9.29067C19.3513 5.27489 16.0816 2.06455 12.0894 2.04836C8.09724 2.03217 4.80167 5.20521 4.52225 9.22954C3.11229 9.55458 2.05372 10.8336 2.04751 12.3652L2.03945 14.3517C2.03223 16.1316 3.44442 17.5962 5.19784 17.6033L6.61595 17.609C7.60852 17.6131 8.41256 16.7986 8.41667 15.785L8.4361 10.9943C8.44021 9.98069 7.6428 9.15975 6.65023 9.15572L6.04233 9.15326C6.35895 6.03022 8.95241 3.5983 12.0831 3.61099C15.2138 3.62369 17.7874 6.07557 18.0787 9.20207L17.4708 9.1996C16.4782 9.19558 15.6742 10.01 15.6701 11.0236L15.6507 15.8143C15.6465 16.8279 16.444 17.6489 17.4365 17.6529L17.9228 17.6549C17.6267 18.3055 16.9866 18.7578 16.2466 18.7548L14.7573 18.7488C14.5989 17.7549 13.7614 16.9862 12.7479 16.9821L11.3193 16.9763C10.1843 16.9717 9.25913 17.9098 9.25443 19.0679L9.25091 19.937C9.24621 21.0961 10.1647 22.0407 11.2988 22.0453L12.7273 22.0511C13.7408 22.0552 14.5845 21.2933 14.751 20.3007L16.2403 20.3068C17.8617 20.3134 19.2238 19.1394 19.5446 17.578C20.964 17.2627 22.033 15.9741 22.0393 14.4328L22.0473 12.4463ZM6.91635 10.9881L6.89693 15.7788C6.8963 15.934 6.77422 16.0577 6.62224 16.0571L5.20414 16.0513C4.29229 16.0476 3.55542 15.289 3.5592 14.3578L3.56725 12.3713C3.57103 11.4402 4.31403 10.6876 5.22587 10.6913L6.64398 10.697C6.79595 10.6976 6.91703 10.8223 6.9164 10.9775L6.91635 10.9881ZM13.2631 19.9436C13.2619 20.2433 13.0177 20.4906 12.7242 20.4894L11.2956 20.4836C11.0021 20.4825 10.76 20.2332 10.7612 19.9334L10.7647 19.0643C10.7659 18.7646 11.0101 18.5173 11.3036 18.5185L12.7322 18.5243C13.0257 18.5255 13.2678 18.7748 13.2666 19.0745L13.2631 19.9436ZM20.5195 14.4266C20.5158 15.3578 19.7728 16.1104 18.8609 16.1067L17.4428 16.101C17.2908 16.1003 17.1698 15.9757 17.1704 15.8205L17.1898 11.0298C17.1905 10.8746 17.3125 10.7509 17.4645 10.7516L18.8826 10.7573C19.7945 10.761 20.5313 11.5196 20.5276 12.4508L20.5195 14.4373L20.5195 14.4266Z"
fill="black"
fill="currentColor"
/> />
</svg> </svg>
); );

Loading…
Cancel
Save