Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/Components/Announcer.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Global ARIA live-region announcer. Renders two visually-hidden regions that
// persist in the DOM for the page lifetime so screen readers pick up dynamic
// status/error messages. Messages auto-clear after 5s so stale text is not
// re-read on subsequent focus.
@react.component
let make = () => {
let (announcement, setAnnouncement) = Recoil.useRecoilState(
AccessibilityAnnouncer.announcementAtom,
)
let {localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom)

React.useEffect(() => {
let handle = (ev: Window.event) => {
try {
let dict = ev.data->Utils.safeParse->Utils.getDictFromJson
switch dict->Dict.get("submitSuccessful")->Option.flatMap(JSON.Decode.bool) {
| Some(false) =>
let message =
dict
->Utils.getDictFromDict("error")
->Utils.getString("message", localeString.enterValidDetailsText)
setAnnouncement(_ => {
message,
assertive: true,
})
| _ => ()
}
} catch {
| _ => ()
}
}
Window.addEventListener("message", handle)
Some(() => Window.removeEventListener("message", handle))
}, [localeString.enterValidDetailsText])

React.useEffect(() => {
if announcement.message !== "" {
let timeoutId = setTimeout(() => {
setAnnouncement(_ => AccessibilityAnnouncer.defaultAnnouncement)
}, 5000)
Some(() => clearTimeout(timeoutId))
} else {
None
}
}, [announcement.message])

<div className={AccessibilityUtils.visuallyHiddenClass}>
<div id="hyperswitch-sdk-live-status" role="status" ariaLive={#polite} ariaAtomic=true>
{(announcement.assertive ? "" : announcement.message)->React.string}
</div>
<div id="hyperswitch-sdk-live-alert" role="alert" ariaLive={#assertive} ariaAtomic=true>
{(announcement.assertive ? announcement.message : "")->React.string}
</div>
</div>
}
10 changes: 6 additions & 4 deletions src/Components/ErrorComponent.res
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ let make = (~errorStr=None, ~cardError="", ~expiryError="", ~cvcError="") => {
switch innerLayout {
| Spaced =>
<RenderIf condition=isSpacedErrorShown>
<div className="Error pt-1" style=errorTextStyle>
{React.string(errorStr->Belt.Option.getWithDefault(""))}
</div>
<LiveError
text={errorStr->Belt.Option.getWithDefault("")}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
text={errorStr->Belt.Option.getWithDefault("")}
text={errorStr->Option.getOr("")}

className="Error pt-1"
style={errorTextStyle}
/>
</RenderIf>
| Compressed =>
<RenderIf condition=isCompressedErrorShown>
<div className="Error pt-1" style=errorTextStyle> {React.string("Invalid input")} </div>
<LiveError text={"Invalid input"} className="Error pt-1" style={errorTextStyle} />
</RenderIf>
}
}
3 changes: 1 addition & 2 deletions src/Components/Loader.res
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ let make = (~branding="auto", ~showText=true) => {
<div
className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-[#0069FD] border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"
role="status">
<span
className="!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 ![clip:rect(0,0,0,0)]">
<span className={AccessibilityUtils.visuallyHiddenClass}>
{"Loading..."->React.string}
</span>
</div>
Expand Down
4 changes: 4 additions & 0 deletions src/Components/PayNowButton.res
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ let make = (~onClickHandler=?, ~label=?) => {
open PaymentTypeContext
let (showLoader, setShowLoader) = React.useState(() => false)
let (isPayNowButtonDisable, setIsPayNowButtonDisable) = React.useState(() => false)
let announce = AccessibilityAnnouncer.useAnnounce()
let {themeObj, localeString} = configAtom->Recoil.useRecoilValueFromAtom
let {sdkHandleConfirmPayment} = optionAtom->Recoil.useRecoilValueFromAtom

Expand All @@ -40,6 +41,7 @@ let make = (~onClickHandler=?, ~label=?) => {
if !(submitSuccessfulVal->JSON.Decode.bool->Option.getOr(false)) {
setIsPayNowButtonDisable(_ => false)
setShowLoader(_ => false)
announce(~assertive=true, localeString.paymentFailedText)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PayNowButton.res — calls announce(~assertive=true, localeString.paymentFailedText) (generic message)

Then Announcer.res— message listener extracts the specific error message from the response payload
Both listen for the same window "message" event with submitSuccessful === false. The Announcer registers its listener at mount time (in useEffect), while PayNowButton registers via addSmartEventListener at click time. Listeners fire in registration order, so Announcer fires first (sets specific error), then PayNowButton fires (overwrites with generic message)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image "submitSuccessful" is used many places, check once if it's breaking for them or not.

}
| None => ()
}
Expand All @@ -55,13 +57,15 @@ let make = (~onClickHandler=?, ~label=?) => {
let handleOnClick = _ => {
setIsPayNowButtonDisable(_ => true)
setShowLoader(_ => true)
announce(localeString.processingPaymentText)
EventListenerManager.addSmartEventListener("message", handleMessage, "onSubmitSuccessful")
messageParentWindow([("handleSdkConfirm", confirmPayload)])
}

<div className="flex flex-col gap-1 h-auto w-full items-center">
<button
disabled=isPayNowButtonDisable
ariaBusy={showLoader}
onClick={onClickHandler->Option.isNone ? handleOnClick : onClickHandlerFunc}
className={`w-full flex flex-row justify-center items-center`}
style={
Expand Down
20 changes: 10 additions & 10 deletions src/Components/SavedCardItem.res
Original file line number Diff line number Diff line change
Expand Up @@ -341,14 +341,14 @@ let make = (
</RenderIf>
<RenderIf
condition={hideCardExpiry && isActive && innerLayout === Spaced && cvcError != ""}>
<div
<LiveError
text={cvcError}
className="Error pt-1 mt-1 ml-3"
style={
style={{
color: themeObj.colorDangerText,
fontSize: themeObj.fontSizeSm,
}>
{React.string(cvcError)}
</div>
}}
/>
</RenderIf>
<RenderIf
condition={isActive && displayBillingDetails && billingDetailsArrayLength > 0}>
Expand All @@ -361,14 +361,14 @@ let make = (
</RenderIf>
<RenderIf
condition={!hideCardExpiry && isActive && innerLayout === Spaced && cvcError != ""}>
<div
<LiveError
text={cvcError}
className="Error pt-1 mt-1 ml-1"
style={
style={{
color: themeObj.colorDangerText,
fontSize: themeObj.fontSizeSm,
}>
{React.string(cvcError)}
</div>
}}
/>
</RenderIf>
<RenderIf condition={isCardExpired}>
<div className="italic mt-3 ml-1" style={fontSize: "14px", opacity: "0.7"}>
Expand Down
4 changes: 3 additions & 1 deletion src/Components/UpdateIntentOverlay.res
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ let make = () => {

<RenderIf condition=isUpdateIntentLoading>
<div
className="absolute inset-0 flex flex-col items-center justify-center gap-2 backdrop-blur-sm bg-white/40 z-[999] rounded-[inherit]">
className="absolute inset-0 flex flex-col items-center justify-center gap-2 backdrop-blur-sm bg-white/40 z-[999] rounded-[inherit]"
role="status"
ariaLive={#polite}>
<div
className="w-6 h-6 animate-spin rounded-full border-3 border-black/10 border-t-black/60"
/>
Expand Down
4 changes: 2 additions & 2 deletions src/Components/VGSInputComponent.res
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ let make = (~fieldName="", ~id="", ~isFocused=false, ~errorStr=?, ~compact=false
fontSize: themeObj.fontSizeLg,
marginBottom: "5px",
opacity: "0.6",
}
ariaHidden=true>
}>
{React.string(fieldName)}
</div>
</RenderIf>
Expand All @@ -44,6 +43,7 @@ let make = (~fieldName="", ~id="", ~isFocused=false, ~errorStr=?, ~compact=false
<div className="flex flex-row">
<div
id
title=fieldName
style={
background: themeObj.colorBackground,
// Compact (saved-card cvc): horizontal padding only + fixed height
Expand Down
2 changes: 2 additions & 0 deletions src/LocaleStrings/ArabicLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -277,4 +277,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "إغلاق",
dialogLabel: "مربع حوار",
paymentMethodsGroupLabel: "طرق الدفع",
processingPaymentText: "جارٍ معالجة الدفع",
paymentFailedText: "فشل الدفع. يرجى مراجعة التفاصيل والمحاولة مجددًا.",
}
2 changes: 2 additions & 0 deletions src/LocaleStrings/CatalanLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -276,4 +276,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "Tancar",
dialogLabel: "Diàleg",
paymentMethodsGroupLabel: "Mètodes de pagament",
processingPaymentText: "Processant el pagament",
paymentFailedText: "El pagament ha fallat. Si us plau, reviseu els vostres detalls i torneu-ho a provar.",
}
2 changes: 2 additions & 0 deletions src/LocaleStrings/ChineseLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "关闭",
dialogLabel: "对话框",
paymentMethodsGroupLabel: "支付方式",
processingPaymentText: "正在处理付款",
paymentFailedText: "付款失败,请检查您的信息后重试。",
}
2 changes: 2 additions & 0 deletions src/LocaleStrings/DeutschLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -275,4 +275,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "Schließen",
dialogLabel: "Dialog",
paymentMethodsGroupLabel: "Zahlungsmethoden",
processingPaymentText: "Zahlung wird verarbeitet",
paymentFailedText: "Zahlung fehlgeschlagen. Bitte überprüfen Sie Ihre Daten und versuchen Sie es erneut.",
}
2 changes: 2 additions & 0 deletions src/LocaleStrings/DutchLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "Sluiten",
dialogLabel: "Dialoogvenster",
paymentMethodsGroupLabel: "Betaalmethoden",
processingPaymentText: "Betaling verwerken",
paymentFailedText: "Betaling mislukt. Controleer uw gegevens en probeer het opnieuw.",
}
2 changes: 2 additions & 0 deletions src/LocaleStrings/EnglishGBLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "Close",
dialogLabel: "Dialog",
paymentMethodsGroupLabel: "Payment methods",
processingPaymentText: "Processing payment",
paymentFailedText: "Payment failed. Please review your details and try again.",
}
2 changes: 2 additions & 0 deletions src/LocaleStrings/EnglishLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "Close",
dialogLabel: "Dialog",
paymentMethodsGroupLabel: "Payment methods",
processingPaymentText: "Processing payment",
paymentFailedText: "Payment failed. Please review your details and try again.",
}
2 changes: 2 additions & 0 deletions src/LocaleStrings/FrenchBelgiumLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -276,4 +276,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "Fermer",
dialogLabel: "Dialogue",
paymentMethodsGroupLabel: "Méthodes de paiement",
processingPaymentText: "Traitement du paiement",
paymentFailedText: "Paiement échoué. Veuillez vérifier vos informations et réessayer.",
}
2 changes: 2 additions & 0 deletions src/LocaleStrings/FrenchLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -276,4 +276,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "Fermer",
dialogLabel: "Dialogue",
paymentMethodsGroupLabel: "Méthodes de paiement",
processingPaymentText: "Traitement du paiement",
paymentFailedText: "Paiement échoué. Veuillez vérifier vos informations et réessayer.",
}
2 changes: 2 additions & 0 deletions src/LocaleStrings/HebrewLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -275,4 +275,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "סגור",
dialogLabel: "תיבת דו-שיח",
paymentMethodsGroupLabel: "אמצעי תשלום",
processingPaymentText: "מעבד תשלום",
paymentFailedText: "התשלום נכשל. אנא בדוק את פרטיך ונסה שוב.",
}
2 changes: 2 additions & 0 deletions src/LocaleStrings/ItalianLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -276,4 +276,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "Chiudi",
dialogLabel: "Finestra di dialogo",
paymentMethodsGroupLabel: "Metodi di pagamento",
processingPaymentText: "Elaborazione del pagamento",
paymentFailedText: "Pagamento non riuscito. Si prega di rivedere i dettagli e riprovare.",
}
2 changes: 2 additions & 0 deletions src/LocaleStrings/JapaneseLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -275,4 +275,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "閉じる",
dialogLabel: "ダイアログ",
paymentMethodsGroupLabel: "支払い方法",
processingPaymentText: "決済処理中",
paymentFailedText: "決済に失敗しました。内容をご確認の上、もう一度お試しください。",
}
2 changes: 2 additions & 0 deletions src/LocaleStrings/LocaleStringTypes.res
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ type localeStrings = {
closeLabel: string,
dialogLabel: string,
paymentMethodsGroupLabel: string,
processingPaymentText: string,
paymentFailedText: string,
}

