Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: HS-146 Added Universal Hyperswitch SDK /demo-store url #71

Closed
wants to merge 1 commit into from
Closed
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/hyperswitch/icons/hyperswitchSDK/tickMarkWhite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
264 changes: 264 additions & 0 deletions public/hyperswitch/icons/solid.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 5 additions & 4 deletions src/components/HyperSwitchAuthWrapper.res
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ let make = (~children) => {

<div className="font-inter-style">
<AuthInfoProvider value={(currentAuthState, setAuthStatus)}>
{switch currentAuthState {
| LoggedOut => <HyperSwitchAuthScreen setAuthStatus />
| LoggedIn(_token) => children
| CheckingAuthStatus => <Loader />
{switch (currentAuthState, url.path) {
| (_, list{"demo-store"}) => <UniversalHyperswitchSDK />
| (LoggedOut, _) => <HyperSwitchAuthScreen setAuthStatus />
| (LoggedIn(_token), _) => children
| (CheckingAuthStatus, _) => <Loader />
}}
</AuthInfoProvider>
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon.res
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ let make = (
~customIconColor="",
~customWidth=?,
~customHeight=?,
~id=?,
) => {
let urlPrefix = ""

Expand All @@ -31,6 +32,7 @@ let make = (
| None => "flex flex-col justify-center"
}}>
<svg
?id
onClick=handleClick
fill=customIconColor
className={`fill-current ${otherClasses}`}
Expand Down
1 change: 1 addition & 0 deletions src/components/Icon.resi
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ let make: (
~customIconColor: string=?,
~customWidth: string=?,
~customHeight: string=?,
~id: string=?,
) => React.element
6 changes: 5 additions & 1 deletion src/entryPoints/hyperswitch/EntryPointUtils.res
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,14 @@ let renderDashboardApp = (~uiConfig, 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(
<div className={`h-screen overflow-hidden flex flex-col font-inter-style`}>
<div className={`h-screen ${demoStoreClass} flex flex-col font-inter-style`}>
<ContextWrapper uiConfig> children </ContextWrapper>
</div>,
)
Expand Down
20 changes: 20 additions & 0 deletions src/libraries/Window.res
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,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"
Original file line number Diff line number Diff line change
@@ -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 => {
<div className={`bg-jb-black-800 absolute w-[0.4rem] ${className}`} />
}

<div>
<HSwitchExtraFeatures isShowFilters />
<div
className={`max-w-[60vw] pt-[3%] m-auto relative ${isDesktop
? ""
: "flex justify-center px-4"}`}>
<div
className={isDesktop ? "" : "relative px-[0.2rem] shadow-mobileFrameShadow rounded-[48px]"}>
<UIUtils.RenderIf condition={!isDesktop}>
{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")}
</UIUtils.RenderIf>
<div
className={isDesktop
? ""
: "bg-grey-mobile_frame w-[336px] rounded-[48px] p-1 flex justify-center box-content"}>
<div className={isDesktop ? "" : "bg-black w-[336px] rounded-[48px]"}>
<div
className={`relative z-0 ${isDesktop ? desktopWrapperClass : mobileWrapperClass}`}
style={isDesktop
? ReactDOMStyle.make(
~backgroundColor=themeColors.backgroundColor,
~color=themeColors.color,
(),
)
: ReactDOMStyle.make()}>
<div className={`flex flex-col ${isDesktop ? "" : mobileHeaderWrapperClass}`}>
<HSwitchHeader />
<div
className={`flex mt-[1px] rounded-lg ${isDesktop
? "relative min-h-[62vh] overflow-hidden max-h-[77vh]"
: `flex-col h-full ${isModalOpen ? "overflow-hidden" : "overflow-scroll"}`}`}
style={!isDesktop
? ReactDOMStyle.make(
~backgroundColor=themeColors.backgroundColor,
~color=themeColors.color,
(),
)
: ReactDOMStyle.make()}>
<HSwitchPaymentCompletePopup />
<div
className={`py-[5%] pr-[5%] border-box ${isDesktop
? "w-1/2 before:content-[' '] before:h-full before:absolute before:right-0 before:top-0 before:w-1/2 before:origin-right before:rounded-br-lg pl-[8%]"
: "w-full mb-8 flex flex-col pl-[5%]"} ${themeColors.boxShadowClassForSDK}`}>
<div className="flex items-center mb-8">
<Icon name="hyperswitch-logo" size=28 className="mr-2 mt-0.5" />
<div
className="text-sm font-medium"
style={ReactDOMStyle.make(~color=themeColors.hyperswitchHeaderColor, ())}>
{React.string("Hyperswitch Demo Store")}
</div>
<div
className="bg-[rgb(255,222,146)] text-[rgb(187,85,4)] text-[11px] font-bold leading-[14.3px] uppercase p-1 rounded-[4px] ml-[0.6rem] text-center">
{React.string(isDesktop ? "Test Mode" : "Test")}
</div>
</div>
<div
className={`flex flex-col mb-[32px] ${isDesktop
? "items-baseline"
: "items-center"}`}>
<UIUtils.RenderIf condition={!isDesktop}>
<img
className="rounded-[6px] h-[130px] max-h-[130px] max-w-full mb-4"
src="assets/hyperswitchSDK/shirt.png"
/>
</UIUtils.RenderIf>
<div
className="font-medium leading-[20.8px]"
style={ReactDOMStyle.make(~color=themeColors.payHeaderColor, ())}>
{React.string("Pay Hyperswitch")}
</div>
<div
className="leading-[46.8px] text-[36px] font-medium"
style={ReactDOMStyle.make(~color=themeColors.amountColor, ())}>
{React.string("US$")}
<span className="ml-1"> {React.string(amount)} </span>
</div>
</div>
<HSwitchViewProducts
shirtQuantity setShirtQuantity capQuantity setCapQuantity
/>
<UIUtils.RenderIf condition={isDesktop}>
<HSwitchTermsAndPrivacy className="mt-[33vh]" />
</UIUtils.RenderIf>
</div>
<div
className={`z-10 ${isDesktop
? "w-1/2 px-[8%] overflow-scroll py-[5%]"
: "w-full px-[5%] pb-[10%] pt-[5%]"}`}>
<UIUtils.RenderIf condition={renderSDK}> {children} </UIUtils.RenderIf>
</div>
<UIUtils.RenderIf condition={!isDesktop}>
<HSwitchTermsAndPrivacy className="mb-4" />
</UIUtils.RenderIf>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<UIUtils.RenderIf condition={isShowTestCards}>
<HSwitchTestCards />
</UIUtils.RenderIf>
</div>
}
Original file line number Diff line number Diff line change
@@ -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])

<div>
<UIUtils.RenderIf condition={isShowFilters}>
<HSwitchFilters />
</UIUtils.RenderIf>
<div
className={`relative z-0 shadow-websiteShadow`}
style={ReactDOMStyle.make(
~backgroundColor=themeColors.backgroundColor,
~color=themeColors.color,
(),
)}>
<HSwitchPaymentCompletePopup />
<div className={`flex flex-col items-center`}>
<div className={`z-10 py-[5%] ${isDesktop ? "w-1/2 px-[5%] max-w-2xl" : "w-[360px] px-8"}`}>
<UIUtils.RenderIf condition={renderSDK}> {children} </UIUtils.RenderIf>
</div>
</div>
</div>
<UIUtils.RenderIf condition={isShowTestCards}>
<HSwitchTestCards />
</UIUtils.RenderIf>
</div>
}
100 changes: 100 additions & 0 deletions src/screens/HyperSwitch/UniversalHyperswitchSDK/HyperswitchSDK.res
Original file line number Diff line number Diff line change
@@ -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)
}

<div>
{switch options {
| Some(val) =>
<HSwitchSDK
options={val}
selectedMenu={selectedMenu}
customerPaymentMethods={[]}
hyperPromise={hyperPromise}
/>
| None => React.null
}}
</div>
}
Loading