@applite/duticotac-react 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -29
- package/dist/index.d.mts +64 -1
- package/dist/index.d.ts +64 -1
- package/dist/index.js +260 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +258 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -3
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/components/payment-modal.tsx","../src/components/provider-selector.tsx","../src/utils/cn.ts","../src/components/phone-input.tsx","../src/components/otp-input.tsx","../src/components/transaction-status.tsx","../src/utils/format.ts","../src/utils/validation.ts"],"sourcesContent":["// Components\nexport { DuticotacPaymentModal } from \"./components/payment-modal\";\nexport type { DuticotacPaymentModalProps } from \"./components/payment-modal\";\n\nexport { ProviderSelector } from \"./components/provider-selector\";\nexport type { ProviderSelectorProps } from \"./components/provider-selector\";\n\nexport { PhoneInput } from \"./components/phone-input\";\nexport type { PhoneInputProps } from \"./components/phone-input\";\n\nexport { OtpInput } from \"./components/otp-input\";\nexport type { OtpInputProps } from \"./components/otp-input\";\n\nexport { TransactionStatus } from \"./components/transaction-status\";\nexport type { TransactionStatusProps } from \"./components/transaction-status\";\n\n// Utilities\nexport { cn } from \"./utils/cn\";\nexport { formatCFA } from \"./utils/format\";\nexport {\n getPhoneRegex,\n validatePhone,\n normalizePhone,\n needsOtp,\n} from \"./utils/validation\";\n","import * as React from \"react\";\nimport {\n DuticotacSDK,\n DuticotacError,\n getErrorMessage,\n} from \"@applite/duticotac\";\nimport type {\n PaymentProvider,\n TransactionModel,\n CoreApp,\n PlatformType,\n} from \"@applite/duticotac\";\nimport { ProviderSelector } from \"./provider-selector\";\nimport { PhoneInput } from \"./phone-input\";\nimport { OtpInput } from \"./otp-input\";\nimport { TransactionStatus } from \"./transaction-status\";\nimport { cn } from \"../utils/cn\";\nimport { formatCFA } from \"../utils/format\";\nimport { validatePhone, normalizePhone, needsOtp } from \"../utils/validation\";\n\n// ─── Polling helpers ─────────────────────────────────────────────────────────\n\nfunction waitForVisibility(signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (typeof document === \"undefined\" || document.visibilityState === \"visible\") {\n resolve();\n return;\n }\n const handler = () => {\n if (document.visibilityState === \"visible\") {\n document.removeEventListener(\"visibilitychange\", handler);\n resolve();\n }\n };\n document.addEventListener(\"visibilitychange\", handler);\n signal?.addEventListener(\"abort\", () => {\n document.removeEventListener(\"visibilitychange\", handler);\n resolve();\n });\n });\n}\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\ntype Step = \"form\" | \"confirming\" | \"success\" | \"error\";\n\nexport interface DuticotacPaymentModalProps {\n open: boolean;\n onClose: () => void;\n onSuccess?: (transaction: TransactionModel) => void;\n\n /** SDK instance or config to create one. */\n sdk: DuticotacSDK;\n\n /** Amount in the smallest currency unit. */\n amount: number;\n\n /** Available payment providers. */\n providers?: PaymentProvider[];\n\n /** Called to get a unique transaction ID before cashout. */\n getTransactionId: () => Promise<string>;\n\n /** Product reference for the cashout. */\n productReference?: string;\n\n /** Pre-fill phone number. */\n initialPhone?: string;\n\n /** Customer details. */\n name?: string;\n email?: string;\n customerId?: string;\n kolaboReference?: string;\n app?: CoreApp;\n platform?: PlatformType;\n\n /** Custom success message. */\n successMessage?: string;\n\n /** Custom title. */\n title?: string;\n\n /** Polling timeout in ms (default: 90000). */\n pollTimeout?: number;\n\n /** Additional class names for the modal container. */\n className?: string;\n\n /**\n * Render prop for the modal wrapper. Receives children and renders them\n * inside your dialog/modal component. If not provided, a simple overlay is used.\n */\n renderModal?: (props: {\n open: boolean;\n onClose: () => void;\n title: string;\n children: React.ReactNode;\n }) => React.ReactNode;\n}\n\nconst DEFAULT_PROVIDERS: PaymentProvider[] = [\n \"OM_CI\",\n \"MTN_CI\",\n \"MOOV_CI\",\n \"WAVE_CI\",\n];\n\nexport function DuticotacPaymentModal({\n open,\n onClose,\n onSuccess,\n sdk,\n amount,\n providers = DEFAULT_PROVIDERS,\n getTransactionId,\n productReference,\n initialPhone = \"\",\n name: customerName,\n email: customerEmail,\n customerId,\n kolaboReference,\n app,\n platform,\n successMessage,\n title = \"Paiement\",\n pollTimeout = 90_000,\n className,\n renderModal,\n}: DuticotacPaymentModalProps) {\n const [step, setStep] = React.useState<Step>(\"form\");\n const [provider, setProvider] = React.useState<PaymentProvider>(providers[0]!);\n const [phone, setPhone] = React.useState(initialPhone);\n const [otp, setOtp] = React.useState(\"\");\n const [phoneError, setPhoneError] = React.useState<string | null>(null);\n const [errorMessage, setErrorMessage] = React.useState(\"\");\n const [paymentUrl, setPaymentUrl] = React.useState<string | null>(null);\n const [elapsedSeconds, setElapsedSeconds] = React.useState(0);\n const [lastTransaction, setLastTransaction] = React.useState<TransactionModel | null>(null);\n\n const abortRef = React.useRef<AbortController | null>(null);\n const elapsedRef = React.useRef<ReturnType<typeof setInterval> | null>(null);\n\n const cleanup = React.useCallback(() => {\n abortRef.current?.abort();\n abortRef.current = null;\n if (elapsedRef.current) {\n clearInterval(elapsedRef.current);\n elapsedRef.current = null;\n }\n }, []);\n\n React.useEffect(() => {\n if (open) {\n setStep(\"form\");\n setErrorMessage(\"\");\n setOtp(\"\");\n setPaymentUrl(null);\n setElapsedSeconds(0);\n setPhoneError(null);\n setLastTransaction(null);\n } else {\n cleanup();\n }\n }, [open, cleanup]);\n\n React.useEffect(() => () => cleanup(), [cleanup]);\n\n const isFormValid =\n phone.replace(/\\s/g, \"\").length >= 8 &&\n (needsOtp(provider) ? otp.length === 4 : true);\n\n const handleSubmit = async () => {\n // Validate phone\n const phoneErr = validatePhone(phone, provider);\n if (phoneErr) {\n setPhoneError(phoneErr);\n return;\n }\n setPhoneError(null);\n\n setStep(\"confirming\");\n setErrorMessage(\"\");\n cleanup();\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n try {\n const transactionId = await getTransactionId();\n\n const result = await sdk.mobileMoney.cashout({\n transactionId,\n ref: productReference,\n phone: normalizePhone(phone),\n name: customerName ?? \"Duticotac App\",\n email: customerEmail ?? \"\",\n paymentMethod: provider,\n amount,\n otp: needsOtp(provider) ? otp : undefined,\n customerId,\n kolaboReference,\n app,\n plateform: platform,\n });\n\n const tx = result.data;\n\n if (tx?.paymentUrl) {\n setPaymentUrl(tx.paymentUrl);\n try {\n window.open(tx.paymentUrl, \"_blank\");\n } catch {\n // Popup may be blocked\n }\n }\n\n // Start polling\n const startTime = Date.now();\n setElapsedSeconds(0);\n elapsedRef.current = setInterval(() => {\n setElapsedSeconds(Math.floor((Date.now() - startTime) / 1000));\n }, 1000);\n\n const confirmed = await sdk.transaction.poll(tx.id ?? transactionId, {\n timeoutMs: pollTimeout,\n signal: controller.signal,\n onPoll: () => {\n // Wait for visibility before each poll\n },\n });\n\n setLastTransaction(confirmed.data);\n setStep(\"success\");\n onSuccess?.(confirmed.data);\n } catch (e) {\n if (controller.signal.aborted) return;\n\n if (e instanceof DuticotacError) {\n setErrorMessage(e.message);\n } else {\n setErrorMessage(\n getErrorMessage(\"unknown-error\"),\n );\n }\n setStep(\"error\");\n }\n };\n\n const handleRetry = () => {\n setStep(\"form\");\n setErrorMessage(\"\");\n setPaymentUrl(null);\n setElapsedSeconds(0);\n };\n\n // ─── Content ──────────────────────────────────────────────────────────────\n\n const content = (\n <div className={cn(\"space-y-5\", className)}>\n {step === \"form\" && (\n <>\n {/* Offer summary */}\n <div className=\"rounded-lg border-l-4 border-l-primary bg-muted/50 p-4\">\n <p className=\"text-xs text-muted-foreground\">Montant</p>\n <p className=\"text-xl font-bold text-primary\">\n {formatCFA(amount)} FCFA\n </p>\n </div>\n\n {/* Provider selection */}\n <div className=\"space-y-2\">\n <p className=\"text-sm font-semibold text-foreground\">\n Moyen de paiement\n </p>\n <ProviderSelector\n providers={providers}\n value={provider}\n onChange={(p) => {\n setProvider(p);\n if (!needsOtp(p)) setOtp(\"\");\n }}\n />\n </div>\n\n {/* Phone input */}\n <PhoneInput\n value={phone}\n onChange={(v) => {\n setPhone(v);\n setPhoneError(null);\n }}\n error={phoneError}\n autoFocus\n />\n\n {/* OTP input (Orange Money only) */}\n {needsOtp(provider) && (\n <OtpInput value={otp} onChange={setOtp} />\n )}\n\n {/* Actions */}\n <div className=\"flex justify-end gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-md border border-border bg-background px-5 py-2.5 text-sm font-semibold text-foreground transition-colors hover:bg-muted\"\n >\n Annuler\n </button>\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={!isFormValid}\n className=\"rounded-md bg-primary px-5 py-2.5 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:pointer-events-none disabled:opacity-50\"\n >\n Payer {formatCFA(amount)} FCFA\n </button>\n </div>\n </>\n )}\n\n {step === \"confirming\" && (\n <TransactionStatus\n status=\"polling\"\n elapsedSeconds={elapsedSeconds}\n paymentUrl={paymentUrl}\n onOpenPaymentUrl={() => {\n if (paymentUrl) window.open(paymentUrl, \"_blank\");\n }}\n onClose={onClose}\n />\n )}\n\n {step === \"success\" && (\n <TransactionStatus\n status=\"success\"\n message={successMessage ?? \"Le paiement a ete effectue avec succes.\"}\n onClose={onClose}\n />\n )}\n\n {step === \"error\" && (\n <TransactionStatus\n status=\"error\"\n errorMessage={errorMessage}\n onRetry={handleRetry}\n onClose={onClose}\n />\n )}\n </div>\n );\n\n // ─── Render ───────────────────────────────────────────────────────────────\n\n if (renderModal) {\n return <>{renderModal({ open, onClose, title, children: content })}</>;\n }\n\n // Default simple overlay modal\n if (!open) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n <div\n className=\"absolute inset-0 bg-black/50\"\n onClick={onClose}\n />\n <div className=\"relative z-10 w-full max-w-md rounded-xl border border-border bg-background p-6 shadow-xl\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-bold text-foreground\">{title}</h2>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M6 18L18 6M6 6l12 12\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n </button>\n </div>\n {content}\n </div>\n </div>\n );\n}\n","import * as React from \"react\";\nimport type { PaymentProvider } from \"@applite/duticotac\";\nimport { getProviderName, getProviderLogo } from \"@applite/duticotac\";\nimport { cn } from \"../utils/cn\";\n\nexport interface ProviderSelectorProps {\n providers: PaymentProvider[];\n value: PaymentProvider;\n onChange: (provider: PaymentProvider) => void;\n disabled?: boolean;\n className?: string;\n}\n\nexport function ProviderSelector({\n providers,\n value,\n onChange,\n disabled,\n className,\n}: ProviderSelectorProps) {\n return (\n <div className={cn(\"grid gap-2\", className)} style={{\n gridTemplateColumns: `repeat(${Math.min(providers.length, 4)}, 1fr)`,\n }}>\n {providers.map((provider) => {\n const selected = value === provider;\n return (\n <button\n key={provider}\n type=\"button\"\n disabled={disabled}\n onClick={() => onChange(provider)}\n className={cn(\n \"relative flex flex-col items-center gap-2 rounded-lg border-2 p-3 transition-all\",\n \"hover:scale-[1.02] disabled:pointer-events-none disabled:opacity-50\",\n selected\n ? \"border-primary bg-primary/5 shadow-sm\"\n : \"border-border bg-background hover:border-primary/40\",\n )}\n >\n {selected && (\n <div className=\"absolute -right-1.5 -top-1.5 flex h-5 w-5 items-center justify-center rounded-full bg-primary\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M5 12l5 5L20 7\"\n stroke=\"#fff\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </div>\n )}\n <img\n src={getProviderLogo(provider)}\n alt={getProviderName(provider)}\n width={44}\n height={44}\n className=\"rounded-lg object-contain\"\n />\n <span\n className={cn(\n \"text-center text-xs leading-tight\",\n selected\n ? \"font-semibold text-primary\"\n : \"font-medium text-muted-foreground\",\n )}\n >\n {getProviderName(provider)}\n </span>\n </button>\n );\n })}\n </div>\n );\n}\n","import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import * as React from \"react\";\nimport { cn } from \"../utils/cn\";\n\nexport interface PhoneInputProps {\n value: string;\n onChange: (value: string) => void;\n label?: string;\n placeholder?: string;\n error?: string | null;\n disabled?: boolean;\n className?: string;\n autoFocus?: boolean;\n}\n\nexport function PhoneInput({\n value,\n onChange,\n label = \"Numero de telephone\",\n placeholder = \"07 01 02 03 04\",\n error,\n disabled,\n className,\n autoFocus,\n}: PhoneInputProps) {\n return (\n <div className={cn(\"space-y-1.5\", className)}>\n {label && (\n <label className=\"block text-sm font-semibold text-foreground\">\n {label}\n </label>\n )}\n <div className=\"flex items-center gap-2\">\n <div className=\"flex h-10 items-center rounded-md border border-border bg-muted px-3 text-sm font-medium text-muted-foreground\">\n +225\n </div>\n <input\n type=\"tel\"\n inputMode=\"numeric\"\n value={value}\n onChange={(e) => {\n const cleaned = e.target.value.replace(/[^\\d\\s]/g, \"\");\n onChange(cleaned);\n }}\n placeholder={placeholder}\n disabled={disabled}\n autoFocus={autoFocus}\n className={cn(\n \"h-10 w-full rounded-md border bg-background px-3 text-sm font-medium outline-none transition-colors\",\n \"placeholder:text-muted-foreground/50\",\n \"focus:border-primary focus:ring-2 focus:ring-primary/20\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n error ? \"border-destructive\" : \"border-border\",\n )}\n />\n </div>\n {error && (\n <p className=\"text-xs font-medium text-destructive\">{error}</p>\n )}\n </div>\n );\n}\n","import * as React from \"react\";\nimport { cn } from \"../utils/cn\";\n\nexport interface OtpInputProps {\n value: string;\n onChange: (value: string) => void;\n length?: number;\n disabled?: boolean;\n className?: string;\n}\n\nexport function OtpInput({\n value,\n onChange,\n length = 4,\n disabled,\n className,\n}: OtpInputProps) {\n const inputRefs = React.useRef<(HTMLInputElement | null)[]>([]);\n const digits = value.padEnd(length, \"\").slice(0, length).split(\"\");\n\n const setDigit = (index: number, char: string) => {\n const next = [...digits];\n next[index] = char;\n onChange(next.join(\"\"));\n };\n\n const handleKeyDown = (\n index: number,\n e: React.KeyboardEvent<HTMLInputElement>,\n ) => {\n if (e.key === \"Backspace\") {\n e.preventDefault();\n if (digits[index]?.trim()) {\n setDigit(index, \"\");\n } else if (index > 0) {\n setDigit(index - 1, \"\");\n inputRefs.current[index - 1]?.focus();\n }\n } else if (e.key === \"ArrowLeft\" && index > 0) {\n inputRefs.current[index - 1]?.focus();\n } else if (e.key === \"ArrowRight\" && index < length - 1) {\n inputRefs.current[index + 1]?.focus();\n }\n };\n\n const handleInput = (\n index: number,\n e: React.FormEvent<HTMLInputElement>,\n ) => {\n const val = (e.target as HTMLInputElement).value;\n const char = val.replace(/\\D/g, \"\").slice(-1);\n if (!char) return;\n setDigit(index, char);\n if (index < length - 1) {\n inputRefs.current[index + 1]?.focus();\n }\n };\n\n const handlePaste = (e: React.ClipboardEvent) => {\n e.preventDefault();\n const pasted = e.clipboardData\n .getData(\"text\")\n .replace(/\\D/g, \"\")\n .slice(0, length);\n if (!pasted) return;\n onChange(pasted.padEnd(length, \"\").slice(0, length).replace(/ /g, \"\"));\n const focusIndex = Math.min(pasted.length, length - 1);\n inputRefs.current[focusIndex]?.focus();\n };\n\n return (\n <div className={cn(\"space-y-2\", className)}>\n <label className=\"block text-sm font-semibold text-muted-foreground\">\n Code OTP\n </label>\n <div className=\"flex justify-center gap-3\" onPaste={handlePaste}>\n {Array.from({ length }).map((_, i) => {\n const filled = !!digits[i]?.trim();\n return (\n <input\n key={i}\n ref={(el) => {\n inputRefs.current[i] = el;\n }}\n type=\"text\"\n inputMode=\"numeric\"\n maxLength={1}\n value={digits[i]?.trim() || \"\"}\n onKeyDown={(e) => handleKeyDown(i, e)}\n onInput={(e) => handleInput(i, e)}\n onFocus={(e) => e.target.select()}\n disabled={disabled}\n className={cn(\n \"h-13 w-13 rounded-md border-2 text-center text-xl font-bold outline-none transition-all\",\n \"focus:border-primary focus:ring-2 focus:ring-primary/20 focus:scale-105\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n filled ? \"border-primary/30 bg-primary/5\" : \"border-border bg-background\",\n )}\n />\n );\n })}\n </div>\n <p className=\"text-center text-xs text-muted-foreground\">\n Faites <strong className=\"text-foreground\">#144*82#</strong> pour\n recevoir le code\n </p>\n </div>\n );\n}\n","import * as React from \"react\";\nimport { cn } from \"../utils/cn\";\n\nexport interface TransactionStatusProps {\n status: \"polling\" | \"success\" | \"error\";\n elapsedSeconds?: number;\n message?: string;\n errorMessage?: string;\n paymentUrl?: string | null;\n onRetry?: () => void;\n onClose?: () => void;\n onOpenPaymentUrl?: () => void;\n className?: string;\n}\n\nfunction Spinner({ className }: { className?: string }) {\n return (\n <svg\n className={cn(\"animate-spin\", className)}\n width=\"56\"\n height=\"56\"\n viewBox=\"0 0 56 56\"\n fill=\"none\"\n >\n <circle\n cx=\"28\"\n cy=\"28\"\n r=\"24\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n className=\"opacity-20\"\n />\n <path\n d=\"M28 4a24 24 0 0 1 24 24\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n strokeLinecap=\"round\"\n className=\"text-primary\"\n />\n </svg>\n );\n}\n\nexport function TransactionStatus({\n status,\n elapsedSeconds = 0,\n message,\n errorMessage,\n paymentUrl,\n onRetry,\n onClose,\n onOpenPaymentUrl,\n className,\n}: TransactionStatusProps) {\n if (status === \"polling\") {\n return (\n <div className={cn(\"flex flex-col items-center gap-4 py-8\", className)}>\n <Spinner />\n <div className=\"space-y-2 text-center\">\n <p className=\"text-base font-semibold text-foreground\">\n {paymentUrl\n ? \"Finalisez le paiement dans l'onglet ouvert...\"\n : \"Confirmez le paiement sur votre telephone...\"}\n </p>\n <p className=\"text-sm text-muted-foreground\">\n {paymentUrl\n ? \"Completez le paiement dans l'onglet, puis revenez ici.\"\n : \"Veuillez valider la transaction sur votre telephone.\"}\n </p>\n {elapsedSeconds > 0 && (\n <p className=\"text-xs text-muted-foreground/70\">\n Verification en cours... {elapsedSeconds}s\n </p>\n )}\n </div>\n {paymentUrl && (\n <div className=\"flex flex-col items-center gap-3\">\n <button\n type=\"button\"\n onClick={onOpenPaymentUrl}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-6 py-2.5 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6M15 3h6v6M10 14L21 3\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n Ouvrir Wave pour payer\n </button>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"text-sm text-muted-foreground underline hover:text-foreground\"\n >\n Annuler\n </button>\n </div>\n )}\n </div>\n );\n }\n\n if (status === \"success\") {\n return (\n <div className={cn(\"flex flex-col items-center gap-4 py-8\", className)}>\n <div className=\"flex h-16 w-16 items-center justify-center rounded-full bg-emerald-500 animate-in zoom-in duration-500\">\n <svg width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M5 12l5 5L20 7\"\n stroke=\"#fff\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </div>\n <div className=\"space-y-2 text-center animate-in fade-in slide-in-from-bottom-4 duration-400 delay-150\">\n <p className=\"text-lg font-bold text-foreground\">\n Paiement reussi !\n </p>\n {message && (\n <p className=\"text-sm text-muted-foreground\">{message}</p>\n )}\n </div>\n {onClose && (\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-md bg-primary px-6 py-2.5 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90\"\n >\n Fermer\n </button>\n )}\n </div>\n );\n }\n\n // error\n return (\n <div className={cn(\"flex flex-col items-center gap-4 py-8\", className)}>\n <div className=\"flex h-16 w-16 items-center justify-center rounded-full bg-red-500 animate-in zoom-in duration-500\">\n <svg width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M6 18L18 6M6 6l12 12\"\n stroke=\"#fff\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n />\n </svg>\n </div>\n <div className=\"space-y-2 text-center animate-in fade-in slide-in-from-bottom-4 duration-400 delay-150\">\n <p className=\"text-lg font-bold text-foreground\">\n Echec du paiement\n </p>\n {errorMessage && (\n <p className=\"text-sm text-muted-foreground\">{errorMessage}</p>\n )}\n </div>\n <div className=\"flex gap-3\">\n {onClose && (\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-md border border-border bg-background px-5 py-2.5 text-sm font-semibold text-foreground transition-colors hover:bg-muted\"\n >\n Fermer\n </button>\n )}\n {onRetry && (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"rounded-md bg-primary px-5 py-2.5 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90\"\n >\n Reessayer\n </button>\n )}\n </div>\n </div>\n );\n}\n","export function formatCFA(n: number): string {\n return new Intl.NumberFormat(\"fr-FR\").format(n);\n}\n","import type { PaymentProvider } from \"@applite/duticotac\";\n\nexport function getPhoneRegex(provider: PaymentProvider): RegExp {\n switch (provider) {\n case \"OM_CI\":\n return /^(\\+225)(07)[0-9]{8}$/;\n case \"MTN_CI\":\n return /^(\\+225)(05)[0-9]{8}$/;\n case \"MOOV_CI\":\n return /^(\\+225)(01)[0-9]{8}$/;\n default:\n return /^(\\+225)(07|05|01)[0-9]{8}$/;\n }\n}\n\nexport function validatePhone(\n phone: string,\n provider: PaymentProvider,\n): string | null {\n if (!phone) return \"Numéro de téléphone requis\";\n const intl = phone.startsWith(\"+225\") ? phone : `+225${phone}`;\n const cleaned = intl.replace(/\\s/g, \"\");\n if (!getPhoneRegex(provider).test(cleaned)) {\n return \"Numéro invalide pour ce provider\";\n }\n return null;\n}\n\nexport function normalizePhone(phone: string): string {\n const cleaned = phone.replace(/\\s/g, \"\");\n return cleaned.startsWith(\"+225\") ? cleaned : `+225${cleaned}`;\n}\n\nexport function needsOtp(provider: PaymentProvider): boolean {\n return provider === \"OM_CI\";\n}\n"],"mappings":"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","names":["React","import_duticotac","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/components/payment-modal.tsx","../src/components/provider-selector.tsx","../src/utils/cn.ts","../src/components/phone-input.tsx","../src/components/otp-input.tsx","../src/components/transaction-status.tsx","../src/utils/format.ts","../src/utils/validation.ts","../src/components/responsive-dialog.tsx","../src/hooks/use-duticotac.tsx"],"sourcesContent":["// Components\nexport { DuticotacPaymentModal } from \"./components/payment-modal\";\nexport type { DuticotacPaymentModalProps } from \"./components/payment-modal\";\n\nexport { ProviderSelector } from \"./components/provider-selector\";\nexport type { ProviderSelectorProps } from \"./components/provider-selector\";\n\nexport { PhoneInput } from \"./components/phone-input\";\nexport type { PhoneInputProps } from \"./components/phone-input\";\n\nexport { OtpInput } from \"./components/otp-input\";\nexport type { OtpInputProps } from \"./components/otp-input\";\n\nexport { TransactionStatus } from \"./components/transaction-status\";\nexport type { TransactionStatusProps } from \"./components/transaction-status\";\n\nexport { ResponsiveDialog } from \"./components/responsive-dialog\";\nexport type { ResponsiveDialogProps } from \"./components/responsive-dialog\";\n\n// Hooks\nexport { useDuticotac } from \"./hooks/use-duticotac\";\nexport type {\n UseDuticotacConfig,\n UseDuticotacReturn,\n PayOptions,\n} from \"./hooks/use-duticotac\";\n\n// Utilities\nexport { cn } from \"./utils/cn\";\nexport { formatCFA } from \"./utils/format\";\nexport {\n getPhoneRegex,\n validatePhone,\n normalizePhone,\n needsOtp,\n} from \"./utils/validation\";\n","import * as React from \"react\";\nimport {\n DuticotacSDK,\n DuticotacError,\n getErrorMessage,\n} from \"@applite/duticotac\";\nimport type {\n PaymentProvider,\n TransactionModel,\n CoreApp,\n PlatformType,\n} from \"@applite/duticotac\";\nimport { ProviderSelector } from \"./provider-selector\";\nimport { PhoneInput } from \"./phone-input\";\nimport { OtpInput } from \"./otp-input\";\nimport { TransactionStatus } from \"./transaction-status\";\nimport { cn } from \"../utils/cn\";\nimport { formatCFA } from \"../utils/format\";\nimport { validatePhone, normalizePhone, needsOtp } from \"../utils/validation\";\n\n// ─── Polling helpers ─────────────────────────────────────────────────────────\n\nfunction waitForVisibility(signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (typeof document === \"undefined\" || document.visibilityState === \"visible\") {\n resolve();\n return;\n }\n const handler = () => {\n if (document.visibilityState === \"visible\") {\n document.removeEventListener(\"visibilitychange\", handler);\n resolve();\n }\n };\n document.addEventListener(\"visibilitychange\", handler);\n signal?.addEventListener(\"abort\", () => {\n document.removeEventListener(\"visibilitychange\", handler);\n resolve();\n });\n });\n}\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\ntype Step = \"form\" | \"confirming\" | \"success\" | \"error\";\n\nexport interface DuticotacPaymentModalProps {\n open: boolean;\n onClose: () => void;\n onSuccess?: (transaction: TransactionModel) => void;\n\n /** SDK instance or config to create one. */\n sdk: DuticotacSDK;\n\n /** Amount in the smallest currency unit. */\n amount: number;\n\n /** Available payment providers. */\n providers?: PaymentProvider[];\n\n /** Called to get a unique transaction ID before cashout. */\n getTransactionId: () => Promise<string>;\n\n /** Product reference for the cashout. */\n productReference?: string;\n\n /** Pre-fill phone number. */\n initialPhone?: string;\n\n /** Customer details. */\n name?: string;\n email?: string;\n customerId?: string;\n kolaboReference?: string;\n app?: CoreApp;\n platform?: PlatformType;\n\n /** Custom success message. */\n successMessage?: string;\n\n /** Custom title. */\n title?: string;\n\n /** Polling timeout in ms (default: 90000). */\n pollTimeout?: number;\n\n /** Additional class names for the modal container. */\n className?: string;\n\n /**\n * Render prop for the modal wrapper. Receives children and renders them\n * inside your dialog/modal component. If not provided, a simple overlay is used.\n */\n renderModal?: (props: {\n open: boolean;\n onClose: () => void;\n title: string;\n children: React.ReactNode;\n }) => React.ReactNode;\n}\n\nconst DEFAULT_PROVIDERS: PaymentProvider[] = [\n \"OM_CI\",\n \"MTN_CI\",\n \"MOOV_CI\",\n \"WAVE_CI\",\n];\n\nexport function DuticotacPaymentModal({\n open,\n onClose,\n onSuccess,\n sdk,\n amount,\n providers = DEFAULT_PROVIDERS,\n getTransactionId,\n productReference,\n initialPhone = \"\",\n name: customerName,\n email: customerEmail,\n customerId,\n kolaboReference,\n app,\n platform,\n successMessage,\n title = \"Paiement\",\n pollTimeout = 90_000,\n className,\n renderModal,\n}: DuticotacPaymentModalProps) {\n const [step, setStep] = React.useState<Step>(\"form\");\n const [provider, setProvider] = React.useState<PaymentProvider>(providers[0]!);\n const [phone, setPhone] = React.useState(initialPhone);\n const [otp, setOtp] = React.useState(\"\");\n const [phoneError, setPhoneError] = React.useState<string | null>(null);\n const [errorMessage, setErrorMessage] = React.useState(\"\");\n const [paymentUrl, setPaymentUrl] = React.useState<string | null>(null);\n const [elapsedSeconds, setElapsedSeconds] = React.useState(0);\n const [lastTransaction, setLastTransaction] = React.useState<TransactionModel | null>(null);\n\n const abortRef = React.useRef<AbortController | null>(null);\n const elapsedRef = React.useRef<ReturnType<typeof setInterval> | null>(null);\n\n const cleanup = React.useCallback(() => {\n abortRef.current?.abort();\n abortRef.current = null;\n if (elapsedRef.current) {\n clearInterval(elapsedRef.current);\n elapsedRef.current = null;\n }\n }, []);\n\n React.useEffect(() => {\n if (open) {\n setStep(\"form\");\n setErrorMessage(\"\");\n setOtp(\"\");\n setPaymentUrl(null);\n setElapsedSeconds(0);\n setPhoneError(null);\n setLastTransaction(null);\n } else {\n cleanup();\n }\n }, [open, cleanup]);\n\n React.useEffect(() => () => cleanup(), [cleanup]);\n\n const isFormValid =\n phone.replace(/\\s/g, \"\").length >= 8 &&\n (needsOtp(provider) ? otp.length === 4 : true);\n\n const handleSubmit = async () => {\n // Validate phone\n const phoneErr = validatePhone(phone, provider);\n if (phoneErr) {\n setPhoneError(phoneErr);\n return;\n }\n setPhoneError(null);\n\n setStep(\"confirming\");\n setErrorMessage(\"\");\n cleanup();\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n try {\n const transactionId = await getTransactionId();\n\n const result = await sdk.mobileMoney.cashout({\n transactionId,\n ref: productReference,\n phone: normalizePhone(phone),\n name: customerName ?? \"Duticotac App\",\n email: customerEmail ?? \"\",\n paymentMethod: provider,\n amount,\n otp: needsOtp(provider) ? otp : undefined,\n customerId,\n kolaboReference,\n app,\n plateform: platform,\n });\n\n const tx = result.data;\n\n if (tx?.paymentUrl) {\n setPaymentUrl(tx.paymentUrl);\n try {\n window.open(tx.paymentUrl, \"_blank\");\n } catch {\n // Popup may be blocked\n }\n }\n\n // Start polling\n const startTime = Date.now();\n setElapsedSeconds(0);\n elapsedRef.current = setInterval(() => {\n setElapsedSeconds(Math.floor((Date.now() - startTime) / 1000));\n }, 1000);\n\n const confirmed = await sdk.transaction.poll(tx.id ?? transactionId, {\n timeoutMs: pollTimeout,\n signal: controller.signal,\n onPoll: () => {\n // Wait for visibility before each poll\n },\n });\n\n setLastTransaction(confirmed.data);\n setStep(\"success\");\n onSuccess?.(confirmed.data);\n } catch (e) {\n if (controller.signal.aborted) return;\n\n if (e instanceof DuticotacError) {\n setErrorMessage(e.message);\n } else {\n setErrorMessage(\n getErrorMessage(\"unknown-error\"),\n );\n }\n setStep(\"error\");\n }\n };\n\n const handleRetry = () => {\n setStep(\"form\");\n setErrorMessage(\"\");\n setPaymentUrl(null);\n setElapsedSeconds(0);\n };\n\n // ─── Content ──────────────────────────────────────────────────────────────\n\n const content = (\n <div className={cn(\"space-y-5\", className)}>\n {step === \"form\" && (\n <>\n {/* Offer summary */}\n <div className=\"rounded-lg border-l-4 border-l-primary bg-muted/50 p-4\">\n <p className=\"text-xs text-muted-foreground\">Montant</p>\n <p className=\"text-xl font-bold text-primary\">\n {formatCFA(amount)} FCFA\n </p>\n </div>\n\n {/* Provider selection */}\n <div className=\"space-y-2\">\n <p className=\"text-sm font-semibold text-foreground\">\n Moyen de paiement\n </p>\n <ProviderSelector\n providers={providers}\n value={provider}\n onChange={(p) => {\n setProvider(p);\n if (!needsOtp(p)) setOtp(\"\");\n }}\n />\n </div>\n\n {/* Phone input */}\n <PhoneInput\n value={phone}\n onChange={(v) => {\n setPhone(v);\n setPhoneError(null);\n }}\n error={phoneError}\n autoFocus\n />\n\n {/* OTP input (Orange Money only) */}\n {needsOtp(provider) && (\n <OtpInput value={otp} onChange={setOtp} />\n )}\n\n {/* Actions */}\n <div className=\"flex justify-end gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-md border border-border bg-background px-5 py-2.5 text-sm font-semibold text-foreground transition-colors hover:bg-muted\"\n >\n Annuler\n </button>\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={!isFormValid}\n className=\"rounded-md bg-primary px-5 py-2.5 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:pointer-events-none disabled:opacity-50\"\n >\n Payer {formatCFA(amount)} FCFA\n </button>\n </div>\n </>\n )}\n\n {step === \"confirming\" && (\n <TransactionStatus\n status=\"polling\"\n elapsedSeconds={elapsedSeconds}\n paymentUrl={paymentUrl}\n onOpenPaymentUrl={() => {\n if (paymentUrl) window.open(paymentUrl, \"_blank\");\n }}\n onClose={onClose}\n />\n )}\n\n {step === \"success\" && (\n <TransactionStatus\n status=\"success\"\n message={successMessage ?? \"Le paiement a ete effectue avec succes.\"}\n onClose={onClose}\n />\n )}\n\n {step === \"error\" && (\n <TransactionStatus\n status=\"error\"\n errorMessage={errorMessage}\n onRetry={handleRetry}\n onClose={onClose}\n />\n )}\n </div>\n );\n\n // ─── Render ───────────────────────────────────────────────────────────────\n\n if (renderModal) {\n return <>{renderModal({ open, onClose, title, children: content })}</>;\n }\n\n // Default simple overlay modal\n if (!open) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n <div\n className=\"absolute inset-0 bg-black/50\"\n onClick={onClose}\n />\n <div className=\"relative z-10 w-full max-w-md rounded-xl border border-border bg-background p-6 shadow-xl\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-bold text-foreground\">{title}</h2>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M6 18L18 6M6 6l12 12\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n </button>\n </div>\n {content}\n </div>\n </div>\n );\n}\n","import * as React from \"react\";\nimport type { PaymentProvider } from \"@applite/duticotac\";\nimport { getProviderName, getProviderLogo } from \"@applite/duticotac\";\nimport { cn } from \"../utils/cn\";\n\nexport interface ProviderSelectorProps {\n providers: PaymentProvider[];\n value: PaymentProvider;\n onChange: (provider: PaymentProvider) => void;\n disabled?: boolean;\n className?: string;\n}\n\nexport function ProviderSelector({\n providers,\n value,\n onChange,\n disabled,\n className,\n}: ProviderSelectorProps) {\n return (\n <div className={cn(\"grid gap-2\", className)} style={{\n gridTemplateColumns: `repeat(${Math.min(providers.length, 4)}, 1fr)`,\n }}>\n {providers.map((provider) => {\n const selected = value === provider;\n return (\n <button\n key={provider}\n type=\"button\"\n disabled={disabled}\n onClick={() => onChange(provider)}\n className={cn(\n \"relative flex flex-col items-center gap-2 rounded-lg border-2 p-3 transition-all\",\n \"hover:scale-[1.02] disabled:pointer-events-none disabled:opacity-50\",\n selected\n ? \"border-primary bg-primary/5 shadow-sm\"\n : \"border-border bg-background hover:border-primary/40\",\n )}\n >\n {selected && (\n <div className=\"absolute -right-1.5 -top-1.5 flex h-5 w-5 items-center justify-center rounded-full bg-primary\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M5 12l5 5L20 7\"\n stroke=\"#fff\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </div>\n )}\n <img\n src={getProviderLogo(provider)}\n alt={getProviderName(provider)}\n width={44}\n height={44}\n className=\"rounded-lg object-contain\"\n />\n <span\n className={cn(\n \"text-center text-xs leading-tight\",\n selected\n ? \"font-semibold text-primary\"\n : \"font-medium text-muted-foreground\",\n )}\n >\n {getProviderName(provider)}\n </span>\n </button>\n );\n })}\n </div>\n );\n}\n","import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import * as React from \"react\";\nimport { cn } from \"../utils/cn\";\n\nexport interface PhoneInputProps {\n value: string;\n onChange: (value: string) => void;\n label?: string;\n placeholder?: string;\n error?: string | null;\n disabled?: boolean;\n className?: string;\n autoFocus?: boolean;\n}\n\nexport function PhoneInput({\n value,\n onChange,\n label = \"Numero de telephone\",\n placeholder = \"07 01 02 03 04\",\n error,\n disabled,\n className,\n autoFocus,\n}: PhoneInputProps) {\n return (\n <div className={cn(\"space-y-1.5\", className)}>\n {label && (\n <label className=\"block text-sm font-semibold text-foreground\">\n {label}\n </label>\n )}\n <div className=\"flex items-center gap-2\">\n <div className=\"flex h-10 items-center rounded-md border border-border bg-muted px-3 text-sm font-medium text-muted-foreground\">\n +225\n </div>\n <input\n type=\"tel\"\n inputMode=\"numeric\"\n value={value}\n onChange={(e) => {\n const cleaned = e.target.value.replace(/[^\\d\\s]/g, \"\");\n onChange(cleaned);\n }}\n placeholder={placeholder}\n disabled={disabled}\n autoFocus={autoFocus}\n className={cn(\n \"h-10 w-full rounded-md border bg-background px-3 text-sm font-medium outline-none transition-colors\",\n \"placeholder:text-muted-foreground/50\",\n \"focus:border-primary focus:ring-2 focus:ring-primary/20\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n error ? \"border-destructive\" : \"border-border\",\n )}\n />\n </div>\n {error && (\n <p className=\"text-xs font-medium text-destructive\">{error}</p>\n )}\n </div>\n );\n}\n","import * as React from \"react\";\nimport { cn } from \"../utils/cn\";\n\nexport interface OtpInputProps {\n value: string;\n onChange: (value: string) => void;\n length?: number;\n disabled?: boolean;\n className?: string;\n}\n\nexport function OtpInput({\n value,\n onChange,\n length = 4,\n disabled,\n className,\n}: OtpInputProps) {\n const inputRefs = React.useRef<(HTMLInputElement | null)[]>([]);\n const digits = value.padEnd(length, \"\").slice(0, length).split(\"\");\n\n const setDigit = (index: number, char: string) => {\n const next = [...digits];\n next[index] = char;\n onChange(next.join(\"\"));\n };\n\n const handleKeyDown = (\n index: number,\n e: React.KeyboardEvent<HTMLInputElement>,\n ) => {\n if (e.key === \"Backspace\") {\n e.preventDefault();\n if (digits[index]?.trim()) {\n setDigit(index, \"\");\n } else if (index > 0) {\n setDigit(index - 1, \"\");\n inputRefs.current[index - 1]?.focus();\n }\n } else if (e.key === \"ArrowLeft\" && index > 0) {\n inputRefs.current[index - 1]?.focus();\n } else if (e.key === \"ArrowRight\" && index < length - 1) {\n inputRefs.current[index + 1]?.focus();\n }\n };\n\n const handleInput = (\n index: number,\n e: React.FormEvent<HTMLInputElement>,\n ) => {\n const val = (e.target as HTMLInputElement).value;\n const char = val.replace(/\\D/g, \"\").slice(-1);\n if (!char) return;\n setDigit(index, char);\n if (index < length - 1) {\n inputRefs.current[index + 1]?.focus();\n }\n };\n\n const handlePaste = (e: React.ClipboardEvent) => {\n e.preventDefault();\n const pasted = e.clipboardData\n .getData(\"text\")\n .replace(/\\D/g, \"\")\n .slice(0, length);\n if (!pasted) return;\n onChange(pasted.padEnd(length, \"\").slice(0, length).replace(/ /g, \"\"));\n const focusIndex = Math.min(pasted.length, length - 1);\n inputRefs.current[focusIndex]?.focus();\n };\n\n return (\n <div className={cn(\"space-y-2\", className)}>\n <label className=\"block text-sm font-semibold text-muted-foreground\">\n Code OTP\n </label>\n <div className=\"flex justify-center gap-3\" onPaste={handlePaste}>\n {Array.from({ length }).map((_, i) => {\n const filled = !!digits[i]?.trim();\n return (\n <input\n key={i}\n ref={(el) => {\n inputRefs.current[i] = el;\n }}\n type=\"text\"\n inputMode=\"numeric\"\n maxLength={1}\n value={digits[i]?.trim() || \"\"}\n onKeyDown={(e) => handleKeyDown(i, e)}\n onInput={(e) => handleInput(i, e)}\n onFocus={(e) => e.target.select()}\n disabled={disabled}\n className={cn(\n \"h-13 w-13 rounded-md border-2 text-center text-xl font-bold outline-none transition-all\",\n \"focus:border-primary focus:ring-2 focus:ring-primary/20 focus:scale-105\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n filled ? \"border-primary/30 bg-primary/5\" : \"border-border bg-background\",\n )}\n />\n );\n })}\n </div>\n <p className=\"text-center text-xs text-muted-foreground\">\n Faites <strong className=\"text-foreground\">#144*82#</strong> pour\n recevoir le code\n </p>\n </div>\n );\n}\n","import * as React from \"react\";\nimport { cn } from \"../utils/cn\";\n\nexport interface TransactionStatusProps {\n status: \"polling\" | \"success\" | \"error\";\n elapsedSeconds?: number;\n message?: string;\n errorMessage?: string;\n paymentUrl?: string | null;\n onRetry?: () => void;\n onClose?: () => void;\n onOpenPaymentUrl?: () => void;\n className?: string;\n}\n\nfunction Spinner({ className }: { className?: string }) {\n return (\n <svg\n className={cn(\"animate-spin\", className)}\n width=\"56\"\n height=\"56\"\n viewBox=\"0 0 56 56\"\n fill=\"none\"\n >\n <circle\n cx=\"28\"\n cy=\"28\"\n r=\"24\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n className=\"opacity-20\"\n />\n <path\n d=\"M28 4a24 24 0 0 1 24 24\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n strokeLinecap=\"round\"\n className=\"text-primary\"\n />\n </svg>\n );\n}\n\nexport function TransactionStatus({\n status,\n elapsedSeconds = 0,\n message,\n errorMessage,\n paymentUrl,\n onRetry,\n onClose,\n onOpenPaymentUrl,\n className,\n}: TransactionStatusProps) {\n if (status === \"polling\") {\n return (\n <div className={cn(\"flex flex-col items-center gap-4 py-8\", className)}>\n <Spinner />\n <div className=\"space-y-2 text-center\">\n <p className=\"text-base font-semibold text-foreground\">\n {paymentUrl\n ? \"Finalisez le paiement dans l'onglet ouvert...\"\n : \"Confirmez le paiement sur votre telephone...\"}\n </p>\n <p className=\"text-sm text-muted-foreground\">\n {paymentUrl\n ? \"Completez le paiement dans l'onglet, puis revenez ici.\"\n : \"Veuillez valider la transaction sur votre telephone.\"}\n </p>\n {elapsedSeconds > 0 && (\n <p className=\"text-xs text-muted-foreground/70\">\n Verification en cours... {elapsedSeconds}s\n </p>\n )}\n </div>\n {paymentUrl && (\n <div className=\"flex flex-col items-center gap-3\">\n <button\n type=\"button\"\n onClick={onOpenPaymentUrl}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-6 py-2.5 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6M15 3h6v6M10 14L21 3\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n Ouvrir Wave pour payer\n </button>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"text-sm text-muted-foreground underline hover:text-foreground\"\n >\n Annuler\n </button>\n </div>\n )}\n </div>\n );\n }\n\n if (status === \"success\") {\n return (\n <div className={cn(\"flex flex-col items-center gap-4 py-8\", className)}>\n <div className=\"flex h-16 w-16 items-center justify-center rounded-full bg-emerald-500 animate-in zoom-in duration-500\">\n <svg width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M5 12l5 5L20 7\"\n stroke=\"#fff\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </div>\n <div className=\"space-y-2 text-center animate-in fade-in slide-in-from-bottom-4 duration-400 delay-150\">\n <p className=\"text-lg font-bold text-foreground\">\n Paiement reussi !\n </p>\n {message && (\n <p className=\"text-sm text-muted-foreground\">{message}</p>\n )}\n </div>\n {onClose && (\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-md bg-primary px-6 py-2.5 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90\"\n >\n Fermer\n </button>\n )}\n </div>\n );\n }\n\n // error\n return (\n <div className={cn(\"flex flex-col items-center gap-4 py-8\", className)}>\n <div className=\"flex h-16 w-16 items-center justify-center rounded-full bg-red-500 animate-in zoom-in duration-500\">\n <svg width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M6 18L18 6M6 6l12 12\"\n stroke=\"#fff\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n />\n </svg>\n </div>\n <div className=\"space-y-2 text-center animate-in fade-in slide-in-from-bottom-4 duration-400 delay-150\">\n <p className=\"text-lg font-bold text-foreground\">\n Echec du paiement\n </p>\n {errorMessage && (\n <p className=\"text-sm text-muted-foreground\">{errorMessage}</p>\n )}\n </div>\n <div className=\"flex gap-3\">\n {onClose && (\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-md border border-border bg-background px-5 py-2.5 text-sm font-semibold text-foreground transition-colors hover:bg-muted\"\n >\n Fermer\n </button>\n )}\n {onRetry && (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"rounded-md bg-primary px-5 py-2.5 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90\"\n >\n Reessayer\n </button>\n )}\n </div>\n </div>\n );\n}\n","export function formatCFA(n: number): string {\n return new Intl.NumberFormat(\"fr-FR\").format(n);\n}\n","import type { PaymentProvider } from \"@applite/duticotac\";\n\nexport function getPhoneRegex(provider: PaymentProvider): RegExp {\n switch (provider) {\n case \"OM_CI\":\n return /^(\\+225)(07)[0-9]{8}$/;\n case \"MTN_CI\":\n return /^(\\+225)(05)[0-9]{8}$/;\n case \"MOOV_CI\":\n return /^(\\+225)(01)[0-9]{8}$/;\n default:\n return /^(\\+225)(07|05|01)[0-9]{8}$/;\n }\n}\n\nexport function validatePhone(\n phone: string,\n provider: PaymentProvider,\n): string | null {\n if (!phone) return \"Numéro de téléphone requis\";\n const intl = phone.startsWith(\"+225\") ? phone : `+225${phone}`;\n const cleaned = intl.replace(/\\s/g, \"\");\n if (!getPhoneRegex(provider).test(cleaned)) {\n return \"Numéro invalide pour ce provider\";\n }\n return null;\n}\n\nexport function normalizePhone(phone: string): string {\n const cleaned = phone.replace(/\\s/g, \"\");\n return cleaned.startsWith(\"+225\") ? cleaned : `+225${cleaned}`;\n}\n\nexport function needsOtp(provider: PaymentProvider): boolean {\n return provider === \"OM_CI\";\n}\n","import * as React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../utils/cn\";\n\nexport interface ResponsiveDialogProps {\n open: boolean;\n onClose: () => void;\n title: string;\n children: React.ReactNode;\n className?: string;\n}\n\nconst ANIMATION_MS = 250;\n\nexport function ResponsiveDialog({\n open,\n onClose,\n title,\n children,\n className,\n}: ResponsiveDialogProps) {\n const [mounted, setMounted] = React.useState(false);\n const [visible, setVisible] = React.useState(false);\n const panelRef = React.useRef<HTMLDivElement>(null);\n\n // Mount on open, animate in, then animate out before unmount\n React.useEffect(() => {\n if (open) {\n setMounted(true);\n // Trigger enter animation next frame\n requestAnimationFrame(() => {\n requestAnimationFrame(() => setVisible(true));\n });\n } else if (mounted) {\n setVisible(false);\n const timer = setTimeout(() => setMounted(false), ANIMATION_MS);\n return () => clearTimeout(timer);\n }\n }, [open]);\n\n // Escape key\n React.useEffect(() => {\n if (!open) return;\n const handler = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n document.addEventListener(\"keydown\", handler);\n return () => document.removeEventListener(\"keydown\", handler);\n }, [open, onClose]);\n\n // Lock body scroll\n React.useEffect(() => {\n if (!mounted) return;\n const prev = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.body.style.overflow = prev;\n };\n }, [mounted]);\n\n if (!mounted) return null;\n\n const dialog = (\n <>\n <style>{`\n .dtc-dialog-backdrop {\n position: fixed;\n inset: 0;\n z-index: 9998;\n background: rgba(0,0,0,0);\n transition: background ${ANIMATION_MS}ms ease;\n }\n .dtc-dialog-backdrop.dtc-visible {\n background: rgba(0,0,0,0.5);\n }\n\n /* ── Desktop: centered modal ── */\n .dtc-dialog-panel {\n position: fixed;\n z-index: 9999;\n background: var(--background, #fff);\n border: 1px solid var(--border, #e5e7eb);\n box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25);\n\n /* Desktop default */\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.95);\n opacity: 0;\n width: calc(100% - 32px);\n max-width: 440px;\n max-height: calc(100vh - 64px);\n border-radius: 16px;\n overflow-y: auto;\n\n transition:\n transform ${ANIMATION_MS}ms cubic-bezier(0.22, 1, 0.36, 1),\n opacity ${ANIMATION_MS}ms ease;\n }\n .dtc-dialog-panel.dtc-visible {\n transform: translate(-50%, -50%) scale(1);\n opacity: 1;\n }\n\n /* ── Mobile: bottom sheet ── */\n @media (max-width: 640px) {\n .dtc-dialog-panel {\n top: auto;\n left: 0;\n right: 0;\n bottom: 0;\n transform: translateY(100%);\n opacity: 1;\n width: 100%;\n max-width: 100%;\n max-height: 90vh;\n border-radius: 20px 20px 0 0;\n border-bottom: none;\n }\n .dtc-dialog-panel.dtc-visible {\n transform: translateY(0);\n }\n }\n `}</style>\n\n {/* Backdrop */}\n <div\n className={cn(\"dtc-dialog-backdrop\", visible && \"dtc-visible\")}\n onClick={onClose}\n aria-hidden\n />\n\n {/* Panel */}\n <div\n ref={panelRef}\n className={cn(\"dtc-dialog-panel\", visible && \"dtc-visible\", className)}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n >\n {/* Drag handle (mobile) */}\n <div className=\"dtc-mobile-handle\" style={{\n display: \"none\",\n justifyContent: \"center\",\n paddingTop: 12,\n paddingBottom: 4,\n }}>\n <div style={{\n width: 36,\n height: 4,\n borderRadius: 2,\n background: \"var(--border, #d1d5db)\",\n }} />\n </div>\n <style>{`\n @media (max-width: 640px) {\n .dtc-mobile-handle { display: flex !important; }\n }\n `}</style>\n\n {/* Header */}\n <div style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n padding: \"16px 20px 0\",\n }}>\n <h2 style={{\n fontSize: 18,\n fontWeight: 700,\n color: \"var(--foreground, #111)\",\n margin: 0,\n }}>\n {title}\n </h2>\n <button\n type=\"button\"\n onClick={onClose}\n style={{\n background: \"none\",\n border: \"none\",\n padding: 4,\n cursor: \"pointer\",\n borderRadius: 6,\n color: \"var(--muted-foreground, #6b7280)\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M6 18L18 6M6 6l12 12\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n </button>\n </div>\n\n {/* Content */}\n <div style={{ padding: \"16px 20px 20px\" }}>\n {children}\n </div>\n </div>\n </>\n );\n\n if (typeof document === \"undefined\") return null;\n return createPortal(dialog, document.body);\n}\n","import * as React from \"react\";\nimport type { DuticotacSDK, PaymentProvider, TransactionModel, CoreApp, PlatformType } from \"@applite/duticotac\";\nimport { DuticotacPaymentModal } from \"../components/payment-modal\";\nimport { ResponsiveDialog } from \"../components/responsive-dialog\";\n\nexport interface UseDuticotacConfig {\n /** DuticotacSDK instance. */\n sdk: DuticotacSDK;\n\n /** Available payment providers. */\n providers?: PaymentProvider[];\n\n /** Called to get a unique transaction ID before cashout. */\n getTransactionId: () => Promise<string>;\n\n /** Shared defaults — can be overridden per pay() call. */\n productReference?: string;\n name?: string;\n email?: string;\n customerId?: string;\n kolaboReference?: string;\n app?: CoreApp;\n platform?: PlatformType;\n initialPhone?: string;\n\n /** Polling timeout in ms (default: 90000). */\n pollTimeout?: number;\n\n /** Dialog title (default: \"Paiement\"). */\n title?: string;\n}\n\nexport interface PayOptions {\n /** Amount in FCFA. */\n amount: number;\n\n /** Override providers for this payment. */\n providers?: PaymentProvider[];\n\n /** Override product reference. */\n productReference?: string;\n\n /** Override customer details. */\n name?: string;\n email?: string;\n customerId?: string;\n\n /** Custom success message. */\n successMessage?: string;\n\n /** Called when payment is confirmed. */\n onSuccess?: (transaction: TransactionModel) => void;\n\n /** Called when the dialog closes (success or cancel). */\n onClose?: () => void;\n}\n\nexport interface UseDuticotacReturn {\n /** Open the payment dialog with the given options. */\n pay: (options: PayOptions) => void;\n\n /** Whether the dialog is currently open. */\n isOpen: boolean;\n\n /** Close the dialog programmatically. */\n close: () => void;\n\n /**\n * The dialog JSX element. Render this once somewhere in your component tree.\n * @example return <>{Dialog}</>\n */\n Dialog: React.ReactNode;\n}\n\nexport function useDuticotac(config: UseDuticotacConfig): UseDuticotacReturn {\n const [open, setOpen] = React.useState(false);\n const [payOptions, setPayOptions] = React.useState<PayOptions | null>(null);\n\n const configRef = React.useRef(config);\n configRef.current = config;\n\n const pay = React.useCallback((options: PayOptions) => {\n setPayOptions(options);\n setOpen(true);\n }, []);\n\n const close = React.useCallback(() => {\n setOpen(false);\n }, []);\n\n const handleClose = React.useCallback(() => {\n setOpen(false);\n payOptions?.onClose?.();\n }, [payOptions]);\n\n const handleSuccess = React.useCallback(\n (tx: TransactionModel) => {\n payOptions?.onSuccess?.(tx);\n },\n [payOptions],\n );\n\n const DialogElement = React.useMemo(() => {\n if (!payOptions) return null;\n\n const cfg = configRef.current;\n\n return (\n <DuticotacPaymentModal\n open={open}\n onClose={handleClose}\n onSuccess={handleSuccess}\n sdk={cfg.sdk}\n amount={payOptions.amount}\n providers={payOptions.providers ?? cfg.providers}\n getTransactionId={cfg.getTransactionId}\n productReference={payOptions.productReference ?? cfg.productReference}\n initialPhone={cfg.initialPhone}\n name={payOptions.name ?? cfg.name}\n email={payOptions.email ?? cfg.email}\n customerId={payOptions.customerId ?? cfg.customerId}\n kolaboReference={cfg.kolaboReference}\n app={cfg.app}\n platform={cfg.platform}\n pollTimeout={cfg.pollTimeout}\n title={cfg.title}\n successMessage={payOptions.successMessage}\n renderModal={({ open: isOpen, onClose: modalClose, title: modalTitle, children }) => (\n <ResponsiveDialog\n open={isOpen}\n onClose={modalClose}\n title={modalTitle}\n >\n {children}\n </ResponsiveDialog>\n )}\n />\n );\n }, [open, payOptions, handleClose, handleSuccess]);\n\n return {\n pay,\n isOpen: open,\n close,\n Dialog: DialogElement,\n };\n}\n"],"mappings":"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","names":["React","import_duticotac","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","React","import_jsx_runtime","React","import_jsx_runtime"]}
|
package/dist/index.mjs
CHANGED
|
@@ -661,17 +661,275 @@ function DuticotacPaymentModal({
|
|
|
661
661
|
] })
|
|
662
662
|
] });
|
|
663
663
|
}
|
|
664
|
+
|
|
665
|
+
// src/components/responsive-dialog.tsx
|
|
666
|
+
import * as React3 from "react";
|
|
667
|
+
import { createPortal } from "react-dom";
|
|
668
|
+
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
669
|
+
var ANIMATION_MS = 250;
|
|
670
|
+
function ResponsiveDialog({
|
|
671
|
+
open,
|
|
672
|
+
onClose,
|
|
673
|
+
title,
|
|
674
|
+
children,
|
|
675
|
+
className
|
|
676
|
+
}) {
|
|
677
|
+
const [mounted, setMounted] = React3.useState(false);
|
|
678
|
+
const [visible, setVisible] = React3.useState(false);
|
|
679
|
+
const panelRef = React3.useRef(null);
|
|
680
|
+
React3.useEffect(() => {
|
|
681
|
+
if (open) {
|
|
682
|
+
setMounted(true);
|
|
683
|
+
requestAnimationFrame(() => {
|
|
684
|
+
requestAnimationFrame(() => setVisible(true));
|
|
685
|
+
});
|
|
686
|
+
} else if (mounted) {
|
|
687
|
+
setVisible(false);
|
|
688
|
+
const timer = setTimeout(() => setMounted(false), ANIMATION_MS);
|
|
689
|
+
return () => clearTimeout(timer);
|
|
690
|
+
}
|
|
691
|
+
}, [open]);
|
|
692
|
+
React3.useEffect(() => {
|
|
693
|
+
if (!open) return;
|
|
694
|
+
const handler = (e) => {
|
|
695
|
+
if (e.key === "Escape") onClose();
|
|
696
|
+
};
|
|
697
|
+
document.addEventListener("keydown", handler);
|
|
698
|
+
return () => document.removeEventListener("keydown", handler);
|
|
699
|
+
}, [open, onClose]);
|
|
700
|
+
React3.useEffect(() => {
|
|
701
|
+
if (!mounted) return;
|
|
702
|
+
const prev = document.body.style.overflow;
|
|
703
|
+
document.body.style.overflow = "hidden";
|
|
704
|
+
return () => {
|
|
705
|
+
document.body.style.overflow = prev;
|
|
706
|
+
};
|
|
707
|
+
}, [mounted]);
|
|
708
|
+
if (!mounted) return null;
|
|
709
|
+
const dialog = /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
710
|
+
/* @__PURE__ */ jsx6("style", { children: `
|
|
711
|
+
.dtc-dialog-backdrop {
|
|
712
|
+
position: fixed;
|
|
713
|
+
inset: 0;
|
|
714
|
+
z-index: 9998;
|
|
715
|
+
background: rgba(0,0,0,0);
|
|
716
|
+
transition: background ${ANIMATION_MS}ms ease;
|
|
717
|
+
}
|
|
718
|
+
.dtc-dialog-backdrop.dtc-visible {
|
|
719
|
+
background: rgba(0,0,0,0.5);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/* \u2500\u2500 Desktop: centered modal \u2500\u2500 */
|
|
723
|
+
.dtc-dialog-panel {
|
|
724
|
+
position: fixed;
|
|
725
|
+
z-index: 9999;
|
|
726
|
+
background: var(--background, #fff);
|
|
727
|
+
border: 1px solid var(--border, #e5e7eb);
|
|
728
|
+
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25);
|
|
729
|
+
|
|
730
|
+
/* Desktop default */
|
|
731
|
+
top: 50%;
|
|
732
|
+
left: 50%;
|
|
733
|
+
transform: translate(-50%, -50%) scale(0.95);
|
|
734
|
+
opacity: 0;
|
|
735
|
+
width: calc(100% - 32px);
|
|
736
|
+
max-width: 440px;
|
|
737
|
+
max-height: calc(100vh - 64px);
|
|
738
|
+
border-radius: 16px;
|
|
739
|
+
overflow-y: auto;
|
|
740
|
+
|
|
741
|
+
transition:
|
|
742
|
+
transform ${ANIMATION_MS}ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
743
|
+
opacity ${ANIMATION_MS}ms ease;
|
|
744
|
+
}
|
|
745
|
+
.dtc-dialog-panel.dtc-visible {
|
|
746
|
+
transform: translate(-50%, -50%) scale(1);
|
|
747
|
+
opacity: 1;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/* \u2500\u2500 Mobile: bottom sheet \u2500\u2500 */
|
|
751
|
+
@media (max-width: 640px) {
|
|
752
|
+
.dtc-dialog-panel {
|
|
753
|
+
top: auto;
|
|
754
|
+
left: 0;
|
|
755
|
+
right: 0;
|
|
756
|
+
bottom: 0;
|
|
757
|
+
transform: translateY(100%);
|
|
758
|
+
opacity: 1;
|
|
759
|
+
width: 100%;
|
|
760
|
+
max-width: 100%;
|
|
761
|
+
max-height: 90vh;
|
|
762
|
+
border-radius: 20px 20px 0 0;
|
|
763
|
+
border-bottom: none;
|
|
764
|
+
}
|
|
765
|
+
.dtc-dialog-panel.dtc-visible {
|
|
766
|
+
transform: translateY(0);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
` }),
|
|
770
|
+
/* @__PURE__ */ jsx6(
|
|
771
|
+
"div",
|
|
772
|
+
{
|
|
773
|
+
className: cn("dtc-dialog-backdrop", visible && "dtc-visible"),
|
|
774
|
+
onClick: onClose,
|
|
775
|
+
"aria-hidden": true
|
|
776
|
+
}
|
|
777
|
+
),
|
|
778
|
+
/* @__PURE__ */ jsxs6(
|
|
779
|
+
"div",
|
|
780
|
+
{
|
|
781
|
+
ref: panelRef,
|
|
782
|
+
className: cn("dtc-dialog-panel", visible && "dtc-visible", className),
|
|
783
|
+
role: "dialog",
|
|
784
|
+
"aria-modal": "true",
|
|
785
|
+
"aria-label": title,
|
|
786
|
+
children: [
|
|
787
|
+
/* @__PURE__ */ jsx6("div", { className: "dtc-mobile-handle", style: {
|
|
788
|
+
display: "none",
|
|
789
|
+
justifyContent: "center",
|
|
790
|
+
paddingTop: 12,
|
|
791
|
+
paddingBottom: 4
|
|
792
|
+
}, children: /* @__PURE__ */ jsx6("div", { style: {
|
|
793
|
+
width: 36,
|
|
794
|
+
height: 4,
|
|
795
|
+
borderRadius: 2,
|
|
796
|
+
background: "var(--border, #d1d5db)"
|
|
797
|
+
} }) }),
|
|
798
|
+
/* @__PURE__ */ jsx6("style", { children: `
|
|
799
|
+
@media (max-width: 640px) {
|
|
800
|
+
.dtc-mobile-handle { display: flex !important; }
|
|
801
|
+
}
|
|
802
|
+
` }),
|
|
803
|
+
/* @__PURE__ */ jsxs6("div", { style: {
|
|
804
|
+
display: "flex",
|
|
805
|
+
alignItems: "center",
|
|
806
|
+
justifyContent: "space-between",
|
|
807
|
+
padding: "16px 20px 0"
|
|
808
|
+
}, children: [
|
|
809
|
+
/* @__PURE__ */ jsx6("h2", { style: {
|
|
810
|
+
fontSize: 18,
|
|
811
|
+
fontWeight: 700,
|
|
812
|
+
color: "var(--foreground, #111)",
|
|
813
|
+
margin: 0
|
|
814
|
+
}, children: title }),
|
|
815
|
+
/* @__PURE__ */ jsx6(
|
|
816
|
+
"button",
|
|
817
|
+
{
|
|
818
|
+
type: "button",
|
|
819
|
+
onClick: onClose,
|
|
820
|
+
style: {
|
|
821
|
+
background: "none",
|
|
822
|
+
border: "none",
|
|
823
|
+
padding: 4,
|
|
824
|
+
cursor: "pointer",
|
|
825
|
+
borderRadius: 6,
|
|
826
|
+
color: "var(--muted-foreground, #6b7280)",
|
|
827
|
+
display: "flex",
|
|
828
|
+
alignItems: "center",
|
|
829
|
+
justifyContent: "center"
|
|
830
|
+
},
|
|
831
|
+
children: /* @__PURE__ */ jsx6("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx6(
|
|
832
|
+
"path",
|
|
833
|
+
{
|
|
834
|
+
d: "M6 18L18 6M6 6l12 12",
|
|
835
|
+
stroke: "currentColor",
|
|
836
|
+
strokeWidth: "2",
|
|
837
|
+
strokeLinecap: "round"
|
|
838
|
+
}
|
|
839
|
+
) })
|
|
840
|
+
}
|
|
841
|
+
)
|
|
842
|
+
] }),
|
|
843
|
+
/* @__PURE__ */ jsx6("div", { style: { padding: "16px 20px 20px" }, children })
|
|
844
|
+
]
|
|
845
|
+
}
|
|
846
|
+
)
|
|
847
|
+
] });
|
|
848
|
+
if (typeof document === "undefined") return null;
|
|
849
|
+
return createPortal(dialog, document.body);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// src/hooks/use-duticotac.tsx
|
|
853
|
+
import * as React4 from "react";
|
|
854
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
855
|
+
function useDuticotac(config) {
|
|
856
|
+
const [open, setOpen] = React4.useState(false);
|
|
857
|
+
const [payOptions, setPayOptions] = React4.useState(null);
|
|
858
|
+
const configRef = React4.useRef(config);
|
|
859
|
+
configRef.current = config;
|
|
860
|
+
const pay = React4.useCallback((options) => {
|
|
861
|
+
setPayOptions(options);
|
|
862
|
+
setOpen(true);
|
|
863
|
+
}, []);
|
|
864
|
+
const close = React4.useCallback(() => {
|
|
865
|
+
setOpen(false);
|
|
866
|
+
}, []);
|
|
867
|
+
const handleClose = React4.useCallback(() => {
|
|
868
|
+
setOpen(false);
|
|
869
|
+
payOptions?.onClose?.();
|
|
870
|
+
}, [payOptions]);
|
|
871
|
+
const handleSuccess = React4.useCallback(
|
|
872
|
+
(tx) => {
|
|
873
|
+
payOptions?.onSuccess?.(tx);
|
|
874
|
+
},
|
|
875
|
+
[payOptions]
|
|
876
|
+
);
|
|
877
|
+
const DialogElement = React4.useMemo(() => {
|
|
878
|
+
if (!payOptions) return null;
|
|
879
|
+
const cfg = configRef.current;
|
|
880
|
+
return /* @__PURE__ */ jsx7(
|
|
881
|
+
DuticotacPaymentModal,
|
|
882
|
+
{
|
|
883
|
+
open,
|
|
884
|
+
onClose: handleClose,
|
|
885
|
+
onSuccess: handleSuccess,
|
|
886
|
+
sdk: cfg.sdk,
|
|
887
|
+
amount: payOptions.amount,
|
|
888
|
+
providers: payOptions.providers ?? cfg.providers,
|
|
889
|
+
getTransactionId: cfg.getTransactionId,
|
|
890
|
+
productReference: payOptions.productReference ?? cfg.productReference,
|
|
891
|
+
initialPhone: cfg.initialPhone,
|
|
892
|
+
name: payOptions.name ?? cfg.name,
|
|
893
|
+
email: payOptions.email ?? cfg.email,
|
|
894
|
+
customerId: payOptions.customerId ?? cfg.customerId,
|
|
895
|
+
kolaboReference: cfg.kolaboReference,
|
|
896
|
+
app: cfg.app,
|
|
897
|
+
platform: cfg.platform,
|
|
898
|
+
pollTimeout: cfg.pollTimeout,
|
|
899
|
+
title: cfg.title,
|
|
900
|
+
successMessage: payOptions.successMessage,
|
|
901
|
+
renderModal: ({ open: isOpen, onClose: modalClose, title: modalTitle, children }) => /* @__PURE__ */ jsx7(
|
|
902
|
+
ResponsiveDialog,
|
|
903
|
+
{
|
|
904
|
+
open: isOpen,
|
|
905
|
+
onClose: modalClose,
|
|
906
|
+
title: modalTitle,
|
|
907
|
+
children
|
|
908
|
+
}
|
|
909
|
+
)
|
|
910
|
+
}
|
|
911
|
+
);
|
|
912
|
+
}, [open, payOptions, handleClose, handleSuccess]);
|
|
913
|
+
return {
|
|
914
|
+
pay,
|
|
915
|
+
isOpen: open,
|
|
916
|
+
close,
|
|
917
|
+
Dialog: DialogElement
|
|
918
|
+
};
|
|
919
|
+
}
|
|
664
920
|
export {
|
|
665
921
|
DuticotacPaymentModal,
|
|
666
922
|
OtpInput,
|
|
667
923
|
PhoneInput,
|
|
668
924
|
ProviderSelector,
|
|
925
|
+
ResponsiveDialog,
|
|
669
926
|
TransactionStatus,
|
|
670
927
|
cn,
|
|
671
928
|
formatCFA,
|
|
672
929
|
getPhoneRegex,
|
|
673
930
|
needsOtp,
|
|
674
931
|
normalizePhone,
|
|
932
|
+
useDuticotac,
|
|
675
933
|
validatePhone
|
|
676
934
|
};
|
|
677
935
|
//# sourceMappingURL=index.mjs.map
|