type constantStrings = {
Expand Down
2 changes: 2 additions & 0 deletions src/LocaleStrings/PolishLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -275,4 +275,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "Zamknij",
dialogLabel: "Okno dialogowe",
paymentMethodsGroupLabel: "Metody płatności",
processingPaymentText: "Przetwarzanie płatności",
paymentFailedText: "Płatność nieudana. Sprawdź swoje dane i spróbuj ponownie.",
}
2 changes: 2 additions & 0 deletions src/LocaleStrings/PortugueseLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -275,4 +275,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "Fechar",
dialogLabel: "Diálogo",
paymentMethodsGroupLabel: "Métodos de pagamento",
processingPaymentText: "Processando pagamento",
paymentFailedText: "Pagamento falhou. Por favor, revise seus dados e tente novamente.",
}
2 changes: 2 additions & 0 deletions src/LocaleStrings/RussianLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "Закрыть",
dialogLabel: "Диалог",
paymentMethodsGroupLabel: "Способы оплаты",
processingPaymentText: "Обработка платежа",
paymentFailedText: "Платёж не выполнен. Проверьте ваши данные и попробуйте снова.",
}
2 changes: 2 additions & 0 deletions src/LocaleStrings/SpanishLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -275,4 +275,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "Cerrar",
dialogLabel: "Diálogo",
paymentMethodsGroupLabel: "Métodos de pago",
processingPaymentText: "Procesando pago",
paymentFailedText: "Pago fallido. Por favor, revise sus datos e intente de nuevo.",
}
2 changes: 2 additions & 0 deletions src/LocaleStrings/SwedishLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "Stäng",
dialogLabel: "Dialog",
paymentMethodsGroupLabel: "Betalningsmetoder",
processingPaymentText: "Bearbetar betalning",
paymentFailedText: "Betalningen misslyckades. Granska dina uppgifter och försök igen.",
}
2 changes: 2 additions & 0 deletions src/LocaleStrings/TraditionalChineseLocale.res
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,6 @@ let localeStrings: LocaleStringTypes.localeStrings = {
closeLabel: "關閉",
dialogLabel: "對話框",
paymentMethodsGroupLabel: "付款方式",
processingPaymentText: "正在處理付款",
paymentFailedText: "付款失敗。請檢查您的詳細信息並重試。",
}
1 change: 1 addition & 0 deletions src/PaymentManagement.res
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ let make = (
}, (isExpiryValid, CardUtils.isExpiryComplete(cardExpiry)))

