From 903aa11d7670a0093a4a90b37ea4f14c68ffdb34 Mon Sep 17 00:00:00 2001 From: Arush Kapoor Date: Tue, 7 Apr 2026 03:20:38 +0530 Subject: [PATCH 1/2] feat(demo-app): convert Hyperswitch-React-Demo-App to TypeScript - Rename all 7 source files from .js to .tsx/.ts - Add tsconfig.json with strict mode and moduleResolution: bundler - Add globals.d.ts for webpack DefinePlugin globals and asset modules - Add local ambient type declarations for @juspay-tech/hyper-js and @juspay-tech/react-hyper-js (subset used by demo app) - Update webpack.common.js: add @babel/preset-typescript, .ts/.tsx extensions - Add proper type annotations: component props, state, event handlers, async function signatures, CartItem interface - All 0 tsc --noEmit errors with strict mode enabled --- Hyperswitch-React-Demo-App/package-lock.json | 267 ++++++++++++------ Hyperswitch-React-Demo-App/package.json | 6 +- .../src/{App.js => App.tsx} | 0 .../src/{Cart.js => Cart.tsx} | 12 +- .../src/{CheckoutForm.js => CheckoutForm.tsx} | 10 +- .../src/{Completion.js => Completion.tsx} | 2 +- .../src/{Payment.js => Payment.tsx} | 7 +- Hyperswitch-React-Demo-App/src/globals.d.ts | 98 +++++++ .../src/{index.js => index.tsx} | 2 +- .../src/{utils.js => utils.ts} | 44 ++- Hyperswitch-React-Demo-App/tsconfig.json | 17 ++ Hyperswitch-React-Demo-App/webpack.common.js | 7 +- 12 files changed, 356 insertions(+), 116 deletions(-) rename Hyperswitch-React-Demo-App/src/{App.js => App.tsx} (100%) rename Hyperswitch-React-Demo-App/src/{Cart.js => Cart.tsx} (92%) rename Hyperswitch-React-Demo-App/src/{CheckoutForm.js => CheckoutForm.tsx} (91%) rename Hyperswitch-React-Demo-App/src/{Completion.js => Completion.tsx} (97%) rename Hyperswitch-React-Demo-App/src/{Payment.js => Payment.tsx} (90%) create mode 100644 Hyperswitch-React-Demo-App/src/globals.d.ts rename Hyperswitch-React-Demo-App/src/{index.js => index.tsx} (73%) rename Hyperswitch-React-Demo-App/src/{utils.js => utils.ts} (69%) create mode 100644 Hyperswitch-React-Demo-App/tsconfig.json diff --git a/Hyperswitch-React-Demo-App/package-lock.json b/Hyperswitch-React-Demo-App/package-lock.json index a506fbea3..9e4de0786 100644 --- a/Hyperswitch-React-Demo-App/package-lock.json +++ b/Hyperswitch-React-Demo-App/package-lock.json @@ -27,6 +27,9 @@ "@babel/core": "^7.23.3", "@babel/preset-env": "^7.23.3", "@babel/preset-react": "^7.23.3", + "@babel/preset-typescript": "^7.28.5", + "@types/react": "^18.3.28", + "@types/react-dom": "^18.3.7", "babel-loader": "^9.1.3", "concurrently": "^9.2.0", "copy-webpack-plugin": "^11.0.0", @@ -34,6 +37,7 @@ "prettier": "^2.7.1", "style-loader": "^4.0.0", "terser-webpack-plugin": "^5.3.10", + "typescript": "^5.9.3", "webpack": "^5.93.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^5.1.4", @@ -55,13 +59,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -111,16 +114,15 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -158,18 +160,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", - "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", + "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "engines": { @@ -214,15 +215,23 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -274,11 +283,10 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -302,15 +310,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -344,11 +351,10 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -393,13 +399,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -553,6 +558,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-unicode-sets-regex": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", @@ -1409,6 +1429,25 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", @@ -1596,49 +1635,65 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1664,18 +1719,13 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, - "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -1688,16 +1738,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/source-map": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", @@ -1717,11 +1757,10 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, - "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1985,6 +2024,12 @@ "@types/node": "*" } }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -1999,6 +2044,25 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, "node_modules/@types/retry": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", @@ -3143,6 +3207,12 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true + }, "node_modules/cycle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", @@ -6758,6 +6828,19 @@ "node": ">= 0.6" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/undici-types": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", diff --git a/Hyperswitch-React-Demo-App/package.json b/Hyperswitch-React-Demo-App/package.json index 2bb59e82a..b0aedd7b9 100644 --- a/Hyperswitch-React-Demo-App/package.json +++ b/Hyperswitch-React-Demo-App/package.json @@ -27,7 +27,7 @@ "build": "npm run build-base", "build:v2": "SDK_VERSION=v2 npm run build-base", "build-base": "webpack --config webpack.common.js", - "format": "prettier --write \"**/*.{js,jsx}\"" + "format": "prettier --write \"**/*.{js,jsx,ts,tsx}\"" }, "eslintConfig": { "extends": [ @@ -52,6 +52,9 @@ "@babel/core": "^7.23.3", "@babel/preset-env": "^7.23.3", "@babel/preset-react": "^7.23.3", + "@babel/preset-typescript": "^7.28.5", + "@types/react": "^18.3.28", + "@types/react-dom": "^18.3.7", "babel-loader": "^9.1.3", "concurrently": "^9.2.0", "copy-webpack-plugin": "^11.0.0", @@ -59,6 +62,7 @@ "prettier": "^2.7.1", "style-loader": "^4.0.0", "terser-webpack-plugin": "^5.3.10", + "typescript": "^5.9.3", "webpack": "^5.93.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^5.1.4", diff --git a/Hyperswitch-React-Demo-App/src/App.js b/Hyperswitch-React-Demo-App/src/App.tsx similarity index 100% rename from Hyperswitch-React-Demo-App/src/App.js rename to Hyperswitch-React-Demo-App/src/App.tsx diff --git a/Hyperswitch-React-Demo-App/src/Cart.js b/Hyperswitch-React-Demo-App/src/Cart.tsx similarity index 92% rename from Hyperswitch-React-Demo-App/src/Cart.js rename to Hyperswitch-React-Demo-App/src/Cart.tsx index c5aab8193..e6ea0e886 100644 --- a/Hyperswitch-React-Demo-App/src/Cart.js +++ b/Hyperswitch-React-Demo-App/src/Cart.tsx @@ -3,7 +3,17 @@ import logo from "../public/assets/hyperswitchLogo.svg"; import shirt from "../public/assets/shirt.png"; import cap from "../public/assets/cap.png"; -const cartItems = [ +interface CartItem { + id: number; + name: string; + price: number; + image: string; + size: string; + qty: number; + color: string; +} + +const cartItems: CartItem[] = [ { id: 1, name: "HS Tshirt", diff --git a/Hyperswitch-React-Demo-App/src/CheckoutForm.js b/Hyperswitch-React-Demo-App/src/CheckoutForm.tsx similarity index 91% rename from Hyperswitch-React-Demo-App/src/CheckoutForm.js rename to Hyperswitch-React-Demo-App/src/CheckoutForm.tsx index 48b57c5c8..94c223aec 100644 --- a/Hyperswitch-React-Demo-App/src/CheckoutForm.js +++ b/Hyperswitch-React-Demo-App/src/CheckoutForm.tsx @@ -20,13 +20,13 @@ export default function CheckoutForm() { const elements = useWidgets(); const [isSuccess, setIsSuccess] = useState(false); - const [message, setMessage] = useState(null); + const [message, setMessage] = useState(null); const [isProcessing, setIsProcessing] = useState(false); const clientSecret = getClientSecretFromUrl(); // Handle form submission - const handleSubmit = async (e) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); // Prevent submission if Hyper isn't ready or already processing @@ -52,8 +52,10 @@ export default function CheckoutForm() { if (status) { handlePaymentStatus(status, setMessage, setIsSuccess); } - } catch (err) { - setMessage(`Error confirming payment: ${err.message}`); + } catch (err: unknown) { + const errorMessage = + err instanceof Error ? err.message : "An unknown error occurred"; + setMessage(`Error confirming payment: ${errorMessage}`); } finally { setIsProcessing(false); } diff --git a/Hyperswitch-React-Demo-App/src/Completion.js b/Hyperswitch-React-Demo-App/src/Completion.tsx similarity index 97% rename from Hyperswitch-React-Demo-App/src/Completion.js rename to Hyperswitch-React-Demo-App/src/Completion.tsx index 160fa8c42..e7059d718 100644 --- a/Hyperswitch-React-Demo-App/src/Completion.js +++ b/Hyperswitch-React-Demo-App/src/Completion.tsx @@ -13,7 +13,7 @@ function Completion() {

Thanks for your order!

- Yayyy! You successfully made a payment with Hyperswitch. If it’s a real + Yayyy! You successfully made a payment with Hyperswitch. If it's a real store, your items would have been on their way.

diff --git a/Hyperswitch-React-Demo-App/src/Payment.js b/Hyperswitch-React-Demo-App/src/Payment.tsx similarity index 90% rename from Hyperswitch-React-Demo-App/src/Payment.js rename to Hyperswitch-React-Demo-App/src/Payment.tsx index a3404fa65..1748de1be 100644 --- a/Hyperswitch-React-Demo-App/src/Payment.js +++ b/Hyperswitch-React-Demo-App/src/Payment.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; import { HyperElements } from "@juspay-tech/react-hyper-js"; +import type { HyperInstance } from "@juspay-tech/hyper-js"; import CheckoutForm from "./CheckoutForm"; import { getQueryParam, @@ -11,10 +12,10 @@ import { } from "./utils"; function Payment() { - const [hyperPromise, setHyperPromise] = useState(null); + const [hyperPromise, setHyperPromise] = useState | null>(null); const [clientSecret, setClientSecret] = useState(""); const [paymentId, setPaymentId] = useState(""); - const [error, setError] = useState(null); + const [error, setError] = useState(null); const [isScriptLoaded, setIsScriptLoaded] = useState(false); const isCypressTestMode = getQueryParam("isCypressTestMode") === "true"; @@ -55,7 +56,7 @@ function Payment() { if (isMounted) { setClientSecret(paymentIntentData.clientSecret); if (SDK_VERSION === "v2") { - setPaymentId(paymentIntentData.paymentId); + setPaymentId(paymentIntentData.paymentId ?? ""); } setHyperPromise(Promise.resolve(hyper)); } diff --git a/Hyperswitch-React-Demo-App/src/globals.d.ts b/Hyperswitch-React-Demo-App/src/globals.d.ts new file mode 100644 index 000000000..601f6e341 --- /dev/null +++ b/Hyperswitch-React-Demo-App/src/globals.d.ts @@ -0,0 +1,98 @@ +// Webpack DefinePlugin globals (from webpack.common.js) +declare const ENDPOINT: string; +declare const SCRIPT_SRC: string; +declare const SELF_SERVER_URL: string; +declare const SDK_VERSION: string; + +// Window augmentation for dynamically loaded Hyper SDK +interface Window { + Hyper: ( + options: { publishableKey: string; profileId?: string }, + config?: { customBackendUrl?: string } + ) => any; +} + +// Asset modules +declare module "*.svg" { + const content: string; + export default content; +} +declare module "*.png" { + const content: string; + export default content; +} +declare module "*.css" {} + +// --------------------------------------------------------------------------- +// @juspay-tech/hyper-js — subset used by the demo app +// --------------------------------------------------------------------------- +declare module "@juspay-tech/hyper-js" { + export interface HyperInstance { + confirmPayment(params: any): Promise; + elements(options: any): Element; + confirmCardPayment( + clientSecret: string, + data?: object, + options?: object + ): Promise; + retrievePaymentIntent(paymentIntentId: string): Promise; + widgets(options: any): Element; + paymentRequest(options: object): object; + [key: string]: any; + } + + export interface Element { + getElement(componentName: string): any; + update(options: any): void; + fetchUpdates(): Promise; + create(componentType: string, options?: object): any; + } +} + +// --------------------------------------------------------------------------- +// @juspay-tech/react-hyper-js — subset used by the demo app +// --------------------------------------------------------------------------- +declare module "@juspay-tech/react-hyper-js" { + import type { ReactNode } from "react"; + import type { HyperInstance } from "@juspay-tech/hyper-js"; + + export interface UseHyperReturn { + clientSecret: string; + confirmPayment(params: any): Promise; + confirmCardPayment( + clientSecret: string, + data?: any, + options?: any + ): Promise; + retrievePaymentIntent(paymentIntentId: string): Promise; + paymentRequest(options: any): any; + [key: string]: any; + } + + export interface UseWidgetsReturn { + options: Record; + update(options: any): void; + getElement(componentName: string): any | null; + fetchUpdates(): Promise; + create(componentType: string, options: any): any; + } + + export function useHyper(): UseHyperReturn; + export function useWidgets(): UseWidgetsReturn; + + export function HyperElements(props: { + hyper: Promise; + options: Record; + children: ReactNode; + }): JSX.Element | null; + + export function PaymentElement(props: { + id?: string; + options?: Record; + onChange?: (data?: any) => void; + onReady?: (data?: any) => void; + onFocus?: (data?: any) => void; + onBlur?: (data?: any) => void; + onClick?: (data?: any) => void; + }): JSX.Element | null; +} diff --git a/Hyperswitch-React-Demo-App/src/index.js b/Hyperswitch-React-Demo-App/src/index.tsx similarity index 73% rename from Hyperswitch-React-Demo-App/src/index.js rename to Hyperswitch-React-Demo-App/src/index.tsx index 2c0cf6fdd..f2238b60f 100644 --- a/Hyperswitch-React-Demo-App/src/index.js +++ b/Hyperswitch-React-Demo-App/src/index.tsx @@ -3,7 +3,7 @@ import ReactDOM from "react-dom/client"; import "./index.css"; import App from "./App"; -const root = ReactDOM.createRoot(document.getElementById("app")); +const root = ReactDOM.createRoot(document.getElementById("app")!); root.render( diff --git a/Hyperswitch-React-Demo-App/src/utils.js b/Hyperswitch-React-Demo-App/src/utils.ts similarity index 69% rename from Hyperswitch-React-Demo-App/src/utils.js rename to Hyperswitch-React-Demo-App/src/utils.ts index 4641724f6..ddf2e3f1a 100644 --- a/Hyperswitch-React-Demo-App/src/utils.js +++ b/Hyperswitch-React-Demo-App/src/utils.ts @@ -1,12 +1,23 @@ +import React from "react"; + +interface PaymentStatusMessages { + [key: string]: string; +} + export const getPaymentIntentData = async ({ baseUrl, isCypressTestMode, clientSecretQueryParam, setError, -}) => { +}: { + baseUrl: string; + isCypressTestMode: boolean; + clientSecretQueryParam: string | null; + setError: React.Dispatch>; +}): Promise<{ clientSecret: string; paymentId?: string } | null> => { try { if (isCypressTestMode) { - return { clientSecret: clientSecretQueryParam }; + return { clientSecret: clientSecretQueryParam ?? "" }; } const res = await fetch(`${baseUrl}/create-intent`); @@ -20,10 +31,12 @@ export const getPaymentIntentData = async ({ } }; -export const getQueryParam = (param) => +export const getQueryParam = (param: string): string | null => new URLSearchParams(window.location.search).get(param); -export const fetchConfigAndUrls = async (baseUrl) => { +export const fetchConfigAndUrls = async ( + baseUrl: string +): Promise<{ configData: any; urlsData: any }> => { const [configRes, urlsRes] = await Promise.all([ fetch(`${baseUrl}/config`), fetch(`${baseUrl}/urls`), @@ -46,7 +59,14 @@ export const loadHyperScript = ({ profileId, isScriptLoaded, setIsScriptLoaded, -}) => { +}: { + clientUrl: string; + publishableKey: string; + customBackendUrl?: string; + profileId?: string; + isScriptLoaded: boolean; + setIsScriptLoaded: React.Dispatch>; +}): Promise => { return new Promise((resolve, reject) => { if (isScriptLoaded) return resolve(window.Hyper); @@ -74,13 +94,17 @@ export const loadHyperScript = ({ }); }; -export const getClientSecretFromUrl = () => +export const getClientSecretFromUrl = (): string | null => new URLSearchParams(window.location.search).get( "payment_intent_client_secret" ); -export const handlePaymentStatus = (status, setMessage, setIsSuccess) => { - const statusMessages = { +export const handlePaymentStatus = ( + status: string, + setMessage: React.Dispatch>, + setIsSuccess: React.Dispatch> +): void => { + const statusMessages: PaymentStatusMessages = { succeeded: "Payment successful.", processing: "Your payment is processing.", requires_payment_method: @@ -110,7 +134,7 @@ export const paymentElementOptions = { }, }; -export const hyperOptionsV1 = (clientSecret) => { +export const hyperOptionsV1 = (clientSecret: string) => { return { clientSecret, appearance: { @@ -119,7 +143,7 @@ export const hyperOptionsV1 = (clientSecret) => { }; }; -export const hyperOptionsV2 = (clientSecret, paymentId) => { +export const hyperOptionsV2 = (clientSecret: string, paymentId: string) => { return { clientSecret, paymentId, diff --git a/Hyperswitch-React-Demo-App/tsconfig.json b/Hyperswitch-React-Demo-App/tsconfig.json new file mode 100644 index 000000000..a22abb447 --- /dev/null +++ b/Hyperswitch-React-Demo-App/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "outDir": "./dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/Hyperswitch-React-Demo-App/webpack.common.js b/Hyperswitch-React-Demo-App/webpack.common.js index 7d202887e..06d3dc416 100644 --- a/Hyperswitch-React-Demo-App/webpack.common.js +++ b/Hyperswitch-React-Demo-App/webpack.common.js @@ -17,7 +17,7 @@ const sdkVersionValue = getEnvVariable("SDK_VERSION", "v1"); module.exports = (endpoint, publicPath = "auto") => { const entries = { - app: "./src/index.js", + app: "./src/index.tsx", }; return { @@ -29,7 +29,7 @@ module.exports = (endpoint, publicPath = "auto") => { }, // Add this resolve section to fix the jsx-runtime issue resolve: { - extensions: [".js", ".jsx", ".json", ".mjs"], + extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".mjs"], alias: { "react/jsx-runtime": require.resolve("react/jsx-runtime"), }, @@ -74,7 +74,7 @@ module.exports = (endpoint, publicPath = "auto") => { module: { rules: [ { - test: /\.?js$/, + test: /\.[jt]sx?$/, exclude: /node_modules/, use: { loader: "babel-loader", @@ -82,6 +82,7 @@ module.exports = (endpoint, publicPath = "auto") => { presets: [ "@babel/preset-env", ["@babel/preset-react", { runtime: "automatic" }], + "@babel/preset-typescript", ], }, }, From 81f05fba077cecc0feafadb87ed6f0e39115dc0b Mon Sep 17 00:00:00 2001 From: Arush Kapoor Date: Tue, 7 Apr 2026 16:13:58 +0530 Subject: [PATCH 2/2] =?UTF-8?q?fix(demo-app):=20improve=20type=20safety=20?= =?UTF-8?q?=E2=80=94=20replace=20`any`=20returns=20with=20HyperInstance,?= =?UTF-8?q?=20remove=20index=20signatures,=20add=20TODO=20for=20temporary?= =?UTF-8?q?=20ambient=20declarations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Hyperswitch-React-Demo-App/src/globals.d.ts | 16 ++++++++++++--- Hyperswitch-React-Demo-App/src/utils.ts | 22 +++++++++++---------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/Hyperswitch-React-Demo-App/src/globals.d.ts b/Hyperswitch-React-Demo-App/src/globals.d.ts index 601f6e341..fb719324f 100644 --- a/Hyperswitch-React-Demo-App/src/globals.d.ts +++ b/Hyperswitch-React-Demo-App/src/globals.d.ts @@ -9,9 +9,13 @@ interface Window { Hyper: ( options: { publishableKey: string; profileId?: string }, config?: { customBackendUrl?: string } - ) => any; + ) => HyperInstance; } +// Forward-declare HyperInstance at global scope so Window.Hyper can reference it. +// The full definition lives in the @juspay-tech/hyper-js ambient module below. +type HyperInstance = import("@juspay-tech/hyper-js").HyperInstance; + // Asset modules declare module "*.svg" { const content: string; @@ -25,6 +29,12 @@ declare module "*.css" {} // --------------------------------------------------------------------------- // @juspay-tech/hyper-js — subset used by the demo app +// +// TODO: These ambient module declarations are TEMPORARY. They exist because the +// published npm packages (@juspay-tech/hyper-js, @juspay-tech/react-hyper-js) +// do not yet ship .d.ts files. Once PRs juspay/hyper-js#18 and +// juspay/react-hyper-js#11 are merged and released, DELETE these declare module +// blocks — the real .d.ts files from node_modules will take over automatically. // --------------------------------------------------------------------------- declare module "@juspay-tech/hyper-js" { export interface HyperInstance { @@ -38,7 +48,6 @@ declare module "@juspay-tech/hyper-js" { retrievePaymentIntent(paymentIntentId: string): Promise; widgets(options: any): Element; paymentRequest(options: object): object; - [key: string]: any; } export interface Element { @@ -51,6 +60,8 @@ declare module "@juspay-tech/hyper-js" { // --------------------------------------------------------------------------- // @juspay-tech/react-hyper-js — subset used by the demo app +// +// TODO: Same as above — delete this block once juspay/react-hyper-js#11 ships. // --------------------------------------------------------------------------- declare module "@juspay-tech/react-hyper-js" { import type { ReactNode } from "react"; @@ -66,7 +77,6 @@ declare module "@juspay-tech/react-hyper-js" { ): Promise; retrievePaymentIntent(paymentIntentId: string): Promise; paymentRequest(options: any): any; - [key: string]: any; } export interface UseWidgetsReturn { diff --git a/Hyperswitch-React-Demo-App/src/utils.ts b/Hyperswitch-React-Demo-App/src/utils.ts index ddf2e3f1a..20681ba37 100644 --- a/Hyperswitch-React-Demo-App/src/utils.ts +++ b/Hyperswitch-React-Demo-App/src/utils.ts @@ -1,4 +1,5 @@ -import React from "react"; +import type { HyperInstance } from "@juspay-tech/hyper-js"; +import type React from "react"; interface PaymentStatusMessages { [key: string]: string; @@ -52,6 +53,10 @@ export const fetchConfigAndUrls = async ( return { configData, urlsData }; }; +// Cached singleton — avoids creating multiple Hyper instances when the merchant +// (or React strict-mode) triggers loadHyperScript more than once. +let hyperInstance: HyperInstance | null = null; + export const loadHyperScript = ({ clientUrl, publishableKey, @@ -66,9 +71,9 @@ export const loadHyperScript = ({ profileId?: string; isScriptLoaded: boolean; setIsScriptLoaded: React.Dispatch>; -}): Promise => { +}): Promise => { return new Promise((resolve, reject) => { - if (isScriptLoaded) return resolve(window.Hyper); + if (isScriptLoaded && hyperInstance) return resolve(hyperInstance); const script = document.createElement("script"); script.src = `${clientUrl}/HyperLoader.js`; @@ -76,14 +81,11 @@ export const loadHyperScript = ({ script.onload = () => { setIsScriptLoaded(true); - resolve( - window.Hyper( - { publishableKey, profileId }, - { - customBackendUrl, - } - ) + hyperInstance = window.Hyper( + { publishableKey, profileId }, + { customBackendUrl } ); + resolve(hyperInstance); }; script.onerror = () => {