UNPKG

@splito/sdk

Version:

Official JavaScript/TypeScript SDK for Splito - Split payments across multiple recipients

1,065 lines (1,052 loc) 57.1 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/lib/utils.ts function cn(...inputs) { return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs)); } var import_clsx, import_tailwind_merge; var init_utils = __esm({ "src/lib/utils.ts"() { import_clsx = require("clsx"); import_tailwind_merge = require("tailwind-merge"); } }); // src/components/ui/button.tsx var React, import_react_slot, import_class_variance_authority, import_jsx_runtime, buttonVariants, Button; var init_button = __esm({ "src/components/ui/button.tsx"() { React = __toESM(require("react")); import_react_slot = require("@radix-ui/react-slot"); import_class_variance_authority = require("class-variance-authority"); init_utils(); import_jsx_runtime = require("react/jsx-runtime"); buttonVariants = (0, import_class_variance_authority.cva)( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline" }, size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", icon: "h-10 w-10" } }, defaultVariants: { variant: "default", size: "default" } } ); Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? import_react_slot.Slot : "button"; return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Comp, { className: cn(buttonVariants({ variant, size, className })), ref, ...props }); } ); Button.displayName = "Button"; } }); // src/components/ui/card.tsx var React2, import_jsx_runtime2, Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter; var init_card = __esm({ "src/components/ui/card.tsx"() { React2 = __toESM(require("react")); init_utils(); import_jsx_runtime2 = require("react/jsx-runtime"); Card = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref, className: cn("rounded-lg border bg-card text-card-foreground shadow-sm", className), ...props })); Card.displayName = "Card"; CardHeader = React2.forwardRef( ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref, className: cn("flex flex-col space-y-1.5 p-6", className), ...props }) ); CardHeader.displayName = "CardHeader"; CardTitle = React2.forwardRef( ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { ref, className: cn("text-2xl font-semibold leading-none tracking-tight", className), ...props }) ); CardTitle.displayName = "CardTitle"; CardDescription = React2.forwardRef( ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { ref, className: cn("text-sm text-muted-foreground", className), ...props }) ); CardDescription.displayName = "CardDescription"; CardContent = React2.forwardRef( ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref, className: cn("p-6 pt-0", className), ...props }) ); CardContent.displayName = "CardContent"; CardFooter = React2.forwardRef( ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref, className: cn("flex items-center p-6 pt-0", className), ...props }) ); CardFooter.displayName = "CardFooter"; } }); // src/components/ui/input.tsx var React3, import_jsx_runtime3, Input; var init_input = __esm({ "src/components/ui/input.tsx"() { React3 = __toESM(require("react")); init_utils(); import_jsx_runtime3 = require("react/jsx-runtime"); Input = React3.forwardRef( ({ className, type, ...props }, ref) => { return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( "input", { type, className: cn( "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", className ), ref, ...props } ); } ); Input.displayName = "Input"; } }); // src/components/ui/label.tsx var React4, LabelPrimitive, import_class_variance_authority2, import_jsx_runtime4, labelVariants, Label; var init_label = __esm({ "src/components/ui/label.tsx"() { React4 = __toESM(require("react")); LabelPrimitive = __toESM(require("@radix-ui/react-label")); import_class_variance_authority2 = require("class-variance-authority"); init_utils(); import_jsx_runtime4 = require("react/jsx-runtime"); labelVariants = (0, import_class_variance_authority2.cva)("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"); Label = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(LabelPrimitive.Root, { ref, className: cn(labelVariants(), className), ...props })); Label.displayName = LabelPrimitive.Root.displayName; } }); // src/components/ui/badge.tsx function Badge({ className, variant, ...props }) { return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props }); } var import_class_variance_authority3, import_jsx_runtime5, badgeVariants; var init_badge = __esm({ "src/components/ui/badge.tsx"() { import_class_variance_authority3 = require("class-variance-authority"); init_utils(); import_jsx_runtime5 = require("react/jsx-runtime"); badgeVariants = (0, import_class_variance_authority3.cva)( "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", { variants: { variant: { default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", outline: "text-foreground", success: "border-transparent bg-success/10 text-success hover:bg-success/20", warning: "border-transparent bg-warning/10 text-warning hover:bg-warning/20", error: "border-transparent bg-destructive/10 text-destructive hover:bg-destructive/20" } }, defaultVariants: { variant: "default" } } ); } }); // src/components/ui/dialog.tsx var React5, DialogPrimitive, import_lucide_react, import_jsx_runtime6, Dialog, DialogPortal, DialogOverlay, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription; var init_dialog = __esm({ "src/components/ui/dialog.tsx"() { React5 = __toESM(require("react")); DialogPrimitive = __toESM(require("@radix-ui/react-dialog")); import_lucide_react = require("lucide-react"); init_utils(); import_jsx_runtime6 = require("react/jsx-runtime"); Dialog = DialogPrimitive.Root; DialogPortal = DialogPrimitive.Portal; DialogOverlay = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( DialogPrimitive.Overlay, { ref, className: cn( "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", className ), ...props } )); DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; DialogContent = React5.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(DialogPortal, { children: [ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DialogOverlay, {}), /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)( DialogPrimitive.Content, { ref, className: cn( "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", className ), ...props, children: [ children, /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(DialogPrimitive.Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity data-[state=open]:bg-accent data-[state=open]:text-muted-foreground hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none", children: [ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react.X, { className: "h-4 w-4" }), /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "sr-only", children: "Close" }) ] }) ] } ) ] })); DialogContent.displayName = DialogPrimitive.Content.displayName; DialogHeader = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: cn("flex flex-col space-y-1.5 text-center sm:text-left", className), ...props }); DialogHeader.displayName = "DialogHeader"; DialogFooter = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className), ...props }); DialogFooter.displayName = "DialogFooter"; DialogTitle = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( DialogPrimitive.Title, { ref, className: cn("text-lg font-semibold leading-none tracking-tight", className), ...props } )); DialogTitle.displayName = DialogPrimitive.Title.displayName; DialogDescription = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DialogPrimitive.Description, { ref, className: cn("text-sm text-muted-foreground", className), ...props })); DialogDescription.displayName = DialogPrimitive.Description.displayName; } }); // src/components/ui/drawer.tsx var React6, import_vaul, import_jsx_runtime7, Drawer, DrawerTrigger, DrawerPortal, DrawerClose, DrawerOverlay, DrawerContent, DrawerHeader, DrawerFooter, DrawerTitle, DrawerDescription; var init_drawer = __esm({ "src/components/ui/drawer.tsx"() { React6 = __toESM(require("react")); import_vaul = require("vaul"); init_utils(); import_jsx_runtime7 = require("react/jsx-runtime"); Drawer = ({ shouldScaleBackground = true, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_vaul.Drawer.Root, { shouldScaleBackground, ...props }); Drawer.displayName = "Drawer"; DrawerTrigger = import_vaul.Drawer.Trigger; DrawerPortal = import_vaul.Drawer.Portal; DrawerClose = import_vaul.Drawer.Close; DrawerOverlay = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_vaul.Drawer.Overlay, { ref, className: cn("fixed inset-0 z-50 bg-black/80", className), ...props })); DrawerOverlay.displayName = import_vaul.Drawer.Overlay.displayName; DrawerContent = React6.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(DrawerPortal, { children: [ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DrawerOverlay, {}), /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)( import_vaul.Drawer.Content, { ref, className: cn( "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background", className ), ...props, children: [ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" }), children ] } ) ] })); DrawerContent.displayName = "DrawerContent"; DrawerHeader = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: cn("grid gap-1.5 p-4 text-center sm:text-left", className), ...props }); DrawerHeader.displayName = "DrawerHeader"; DrawerFooter = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: cn("mt-auto flex flex-col gap-2 p-4", className), ...props }); DrawerFooter.displayName = "DrawerFooter"; DrawerTitle = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)( import_vaul.Drawer.Title, { ref, className: cn("text-lg font-semibold leading-none tracking-tight", className), ...props } )); DrawerTitle.displayName = import_vaul.Drawer.Title.displayName; DrawerDescription = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_vaul.Drawer.Description, { ref, className: cn("text-sm text-muted-foreground", className), ...props })); DrawerDescription.displayName = import_vaul.Drawer.Description.displayName; } }); // src/hooks/use-toast.ts function genId() { count = (count + 1) % Number.MAX_SAFE_INTEGER; return count.toString(); } function dispatch(action) { memoryState = reducer(memoryState, action); listeners.forEach((listener) => { listener(memoryState); }); } function toast({ ...props }) { const id = genId(); const update = (props2) => dispatch({ type: "UPDATE_TOAST", toast: { ...props2, id } }); const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); dispatch({ type: "ADD_TOAST", toast: { ...props, id, open: true, onOpenChange: (open) => { if (!open) dismiss(); } } }); return { id, dismiss, update }; } function useToast() { const [state, setState] = React7.useState(memoryState); React7.useEffect(() => { listeners.push(setState); return () => { const index = listeners.indexOf(setState); if (index > -1) { listeners.splice(index, 1); } }; }, [state]); return { ...state, toast, dismiss: (toastId) => dispatch({ type: "DISMISS_TOAST", toastId }) }; } var React7, TOAST_LIMIT, TOAST_REMOVE_DELAY, count, toastTimeouts, addToRemoveQueue, reducer, listeners, memoryState; var init_use_toast = __esm({ "src/hooks/use-toast.ts"() { React7 = __toESM(require("react")); TOAST_LIMIT = 1; TOAST_REMOVE_DELAY = 1e6; count = 0; toastTimeouts = /* @__PURE__ */ new Map(); addToRemoveQueue = (toastId) => { if (toastTimeouts.has(toastId)) { return; } const timeout = setTimeout(() => { toastTimeouts.delete(toastId); dispatch({ type: "REMOVE_TOAST", toastId }); }, TOAST_REMOVE_DELAY); toastTimeouts.set(toastId, timeout); }; reducer = (state, action) => { switch (action.type) { case "ADD_TOAST": return { ...state, toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT) }; case "UPDATE_TOAST": return { ...state, toasts: state.toasts.map((t) => t.id === action.toast.id ? { ...t, ...action.toast } : t) }; case "DISMISS_TOAST": { const { toastId } = action; if (toastId) { addToRemoveQueue(toastId); } else { state.toasts.forEach((toast2) => { addToRemoveQueue(toast2.id); }); } return { ...state, toasts: state.toasts.map( (t) => t.id === toastId || toastId === void 0 ? { ...t, open: false } : t ) }; } case "REMOVE_TOAST": if (action.toastId === void 0) { return { ...state, toasts: [] }; } return { ...state, toasts: state.toasts.filter((t) => t.id !== action.toastId) }; } }; listeners = []; memoryState = { toasts: [] }; } }); // src/integrations/supabase/client.ts var import_supabase_js, SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, supabase; var init_client = __esm({ "src/integrations/supabase/client.ts"() { import_supabase_js = require("@supabase/supabase-js"); SUPABASE_URL = "https://mhskwcysroougxpjacdz.supabase.co"; SUPABASE_PUBLISHABLE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1oc2t3Y3lzcm9vdWd4cGphY2R6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTg1OTQ4NjMsImV4cCI6MjA3NDE3MDg2M30.7R5jmf1okIVMQo10w-H0pfqkukfq90vXLJEkN7IvzBY"; supabase = (0, import_supabase_js.createClient)(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, { auth: { storage: localStorage, persistSession: true, autoRefreshToken: true } }); } }); // src/sdk/SplitModal.tsx function SplitModalContent({ token, baseUrl }) { const { toast: toast2 } = useToast(); const [paymentIntent, setPaymentIntent] = (0, import_react.useState)(null); const [recipients, setRecipients] = (0, import_react.useState)([]); const [loading, setLoading] = (0, import_react.useState)(true); const [paymentForm, setPaymentForm] = (0, import_react.useState)({ name: "", email: "", amount: 0 }); const [selectedAmount, setSelectedAmount] = (0, import_react.useState)(null); const [selectedPaymentMethod, setSelectedPaymentMethod] = (0, import_react.useState)("stripe"); const [inviteForm, setInviteForm] = (0, import_react.useState)({ name: "", email: "" }); const [isInviting, setIsInviting] = (0, import_react.useState)(false); const [showInviteForm, setShowInviteForm] = (0, import_react.useState)(false); const [isGeneratingQR, setIsGeneratingQR] = (0, import_react.useState)(false); const [showQRInvitation, setShowQRInvitation] = (0, import_react.useState)(false); const [qrCodeData, setQrCodeData] = (0, import_react.useState)(null); const [invitationUrl, setInvitationUrl] = (0, import_react.useState)(null); const [isProcessingPayment, setIsProcessingPayment] = (0, import_react.useState)(false); (0, import_react.useEffect)(() => { if (token) { fetchPaymentDetails(); } }, [token]); (0, import_react.useEffect)(() => { if (recipients.length > 0 && !paymentForm.name) { const urlParams = new URLSearchParams(window.location.search); const inviteeEmail = urlParams.get("invitee"); if (inviteeEmail) { const matchingRecipient = recipients.find( (r) => r.email.toLowerCase() === decodeURIComponent(inviteeEmail).toLowerCase() ); if (matchingRecipient) { setPaymentForm((prev) => ({ ...prev, name: matchingRecipient.name, email: matchingRecipient.email })); toast2({ title: "Welcome!", description: `Hi ${matchingRecipient.name}, we've pre-filled your details.` }); } } } }, [recipients]); const fetchPaymentDetails = async () => { try { const response = await fetch(`${baseUrl}/functions/v1/get-splitting-link`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ token }) }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.error || `HTTP ${response.status}`); } const data = await response.json(); if (!data) { throw new Error("No data received"); } setPaymentIntent(data); setRecipients(data.recipients || []); } catch (error) { console.error("Error fetching payment details:", error); toast2({ title: "Error", description: error instanceof Error ? error.message : "Failed to load payment details", variant: "destructive" }); } finally { setLoading(false); } }; const processPayment = async () => { if (!paymentForm.name || !paymentForm.email || !selectedAmount) { toast2({ title: "Error", description: "Please fill in all required fields", variant: "destructive" }); return; } setIsProcessingPayment(true); try { if (selectedPaymentMethod === "stripe") { const { data, error } = await supabase.functions.invoke("process-connected-payment", { body: { payment_intent_id: paymentIntent?.id, payer_name: paymentForm.name, payer_email: paymentForm.email, amount_cents: selectedAmount } }); if (error) throw error; if (data?.checkout_url) { toast2({ title: "Redirecting to Payment", description: "You'll be redirected to complete your payment securely." }); await new Promise((resolve) => setTimeout(resolve, 500)); window.open(data.checkout_url, "_blank"); } } else if (selectedPaymentMethod === "paypal") { const { data, error } = await supabase.functions.invoke("create-paypal-payment", { body: { payment_intent_id: paymentIntent?.id, payer_name: paymentForm.name, payer_email: paymentForm.email, amount_cents: selectedAmount } }); if (error) throw error; if (data?.approval_url) { toast2({ title: "Redirecting to PayPal", description: "You'll be redirected to PayPal to complete your payment." }); await new Promise((resolve) => setTimeout(resolve, 500)); window.open(data.approval_url, "_blank"); } } } catch (error) { toast2({ title: "Payment Error", description: error.message || "Failed to process payment", variant: "destructive" }); } finally { setIsProcessingPayment(false); } }; const generateQRInvitation = async () => { if (showQRInvitation) { setShowQRInvitation(false); return; } setIsGeneratingQR(true); try { const url = `${window.location.origin}/split/${token}`; const qrData = await import_qrcode.default.toDataURL(url, { width: 300, margin: 2, color: { dark: "#000000", light: "#FFFFFF" } }); setQrCodeData(qrData); setInvitationUrl(url); setShowQRInvitation(true); } catch (error) { toast2({ title: "Error", description: "Failed to generate QR code", variant: "destructive" }); } finally { setIsGeneratingQR(false); } }; const copyInvitationUrl = () => { if (invitationUrl) { navigator.clipboard.writeText(invitationUrl); toast2({ title: "Copied!", description: "Invitation link copied to clipboard" }); } }; const shareInvitation = async () => { if (navigator.share && invitationUrl) { try { await navigator.share({ title: "Split Payment Invitation", text: "Join me in splitting this payment", url: invitationUrl }); } catch (error) { copyInvitationUrl(); } } else { copyInvitationUrl(); } }; const sendInvitation = async () => { if (!inviteForm.name || !inviteForm.email) { toast2({ title: "Error", description: "Please fill in all invitation fields", variant: "destructive" }); return; } if (!paymentIntent || !token) return; setIsInviting(true); try { const { data, error } = await supabase.functions.invoke("send-invitation", { body: { paymentIntentId: paymentIntent.id, inviteeEmail: inviteForm.email, inviteeName: inviteForm.name, inviterName: "Anonymous", inviterEmail: "noreply@splito.com", splittingToken: token } }); if (error) throw error; const newRecipient = { id: crypto.randomUUID(), name: inviteForm.name, email: inviteForm.email, amount_cents: 0, status: "pending", payment_completed_at: null }; setRecipients((prev) => [newRecipient, ...prev]); toast2({ title: "Invitation Sent!", description: `${inviteForm.name} has been invited and added to participants.` }); setInviteForm({ name: "", email: "" }); setShowInviteForm(false); fetchPaymentDetails(); } catch (error) { console.error("Error sending invitation:", error); toast2({ title: "Error", description: error.message || "Failed to send invitation", variant: "destructive" }); } finally { setIsInviting(false); } }; if (loading) { return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Loader2, { className: "w-8 h-8 animate-spin text-primary" }) }); } if (!paymentIntent) { return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "p-6 text-center", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.AlertTriangle, { className: "w-12 h-12 text-warning mx-auto mb-4" }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h2", { className: "text-xl font-semibold mb-2", children: "Splitting Link Not Found" }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-muted-foreground", children: "This splitting link is invalid or has expired." }) ] }); } const totalPaid = recipients.filter((r) => r.status === "completed").reduce((sum, r) => sum + r.amount_cents, 0); const remainingAmount = paymentIntent.amount_cents - totalPaid; const progressPercentage = totalPaid / paymentIntent.amount_cents * 100; const quickAmounts = [ { label: "Equal Split", value: Math.ceil(remainingAmount / Math.max(1, (paymentIntent.max_participants || 10) - recipients.length)) }, { label: "25%", value: Math.ceil(paymentIntent.amount_cents * 0.25) }, { label: "50%", value: Math.ceil(paymentIntent.amount_cents * 0.5) }, { label: "Full Amount", value: remainingAmount } ].filter((amount) => amount.value <= remainingAmount && amount.value > 0); return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "space-y-6 max-h-[80vh] overflow-y-auto px-1", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "text-center", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center justify-center gap-4 mb-4", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col items-center", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "w-12 h-12 rounded-xl flex items-center justify-center mb-1 overflow-hidden", children: paymentIntent?.business?.logo_url ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( "img", { src: paymentIntent.business.logo_url, alt: `${paymentIntent.business.name} logo`, className: "w-full h-full object-cover rounded-xl" } ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "w-12 h-12 bg-gradient-to-br from-primary to-primary-hover rounded-xl flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Building2, { className: "w-6 h-6 text-white" }) }) }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-xs font-medium text-muted-foreground", children: paymentIntent?.business?.name || "Business" }) ] }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.RefreshCw, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col items-center", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "w-12 h-12 rounded-xl flex items-center justify-center mb-1 overflow-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( "img", { src: "/logo.png", alt: "Splito", className: "w-10 h-10 rounded object-cover" } ) }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-xs font-medium", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "bg-gradient-to-r from-primary to-primary/80 bg-clip-text text-transparent font-bold", children: "Splito" }) }) ] }) ] }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h2", { className: "text-xl font-bold mb-1", children: "Split Payment Request" }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-sm text-muted-foreground", children: "Join others in splitting this payment" }) ] }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Card, { children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CardHeader, { className: "pb-3", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CardTitle, { className: "flex items-center gap-2 text-lg", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.DollarSign, { className: "w-5 h-5" }), paymentIntent.products?.name || "Payment Request" ] }) }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CardContent, { className: "space-y-3", children: [ paymentIntent.products?.description && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-sm text-muted-foreground", children: paymentIntent.products.description }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "grid grid-cols-2 gap-3 text-sm", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "font-medium", children: "Total Amount" }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("p", { className: "text-lg font-bold text-primary", children: [ "$", (paymentIntent.amount_cents / 100).toFixed(2), " ", paymentIntent.currency ] }) ] }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "font-medium", children: "Remaining" }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("p", { className: "text-lg font-bold text-success", children: [ "$", (remainingAmount / 100).toFixed(2) ] }) ] }) ] }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "space-y-1", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex justify-between text-xs", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-muted-foreground", children: "Progress" }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "font-medium", children: [ progressPercentage.toFixed(0), "%" ] }) ] }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "h-2 bg-secondary rounded-full overflow-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( "div", { className: "h-full bg-gradient-to-r from-primary to-primary/80 transition-all duration-500", style: { width: `${progressPercentage}%` } } ) }) ] }) ] }) ] }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Card, { children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CardHeader, { className: "pb-3", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CardTitle, { className: "flex items-center gap-2 text-base", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Users, { className: "w-4 h-4" }), "Participants (", recipients.length, "/", paymentIntent.max_participants || 10, ")" ] }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex gap-2", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( Button, { variant: showQRInvitation ? "default" : "outline", size: "sm", onClick: generateQRInvitation, disabled: isGeneratingQR, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.QrCode, { className: "w-3 h-3" }) } ), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( Button, { variant: showInviteForm ? "default" : "outline", size: "sm", onClick: () => setShowInviteForm(!showInviteForm), children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.UserPlus, { className: "w-3 h-3" }) } ) ] }) ] }) }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CardContent, { className: "space-y-3", children: [ showQRInvitation && qrCodeData && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Card, { className: "bg-gradient-to-r from-primary/5 to-secondary/5", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CardContent, { className: "pt-4 text-center space-y-3", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: qrCodeData, alt: "QR Code", className: "mx-auto w-48 h-48" }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-xs text-muted-foreground", children: "Scan to join the split" }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex gap-2 justify-center", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Button, { variant: "outline", size: "sm", onClick: copyInvitationUrl, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Copy, { className: "w-3 h-3" }) }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Button, { variant: "outline", size: "sm", onClick: shareInvitation, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Share2, { className: "w-3 h-3" }) }) ] }) ] }) }), showInviteForm && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Card, { className: "bg-gradient-to-r from-primary/5 to-secondary/5 border-primary/20", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CardContent, { className: "pt-3 space-y-2", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-2 mb-1", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Mail, { className: "w-3 h-3 text-primary" }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { className: "text-sm font-medium", children: "Invite someone to contribute" }) ] }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "grid grid-cols-1 gap-2", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Label, { htmlFor: "inviteName", className: "text-xs", children: "Name" }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( Input, { id: "inviteName", placeholder: "Friend's name", value: inviteForm.name, onChange: (e) => setInviteForm((prev) => ({ ...prev, name: e.target.value })), className: "h-8 text-sm" } ) ] }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Label, { htmlFor: "inviteEmail", className: "text-xs", children: "Email" }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( Input, { id: "inviteEmail", type: "email", placeholder: "friend@email.com", value: inviteForm.email, onChange: (e) => setInviteForm((prev) => ({ ...prev, email: e.target.value })), className: "h-8 text-sm" } ) ] }) ] }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex gap-2 pt-1", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( Button, { onClick: sendInvitation, disabled: isInviting || !inviteForm.name || !inviteForm.email, size: "sm", className: "text-xs h-8", children: isInviting ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Clock, { className: "w-3 h-3 animate-spin" }), "Sending..." ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Send, { className: "w-3 h-3" }), "Send Invitation" ] }) } ), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( Button, { variant: "outline", size: "sm", className: "text-xs h-8", onClick: () => { setShowInviteForm(false); setInviteForm({ name: "", email: "" }); }, children: "Cancel" } ) ] }) ] }) }), recipients.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "space-y-2", children: recipients.map((recipient) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center justify-between p-2 border rounded-lg text-sm", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "font-medium text-sm", children: recipient.name }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-xs text-muted-foreground", children: recipient.email }) ] }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-2", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Badge, { variant: recipient.status === "completed" ? "success" : "secondary", className: "text-xs", children: [ "$", (recipient.amount_cents / 100).toFixed(2) ] }), recipient.status === "completed" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.CheckCircle, { className: "w-3 h-3 text-success" }) ] }) ] }, recipient.id)) }) ] }) ] }), remainingAmount > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Card, { children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CardHeader, { className: "pb-3", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CardTitle, { className: "flex items-center gap-2 text-base", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.CreditCard, { className: "w-4 h-4" }), "Choose Your Contribution" ] }) }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CardContent, { className: "space-y-3", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "grid grid-cols-2 gap-2", children: quickAmounts.map((amount) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( Button, { variant: selectedAmount === amount.value ? "default" : "outline", onClick: () => setSelectedAmount(amount.value), className: "text-xs h-9", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "text-center", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "font-semibold", children: amount.label }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "text-xs opacity-80", children: [ "$", (amount.value / 100).toFixed(2) ] }) ] }) }, amount.label )) }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Label, { htmlFor: "customAmount", className: "text-xs", children: "Custom Amount" }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( Input, { id: "customAmount", type: "number", placeholder: "Enter custom amount", value: selectedAmount ? (selectedAmount / 100).toString() : "", onChange: (e) => setSelectedAmount(Math.round(parseFloat(e.target.value || "0") * 100)), min: "0.01", max: (remainingAmount / 100).toFixed(2), step: "0.01", className: "h-8 text-sm" } ) ] }), /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "space-y-2", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Label, { className: "text-xs", children: "Your Details" }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( Input, { placeholder: "Your name", value: paymentForm.name, onChange: (e) => setPaymentForm({ ...paymentForm, name: e.target.value }), className: "h-8 text-sm" } ), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( Input, { type: "email", placeholder: "your.email@example.com", value: paymentForm.email, onChange: (e) => setPaymentForm({ ...paymentForm, email: e.target.value }), className: "h-8 text-sm" } ) ] }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( Button, { onClick: processPayment, disabled: !selectedAmount || !paymentForm.name || !paymentForm.email || isProcessingPayment, className: "w-full", size: "sm", children: isProcessingPayment ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Loader2, { className: "w-4 h-4 mr-2 animate-spin" }), "Processing..." ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.CreditCard, { className: "w-4 h-4 mr-2" }), "Pay $", selectedAmount ? (selectedAmount / 100).toFixed(2) : "0.00" ] }) } ) ] }) ] }) ] }); } function SplitModal({ open, onClose, token, baseUrl, isMobile }) { if (isMobile) { return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Drawer, { open, onOpenChange: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(DrawerContent, { className: "max-h-[95vh]", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DrawerHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DrawerTitle, { children: "Split Payment" }) }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "px-4 pb-6 overflow-y-auto", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SplitModalContent, { token, baseUrl }) }) ] }) }); } return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Dialog, { open, onOpenChange: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(DialogContent, { className: "max-w-2xl max-h-[90vh] overflow-y-auto", children: [ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DialogHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DialogTitle, { children: "Split Payment" }) }), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SplitModalContent, { token, baseUrl }) ] }) }); } var import_react, import_lucide_react2, import_qrcode, import_jsx_runtime8; var init_SplitModal = __esm({ "src/sdk/SplitModal.tsx"() { import_react = require("react"); import_lucide_react2 = require("lucide-react"); init_button(); init_card(); init_input(); init_label(); init_badge(); init_dialog(); init_drawer(); init_use_toast(); init_client(); import_qrcode = __toESM(require("qrcode")); import_jsx_runtime8 = require("react/jsx-runtime"); } }); // src/sdk/modal-initializer.tsx var modal_initializer_exports = {}; __export(modal_initializer_exports, { openSplitModal: () => openSplitModal }); function openSplitModal(token, baseUrl) { const isMobile = window.innerWidth < 768; const apiBaseUrl = baseUrl || window.location.origin; const container = document.createElement("div"); container.id = "splito-modal-root"; container.style.position = "fixed"; container.style.top = "0"; container.style.left = "0"; container.style.width = "100%"; container.style.height = "100%"; container.style.zIndex = "9999"; document.body.appendChild(container); const root = (0, import_client2.createRoot)(container); const handleClose = () => { root.unmount(); document.body.removeChild(container); }; root.render( /* @__PURE__ */ (0, import_jsx_runtime9.jsx)( SplitModal, { open: true, onClose: handleClose, token, baseUrl: apiBaseUrl, isMobile } ) ); } var import_client2, import_jsx_runtime9; var init_modal_initializer = __esm({ "src/sdk/modal-initializer.tsx"() { import_client2 = require("react-dom/client"); init_SplitModal(); import_jsx_runtime9 = require("react/jsx-runtime"); } }); // src/sdk/index.ts var index_exports = {}; __export(index_exports, { Splito: () => Splito, default: () => in