<>
<Announcer />
<RenderIf
condition={showAddScreen &&
paymentManagementListValue.paymentMethodsEnabled->Array.length != 0}>
Expand Down
10 changes: 5 additions & 5 deletions src/Payments/ACHBankDebit.res
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,16 @@ let make = () => {
<div className="flex flex-col">
<AddBankAccount modalData setModalData />
<RenderIf condition={bankError->String.length > 0}>
<div
<LiveError
text={bankError}
className="Error pt-1"
style={
style={{
color: themeObj.colorDangerText,
fontSize: themeObj.fontSizeSm,
alignSelf: "start",
textAlign: "left",
}>
{React.string(bankError)}
</div>
}}
/>
</RenderIf>
</div>
<Surcharge paymentMethod paymentMethodType />
Expand Down
20 changes: 20 additions & 0 deletions src/Utilities/AccessibilityAnnouncer.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// A single announcement consumed by the global <Announcer /> live regions.
// `assertive=true` routes the message to the `role="alert"` region (errors);
// `assertive=false` routes it to the `role="status"` region (status updates).
type announcement = {
message: string,
assertive: bool,
}

let defaultAnnouncement = {
message: "",
assertive: false,
}

let announcementAtom = Recoil.atom("accessibilityAnnouncement", defaultAnnouncement)

// Returns a function components can call to announce a message to screen readers.
let useAnnounce = () => {
let setAnnouncement = Recoil.useSetRecoilState(announcementAtom)
(~assertive=false, message) => setAnnouncement(_ => {message, assertive})
}
Loading