| 1 | "use client"; |
| 2 | |
| 3 | import axios from "axios"; |
| 4 | import { useCallback, useEffect, useRef, useState } from "react"; |
| 5 | |
| 6 | export default function CheckoutLinkPlaceholderDemoPage() { |
| 7 | const [sessionKey, setSessionKey] = useState<string | null>(null); |
| 8 | const [link, setLink] = useState<string | null>(null); |
| 9 | const [iframeLoaded, setIframeLoaded] = useState(false); |
| 10 | |
| 11 | const initiateCheckout = useCallback(async () => { |
| 12 | // Get Session Key |
| 13 | const session = await axios.get("/api/session-key"); |
| 14 | setSessionKey(session.data); |
| 15 | |
| 16 | // Get Checkout Link for iFrame |
| 17 | const checkoutLink = await axios.get("/api/link"); |
| 18 | setLink(checkoutLink.data.link); |
| 19 | }, []); |
| 20 | |
| 21 | useEffect(() => { |
| 22 | initiateCheckout(); |
| 23 | }, [initiateCheckout]); |
| 24 | |
| 25 | const handleMessage = useCallback((event: MessageEvent) => { |
| 26 | // Try to parse if it's a string |
| 27 | let parsedData = event.data; |
| 28 | if (typeof event.data === "string") { |
| 29 | try { |
| 30 | parsedData = JSON.parse(event.data); |
| 31 | } catch (e) { |
| 32 | console.log("Failed to parse as JSON:", e); |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | const { method } = parsedData || {}; |
| 37 | |
| 38 | switch (method) { |
| 39 | case "loaded": |
| 40 | setTimeout(() => { |
| 41 | setIframeLoaded(true); |
| 42 | }, 1000); |
| 43 | break; |
| 44 | } |
| 45 | }, []); |
| 46 | |
| 47 | useEffect(() => { |
| 48 | window.addEventListener("message", handleMessage); |
| 49 | return () => { |
| 50 | window.removeEventListener("message", handleMessage); |
| 51 | }; |
| 52 | }, [handleMessage]); |
| 53 | |
| 54 | const IFrameRef = useRef<HTMLIFrameElement | null>(null); |
| 55 | |
| 56 | const handleIframeLoad = () => { |
| 57 | if (IFrameRef.current) IFrameRef.current.style.opacity = "1"; |
| 58 | }; |
| 59 | |
| 60 | if (!sessionKey || !link) { |
| 61 | return <div>Loading...</div>; |
| 62 | } |
| 63 | |
| 64 | return ( |
| 65 | <div className="relative w-full h-screen bg-white flex"> |
| 66 | <div className="h-180 w-100 m-auto relative"> |
| 67 | <iframe |
| 68 | allow={"payment;camera;clipboard-write"} |
| 69 | src={link} |
| 70 | onLoad={handleIframeLoad} |
| 71 | ref={IFrameRef} |
| 72 | className="w-full h-full border-none" |
| 73 | style={{ |
| 74 | width: "100%", |
| 75 | height: "100%", |
| 76 | opacity: 0, |
| 77 | transition: "opacity 300ms linear", |
| 78 | }} |
| 79 | /> |
| 80 | {!iframeLoaded && ( |
| 81 | <div |
| 82 | className={` |
| 83 | absolute top-0 left-0 w-full h-full bg-zinc-100 |
| 84 | flex flex-col items-center justify-center z-10 |
| 85 | transition-opacity duration-500 ease-out border border-zinc-200 |
| 86 | ${iframeLoaded ? "opacity-0" : "opacity-100"} |
| 87 | `} |
| 88 | > |
| 89 | {/* Skeleton Header */} |
| 90 | <div className="w-3/5 h-10 bg-zinc-300 rounded-lg mb-5 animate-pulse" /> |
| 91 | |
| 92 | {/* Skeleton Content Blocks */} |
| 93 | <div className="w-4/5 flex flex-col gap-4"> |
| 94 | <div className="w-full h-16 bg-zinc-300 rounded-lg animate-pulse" /> |
| 95 | <div className="w-3/4 h-10 bg-zinc-300 rounded-lg animate-pulse" /> |
| 96 | <div className="w-11/12 h-20 bg-zinc-300 rounded-lg animate-pulse" /> |
| 97 | <div className="w-1/2 h-8 bg-zinc-300 rounded-lg animate-pulse" /> |
| 98 | </div> |
| 99 | </div> |
| 100 | )} |
| 101 | </div> |
| 102 | </div> |
| 103 | ); |
| 104 | } |