Browse Source
feat: add multilingual support and new navigation components for candidate interaction
front-test-2
feat: add multilingual support and new navigation components for candidate interaction
front-test-2
34 changed files with 958 additions and 436 deletions
-
1public/assets/images/whatsapp-icon.svg
-
221src/app/candidate-contact/page.tsx
-
136src/app/new-match/profile/page.tsx
-
295src/app/request-accepted/page.tsx
-
29src/components/Componentes/navigation-button.tsx
-
175src/components/Componentes/support-sheet.tsx
-
18src/components/Componentes/swipe-button.tsx
-
2src/data/questions/en.json
-
4src/data/questions/fa.json
-
7src/lib/get-submit-path.ts
-
18src/translations/locales/ar.json
-
18src/translations/locales/az.json
-
18src/translations/locales/bn.json
-
18src/translations/locales/da.json
-
18src/translations/locales/de.json
-
23src/translations/locales/en.json
-
18src/translations/locales/es.json
-
23src/translations/locales/fa.json
-
18src/translations/locales/fr.json
-
18src/translations/locales/gu.json
-
18src/translations/locales/ha.json
-
18src/translations/locales/he.json
-
18src/translations/locales/hi.json
-
18src/translations/locales/id.json
-
18src/translations/locales/ks.json
-
18src/translations/locales/pt.json
-
18src/translations/locales/ru.json
-
18src/translations/locales/sw.json
-
18src/translations/locales/tg.json
-
18src/translations/locales/tr.json
-
18src/translations/locales/ul.json
-
18src/translations/locales/ur.json
-
18src/translations/locales/uz.json
-
18src/translations/locales/zh.json
@ -0,0 +1 @@ |
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/></svg> |
|||
@ -0,0 +1,175 @@ |
|||
"use client"; |
|||
|
|||
import Image from "next/image"; |
|||
import { useEffect, useState } from "react"; |
|||
import { createPortal } from "react-dom"; |
|||
import { FiHeadphones } from "react-icons/fi"; |
|||
import { useI18n } from "@/translations/provider"; |
|||
|
|||
const EXIT_ANIMATION_MS = 220; |
|||
|
|||
export type SupportSheetProps = { |
|||
isOpen: boolean; |
|||
onClose: () => void; |
|||
}; |
|||
|
|||
export function SupportSheet({ isOpen, onClose }: SupportSheetProps) { |
|||
const { dictionary: t } = useI18n(); |
|||
const [isVisible, setIsVisible] = useState(isOpen); |
|||
const [isEntering, setIsEntering] = useState(true); |
|||
const [isClosing, setIsClosing] = useState(false); |
|||
const [mounted, setMounted] = useState(false); |
|||
|
|||
useEffect(() => { |
|||
setMounted(true); |
|||
}, []); |
|||
|
|||
const closeSheet = () => { |
|||
if (isClosing) return; |
|||
setIsClosing(true); |
|||
}; |
|||
|
|||
// Sync with isOpen prop
|
|||
useEffect(() => { |
|||
if (isOpen) { |
|||
setIsVisible(true); |
|||
setIsClosing(false); |
|||
setIsEntering(true); |
|||
} |
|||
}, [isOpen]); |
|||
|
|||
// Entry animation
|
|||
useEffect(() => { |
|||
if (!isVisible) return; |
|||
const frameId = window.requestAnimationFrame(() => { |
|||
setIsEntering(false); |
|||
}); |
|||
return () => window.cancelAnimationFrame(frameId); |
|||
}, [isVisible]); |
|||
|
|||
// Lock body scroll
|
|||
useEffect(() => { |
|||
if (!isVisible) return; |
|||
const previousBodyOverflow = document.body.style.overflow; |
|||
const previousHtmlOverflow = document.documentElement.style.overflow; |
|||
document.body.style.overflow = "hidden"; |
|||
document.documentElement.style.overflow = "hidden"; |
|||
return () => { |
|||
document.body.style.overflow = previousBodyOverflow; |
|||
document.documentElement.style.overflow = previousHtmlOverflow; |
|||
}; |
|||
}, [isVisible]); |
|||
|
|||
// Exit animation
|
|||
useEffect(() => { |
|||
if (!isClosing) return; |
|||
const timeoutId = window.setTimeout(() => { |
|||
setIsVisible(false); |
|||
onClose(); |
|||
}, EXIT_ANIMATION_MS); |
|||
return () => window.clearTimeout(timeoutId); |
|||
}, [isClosing, onClose]); |
|||
|
|||
const handleSupportContact = () => { |
|||
if (typeof window !== "undefined" && "HabibApp" in window) { |
|||
try { |
|||
const app = ( |
|||
window as Window & { |
|||
HabibApp?: { postMessage: (msg: string) => void }; |
|||
} |
|||
).HabibApp; |
|||
app?.postMessage( |
|||
JSON.stringify({ |
|||
action: "open_consultant_page", |
|||
data: { consultant: "habib@gmail.com" }, |
|||
}), |
|||
); |
|||
} catch (err) { |
|||
console.error("Error calling HabibApp bridge", err); |
|||
} |
|||
} else { |
|||
window.open("mailto:habib@gmail.com", "_blank"); |
|||
} |
|||
closeSheet(); |
|||
}; |
|||
|
|||
if (!isVisible || !mounted) return null; |
|||
|
|||
return createPortal( |
|||
<div |
|||
className={[ |
|||
"fixed inset-0 z-[100] flex items-end justify-center transition-all duration-[220ms] select-none", |
|||
isClosing || isEntering |
|||
? "bg-[#171717]/0 opacity-0" |
|||
: "bg-[#171717]/55 opacity-100", |
|||
] |
|||
.filter(Boolean) |
|||
.join(" ")} |
|||
role="dialog" |
|||
aria-modal="true" |
|||
onClick={(event) => { |
|||
if (event.target === event.currentTarget) { |
|||
closeSheet(); |
|||
} |
|||
}} |
|||
> |
|||
<section |
|||
className={[ |
|||
"w-full sm:max-w-[375px] rounded-t-[15px] bg-[#EBEBEB] px-[14px] py-[14px] text-center shadow-[0_20px_60px_rgba(15,23,42,0.08)] transition-transform duration-[220ms] ease-out will-change-transform flex flex-col items-center", |
|||
isClosing || isEntering ? "translate-y-full" : "translate-y-0", |
|||
] |
|||
.filter(Boolean) |
|||
.join(" ")} |
|||
> |
|||
<div className="flex flex-col items-center w-full gap-[11px]"> |
|||
{/* Support Icon */} |
|||
<div className="flex h-[38px] w-[38px] items-center justify-center"> |
|||
<FiHeadphones className="size-[27px] shrink-0 text-[#21C17D]" strokeWidth={2} /> |
|||
</div> |
|||
|
|||
{/* Text Content */} |
|||
<div className="flex flex-col items-start gap-[14px] w-full"> |
|||
<h3 className="w-full font-bold text-[16px] leading-[20px] text-[#111111] text-center"> |
|||
{t.common.supportTitle} |
|||
</h3> |
|||
|
|||
<p className="w-full font-normal text-[14px] leading-[20px] text-[#747474] text-center"> |
|||
{t.common.supportDescription} |
|||
</p> |
|||
</div> |
|||
</div> |
|||
|
|||
{/* Buttons Row */} |
|||
<div className="flex w-full items-center gap-3 mt-[14px]"> |
|||
{/* Cancel Button */} |
|||
<button |
|||
type="button" |
|||
onClick={closeSheet} |
|||
className="flex-1 h-[52px] rounded-[11px] border border-[#747474] bg-transparent text-[#747474] font-semibold text-[16px] flex items-center justify-center cursor-pointer transition-transform active:scale-[0.98]" |
|||
> |
|||
{t.common.cancel} |
|||
</button> |
|||
|
|||
{/* Contact WhatsApp Button */} |
|||
<button |
|||
type="button" |
|||
onClick={handleSupportContact} |
|||
className="flex-1 h-[52px] rounded-[11px] bg-[#00AC78] text-white font-bold text-[16px] flex items-center justify-center gap-1 cursor-pointer transition-transform active:scale-[0.98]" |
|||
> |
|||
<Image |
|||
src="/assets/images/whatsapp-icon.svg" |
|||
alt="" |
|||
width={21} |
|||
height={21} |
|||
className="shrink-0 brightness-0 invert" |
|||
/> |
|||
{t.common.supportSwipeText} |
|||
</button> |
|||
</div> |
|||
</section> |
|||
</div>, |
|||
document.body, |
|||
); |
|||
} |
|||
|
|||
export default SupportSheet; |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue