diff --git a/src/app/globals.css b/src/app/globals.css index 118972e..00f8adb 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -160,3 +160,30 @@ body[data-page-background="custom"] .app-shell { 0 2px 7px rgb(0 0 0 / 22%), 0 0 0 1px rgb(0 0 0 / 8%); } + +/* 3 Dots Pulsing Wave Loader for Buttons & App */ +@keyframes button-dot-slide { + 0%, 100% { + transform: scale(0.35); + opacity: 0.35; + } + 40% { + transform: scale(1.25); + opacity: 1; + } +} + +.animate-dots-slide-1 { + animation: button-dot-slide 0.9s cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite; + animation-delay: 0s; +} + +.animate-dots-slide-2 { + animation: button-dot-slide 0.9s cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite; + animation-delay: 0.18s; +} + +.animate-dots-slide-3 { + animation: button-dot-slide 0.9s cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite; + animation-delay: 0.36s; +} diff --git a/src/app/intro/page.tsx b/src/app/intro/page.tsx index 03d257c..1aafaf5 100644 --- a/src/app/intro/page.tsx +++ b/src/app/intro/page.tsx @@ -35,11 +35,9 @@ export default function Intro() { return; } - const shouldRedirect = - authBridge.isAuthenticated() && - window.sessionStorage.getItem(REDIRECT_SESSION_KEY) === "true"; + const isAuthenticated = authBridge.isAuthenticated(); - if (!shouldRedirect) { + if (!isAuthenticated) { if (!isCancelled) { setIsCheckingRedirect(false); } @@ -52,16 +50,24 @@ export default function Intro() { setIsCheckingRedirect(true); } - const profileResponse = profile ?? (await refetch()).data; - const nextPath = localizePath(getSubmitPath(profileResponse), locale); - window.sessionStorage.removeItem(REDIRECT_SESSION_KEY); + try { + const profileResponse = profile ?? (await refetch()).data; + const nextPath = localizePath(getSubmitPath(profileResponse), locale); + if (typeof window !== "undefined") { + window.sessionStorage.removeItem(REDIRECT_SESSION_KEY); + } - if (!isCancelled) { - router.replace(nextPath); - return; + if (!isCancelled) { + router.replace(nextPath); + return; + } + } catch { + if (!isCancelled) { + setIsCheckingRedirect(false); + } + } finally { + isRedirectingRef.current = false; } - - isRedirectingRef.current = false; }; void redirectIfNeeded(); @@ -208,7 +214,7 @@ export default function Intro() { style={{ paddingBottom: "calc(20px + var(--safe-bottom))" }} className="pointer-events-auto px-[17px] pt-3" > - diff --git a/src/app/new-match/page.tsx b/src/app/new-match/page.tsx index 719f78e..5de9915 100644 --- a/src/app/new-match/page.tsx +++ b/src/app/new-match/page.tsx @@ -1,6 +1,7 @@ "use client"; import Image from "next/image"; +import Link from "next/link"; import { useEffect, useMemo } from "react"; import { useRouter } from "next/navigation"; import { localizePath } from "@/translations/config"; @@ -8,6 +9,7 @@ import { getSubmitPath } from "@/lib/get-submit-path"; import { FaLock } from "react-icons/fa6"; import AdvisorActionsCard from "@/components/ui/advisor-actions-card"; import NavigationButton from "@/components/ui/navigation-button"; +import { DotsLoader } from "@/components/ui/button"; import { PageBackground } from "@/components/utils/page-background"; import type { MarriageField, @@ -250,9 +252,9 @@ export default function NewMatchPage() {
{isLoading ? ( -

- Loading match summary... -

+
+ +
) : isError ? (

Unable to load match summary. @@ -264,7 +266,7 @@ export default function NewMatchPage() { {matchDisplay.name} -

+
{matchDisplay.occupation ? ( ) : null} @@ -288,17 +290,14 @@ export default function NewMatchPage() { {matchDisplay.cityPreference ? ( ) : null} - {matchDisplay.extraFields.map((field) => ( - - ))}
- View Profile - + ) : (

diff --git a/src/app/new-match/profile/page.tsx b/src/app/new-match/profile/page.tsx index 9f6af22..cc687c3 100644 --- a/src/app/new-match/profile/page.tsx +++ b/src/app/new-match/profile/page.tsx @@ -3,7 +3,7 @@ import Image from "next/image"; import { useRouter } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; -import Button from "@/components/ui/button"; +import Button, { DotsLoader } from "@/components/ui/button"; import DismissReasonSheet from "@/components/ui/dismiss-reason-sheet"; import FemaleConsentSheet from "@/components/ui/female-consent-sheet"; import InformationSheet from "@/components/ui/information-sheet"; @@ -361,7 +361,8 @@ export default function NewMatchProfilePage() { ) : ( @@ -338,10 +339,11 @@ export default function RequestAcceptedPage() { onClick={() => { void handleSecondaryAction(); }} + disabled={paymentMutation.isPending} className="max-w-[212px] appearance-none border-0 bg-transparent p-0 text-left" > -

- {secondaryActionText} +
+ {paymentMutation.isPending ? : secondaryActionText}
diff --git a/src/components/questions/question-date.tsx b/src/components/questions/question-date.tsx index 646b17a..daa4669 100644 --- a/src/components/questions/question-date.tsx +++ b/src/components/questions/question-date.tsx @@ -1,9 +1,10 @@ "use client"; -import { useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import type { QuestionField } from "@/data/question-data"; import { useQuestionAnswers } from "./question-answer-storage"; import QuestionTitle from "./question-title"; +import { useI18n } from "@/translations/provider"; type QuestionDateProps = { question: QuestionField; @@ -32,28 +33,48 @@ const DAYS = Array.from({ length: 31 }, (_, i) => { return { value: val, label: `${num}` }; }); +const MIN_AGE = 18; const currentYear = new Date().getFullYear(); -const YEARS = Array.from({ length: 90 }, (_, i) => (currentYear - i).toString()); +const maxBirthYear = currentYear - MIN_AGE; +const YEARS = Array.from({ length: 80 }, (_, i) => (maxBirthYear - i).toString()); export function QuestionDate({ question, questionIndex, disabled, }: QuestionDateProps) { + const { locale } = useI18n(); const { getAnswerValue, setAnswerValue } = useQuestionAnswers(); const value = getAnswerValue(question, questionIndex); const dateValue = typeof value === "string" ? value : ""; - const parts = dateValue ? dateValue.split("-") : []; - const year = parts[0] || ""; - const month = parts[1] || ""; - const day = parts[2] || ""; + const [selectedYear, setSelectedYear] = useState(() => { + const parts = dateValue ? dateValue.split("-") : []; + return parts[0] || ""; + }); + const [selectedMonth, setSelectedMonth] = useState(() => { + const parts = dateValue ? dateValue.split("-") : []; + return parts[1] || ""; + }); + const [selectedDay, setSelectedDay] = useState(() => { + const parts = dateValue ? dateValue.split("-") : []; + return parts[2] || ""; + }); + + useEffect(() => { + const parts = dateValue ? dateValue.split("-") : []; + if (parts.length === 3) { + setSelectedYear(parts[0] || ""); + setSelectedMonth(parts[1] || ""); + setSelectedDay(parts[2] || ""); + } + }, [dateValue]); const calculatedAge = useMemo(() => { - if (!year || !month || !day) return null; - const y = Number.parseInt(year, 10); - const m = Number.parseInt(month, 10); - const d = Number.parseInt(day, 10); + if (!selectedYear || !selectedMonth || !selectedDay) return null; + const y = Number.parseInt(selectedYear, 10); + const m = Number.parseInt(selectedMonth, 10); + const d = Number.parseInt(selectedDay, 10); if (!y || !m || !d || Number.isNaN(y) || Number.isNaN(m) || Number.isNaN(d)) { return null; @@ -70,19 +91,34 @@ export function QuestionDate({ } return age >= 0 ? age : null; - }, [year, month, day]); + }, [selectedYear, selectedMonth, selectedDay]); + + const isUnder18 = calculatedAge !== null && calculatedAge < MIN_AGE; - const handleSelectChange = (newYear: string, newMonth: string, newDay: string) => { - if (!newYear && !newMonth && !newDay) { + const updateDate = (y: string, m: string, d: string) => { + setSelectedYear(y); + setSelectedMonth(m); + setSelectedDay(d); + + if (y && m && d) { + const formattedMonth = m.padStart(2, "0"); + const formattedDay = d.padStart(2, "0"); + setAnswerValue(question, questionIndex, `${y}-${formattedMonth}-${formattedDay}`); + } else { setAnswerValue(question, questionIndex, ""); - return; } + }; - const y = newYear || year || "2000"; - const m = (newMonth || month || "01").padStart(2, "0"); - const d = (newDay || day || "01").padStart(2, "0"); + const handleDayChange = (newDay: string) => { + updateDate(selectedYear, selectedMonth, newDay); + }; + + const handleMonthChange = (newMonth: string) => { + updateDate(selectedYear, newMonth, selectedDay); + }; - setAnswerValue(question, questionIndex, `${y}-${m}-${d}`); + const handleYearChange = (newYear: string) => { + updateDate(newYear, selectedMonth, selectedDay); }; return ( @@ -98,8 +134,8 @@ export function QuestionDate({
{/* Day / روز */} handleSelectChange(year, e.target.value, day)} + value={selectedMonth} + onChange={(e) => handleMonthChange(e.target.value)} disabled={disabled} aria-label="Month" className="h-[54px] w-full cursor-pointer rounded-[15px] border border-[#E7D8D5] bg-white px-3 group-12 text-[#181818] outline-none transition-all focus:border-[#6F6F6F]" @@ -130,8 +166,8 @@ export function QuestionDate({ {/* Year */}