diff --git a/public/hyperswitch/assets/hyperswitchSDK/cap.png b/public/hyperswitch/assets/hyperswitchSDK/cap.png new file mode 100644 index 000000000..51073481c Binary files /dev/null and b/public/hyperswitch/assets/hyperswitchSDK/cap.png differ diff --git a/public/hyperswitch/assets/hyperswitchSDK/powder.png b/public/hyperswitch/assets/hyperswitchSDK/powder.png new file mode 100644 index 000000000..7851596f9 Binary files /dev/null and b/public/hyperswitch/assets/hyperswitchSDK/powder.png differ diff --git a/public/hyperswitch/assets/hyperswitchSDK/shirt.png b/public/hyperswitch/assets/hyperswitchSDK/shirt.png new file mode 100644 index 000000000..5b85fce22 Binary files /dev/null and b/public/hyperswitch/assets/hyperswitchSDK/shirt.png differ diff --git a/public/hyperswitch/icons/hyperswitchSDK/authentication.svg b/public/hyperswitch/icons/hyperswitchSDK/authentication.svg new file mode 100644 index 000000000..e47766ecf --- /dev/null +++ b/public/hyperswitch/icons/hyperswitchSDK/authentication.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/hyperswitch/icons/hyperswitchSDK/tickMarkWhite.svg b/public/hyperswitch/icons/hyperswitchSDK/tickMarkWhite.svg new file mode 100644 index 000000000..4445195e1 --- /dev/null +++ b/public/hyperswitch/icons/hyperswitchSDK/tickMarkWhite.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/public/hyperswitch/icons/solid.svg b/public/hyperswitch/icons/solid.svg index 6920b90d2..a44b4150a 100644 --- a/public/hyperswitch/icons/solid.svg +++ b/public/hyperswitch/icons/solid.svg @@ -8798,4 +8798,268 @@ License) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/HyperSwitchAuthWrapper.res b/src/components/HyperSwitchAuthWrapper.res index ee0d30017..6be78d5ad 100644 --- a/src/components/HyperSwitchAuthWrapper.res +++ b/src/components/HyperSwitchAuthWrapper.res @@ -37,10 +37,11 @@ let make = (~children) => {
- {switch currentAuthState { - | LoggedOut => - | LoggedIn(_token) => children - | CheckingAuthStatus => + {switch (currentAuthState, url.path) { + | (_, list{"demo-store"}) => + | (LoggedOut, _) => + | (LoggedIn(_token), _) => children + | (CheckingAuthStatus, _) => }}
diff --git a/src/components/Icon.res b/src/components/Icon.res index 5d28cac6e..3cb37e0d2 100644 --- a/src/components/Icon.res +++ b/src/components/Icon.res @@ -9,6 +9,7 @@ let make = ( ~customIconColor="", ~customWidth=?, ~customHeight=?, + ~id=?, ) => { let urlPrefix = "" @@ -31,6 +32,7 @@ let make = ( | None => "flex flex-col justify-center" }}> React.element diff --git a/src/entryPoints/hyperswitch/EntryPointUtils.res b/src/entryPoints/hyperswitch/EntryPointUtils.res index 5b67e4f6d..67003d110 100644 --- a/src/entryPoints/hyperswitch/EntryPointUtils.res +++ b/src/entryPoints/hyperswitch/EntryPointUtils.res @@ -42,10 +42,14 @@ let renderDashboardApp = (~uiConfig=UIConfig.defaultUIConfig, children) => { | Some(container) => open ReactDOM.Client open ReactDOM.Client.Root + open Window let root = createRoot(container) + let windowUrl = urlSearch(location.href) + let isDemoStore = windowUrl.href->Js.String2.includes("demo-store") + let demoStoreClass = isDemoStore ? "overflow-scroll bg-[#f6f8fb]" : "overflow-hidden" root->render( -
+
children
, ) diff --git a/src/libraries/Window.res b/src/libraries/Window.res index 8a326afbb..738b74681 100644 --- a/src/libraries/Window.res +++ b/src/libraries/Window.res @@ -206,3 +206,23 @@ type boundingClient = {x: int, y: int, width: int, height: int} type env = {apiBaseUrl?: string, sdkBaseUrl?: string} @val @scope("window") external env: env = "_env_" + +type searchParams = {set: (. string, string) => unit, get: (. string) => Js.Json.t} +type url = {searchParams: searchParams, href: string} +@new external urlSearch: string => url = "URL" + +@val @scope("document") external createElement: string => Dom.element = "createElement" + +type location = {replace: (. string) => unit, href: string} +@val @scope("window") external location: location = "location" + +@set external elementSrc: (Dom.element, string) => unit = "src" +@set external elementOnload: (Dom.element, unit => unit) => unit = "onload" +@set external elementOnerror: (Dom.element, exn => unit) => unit = "onerror" + +type body + +@val @scope("document") +external body: body = "body" + +@send external appendChild: (body, Dom.element) => unit = "appendChild" diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/HSwitchDemoAppPreview.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/HSwitchDemoAppPreview.res new file mode 100644 index 000000000..8c89faf4a --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/HSwitchDemoAppPreview.res @@ -0,0 +1,152 @@ +@react.component +let make = (~isShowFilters, ~isShowTestCards, ~children=React.null) => { + let (shirtQuantity, setShirtQuantity) = React.useState(() => 1) + let (capQuantity, setCapQuantity) = React.useState(() => 2) + let isModalOpen = Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.isModalOpen) + + let renderSDK = Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.renderSDK) + let theme = Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.theme) + let isDesktop = HSwitchSDKUtils.getIsDesktop( + Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.size), + ) + + let themeColors = HSwitchSDKUtils.getThemeColorsFromTheme(theme) + let setSize = Recoil.useSetRecoilState(HSwitchRecoilAtoms.size) + let setIsMobileScreen = Recoil.useSetRecoilState(HSwitchRecoilAtoms.isMobileScreen) + let isMobileScreen = MatchMedia.useMatchMedia("(max-width: 1100px)") + + let (amount, setAmount) = Recoil.useRecoilState(HSwitchRecoilAtoms.amount) + + let mobileWrapperClass = "w-[336px] rounded-[48px] p-2 mx-auto h-[760px] shadow-websiteShadow" + let desktopWrapperClass = "rounded-[8px] shadow-websiteShadow" + + let mobileHeaderWrapperClass = "relative h-full rounded-[40px] overflow-hidden bg-white shadow-mobileHeaderShadow" + + React.useEffect1(() => { + if isMobileScreen { + setSize(._ => "Mobile") + setIsMobileScreen(._ => true) + } else { + setIsMobileScreen(._ => false) + } + + None + }, [isMobileScreen]) + + React.useEffect2(() => { + setAmount(._ => HSwitchSDKUtils.amountToDisplay(~shirtQuantity, ~capQuantity)) + None + }, (shirtQuantity, capQuantity)) + + let getMobileFrameButtonElement = className => { +
+ } + +
+ +
+
+ + {getMobileFrameButtonElement("left-0 top-[7.5rem] h-10 rounded-tl-md rounded-bl-md")} + {getMobileFrameButtonElement("left-0 top-48 h-16 rounded-tl-md rounded-bl-md")} + {getMobileFrameButtonElement("left-0 top-[17rem] h-16 rounded-tl-md rounded-bl-md")} + {getMobileFrameButtonElement("right-0 top-52 h-24 rounded-tr-md rounded-br-md")} + +
+
+
+
+ +
+ +
+
+ +
+ {React.string("Hyperswitch Demo Store")} +
+
+ {React.string(isDesktop ? "Test Mode" : "Test")} +
+
+
+ + + +
+ {React.string("Pay Hyperswitch")} +
+
+ {React.string("US$")} + {React.string(amount)} +
+
+ + + + +
+
+ {children} +
+ + + +
+
+
+
+
+
+
+ + + +
+} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/HSwitchSdkPreview.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/HSwitchSdkPreview.res new file mode 100644 index 000000000..691ba3976 --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/HSwitchSdkPreview.res @@ -0,0 +1,48 @@ +@react.component +let make = (~isShowFilters, ~isShowTestCards, ~children=React.null) => { + let renderSDK = Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.renderSDK) + let theme = Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.theme) + let isDesktop = HSwitchSDKUtils.getIsDesktop( + Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.size), + ) + let setIsMobileScreen = Recoil.useSetRecoilState(HSwitchRecoilAtoms.isMobileScreen) + let isMobileScreen = MatchMedia.useMatchMedia("(max-width: 1100px)") + + let themeColors = HSwitchSDKUtils.getThemeColorsFromTheme(theme) + + let setSize = Recoil.useSetRecoilState(HSwitchRecoilAtoms.size) + + React.useEffect1(() => { + if isMobileScreen { + setSize(._ => "Mobile") + setIsMobileScreen(._ => true) + } else { + setIsMobileScreen(._ => false) + } + + None + }, [isMobileScreen]) + +
+ + + +
+ +
+
+ {children} +
+
+
+ + + +
+} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/HyperswitchSDK.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/HyperswitchSDK.res new file mode 100644 index 000000000..bed3e3e0e --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/HyperswitchSDK.res @@ -0,0 +1,100 @@ +open Promise + +@react.component +let make = (~viewType) => { + let setViewType = Recoil.useSetRecoilState(HSwitchRecoilAtoms.viewType) + let amountToShow = + Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.amount) + ->Belt.Float.fromString + ->Belt.Option.getWithDefault(100.0) *. 100.0 + let amount = amountToShow->Belt.Int.fromFloat->Belt.Int.toString + let fetchApi = AuthHooks.useApiFetcher() + let (options, setOptions) = React.useState(_ => None) + let (selectedMenu, setSelectedMenu) = React.useState(_ => "") + + let theme = Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.theme)->Js.String2.toLowerCase + let customerLocation = Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.customerLocation) + let currency = HSwitchSDKUtils.getCurrencyFromCustomerLocation(customerLocation) + let country = HSwitchSDKUtils.getCountryFromCustomerLocation(customerLocation) + let countryCode = country + + let hyperSwitchToken = LocalStorage.getItem("login")->Js.Nullable.toOption + let fetchDetails = APIUtils.useGetMethod() + let (merchantPublishableKey, setPublishableAPIKey) = React.useState(_ => "") + + let fetchMerchantInfo = async () => { + try { + let accountUrl = APIUtils.getURL(~entityName=MERCHANT_ACCOUNT, ~methodType=Get, ()) + let merchantDetailsJSON = await fetchDetails(accountUrl) + let merchantDetails = merchantDetailsJSON->HSwitchMerchantAccountUtils.getMerchantDetails + setPublishableAPIKey(_ => merchantDetails.publishable_key) + } catch { + | Js.Exn.Error(e) => + let _err = Js.Exn.message(e)->Belt.Option.getWithDefault("Failed to Fetch!") + } + } + + React.useEffect0(() => { + fetchMerchantInfo()->ignore + setViewType(._ => viewType) + None + }) + + React.useEffect1(() => { + let body = HSwitchSDKUtils.getDefaultPayload(amount, currency, country, countryCode) + + let headers = switch hyperSwitchToken { + | Some(key) => + switch key { + | "" => [("Content-Type", "application/json"), ("api-key", HSwitchSDKUtils.defaultAPIKey)] + | key => [ + ("Content-Type", "application/json"), + ("Api-Key", "hyperswitch"), + ("Authorization", `Bearer ${key}`), + ] + } + | None => [("Content-Type", "application/json"), ("api-key", HSwitchSDKUtils.defaultAPIKey)] + } + + fetchApi( + HSwitchSDKUtils.backendEndpointUrl, + ~method_=Fetch.Post, + ~bodyStr=body->Js.Json.stringify, + ~headers=headers->Js.Dict.fromArray, + (), + ) + ->then(resp => { + Fetch.Response.json(resp) + }) + ->then(json => { + let clientSecret = + json + ->Js.Json.decodeObject + ->Belt.Option.flatMap(x => x->Js.Dict.get("client_secret")) + ->Belt.Option.flatMap(Js.Json.decodeString) + ->Belt.Option.getWithDefault("") + setOptions(_ => Some(HSwitchSDKUtils.getOptions(clientSecret, theme))) + json->resolve + }) + ->ignore + None + }, [theme]) + + let hyperPromise = switch merchantPublishableKey { + | "" => HSwitchSDKUtils.loadHyper(HSwitchSDKUtils.defaultPublishableKey) + | key => HSwitchSDKUtils.loadHyper(key) + } + +
+ {switch options { + | Some(val) => + + | None => React.null + }} +
+} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/UniversalHyperswitchSDK.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/UniversalHyperswitchSDK.res new file mode 100644 index 000000000..cca3900b8 --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/UniversalHyperswitchSDK.res @@ -0,0 +1,63 @@ +open Window + +@react.component +let make = () => { + let (viewType, setViewType) = React.useState(_ => HSwitchSDKUtils.DemoApp) + let (isShowFilters, setIsShowFilters) = React.useState(_ => true) + let (isShowTestCards, setIsShowTestCards) = React.useState(_ => true) + + React.useEffect1(() => { + let windowUrl = urlSearch(location.href) + + let paramViewType = windowUrl.searchParams.get(. "viewType")->Js.Json.decodeString + let paramIsShowFilters = windowUrl.searchParams.get(. "isShowFilters")->Js.Json.decodeString + let paramIsShowTestCards = windowUrl.searchParams.get(. "isShowTestCards")->Js.Json.decodeString + + switch paramViewType { + | Some(val) => + switch val->Js.String2.toLowerCase { + | "demoapp" => setViewType(_ => HSwitchSDKUtils.DemoApp) + | "sdkpreview" => setViewType(_ => HSwitchSDKUtils.SdkPreview) + | _ => setViewType(_ => HSwitchSDKUtils.DemoApp) + } + | None => () + } + + switch paramIsShowFilters { + | Some(val) => + switch val->Js.String2.toLowerCase { + | "true" => setIsShowFilters(_ => true) + | "false" => setIsShowFilters(_ => false) + | _ => setIsShowFilters(_ => true) + } + | None => () + } + + switch paramIsShowTestCards { + | Some(val) => + switch val->Js.String2.toLowerCase { + | "true" => setIsShowTestCards(_ => true) + | "false" => setIsShowTestCards(_ => false) + | _ => setIsShowTestCards(_ => true) + } + | None => () + } + + None + }, [location.href]) + +
+ + {switch viewType { + | HSwitchSDKUtils.DemoApp => + + + + | HSwitchSDKUtils.SdkPreview => + + + + }} + +
+} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchCardData.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchCardData.res new file mode 100644 index 000000000..3c77b16db --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchCardData.res @@ -0,0 +1,65 @@ +@val @scope(("navigator", "clipboard")) +external writeText: string => unit = "writeText" + +@react.component +let make = (~icon, ~label, ~number, ~color) => { + let (isCopied, setIsCopied) = React.useState(_ => false) + + let handleIsCopied = () => { + setIsCopied(_ => true) + writeText(number) + } + + let handleMouseLeave = () => { + setIsCopied(_ => false) + } + + let cardClass = "flex min-h-[38px] justify-between items-center p-[10px] bg-white rounded-md cursor-pointer font-bold text-xs border border-solid border-[rgba(60,66,87,.12)] w-full mb-2 relative shadow-testCardsShadow" + let cardAfterClass = `after:opacity-0 after:content-['Copy'] after:absolute after:bg-[rgba(86,86,86,.85)] after:text-white after:font-bold after:text-sm after:h-full after:w-full after:-m-[10px] after:rounded-md after:flex after:justify-center after:items-center after:transition-opacity after:duration-150 after:ease-in-out` + let cardCopiedAfter = "hover:after:content-['Copied'] after:bg-no-repeat bgUrl" + + let cardDotElement = +
+ + let css = ".bgPosition:hover::after { + background-position: 60px; + } + .bgUrl::after { + background-image: url('icons/hyperswitchSDK/tickMarkWhite.svg') + } + " + + <> + + + +} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchCheckoutForm.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchCheckoutForm.res new file mode 100644 index 000000000..c1a33a2d0 --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchCheckoutForm.res @@ -0,0 +1,154 @@ +open HSwitchSDKTypes +open HSwitchTypes +open Promise + +@react.component +let make = (~customerPaymentMethods) => { + let hyper = useHyper() + let theme = Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.theme) + let setPaymentStatus = Recoil.useSetRecoilState(HSwitchRecoilAtoms.paymentStatus) + let amountToShow = Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.amount) + + let (isPaymentProcessing, setIsPaymentProcessing) = React.useState(_ => false) + let (errorMsg, setErrorMsg) = React.useState(_ => None) + + let layout = switch Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.layout) { + | "Tabs" => "tabs" + | "Accordion" => "accordion" + | "Spaced Accordion" => "spaced" + | _ => "tabs" + } + + let themeColors = HSwitchSDKUtils.getThemeColorsFromTheme( + Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.theme), + ) + + let options = HSwitchSDKUtils.getOptionsPayload(customerPaymentMethods, layout, theme) + + let handleFormSubmit = event => { + event->ReactEvent.Form.preventDefault + setIsPaymentProcessing(_ => true) + + let confirmParams = + [ + ( + "confirmParams", + [("return_url", HSwitchSDKUtils.redirectUrl->Js.Json.string)] + ->Js.Dict.fromArray + ->Js.Json.object_, + ), + ] + ->Js.Dict.fromArray + ->Js.Json.object_ + + hyper.confirmPayment(confirmParams) + ->then(val => { + let resDict = val->Js.Json.decodeObject->Belt.Option.getWithDefault(Js.Dict.empty()) + let status = + resDict + ->Js.Dict.get("status") + ->Belt.Option.flatMap(Js.Json.decodeString) + ->Belt.Option.getWithDefault("") + + setIsPaymentProcessing(_ => false) + setPaymentStatus(._ => status) + + resolve() + }) + ->ignore + } + + let css = `.spinner, + .spinner:before, + .spinner:after { + border-radius: 50%; + } + + .spinner { + color: #ffffff; + font-size: 22px; + text-indent: -99999px; + margin: 0px auto; + position: relative; + width: 20px; + height: 20px; + box-shadow: inset 0 0 0 2px; + -webkit-transform: translateZ(0); + -ms-transform: translateZ(0); + transform: translateZ(0); + } + + .spinner:before, + .spinner:after { + position: absolute; + content: ''; + } + + .spinner:before { + width: 10.4px; + height: 20.4px; + background: ${"rgb(25,37,82)"}; + border-radius: 20.4px 0 0 20.4px; + top: -0.2px; + left: -0.2px; + -webkit-transform-origin: 10.4px 10.2px; + transform-origin: 10.4px 10.2px; + -webkit-animation: loading 2s infinite ease 1.5s; + animation: loading 2s infinite ease 1.5s; + } + + .spinner:after { + width: 10.4px; + height: 10.2px; + background: ${"rgb(25,37,82)"}; + border-radius: 0 10.2px 10.2px 0; + top: -0.1px; + left: 10.2px; + -webkit-transform-origin: 0px 10.2px; + transform-origin: 0px 10.2px; + -webkit-animation: loading 2s infinite ease; + animation: loading 2s infinite ease; + } + + @keyframes loading { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + }` + + let errorDiv = error => { +
{React.string(error)}
+ } + + let errorHandlingClass = errorMsg->Belt.Option.isSome ? "mt-1" : "mt-4" + +
+ +
handleFormSubmit(event)}> + + {switch errorMsg { + | Some(error) => errorDiv(error) + | None => React.null + }} + + +
+} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchExtraFeatures.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchExtraFeatures.res new file mode 100644 index 000000000..cc8393fb9 --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchExtraFeatures.res @@ -0,0 +1,30 @@ +@react.component +let make = (~isShowFilters) => { + let isMobileScreen = Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.isMobileScreen) + +
+
+
+ + {React.string("Hyperswitch")} + + +
{React.string("Explore Hyperswitch")}
+ + + +
+
+} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchFilterData.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchFilterData.res new file mode 100644 index 000000000..6c97c73f2 --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchFilterData.res @@ -0,0 +1,33 @@ +type tabName = CustomerLocation | Size | Theme | Layout + +let getStringFromTabName = name => { + switch name { + | CustomerLocation => "Customer Location" + | Size => "Size" + | Theme => "Theme" + | Layout => "Layout" + } +} + +let customerLocations = [ + "United Arab Emirates (AED)", + "Australia (AUD)", + "Brazil (BRL)", + "China (CNY)", + "Germany (EUR)", + "United Kingdom (GBP)", + "Indonesia (IDR)", + "Japan (JPY)", + "Mexico (MXN)", + "Malaysia (MYR)", + "Poland (PLN)", + "Singapore (SGD)", + "Thailand (THB)", + "United States (USD)", +] + +let sizes = ["Desktop", "Mobile"] + +let themes = ["Default", "Brutal", "Midnight", "Soft", "Charcoal"] + +let layouts = ["Tabs", "Accordion", "Spaced Accordion"] diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchFilterDropdown.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchFilterDropdown.res new file mode 100644 index 000000000..80a0b600e --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchFilterDropdown.res @@ -0,0 +1,63 @@ +@react.component +let make = ( + ~list, + ~selectedValue, + ~setSelectedValue, + ~showExtra, + ~extraTitle, + ~extraDesc, + ~extraWidth, + ~isShowFlag, +) => { + let setRenderSDK = Recoil.useSetRecoilState(HSwitchRecoilAtoms.renderSDK) + let isDesktop = HSwitchSDKUtils.getIsDesktop( + Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.size), + ) + let isMobileScreen = Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.isMobileScreen) + + let handleChange = name => { + setSelectedValue(._ => name) + + setRenderSDK(._ => false) + let _ = Js.Global.setTimeout(() => { + setRenderSDK(._ => true) + }, 500) + } + +
+
+ +
+
+ {list + ->Js.Array2.map(name => { + let iconName = name->HSwitchSDKUtils.getCountryFromCustomerLocation->Js.String2.toLowerCase + + }) + ->React.array} + +
+
+ +
{React.string(extraTitle)}
+
+
{React.string(extraDesc)}
+
+
+
+
+} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchFilterTab.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchFilterTab.res new file mode 100644 index 000000000..fa000167b --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchFilterTab.res @@ -0,0 +1,104 @@ +open HSwitchRecoilAtoms + +@react.component +let make = (~tabName: HSwitchFilterData.tabName) => { + let (isShowFilterDropdown, setIsShowFilterDropdown) = React.useState(_ => false) + let isMobileScreen = Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.isMobileScreen) + + let list = switch tabName { + | CustomerLocation => HSwitchFilterData.customerLocations + | Size => HSwitchFilterData.sizes + | Theme => HSwitchFilterData.themes + | Layout => HSwitchFilterData.layouts + } + + let (selectedValue, setSelectedValue) = switch tabName { + | CustomerLocation => Recoil.useRecoilState(customerLocation) + | Size => Recoil.useRecoilState(size) + | Theme => Recoil.useRecoilState(theme) + | Layout => Recoil.useRecoilState(layout) + } + + let showExtra = switch tabName { + | CustomerLocation + | Theme => true + | _ => false + } + + let extraTitle = switch tabName { + | CustomerLocation => HSwitchSDKUtils.customerLocationExtraTitle + | Theme => HSwitchSDKUtils.themeExtraTitle + | _ => "" + } + + let extraDesc = switch tabName { + | CustomerLocation => HSwitchSDKUtils.customerLocationExtraDesc + | Theme => HSwitchSDKUtils.themeExtraDesc + | _ => "" + } + + let extraWidth = switch tabName { + | CustomerLocation => "250px" + | Theme => "185px" + | _ => "185px" + } + + let isShowFlag = tabName === CustomerLocation + let isSize = tabName === Size + + let iconName = + selectedValue->HSwitchSDKUtils.getCountryFromCustomerLocation->Js.String2.toLowerCase + + let tabNameString = tabName->HSwitchFilterData.getStringFromTabName + let filterId = `filter-btn-${tabNameString->Js.String2.replace(" ", "_")}` + + React.useLayoutEffect0(() => { + Window.addEventListener("click", ev => { + let targetId = ReactEvent.Mouse.target(ev)["id"] + if targetId !== filterId && targetId !== filterId->Js.String2.concat("-arrow") { + setIsShowFilterDropdown(_ => false) + } + }) + Some( + () => { + Window.removeEventListener("click", ev => { + let targetId = ReactEvent.Mouse.target(ev)["id"] + if targetId !== filterId && targetId !== filterId->Js.String2.concat("-arrow") { + setIsShowFilterDropdown(_ => false) + } + }) + }, + ) + }) + +
+
+ {React.string(tabName->HSwitchFilterData.getStringFromTabName)} +
+
{React.string("|")}
+
setIsShowFilterDropdown(_ => true)}> + + + + + Js.String2.toLowerCase}-hs`} className="mr-1" /> + + {React.string(selectedValue)} + Js.String2.concat("-arrow")} + name="arrow-down-hs" + size=9 + className="ml-1 text-[#5d5d5d]" + /> +
+ + + +
+} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchFilters.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchFilters.res new file mode 100644 index 000000000..33efd2b1b --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchFilters.res @@ -0,0 +1,14 @@ +@react.component +let make = () => { + let isMobileView = MatchMedia.useMatchMedia("(max-width: 830px)") + +
+ + + + +
+} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchHeader.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchHeader.res new file mode 100644 index 000000000..822f53178 --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchHeader.res @@ -0,0 +1,37 @@ +@react.component +let make = () => { + let isDesktop = HSwitchSDKUtils.getIsDesktop( + Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.size), + ) + + let desktopHeaderClass = "rounded-lg py-[6px] px-3" + + let mobileDomainClass = "p-4" + let desktopDomainClass = "bg-[rgba(236,242,247,.4)] rounded-xl" + + let windowControlElement =
+ +
+ +
+ {windowControlElement} + {windowControlElement} + {windowControlElement} +
+
+
+
    +
  • + +
    {React.string(HSwitchSDKUtils.websiteDomain)}
    +
  • +
+
+
+} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchModal.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchModal.res new file mode 100644 index 000000000..3680a2ac3 --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchModal.res @@ -0,0 +1,54 @@ +@react.component +let make = (~isModalOpen, ~setIsModalOpen, ~children) => { + let setIsGlobalModalOpen = Recoil.useSetRecoilState(HSwitchRecoilAtoms.isModalOpen) + + let themeColors = HSwitchSDKUtils.getThemeColorsFromTheme( + Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.theme), + ) + let isDesktop = HSwitchSDKUtils.getIsDesktop( + Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.size), + ) + + let handleModalClose = () => { + if !isDesktop { + setIsModalOpen(_ => false) + setIsGlobalModalOpen(._ => false) + } + } + + React.useLayoutEffect0(() => { + Window.addEventListener("click", ev => { + let targetId = ReactEvent.Mouse.target(ev)["id"] + if targetId === "modal-wrapper" { + handleModalClose() + } + }) + Some( + () => { + Window.addEventListener("click", ev => { + let targetId = ReactEvent.Mouse.target(ev)["id"] + if targetId === "modal-wrapper" { + handleModalClose() + } + }) + }, + ) + }) + + + + +} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchPaymentCompletePopup.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchPaymentCompletePopup.res new file mode 100644 index 000000000..3d37065ec --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchPaymentCompletePopup.res @@ -0,0 +1,89 @@ +open Window + +@react.component +let make = () => { + let (isModalOpen, setIsModalOpen) = React.useState(_ => false) + let (paymentStatus, setPaymentStatus) = React.useState(_ => "") + let (paymentMsg, setPaymentMsg) = React.useState(_ => "") + let (iconName, setIconName) = React.useState(_ => "") + let themeColors = HSwitchSDKUtils.getThemeColorsFromTheme( + Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.theme), + ) + let paymentStatusState = Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.paymentStatus) + let (isClicked, setIsClicked) = React.useState(_ => false) + + React.useEffect1(() => { + let windowUrl = urlSearch(location.href) + let status = windowUrl.searchParams.get(. "status")->Js.Json.decodeString + + let finalStatus = + status === None && paymentStatusState !== "" ? Some(paymentStatusState) : status + + switch finalStatus { + | Some(val) => { + switch val { + | "succeeded" => { + setPaymentStatus(_ => val) + setPaymentMsg(_ => HSwitchSDKUtils.successPaymentMsg) + setIconName(_ => "circle-tick") + } + | "failed" => + setPaymentStatus(_ => val) + setPaymentMsg(_ => HSwitchSDKUtils.failurePaymentMsg) + setIconName(_ => "circle-cross") + | _ => { + setPaymentStatus(_ => "processing") + setPaymentMsg(_ => HSwitchSDKUtils.processingPaymentMsg) + setIconName(_ => "circle-tick") + } + } + setIsModalOpen(_ => true) + } + | None => () + } + + None + }, [paymentStatusState]) + + let handleRedirect = () => { + if !isClicked { + setIsClicked(_ => true) + let url = urlSearch(HSwitchSDKUtils.redirectUrl) + location.replace(. url.href) + } + } + + let redirectMsg = isClicked ? "Restarting Demo..." : "Restart Demo" + +
+ +
+ +
+ {React.string(`Payment ${paymentStatus}`)} +
+
{React.string(paymentMsg)}
+ +
+
+
+} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchProduct.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchProduct.res new file mode 100644 index 000000000..2698a46c1 --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchProduct.res @@ -0,0 +1,50 @@ +@react.component +let make = (~productImg, ~productTitle, ~productAmount, ~productQuantity, ~setProductQuantity) => { + let setIsGlobalModalOpen = Recoil.useSetRecoilState(HSwitchRecoilAtoms.isModalOpen) + let (isModalOpen, setIsModalOpen) = React.useState(_ => false) + + let theme = Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.theme) + let themeColors = HSwitchSDKUtils.getThemeColorsFromTheme(theme) + + let handleModalOpen = () => { + setIsModalOpen(_ => true) + setIsGlobalModalOpen(._ => true) + } + +
+ +
+
+
{React.string(productTitle)}
+
+ {React.string( + `US$ ${(productAmount *. productQuantity->Belt.Int.toFloat) + ->Js.Float.toFixedWithPrecision(~digits=2)}`, + )} +
+
+
+
handleModalOpen()}> +
{React.string(`Qty ${productQuantity->Belt.Int.toString}`)}
+ +
+ 1}> +
+ {React.string(`US $${productAmount->Js.Float.toFixedWithPrecision(~digits=2)} each`)} +
+
+
+
+ + + +
+} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchSDK.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchSDK.res new file mode 100644 index 000000000..095305712 --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchSDK.res @@ -0,0 +1,12 @@ +external objToJson: {..} => Js.Json.t = "%identity" + +open HSwitchSDKTypes + +@react.component +let make = (~options, ~selectedMenu, ~customerPaymentMethods, ~hyperPromise) => { +
+ objToJson} hyper={hyperPromise}> + + +
+} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchTermsAndPrivacy.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchTermsAndPrivacy.res new file mode 100644 index 000000000..36fcb4809 --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchTermsAndPrivacy.res @@ -0,0 +1,34 @@ +@react.component +let make = (~className="") => { + let themeColors = HSwitchSDKUtils.getThemeColorsFromTheme( + Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.theme), + ) + let isDesktop = HSwitchSDKUtils.getIsDesktop( + Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.size), + ) + + let aTagClassName = `mr-3 underline decoration-dotted decoration-[${themeColors.textSecondaryColor}] text-[${themeColors.textSecondaryColor}] text-xs cursor-pointer` + + let css = `.desktopWrapperClass { + position: absolute; + bottom: 7%; + }` + + <> + + + +} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchTestCards.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchTestCards.res new file mode 100644 index 000000000..832aebd98 --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchTestCards.res @@ -0,0 +1,48 @@ +@react.component +let make = () => { + let (isToggle, setIsToggle) = React.useState(_ => false) + + let handleToggleClick = () => { + setIsToggle(prev => !prev) + } + + React.useEffect0(() => { + handleToggleClick() + Js.Global.setTimeout(() => { + handleToggleClick() + }, 1500)->ignore + None + }) + +
+
handleToggleClick()}> +
+ +
{React.string("Test Cards")}
+
+ +
+
+ + + +
+
+ {React.string(HSwitchSDKUtils.testCardsInfo)} +
+
+} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchUpdateProductQuantity.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchUpdateProductQuantity.res new file mode 100644 index 000000000..e6dcb7fed --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchUpdateProductQuantity.res @@ -0,0 +1,93 @@ +@react.component +let make = (~setIsModalOpen, ~productQuantity, ~setProductQuantity) => { + let setIsGlobalModalOpen = Recoil.useSetRecoilState(HSwitchRecoilAtoms.isModalOpen) + let (quantity, setQuantity) = React.useState(_ => productQuantity) + let themeColors = HSwitchSDKUtils.getThemeColorsFromTheme( + Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.theme), + ) + + let handleClose = () => { + setIsModalOpen(_ => false) + setIsGlobalModalOpen(._ => false) + } + + let handleDec = () => { + if quantity > 1 { + setQuantity(prev => prev - 1) + } + } + + let handleInc = () => { + if quantity < 10 { + setQuantity(prev => prev + 1) + } + } + + let handleChange = ev => { + let val = ReactEvent.Form.target(ev)["value"] + switch val->Belt.Int.fromString { + | Some(num) => + if num > 0 && num <= 99 { + setQuantity(_ => num) + } + | None => () + } + } + + let handleUpdate = () => { + setProductQuantity(_ => quantity) + handleClose() + } + + let counterBtnClass = "text-sm font-normal mx-[15px] p-[10px] rounded-[20px] text-center w-8 h-8 flex items-center justify-center cursor-pointer" + + <> +
+
+ +
+
+ {React.string("Update Quantity")} +
+
+ {React.string("The Pure Set")} +
+
+
+ handleClose()} /> +
+
+ + Belt.Int.toString} + onChange={handleChange} + /> + +
+ + +} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchViewProducts.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchViewProducts.res new file mode 100644 index 000000000..972107773 --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/HSwitchViewProducts.res @@ -0,0 +1,50 @@ +@react.component +let make = (~shirtQuantity, ~setShirtQuantity, ~capQuantity, ~setCapQuantity) => { + let isDesktop = HSwitchSDKUtils.getIsDesktop( + Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.size), + ) + let themeColors = HSwitchSDKUtils.getThemeColorsFromTheme( + Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.theme), + ) + + let totalAmount = HSwitchSDKUtils.getTotalAmount(~shirtQuantity, ~capQuantity) + let taxAmount = HSwitchSDKUtils.getTaxAmount(~shirtQuantity, ~capQuantity) + let amountToDisplay = HSwitchSDKUtils.amountToDisplay(~shirtQuantity, ~capQuantity) + + let dividerElement =
+ +
+ + + {dividerElement} +
+
{React.string("Subtotal")}
+
{React.string(`US$ ${totalAmount}`)}
+
+
+
{React.string("Tax")}
+
{React.string(`US$ ${taxAmount}`)}
+
+
+
{React.string("Shipping")}
+
{React.string(`Free`)}
+
+ {dividerElement} +
+
{React.string("Total")}
+
{React.string(`US$ ${amountToDisplay}`)}
+
+
+} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/PoweredBy.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/PoweredBy.res new file mode 100644 index 000000000..c820702e2 --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/components/PoweredBy.res @@ -0,0 +1,10 @@ +@react.component +let make = (~className="pt-4") => { + let theme = Recoil.useRecoilValueFromAtom(HSwitchRecoilAtoms.theme) + let themeColors = HSwitchSDKUtils.getThemeColorsFromTheme(theme) +
+ +
+} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/libraries/HyperSwitch/HSwitchSDKTypes.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/libraries/HyperSwitch/HSwitchSDKTypes.res new file mode 100644 index 000000000..c67fba1e0 --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/libraries/HyperSwitch/HSwitchSDKTypes.res @@ -0,0 +1,40 @@ +open HSwitchTypes + +@module("@juspay-tech/hyper-js") +external loadHyper: string => hyperPromise = "loadHyper" + +@module("@juspay-tech/react-hyper-js") +external useHyper: unit => hyperType = "useHyper" + +module HyperElements = { + @module("@juspay-tech/react-hyper-js") @react.component + external make: ( + ~options: Js.Json.t, + ~hyper: Js.Promise.t, + ~children: React.element, + ) => React.element = "HyperElements" +} + +module PaymentElement = { + @module("@juspay-tech/react-hyper-js") @react.component + external make: (~id: string, ~options: Js.Json.t) => React.element = "PaymentElement" +} + +module CardWidget = { + @module("@juspay-tech/react-hyper-js") @react.component + external make: (~id: string, ~options: options2) => React.element = "CardWidget" +} + +module ElementsTest = { + @module("@juspay-tech/react-hyper-js") @react.component + external make: ( + ~options: optionsTest, + ~stripe: hyperPromise, + ~children: React.element, + ) => React.element = "Elements" +} + +module PaymentElementTest = { + @module("@juspay-tech/react-hyper-js") @react.component + external make: (~id: string, ~options: options2) => React.element = "PaymentElement" +} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/libraries/HyperSwitch/HSwitchTypes.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/libraries/HyperSwitch/HSwitchTypes.res new file mode 100644 index 000000000..eecc00254 --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/libraries/HyperSwitch/HSwitchTypes.res @@ -0,0 +1,36 @@ +type hyperPromise +type sdkType = ELEMENT | WIDGET +type paymentStatus = + SUCCESS | INCOMPLETE | FAILED | LOADING | PROCESSING | CHECKCONFIGURATION | CUSTOMSTATE +type confirmParamType = {return_url: string} +type confirmPaymentObj = {confirmParams: confirmParamType} +type appearanceType = {theme: string} +type options +type options2 +type options1 +type dataValueType +type dataType = { + elementType: string, + complete: bool, + empty: bool, + collapsed: bool, + value: dataValueType, +} +type hyperType = { + clientSecret: string, + confirmPayment: Js.Json.t => Promise.t, + retrievePaymentIntent: string => Promise.t, + paymentRequest: Js.Json.t => Js.Json.t, +} +type appearanceTestType = {theme: string} +type optionsTest = { + clientSecret: string, + appearance: appearanceTestType, + hideIcon: bool, +} +type country = { + isoAlpha3: string, + currency: string, + countryName: string, + isoAlpha2: string, +} diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/utilities/HSwitchRecoilAtoms.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/utilities/HSwitchRecoilAtoms.res new file mode 100644 index 000000000..e5a2720c2 --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/utilities/HSwitchRecoilAtoms.res @@ -0,0 +1,10 @@ +let amount = Recoil.atom(. "amount", "20000") +let customerLocation = Recoil.atom(. "customerLocation", "United States (USD)") +let size = Recoil.atom(. "size", "Desktop") +let theme = Recoil.atom(. "theme", "Default") +let layout = Recoil.atom(. "layout", "Tabs") +let renderSDK = Recoil.atom(. "renderSDK", true) +let isModalOpen = Recoil.atom(. "isModalOpen", false) +let viewType = Recoil.atom(. "viewType", HSwitchSDKUtils.DemoApp) +let isMobileScreen = Recoil.atom(. "isMobileScreen", false) +let paymentStatus = Recoil.atom(. "paymentStatus", "") diff --git a/src/screens/HyperSwitch/UniversalHyperswitchSDK/utilities/HSwitchSDKUtils.res b/src/screens/HyperSwitch/UniversalHyperswitchSDK/utilities/HSwitchSDKUtils.res new file mode 100644 index 000000000..c13414355 --- /dev/null +++ b/src/screens/HyperSwitch/UniversalHyperswitchSDK/utilities/HSwitchSDKUtils.res @@ -0,0 +1,294 @@ +external objToJson: {..} => Js.Json.t = "%identity" +@val @scope("window") external hyper: string => HSwitchTypes.hyperPromise = "Hyper" + +let loadHyper = str => { + Js.Promise.make((~resolve, ~reject) => { + let scriptURL = "https://beta.hyperswitch.io/v1/HyperLoader.js" + let script = Window.createElement("script") + script->Window.elementSrc(scriptURL) + script->Window.elementOnload(() => resolve(. hyper(str))) + script->Window.elementOnerror(err => { + reject(. err) + }) + Window.body->Window.appendChild(script) + }) +} + +let getCurrencyFromCustomerLocation = customerLocation => { + customerLocation->Js.String2.slice(~from=-4, ~to_=-1) +} + +let getCountryFromCustomerLocation = customerLocation => { + switch customerLocation { + | "Germany (EUR)" => "DE" + | _ => customerLocation->Js.String2.slice(~from=-4, ~to_=-2) + } +} + +type themeColor = { + backgroundColor: string, + color: string, + hyperswitchHeaderColor: string, + payHeaderColor: string, + boxShadowClassForSDK: string, + textSecondaryColor: string, + tabLabelColor: string, + checkoutButtonClass: string, + checkoutButtonShimmerClass: string, + amountColor: string, + backgroundSecondaryClass: string, + modalBackgroundColor: string, + counterButtonClass: string, + plusIcon: string, + inputClass: string, + productBorderClass: string, + productDividerClass: string, +} + +let defaultThemeColor = { + backgroundColor: "#fff", + color: "#000", + hyperswitchHeaderColor: "rgba(26,26,26,0.9)", + payHeaderColor: "rgba(26,26,26,0.6)", + boxShadowClassForSDK: "before:shadow-defaultBoxShadowClassForSDKShadow", + textSecondaryColor: "rgba(26,26,26,0.5)", + tabLabelColor: "", + checkoutButtonClass: "text-white border-none bg-[rgb(0,109,249)]", + checkoutButtonShimmerClass: "bg-default_theme_button_shimmer", + amountColor: "", + backgroundSecondaryClass: "bg-[rgba(26,26,26,0.05)]", + modalBackgroundColor: "#fff", + counterButtonClass: "bg-[rgba(0,0,0,0.03)] text-[rgba(26,26,26,0.6)]", + plusIcon: "plus", + inputClass: "border-none shadow-defaultModalInputShadow focus:border focus:border-solid focus:border-[#006df9] focus:shadow-defaultModalInputFocusShadow", + productBorderClass: "border-b-[rgba(26,26,26,0.1)]", + productDividerClass: "bg-[rgb(230,230,230)]", +} + +let getThemeColorsFromTheme = theme => { + switch theme { + | "Default" => defaultThemeColor + | "Brutal" => { + ...defaultThemeColor, + hyperswitchHeaderColor: "rgba(0,0,0,0.9)", + payHeaderColor: "rgba(0,0,0,0.6)", + textSecondaryColor: "rgba(0,0,0,0.5)", + backgroundColor: "rgba(124,255,112,0.54)", + tabLabelColor: "#000000", + checkoutButtonClass: "shadow-brutalButtonShadow text-[#000000] border-[0.17em] border-solid border-black bg-[#f5fb1f] active:translate-x-[0.05em] active:translate-y-[0.05em] active:shadow-brutalButtonActiveShadow", + checkoutButtonShimmerClass: "bg-brutal_theme_button_shimmer", + backgroundSecondaryClass: "shadow-brutalButtonShadow border-[0.17em] border-solid border-black bg-white active:translate-x-[0.05em] active:translate-y-[0.05em] active:shadow-brutalButtonActiveShadow", + counterButtonClass: "border-[0.17em] border-solid border-black shadow-brutalButtonShadow text-black active:translate-x-[0.05em] active:translate-y-[0.05em] active:shadow-brutalButtonActiveShadow", + plusIcon: "plusBlack", + inputClass: "shadow-brutalModalInputShadow border-[0.1em] border-solid border-black focus:translate-x-[0.05em] focus:translate-y-[0.05em] focus:shadow-brutalModalInputFocusShadow", + productDividerClass: "bg-[rgb(86,97,134)]", + } + | "Midnight" => { + backgroundColor: "rgb(26, 31, 54)", + color: "#fff", + hyperswitchHeaderColor: "rgba(229,229,229,0.9)", + payHeaderColor: "rgba(229,229,229,0.6)", + boxShadowClassForSDK: "before:shadow-midnightBoxShadowClassForSDKShadow", + textSecondaryColor: "rgba(229,229,229,0.5)", + tabLabelColor: "#000000", + checkoutButtonClass: "bg-[#85d996]", + checkoutButtonShimmerClass: "bg-midnight_theme_button_shimmer", + amountColor: "#85d996", + backgroundSecondaryClass: "bg-[rgba(229,229,229,0.05)]", + modalBackgroundColor: "#30313d", + counterButtonClass: "bg-[rgba(255,255,255,0.03)] text-[rgb(229,229,229)]", + plusIcon: "plusWhite", + inputClass: "border border-solid border-[#424353] text-white bg-[rgb(48,49,61)] shadow-midnightModalInputShadow focus-visible:border focus-visible:border-solid focus-visible:border-[#85d996] focus-visible:shadow-midnightModalInputFocusShadow", + productBorderClass: "border-b-[rgba(229,229,229,0.1)]", + productDividerClass: "bg-[rgb(66,67,83)]", + } + | "Soft" => { + ...defaultThemeColor, + color: "rgb(224,224,224)", + hyperswitchHeaderColor: "rgba(224,224,224,0.9)", + payHeaderColor: "rgba(224,224,224,0.6)", + textSecondaryColor: "rgba(224,224,224,0.5)", + boxShadowClassForSDK: "before:shadow-midnightBoxShadowClassForSDKShadow", + backgroundColor: "rgb(62, 62, 62)", + checkoutButtonClass: "shadow-softButtonShadow text-[rgb(125,143,255)]", + checkoutButtonShimmerClass: "bg-soft_theme_button_shimmer", + amountColor: "#7d8fff", + backgroundSecondaryClass: "shadow-softButtonShadow", + modalBackgroundColor: "rgb(62, 62, 62)", + counterButtonClass: "shadow-softButtonShadow", + inputClass: "bg-[rgb(60,61,62)] text-[#e0e0e0] shadow-softModalInputShadow", + plusIcon: "plusWhite", + productDividerClass: "bg-[rgb(86,97,134)]", + } + | "Charcoal" => { + ...defaultThemeColor, + backgroundColor: "rgba(221, 216, 216, 0.07)", + checkoutButtonClass: "bg-black text-white", + checkoutButtonShimmerClass: "bg-charcoal_theme_button_shimmer", + inputClass: "border-none shadow-charcoalModalInputShadow focus:border focus:border-solid focus:border-black focus:shadow-charcoalModalInputFocusShadow", + productDividerClass: "bg-black", + } + | _ => defaultThemeColor + } +} + +let getTextColorFromTheme = theme => { + switch theme { + | "Default" => "#000" + | "Brutal" => "#000" + | "Midnight" => "rgb(255, 255, 255)" + | "Soft" + | "Charcoal" => "rgba(221, 216, 216, 0.07)" + | _ => "#fff" + } +} + +let getIsDesktop = size => { + size === "Desktop" +} + +let getSizeIconFromSize = size => { + if getIsDesktop(size) { + "desktop" + } else { + "desktop" + } +} + +let redirectUrl = "https://hyperswitch-demo-store.netlify.app" + +let hyperswitchDocsUrl = "https://hyperswitch.io/docs" +let hyperswitchRegisterUrl = "https://app.hyperswitch.io/register" +let hyperswitchTermsOfServiceUrl = "https://hyperswitch.io/terms-of-services" +let hyperswitchPrivacyPolicyUrl = "https://hyperswitch.io/privacyPolicy" + +let successTestCardNumber = "4242424242424242" +let authenticationTestCardNumber = "4000000000003220" +let declineTestCardNumber = "4000000000000002" + +let testCardsInfo = "Click to copy the card number. Use any future expiration date and three-number CVC." + +let customerLocationExtraTitle = "Every country pays differently" +let customerLocationExtraDesc = "The Payment Element supports 135+ currencies. Only a sample is shown here. Hyperswitch automatically reorders payment methods to increase potential conversion." +let themeExtraTitle = "Customize it" +let themeExtraDesc = "Create a theme to match your brand with the Appearance API." + +let websiteDomain = "checkout.hyperswitch.io" + +let successPaymentMsg = "After a successful payment, the customer returns to your website" +let failurePaymentMsg = "After a failed payment, the customer returns to your website" +let processingPaymentMsg = "If the payment status is processing, the customer will be redirected to your website and you will receive webhooks on the payment status" + +let getDefaultPayload = (amount, currency, country, countryCode) => + { + "amount": amount->Belt.Int.fromString->Belt.Option.getWithDefault(20000), + "currency": currency, + "shipping": { + "address": { + "country": country, + "state": "test", + "zip": "571201", + "line1": "test line 1", + "city": "test city", + "first_name": "Bopanna", + "last_name": "MJ", + }, + "phone": { + "number": "+918105528927", + "counrty_code": countryCode, + }, + }, + "order_details": [ + { + "product_name": "Apple iphone 15", + "quantity": 1, + "amount": amount->Belt.Int.fromString->Belt.Option.getWithDefault(20000), + }, + ], + "billing": { + "address": { + "country": country, + }, + }, + "authentication_type": "no_three_ds", + "customer_id": "Bopanna17", + }->objToJson + +let defaultAPIKey = "" +let defaultPublishableKey = "" + +let getOptions = (clientSecret, theme) => { + { + "clientSecret": clientSecret, + "appearance": { + "theme": theme, + }, + "fonts": [ + { + "cssSrc": "https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600;700&display=swap", + }, + { + "cssSrc": "https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;600;700&family=Qwitcher+Grypen:wght@400;700&display=swap", + }, + { + "cssSrc": "https://fonts.googleapis.com/css2?family=Combo&display=swap", + }, + ], + "locale": "", + "loader": "always", + } +} + +let backendEndpointUrl = "https://sandbox.hyperswitch.io/payments" + +let getLayoutPayload = layout => { + { + "type": layout === "spaced" ? "accordion" : layout, + "defaultCollapsed": false, + "radios": true, + "spacedAccordionItems": layout === "spaced", + } +} + +let getOptionsPayload = (customerPaymentMethods, layout, theme) => { + { + "customerPaymentMethods": customerPaymentMethods, + "layout": getLayoutPayload(layout), + "wallets": { + "walletReturnUrl": redirectUrl, + "applePay": "auto", + "googlePay": "auto", + "style": { + "theme": switch theme { + | "Default" + | "Charcoal" => "dark" + | _ => "light" + }, + }, + }, + }->objToJson +} + +type viewType = DemoApp | SdkPreview +type sizeType = Mobile | Desktop + +let getTotalAmountFloat = (~shirtQuantity, ~capQuantity) => { + 65.00 *. shirtQuantity->Belt.Int.toFloat +. 32.00 *. capQuantity->Belt.Int.toFloat +} + +let getTotalAmount = (~shirtQuantity, ~capQuantity) => { + getTotalAmountFloat(~shirtQuantity, ~capQuantity)->Js.Float.toFixedWithPrecision(~digits=2) +} + +let getTaxAmountFloat = (~shirtQuantity, ~capQuantity) => { + getTotalAmountFloat(~shirtQuantity, ~capQuantity) *. 0.1 +} + +let getTaxAmount = (~shirtQuantity, ~capQuantity) => { + getTaxAmountFloat(~shirtQuantity, ~capQuantity)->Js.Float.toFixedWithPrecision(~digits=2) +} + +let amountToDisplay = (~shirtQuantity, ~capQuantity) => { + (getTotalAmountFloat(~shirtQuantity, ~capQuantity) +. + getTaxAmountFloat(~shirtQuantity, ~capQuantity))->Js.Float.toFixedWithPrecision(~digits=2) +} diff --git a/src/screens/login/HSwitchLoginFlow/HyperSwitchAuthScreen.res b/src/screens/login/HSwitchLoginFlow/HyperSwitchAuthScreen.res index ed749027e..5191c6090 100644 --- a/src/screens/login/HSwitchLoginFlow/HyperSwitchAuthScreen.res +++ b/src/screens/login/HSwitchLoginFlow/HyperSwitchAuthScreen.res @@ -92,7 +92,11 @@ let make = (~setAuthStatus: HyperSwitchAuthTypes.authStatus => unit) => { | list{"register", ..._}, ) => () // to prevent duplicate push | (LoginWithPassword | LoginWithEmail, _) => - `${HSwitchGlobalVars.hyperSwitchFEPrefix}/login`->RescriptReactRouter.replace + let loginUrl = HSwitchGlobalVars.hyperSwitchFEPrefix->Js.String2.concat("/login") + + ( + url.search === "" ? loginUrl : loginUrl->Js.String2.concat(`?${url.search}`) + )->RescriptReactRouter.replace | (SignUP, list{"register", ..._}) => () // to prevent duplicate push | (SignUP, _) => "register"->RescriptReactRouter.push | (ForgetPassword | ForgetPasswordEmailSent, list{"forget-password", ..._}) => () // to prevent duplicate push diff --git a/tailwindHyperSwitch.config.js b/tailwindHyperSwitch.config.js index 5a4b223e9..6f61314da 100644 --- a/tailwindHyperSwitch.config.js +++ b/tailwindHyperSwitch.config.js @@ -76,6 +76,7 @@ module.exports = { }, animation: { "spin-slow": "spin 3s linear infinite", + spin: "spin 1s linear infinite", slideUp: "slideUp 200ms ease-out forwards", fade: "fadeOut 1s ease-in-out forwards", secondsOnes: "secondsOnes 10s 0s 18 reverse", // format keyframe, duration, delay, iteration-count, direction @@ -92,6 +93,7 @@ module.exports = { ripple: "ripple 10s ease-in-out", horizontalShaking: "horizontalShaking 0.5s 1s", horizontalShakingDelay: "horizontalShaking 0.5s 4s", + shimmerMove: "shimmerMove 3s infinite ease", }, transitionDelay: { 12: "12000ms", @@ -314,6 +316,17 @@ module.exports = { }, "100%": { transform: "translateX(0%)" }, }, + spin: { + "100%": { transform: "rotate(1turn)" }, + }, + shimmerMove: { + "0%": { + transform: "translateX(calc(150%*-1));", + }, + "100%": { + transform: "translateX(150%)", + }, + }, }), fontSize: { body: "1rem", @@ -358,6 +371,7 @@ module.exports = { 850: "#151A1F", 900: "#333333", 950: "#212830", + mobile_frame: "#404040c9", }, green: { 50: "#EFF4EF", @@ -544,6 +558,29 @@ module.exports = { "ardra-purple": "#7984E6", statusArdra: "#E6A779", statusCreated: "#5C6073", + "jb-black": { + DEFAULT: "#000000", + 50: "#F3F3F3", + 100: "#DADADA", + 200: "#C0C0C0", + 300: "#A8A8A8", + 400: "#909090", + 500: "#787878", + 600: "#626262", + 700: "#4C4C4C", + 800: "#373737", + 900: "#242424", + }, + default_theme_button_shimmer: + "linear-gradient(to_right,rgba(0,109,249,0)_0%,rgb(43,136,255)_50%,rgba(0,109,249,0)_100%)", + brutal_theme_button_shimmer: + "linear-gradient(to_right,rgba(245,251,31,0)_0%,rgb(251,252,198)_50%,rgba(245,251,31,0)_100%)", + midnight_theme_button_shimmer: + "linear-gradient(to_right,rgba(133,217,150,0)_0%,rgb(176,222,185)_50%,rgba(133,217,150,0)_100%)", + soft_theme_button_shimmer: + "linear-gradient(to_right,rgba(62,62,62,0)_0%,rgb(70,70,70)_50%,rgba(62,62,62,0)_100%)", + charcoal_theme_button_shimmer: + "linear-gradient(to_right,rgba(0,0,0,0)_0%,rgb(70,70,70)_50%,rgba(0,0,0,0)_100%)", }, fontSize: { "fs-10": "10px", @@ -575,6 +612,38 @@ module.exports = { boxShadowMultiple: "2px -2px 24px 0px rgba(0, 0, 0, 0.04), -2px 2px 24px 0px rgba(0, 0, 0, 0.02)", homePageBoxShadow: "0px 2px 16px 2px rgba(51, 51, 51, 0.16)", + websiteShadow: + "0 20px 44px rgba(50, 50, 93, .12), 0 -1px 32px rgba(50, 50, 93, .06), 0 3px 12px rgba(0, 0, 0, .08)", + mobileHeaderShadow: "0 0 2px rgba(10, 37, 64, .1)", + mobileFrameShadow: + "0 20px 44px rgba(50, 50, 93, .12), 0 -1px 32px rgba(50, 50, 93, .06), 0 3px 12px rgba(0, 0, 0, .08), inset 0 -2px 5px rgba(10, 37, 64, .35)", + testCardsShadow: + "0 2px 5px rgba(60, 66, 87, .12), 0 1px 1px rgba(0, 0, 0, .08)", + filterDropdownShadow: + "0 0 0 1px rgba(136, 152, 170, .1), 0 15px 35px 0 rgba(49, 49, 93, .1), 0 5px 15px 0 rgba(0, 0, 0, .08)", + filterExtraShadow: "rgb(227, 232, 238) 0px 1px 0px 0px inset", + websiteHeaderShadow: "0 0.5px 0 #ecf2f7", + modalShadow: + "rgba(0,0,0,0.2)_0px_40px_100px_0px,rgba(0,0,0,0.03)_0px_6px_12px_0px", + defaultBoxShadowClassForSDKShadow: "rgba(0,0,0,0.18)_15px_0px_30px_0px", + midnightBoxShadowClassForSDKShadow: + "rgba(0,0,0,0.82)_15px_0px_30px_0px", + defaultModalInputShadow: + "rgb(224,224,224)_0px_0px_0px_1px,rgba(0,0,0,0.07)_0px_2px_4px_0px,rgba(0,0,0,0.05)_0px_1px_1.5px_0px", + defaultModalInputFocusShadow: "#006df94c_0px_0px_0px_3px", + brutalModalInputShadow: "0.12em_0.12em", + brutalModalInputFocusShadow: "0.02em_0.02em", + midnightModalInputShadow: + "0px_2px_4px_rgb(0,0,0,0.5),0px_1px_6px_rgba(0,0,0,0.25)", + midnightModalInputFocusShadow: "#85d9964c_0px_0p_0px_3px", + softModalInputShadow: + "inset_4px_4px_5px_#353637,inset_-4px_-3px_7px_#434445", + charcoalModalInputShadow: + "rgb(224,224,224)_0px_0px_0px_1px,rgba(0,0,0,0.07)_0px_2px_4px_0px,rgba(0,0,0,0.05)_0px_1px_1.5px_0px", + charcoalModalInputFocusShadow: "#0000004c_0px_0px_0px_3px", + brutalButtonShadow: "0.15em_0.15em", + brutalButtonActiveShadow: "0.02em_0.02em", + softButtonShadow: "4px_4px_5px_#353637,-4px_-4px_5px_#434445", }, gridTemplateColumns: { 6: "repeat(6, minmax(0, 1fr))",