import { CLOUDFLARE_CDN_FOR_QRCODE, IDAPP_HOSTS } from "../core"; import concordiumLogo from "../../public/concordium_logo.svg"; import appStoreLogo from "../../public/app_store.svg"; import playStoreLogo from "../../public/play_store.svg"; export class ConcordiumIDAppPoup { private static injectPopupStyles() { if (document.getElementById("sdk-popup-styles")) return; const style = document.createElement("style"); style.id = "sdk-popup-styles"; style.innerHTML = ` .authCode { margin: 16px auto; width: 86px; height: 86px; border: 2px solid #0047ab; display: flex; align-items: center; justify-content: center; font-family: Arial, sans-serif; font-size: 20px; color: #0047ab; border-radius: 100%; border: 2.15px solid #1143A7; background: linear-gradient(180deg, #FFF 0%, #EEE 100%); } .sdk-popup-overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 9999; } .sdk-popup-box { position: relative; background: #fff; border-radius: 12px; text-align: center; max-width: 330px; width: 100%; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); font-family: -apple-system, BlinkMacSystemFont, sans-serif; } .app-message { padding: 1.5rem 2rem; } .no-app-msg{ padding: 1.5rem 2rem; background: #F2F1F1; border-radius: 0 0 12px 12px; } /* Close button */ .sdk-close-btn { position: absolute; top: 0.5rem; right: 0.5rem; background: none; border: none; font-size: 1.5rem; line-height: 1; cursor: pointer; color: #555; } /* Hide both steps by default */ .sdk-step { display: block; } /* Only show step 1 initially */ .sdk-step--1 { display: block;} .sdk-step--2 { display: block; } /* Logo */ .sdk-logo { display: block; margin: 0 auto 1rem; max-width: 120px; } /* Main copy */ .sdk-copy { color: #0D121C; text-align: center; font-size: 16px; font-style: normal; font-weight: 600; line-height: 130%; letter-spacing: -0.25px; margin-bottom: 32px; } /* Buttons */ .sdk-btns { display: flex; flex-direction: column; gap: 0.75rem; margin: 32px 0 0 0; } .sdk-btn { width: 100%; padding: 0.75rem 1rem; font-size: 1rem; font-weight: 600; border-radius: 6px; border: none; cursor: pointer; } .sdk-btn--primary { background-color: #004a93; color: #fff; } .sdk-btn--secondary { background-color: #fff; color: #000; border: 2px solid #000; } /* Install copy (only step 1) */ .sdk-install { color: #0D121C; text-align: center; font-size: 13px; font-style: normal; font-weight: 600; line-height: 130%; /* 16.9px */ letter-spacing: -0.25px; margin: 0 0 20px 0; } /* Store badges */ .sdk-store-links { display: flex; justify-content: center; gap: 1rem; } .sdk-store-links img { height: 36px; display: block; } .wc-session { max-width: 95%; word-wrap: break-word; color: grey; display: none } .Rtable { display: flex; flex-wrap: wrap; padding: 0; } .Rtable-cell { box-sizing: border-box; flex-grow: 1; width: 100%; list-style: none; background: fade(green, 20%); text-align: center; } .dot { height: 16px; width: 16px; background-color: #1143A7; border-radius: 50%; display: inline-block; z-index: 2; position: relative; } .dot-no-fill { height: 16px; width: 16px; background-color: white; border: 1px solid black; border-radius: 50%; display: inline-block; z-index: 2; position: relative; } .Rtable--3cols > .Rtable-cell { width: 33.33%; } .Rtable { position: relative; } .Rtable-cell .text { color: rgba(13, 18, 28, 0.70); text-align: center; font-size: 11px; font-style: normal; font-weight: 500; line-height: 120%; } .Rtable-cell .text.active { color: #0D121C; text-align: center; font-size: 11px; font-style: normal; font-weight: 700; line-height: 120%; } .line-no-fill { width: 100%; height: 1px; display: block; position: relative; background: rgba(10, 12, 30, 0.2); top: -15px; left: 50%; margin: 0 auto; } .hr-line{ margin: 16px 0; } `; document.head.appendChild(style); } /** * Closes the popup and removes styles. */ static closePopup() { const wrapper = document.getElementById("sdk-popup-wrapper"); if (wrapper) wrapper.remove(); const style = document.getElementById("sdk-popup-styles"); if (style) style.remove(); } /** * Opens the ID App using a deep link. * This method is used to redirect the user to the ID App on mobile devices or open on desktop. * @param param0 */ static openIdapp = ({ wallectConnectMobileUrl, walletConnectDesktopUrl, }: { wallectConnectMobileUrl: string; walletConnectDesktopUrl?: string; }) => { // On mobile, hand off to the native app: if (/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)) { window.location.href = wallectConnectMobileUrl; } else { // Desktop fallback: show instructions or open a popup for testing if (walletConnectDesktopUrl) { const width = 400; const height = 700; const top = 0; const left = window.screen.availWidth - width; window.open( walletConnectDesktopUrl, "Idapp", `width=${width},height=${height},top=${top},left=${left}`, ); } } }; /** * Injects the popup HTML and styles, then invokes the ID App deep link. * This function creates a popup that prompts the user to open the ID App for account activation. * @param param0 */ static async invokeIdAppDeepLinkPopup({ walletConnectUri, actionType = "create-account", }: { walletConnectUri: string; actionType?: "create-account" | "generate-proof"; }) { if (!navigator && !window) { throw new Error( "ConcordiumIDAppPoup.invokeIdAppDeepLinkPopup() requires a browser environment", ); } if (!walletConnectUri) { throw new Error( "ConcordiumIDAppPoup.invokeIdAppDeepLinkPopup() requires a valid walletConnectUri", ); } ConcordiumIDAppPoup.injectPopupStyles(); const wallectConnectMobileUrl = `${IDAPP_HOSTS.mobile}wallet-connect?encodedUri=${walletConnectUri}`; // const walletConnectDesktopUrl = `${IDAPP_HOSTS.web}wallet-connect?encodedUri=${walletConnectUri}`; const isGenerateProof = actionType === "generate-proof"; const lastStepText = isGenerateProof ? "Generate
Proof" : "Create
Account"; const copyText = isGenerateProof ? "Please follow and complete the
proof generation in [ID App]." : "Please follow and complete the
account setup in [ID App]."; const wrapper = document.createElement("div"); wrapper.id = "sdk-popup-wrapper"; wrapper.innerHTML = `
Connect /
Pair Apps
Complete ID
Verification
${lastStepText}

${copyText}

If you don’t have ID App, install it then return here to continue.

`; if (!document.getElementById("qrcode-lib")) { const script = document.createElement("script"); script.id = "qrcode-lib"; script.src = CLOUDFLARE_CDN_FOR_QRCODE; document.head.appendChild(script); } const renderQRCode = () => { const qrContainer = document.getElementById("sdk-qr-code"); // eslint-disable-next-line @typescript-eslint/no-explicit-any if (qrContainer && (window as any).QRCode) { qrContainer.style = "display: flex;justify-content: center;margin: 1.0rem 0;"; // eslint-disable-next-line @typescript-eslint/no-explicit-any new (window as any).QRCode(qrContainer, { text: wallectConnectMobileUrl, width: 160, height: 160, colorDark: "#000000", colorLight: "#ffffff", // eslint-disable-next-line @typescript-eslint/no-explicit-any correctLevel: (window as any).QRCode.CorrectLevel.H, }); } else { setTimeout(renderQRCode, 100); // wait for script to load } }; const openAppBtn = wrapper.querySelector("#open-idapp-btn")!; const closeBtn = wrapper.querySelector(".sdk-close-btn")!; document.body.appendChild(wrapper); renderQRCode(); closeBtn.addEventListener("click", () => ConcordiumIDAppPoup.closePopup()); openAppBtn?.addEventListener("click", async () => { try { // window.location.href = wallectConnectMobileUrl; ConcordiumIDAppPoup.openIdapp({ wallectConnectMobileUrl }); } catch (e) { // emit error event console.log(e); } }); } /** * Injects the styles for the popup into the document. * This method is called to ensure that the popup has the necessary styles applied. * @param param0 */ static async invokeIdAppActionsPopup({ onCreateAccount, walletConnectSessionTopic, onGenerateProof, }: { onCreateAccount?: () => Promise; walletConnectSessionTopic?: string; onGenerateProof?: () => Promise; }) { // Check if atleast one of the handlers is provided if (!onCreateAccount && !onGenerateProof) { throw new Error("Atleast one of the handlers must be provided"); } if (!walletConnectSessionTopic) { throw new Error("Wallet Connect's session.topic is required"); } // Remove any existing popup wrapper const existingWrapper = document.getElementById("sdk-popup-wrapper"); if (existingWrapper) { existingWrapper.remove(); } //this helper function to get the stepper name const getActionText = () => { if (onGenerateProof) { return "Generate
Proof"; } return "Create
Account"; }; // this helper function to get the action buttons const getActionsButtonsHTML = () => { if (onGenerateProof) { return `
`; } return `
`; }; ConcordiumIDAppPoup.injectPopupStyles(); // Build the wrapper const wrapper = document.createElement("div"); wrapper.id = "sdk-popup-wrapper"; wrapper.innerHTML = `
Connect /
Pair Apps
Complete ID
Verification
${getActionText()}

Only once you’ve completed the ID Verification, choose your next step.

${getActionsButtonsHTML()}
${ walletConnectSessionTopic && onCreateAccount ? `

To Create an Account, match the code below in the [ID App]

${walletConnectSessionTopic.substr(0, 4).toUpperCase()}
` : "" }
`; document.body.appendChild(wrapper); // Element refs const closeBtn = wrapper.querySelector(".sdk-close-btn")!; closeBtn.addEventListener("click", () => ConcordiumIDAppPoup.closePopup()); // Step switch: Create New Account → Step 2 requestAnimationFrame(() => { const createBtn = wrapper.querySelector("#create-id-btn"); if (onCreateAccount && createBtn) { createBtn.addEventListener("click", async (e) => { e.preventDefault(); createBtn.disabled = true; createBtn.textContent = "⏳ Please wait"; try { await onCreateAccount(); } catch (err) { console.error(err); createBtn.textContent = "Create New Account"; createBtn.disabled = false; } }); } else if (createBtn) { createBtn.style.display = "none"; } const generateProofBtn = wrapper.querySelector( "#generate-proof-btn", ); if (onGenerateProof && generateProofBtn) { generateProofBtn.addEventListener("click", async (e) => { e.preventDefault(); generateProofBtn.disabled = true; generateProofBtn.textContent = "⏳ Please Wait"; try { await onGenerateProof(); } catch (err) { console.error(err); generateProofBtn.textContent = "Generate Proof"; generateProofBtn.disabled = false; } }); } }); } }