diff --git a/example/.env.local b/example/.env.local index c03608d82..4a958e7e3 100644 --- a/example/.env.local +++ b/example/.env.local @@ -1 +1 @@ -AUTUMN_SECRET_KEY=am_sk_test_RCM3U5M8ptWm6AWhE68hTeVn0sACxX6JE0zpHJi3SV # Example Org \ No newline at end of file +AUTUMN_SECRET_KEY=am_sk_test_8bDtE5xZ3pLO4Pl1yQvpKc91fYAvdNTVZ2vOkR5jNe # Example Org \ No newline at end of file diff --git a/example/package-lock.json b/example/package-lock.json index b6aa61c89..4d9023737 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -12,6 +12,7 @@ "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.8", + "autumn-js": "^0.0.7", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.483.0", @@ -48,6 +49,134 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@better-auth/utils": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.2.4.tgz", + "integrity": "sha512-ayiX87Xd5sCHEplAdeMgwkA0FgnXsEZBgDn890XHHwSWNqqRZDYOq3uj2Ei2leTv1I2KbG5HHn60Ah1i2JWZjQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "typescript": "^5.8.2", + "uncrypto": "^0.1.3" + } + }, + "node_modules/@better-fetch/fetch": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.18.tgz", + "integrity": "sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==", + "peer": true + }, + "node_modules/@clerk/backend": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-1.29.2.tgz", + "integrity": "sha512-mBvGyHjNj/t2X2y0Dn1V1EgsffhgNH1TJhB98k40dHxVxvosY9QdgCDMtru/Q1uZ7EXNOzYqzcB5FzAKCxFRZw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@clerk/shared": "^3.7.3", + "@clerk/types": "^4.55.0", + "cookie": "1.0.2", + "snakecase-keys": "8.0.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "svix": "^1.62.0" + }, + "peerDependenciesMeta": { + "svix": { + "optional": true + } + } + }, + "node_modules/@clerk/clerk-react": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@clerk/clerk-react/-/clerk-react-5.29.0.tgz", + "integrity": "sha512-S8wenyl5R/8YmUT1tElQZdmHe/MASM9JDJRKqhy0kVYXdKzjhuwTBgT+m3hXl3zdq/Gwi3hgv7pRnQWpQisutQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@clerk/shared": "^3.7.3", + "@clerk/types": "^4.55.0", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" + } + }, + "node_modules/@clerk/nextjs": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@clerk/nextjs/-/nextjs-6.17.0.tgz", + "integrity": "sha512-ik+UU/cKZgC5pafJJ9oct0BUHvytUqof+r1RjFb71xU1TM7EOeL5XYafVxh+hlgwsE4jmrw+FVYe4DXDcyPeTQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@clerk/backend": "^1.29.2", + "@clerk/clerk-react": "^5.29.0", + "@clerk/shared": "^3.7.3", + "@clerk/types": "^4.55.0", + "server-only": "0.0.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "next": "^13.5.7 || ^14.2.25 || ^15.2.3", + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" + } + }, + "node_modules/@clerk/shared": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-3.7.3.tgz", + "integrity": "sha512-IMpz1bgZZzlxNLyFliE0GrA3NT1Wgidl2CBORkE2S/YUSCH4ge8EPg5tfQq9Ja6d17TfarkzEafrm1b/BHxSWg==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@clerk/types": "^4.55.0", + "dequal": "2.0.3", + "glob-to-regexp": "0.4.1", + "js-cookie": "3.0.5", + "std-env": "^3.8.1", + "swr": "^2.3.3" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@clerk/types": { + "version": "4.55.0", + "resolved": "https://registry.npmjs.org/@clerk/types/-/types-4.55.0.tgz", + "integrity": "sha512-MJc/zo9vJOq1mY37RbRjrZ883AKe9gitlYFyVB3R+ChKm0uBl2Mm8R2gkYy1GGKYvqBm27GJooWHxdcM1Yv1eQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "3.1.3" + }, + "engines": { + "node": ">=18.17.0" + } + }, "node_modules/@emnapi/core": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.3.1.tgz", @@ -257,6 +386,13 @@ "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", "license": "MIT" }, + "node_modules/@hexagon/base64": { + "version": "1.1.28", + "resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz", + "integrity": "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==", + "license": "MIT", + "peer": true + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -684,6 +820,13 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@levischuck/tiny-cbor": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@levischuck/tiny-cbor/-/tiny-cbor-0.2.11.tgz", + "integrity": "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==", + "license": "MIT", + "peer": true + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.7.tgz", @@ -841,6 +984,29 @@ "node": ">= 10" } }, + "node_modules/@noble/ciphers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.6.0.tgz", + "integrity": "sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -889,6 +1055,69 @@ "node": ">=12.4.0" } }, + "node_modules/@peculiar/asn1-android": { + "version": "2.3.16", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-android/-/asn1-android-2.3.16.tgz", + "integrity": "sha512-a1viIv3bIahXNssrOIkXZIlI2ePpZaNmR30d4aBL99mu2rO+mT9D6zBsp7H6eROWGtmwv0Ionp5olJurIo09dw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.15", + "asn1js": "^3.0.5", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.3.15.tgz", + "integrity": "sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.15", + "@peculiar/asn1-x509": "^2.3.15", + "asn1js": "^3.0.5", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.3.15.tgz", + "integrity": "sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.15", + "@peculiar/asn1-x509": "^2.3.15", + "asn1js": "^3.0.5", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.15.tgz", + "integrity": "sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==", + "license": "MIT", + "peer": true, + "dependencies": { + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.3.15.tgz", + "integrity": "sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.15", + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, "node_modules/@radix-ui/primitive": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", @@ -1392,6 +1621,32 @@ "dev": true, "license": "MIT" }, + "node_modules/@simplewebauthn/browser": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.1.0.tgz", + "integrity": "sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg==", + "license": "MIT", + "peer": true + }, + "node_modules/@simplewebauthn/server": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-13.1.1.tgz", + "integrity": "sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@hexagon/base64": "^1.1.27", + "@levischuck/tiny-cbor": "^0.2.2", + "@peculiar/asn1-android": "^2.3.10", + "@peculiar/asn1-ecc": "^2.3.8", + "@peculiar/asn1-rsa": "^2.3.8", + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/asn1-x509": "^2.3.8" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -2350,6 +2605,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asn1js": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.6.tgz", + "integrity": "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -2367,6 +2637,18 @@ "node": ">= 0.4" } }, + "node_modules/autumn-js": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/autumn-js/-/autumn-js-0.0.7.tgz", + "integrity": "sha512-sk+i5aMzB1xPi3N/4PD00k1cS9FlR/0xkZ2mqOyksCD23CcaczmRir45o6zzhNFQ2Q1K1icUwRyljwlW3RHWTQ==", + "license": "MIT", + "peerDependencies": { + "@clerk/nextjs": "^6.16.0", + "better-auth": "^1.2.7", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -2410,6 +2692,38 @@ "dev": true, "license": "MIT" }, + "node_modules/better-auth": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.2.7.tgz", + "integrity": "sha512-2hCB263GSrgetsMUZw8vv9O1e4S4AlYJW3P4e8bX9u3Q3idv4u9BzDFCblpTLuL4YjYovghMCN0vurAsctXOAQ==", + "peer": true, + "dependencies": { + "@better-auth/utils": "0.2.4", + "@better-fetch/fetch": "^1.1.18", + "@noble/ciphers": "^0.6.0", + "@noble/hashes": "^1.6.1", + "@simplewebauthn/browser": "^13.0.0", + "@simplewebauthn/server": "^13.0.0", + "better-call": "^1.0.8", + "defu": "^6.1.4", + "jose": "^5.9.6", + "kysely": "^0.27.6", + "nanostores": "^0.11.3", + "zod": "^3.24.1" + } + }, + "node_modules/better-call": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.0.8.tgz", + "integrity": "sha512-/PV8JLqDRUN7JyBPbklVsS/8E4SO3pnf8hbpa8B7xrBrr+BBYpeOAxoqtnsyk/pRs35vNB4MZx8cn9dBuNlLDA==", + "peer": true, + "dependencies": { + "@better-fetch/fetch": "^1.1.4", + "rou3": "^0.5.1", + "set-cookie-parser": "^2.7.1", + "uncrypto": "^0.1.3" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2621,6 +2935,16 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2640,7 +2964,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -2765,6 +3088,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT", + "peer": true + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -2794,6 +3134,17 @@ "node": ">=0.10.0" } }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "peer": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3717,6 +4068,13 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause", + "peer": true + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -4357,6 +4715,26 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4436,6 +4814,16 @@ "json-buffer": "3.0.1" } }, + "node_modules/kysely": { + "version": "0.27.6", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.27.6.tgz", + "integrity": "sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -4744,6 +5132,16 @@ "loose-envify": "cli.js" } }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lucide-react": { "version": "0.483.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.483.0.tgz", @@ -4753,6 +5151,19 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4835,6 +5246,22 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/nanostores": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-0.11.4.tgz", + "integrity": "sha512-k1oiVNN4hDK8NcNERSZLQiMfRzEGtfnvZvdBvey3SQbgn8Dcrk0h1I6vpxApjb10PFUflZrgJ2WEZyJQ+5v7YQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4934,6 +5361,17 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "peer": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5255,6 +5693,26 @@ "node": ">=6" } }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5473,6 +5931,13 @@ "node": ">=0.10.0" } }, + "node_modules/rou3": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.5.1.tgz", + "integrity": "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==", + "license": "MIT", + "peer": true + }, "node_modules/rspack-resolver": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/rspack-resolver/-/rspack-resolver-1.2.2.tgz", @@ -5597,6 +6062,20 @@ "node": ">=10" } }, + "node_modules/server-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", + "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", + "license": "MIT", + "peer": true + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT", + "peer": true + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -5795,6 +6274,32 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", + "peer": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/snakecase-keys": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-8.0.1.tgz", + "integrity": "sha512-Sj51kE1zC7zh6TDlNNz0/Jn1n5HiHdoQErxO8jLtnyrkJW/M5PrI7x05uDgY3BO7OUQYKCvmeMurW6BPUdwEOw==", + "license": "MIT", + "peer": true, + "dependencies": { + "map-obj": "^4.1.0", + "snake-case": "^3.0.4", + "type-fest": "^4.15.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/sonner": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.1.tgz", @@ -5821,6 +6326,13 @@ "dev": true, "license": "MIT" }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "license": "MIT", + "peer": true + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -6014,6 +6526,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.3.tgz", + "integrity": "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==", + "license": "MIT", + "peer": true, + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/tailwind-merge": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz", @@ -6153,6 +6679,19 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz", + "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==", + "license": "(MIT OR CC0-1.0)", + "peer": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -6235,7 +6774,6 @@ "version": "5.8.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -6264,6 +6802,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT", + "peer": true + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -6324,6 +6869,16 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6451,6 +7006,16 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.24.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz", + "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/example/package.json b/example/package.json index c9db7e2f8..3e7e27537 100644 --- a/example/package.json +++ b/example/package.json @@ -13,6 +13,7 @@ "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.8", + "autumn-js": "^0.0.7", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.483.0", diff --git a/example/src/app/autumn-functions.tsx b/example/src/app/autumn-functions.tsx deleted file mode 100644 index d55f9e2d5..000000000 --- a/example/src/app/autumn-functions.tsx +++ /dev/null @@ -1,104 +0,0 @@ -"use server"; - -import { Autumn } from "@/sdk/autumn"; - -// ONLY 3 ENDPOINTS NEEDED TO GET STARTED - -// Entitled: Check if your user should be allowed to use a feature -export const entitled = async ({ - customerId, - featureId, -}: { - customerId: string; - featureId: string; -}) => { - const response = await fetch(`https://api.useautumn.com/v1/entitled`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${process.env.AUTUMN_SECRET_KEY}`, - }, - body: JSON.stringify({ - customer_id: customerId, - feature_id: featureId, - }), - }); - - const data = await response.json(); - return data.allowed; -}; - -//Events: send a usage event to Autumn to track a user's usage of a feature -export const sendEvent = async ({ - customerId, - featureId, -}: { - customerId: string; - featureId: string; -}) => { - await fetch("https://api.useautumn.com/v1/events", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${process.env.AUTUMN_SECRET_KEY}`, - }, - body: JSON.stringify({ - customer_id: customerId, - feature_id: featureId, - }), - }); -}; - -//Attach: get a checkout URL from Autumn so the user can upgrade -export const attachProduct = async ({ - customerId, - productId, -}: { - customerId: string; - productId: string; -}) => { - const response = await fetch(`https://api.useautumn.com/v1/attach`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${process.env.AUTUMN_SECRET_KEY}`, - }, - body: JSON.stringify({ - customer_id: customerId, - product_id: productId, - force_checkout: true, - }), - }); - - const data = await response.json(); - if (response.status !== 200) { - throw new Error(data.message || "Failed to attach pro product"); - } - return data; -}; - -// ADDITIONAL ENDPOINT TO FETCH CUSTOMER DETAILS - -export const getOrCreateCustomer = async (customerId: string) => { - const response = await fetch(`https://api.useautumn.com/v1/customers`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${process.env.AUTUMN_SECRET_KEY}`, - }, - body: JSON.stringify({ - id: customerId, - }), - }); - - const data = await response.json(); - return data; -}; - -export const getCustomer = async (customerId: string) => { - const autumn = new Autumn(); - - const data = await autumn.customers.get(customerId); - const { entitlements, invoices } = data; - return data; -}; diff --git a/example/src/app/demo.tsx b/example/src/app/demo.tsx new file mode 100644 index 000000000..88676dd3a --- /dev/null +++ b/example/src/app/demo.tsx @@ -0,0 +1,27 @@ +//
{customer?.name}
+// + +// import Application from "@/components/application"; +// import CustomerDetailsExample from "@/components/billing"; +// import Intro from "@/components/introduction"; + +// export default function Home() { +// return ( +//
+// + +//
+// +// +//
+//
+// ); +// } diff --git a/example/src/app/layout.tsx b/example/src/app/layout.tsx index 7ffcbed4e..809968ebf 100644 --- a/example/src/app/layout.tsx +++ b/example/src/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import { Toaster } from "@/components/ui/sonner"; +import { AutumnProvider } from "autumn-js/next"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -25,12 +26,20 @@ export default function RootLayout({ }>) { return ( - - - {children} - + + + {children} + + ); } diff --git a/example/src/app/page.tsx b/example/src/app/page.tsx index a122c8032..7220729d3 100644 --- a/example/src/app/page.tsx +++ b/example/src/app/page.tsx @@ -1,40 +1,21 @@ "use client"; -// import { getOrCreateCustomer } from "./autumn-functions"; -// import { useState } from "react"; -// import { useEffect } from "react"; -import Intro from "@/components/introduction"; - -// import { Autumn } from "useautumn"; -// import CustomerDetailsExample from "@/components/billing"; -// import Application from "@/components/application"; - -// Replace this with your internal user ID +import { useAutumn } from "autumn-js/next"; export default function Home() { - // const [customerData, setCustomerData] = useState(null); - - // const fetchCustomer = async () => { - // const customer = await getOrCreateCustomer(CUSTOMER_ID); - // setCustomerData(customer); - // }; - - // useEffect(() => { - // fetchCustomer(); - // }, []); - - // if (!customerData) { - // return <>; - // } - + const { customer, attach } = useAutumn(); return ( -
- - - {/*
- - -
*/} +
+
{customer?.name}
+
); } diff --git a/example/src/components/application.tsx b/example/src/components/application.tsx index 2fcde1f02..551c45494 100644 --- a/example/src/components/application.tsx +++ b/example/src/components/application.tsx @@ -1,19 +1,12 @@ -import { sendEvent } from "@/app/autumn-functions"; -import { entitled } from "@/app/autumn-functions"; +import { useAutumn } from "autumn-js/next"; import { MessageSquare } from "lucide-react"; import { toast } from "sonner"; -export default function Application({ - customerId, - fetchCustomer, -}: { - customerId: string; - fetchCustomer: () => void; -}) { +export default function Application() { + const { entitled, sendEvent, refetch } = useAutumn(); const sendMessageClicked = async (featureId: string) => { - const allowed = await entitled({ - customerId, + const { allowed } = await entitled({ featureId, }); @@ -23,10 +16,11 @@ export default function Application({ } await sendEvent({ - customerId, featureId, }); + await refetch(); + toast.success(`${featureId} used!`); }; @@ -61,21 +55,10 @@ export default function Application({ - -
diff --git a/example/src/components/billing.tsx b/example/src/components/billing.tsx index 519b037bc..c9f6d3c88 100644 --- a/example/src/components/billing.tsx +++ b/example/src/components/billing.tsx @@ -1,49 +1,40 @@ "use client"; -import { attachProduct } from "@/app/autumn-functions"; import { CreditCard } from "lucide-react"; import { toast } from "sonner"; +import { useAutumn } from "autumn-js/next"; +export default function CustomerDetailsExample() { + const { customer, attach, openBillingPortal } = useAutumn(); + const productId = "pro-example"; -export default function CustomerDetailsExample({ - customerData, -}: { - customerData: any; -}) { - const { customer, entitlements, products } = customerData; - const getEntitlement = (featureId: string) => { - return entitlements.find( + return customer?.features.find( (entitlement: any) => entitlement.feature_id === featureId ); }; const upgradeClicked = async () => { try { - const res = await attachProduct({ - customerId: customer.id, - productId: "pro", + await attach({ + productId, }); - window.open(res.checkout_url, "_blank"); } catch (error: any) { - toast.error(`${error}`); + toast.error(`${error.message}`); } }; - const buyExtraCreditsClicked = async () => { + const manageBillingClicked = async () => { try { - const res = await attachProduct({ - customerId: customer.id, - productId: "extra-credits", - }); - window.open(res.checkout_url, "_blank"); + await openBillingPortal(); } catch (error: any) { - toast.error(`${error}`); + toast.error(`${error.message}`); } }; - const messageCredits = getEntitlement("message-credits"); - const premiumCredits = getEntitlement("premium-credits"); - const hasPro = products.length > 0 && products[0].id === "pro"; + const messageCredits = getEntitlement("chat-messages"); + + const hasPro = + customer?.products?.length && customer?.products[0].id === productId; return (
@@ -66,21 +57,16 @@ export default function CustomerDetailsExample({
Customer ID - {customer.id} + {customer?.id}
- Standard Messages Remaining + Chat Messages Remaining - {messageCredits?.balance || 0} - -
- -
- Premium Messages Remaining - - {premiumCredits?.balance || 0} + {messageCredits?.unlimited + ? "Unlimited" + : messageCredits?.balance || 0}
@@ -101,24 +87,16 @@ export default function CustomerDetailsExample({
-
{!hasPro && ( - )} -
-
- -
+
); diff --git a/example/src/sdk/autumn.ts b/example/src/sdk/autumn.ts deleted file mode 100644 index 3fc290587..000000000 --- a/example/src/sdk/autumn.ts +++ /dev/null @@ -1,361 +0,0 @@ -import { CreateRewardProgram, ErrCode } from "@autumn/shared"; - -export default class AutumnError extends Error { - message: string; - code: string; - - constructor({ message, code }: { message: string; code: string }) { - super(message); - this.message = message; - this.code = code; - } - - toString(): string { - return `${this.message} (code: ${this.code})`; - } -} - -export class Autumn { - private apiKey: string; - public headers: Record; - public baseUrl: string; - - constructor(apiKey?: string, baseUrl?: string) { - this.apiKey = apiKey || process.env.AUTUMN_SECRET_KEY || ""; - - this.headers = { - Authorization: `Bearer ${this.apiKey}`, - "Content-Type": "application/json", - }; - this.baseUrl = "https://api.useautumn.com/v1"; - // this.baseUrl = baseUrl || "http://localhost:8080/v1"; - } - - async get(path: string) { - const response = await fetch(`${this.baseUrl}${path}`, { - headers: this.headers, - }); - return response.json(); - } - - async post(path: string, body: any) { - const response = await fetch(`${this.baseUrl}${path}`, { - method: "POST", - headers: this.headers, - body: JSON.stringify(body), - }); - - if (response.status != 200) { - let error: any; - try { - error = await response.json(); - } catch (error) { - throw new AutumnError({ - message: "Failed to parse Autumn API error response", - code: ErrCode.InternalError, - }); - } - - throw new AutumnError({ - message: error.message, - code: error.code, - }); - } - - return response.json(); - } - - async delete( - path: string, - { - deleteInStripe = false, - }: { - deleteInStripe?: boolean; - } = {} - ) { - const response = await fetch( - `${this.baseUrl}${path}?${deleteInStripe ? "delete_in_stripe=true" : ""}`, - { - method: "DELETE", - headers: this.headers, - } - ); - - if (response.status != 200) { - let error: any; - try { - error = await response.json(); - } catch (error) { - throw new AutumnError({ - message: "Failed to parse Autumn API error response", - code: ErrCode.InternalError, - }); - } - - throw new AutumnError({ - message: error.message, - code: error.code, - }); - } - - return response.json(); - } - - async createCustomer({ - id, - email, - name, - fingerprint, - }: { - id: string; - email: string; - name: string; - fingerprint?: string; - }) { - const data = await this.post("/customers", { - id, - email, - name, - fingerprint, - }); - - return data; - } - - async attach({ - customerId, - productId, - options, - }: { - customerId: string; - productId: string; - options?: any; - }) { - const data = await this.post(`/attach`, { - customer_id: customerId, - product_id: productId, - options, - }); - - return data; - } - - async sendEvent({ - customerId, - eventName, - properties, - customer_data, - idempotency_key, - }: { - customerId: string; - eventName: string; - properties?: any; - customer_data?: any; - idempotency_key?: string; - }) { - const data = await this.post(`/events`, { - customer_id: customerId, - event_name: eventName, - properties, - customer_data, - idempotency_key, - }); - - return data; - } - - async entitled({ - customerId, - featureId, - quantity, - customer_data, - }: { - customerId: string; - featureId: string; - quantity?: number; - customer_data?: any; - }) { - const data = await this.post(`/entitled`, { - customer_id: customerId, - feature_id: featureId, - quantity, - customer_data, - }); - - return data; - } - - customers = { - get: async (customerId: string) => { - const data = await this.get(`/customers/${customerId}`); - return data; - }, - - create: async (customer: { id: string; email: string; name: string }) => { - const data = await this.post(`/customers`, customer); - return data; - }, - delete: async ( - customerId: string, - { - deleteInStripe = false, - }: { - deleteInStripe?: boolean; - } = {} - ) => { - const data = await this.delete(`/customers/${customerId}`, { - deleteInStripe, - }); - return data; - }, - }; - - entities = { - create: async ( - customerId: string, - entity: - | { - id: string; - name: string; - featureId: string; - } - | { - id: string; - name: string; - featureId: string; - }[] - ) => { - let entities = Array.isArray(entity) ? entity : [entity]; - const data = await this.post( - `/customers/${customerId}/entities`, - entities.map((e: any) => { - return { - id: e.id, - name: e.name, - feature_id: e.featureId, - }; - }) - ); - - return data; - }, - - list: async (customerId: string) => { - const data = await this.get(`/customers/${customerId}/entities`); - return data; - }, - - delete: async (customerId: string, entityId: string) => { - const data = await this.delete( - `/customers/${customerId}/entities/${entityId}` - ); - return data; - }, - }; - - products = { - update: async (productId: string, product: any) => { - if (product.items && typeof product.items === "object") { - product.items = Object.values(product.items); - } - const data = await this.post(`/products/${productId}`, product); - return data; - }, - - get: async ( - productId: string, - { v1Schema = false }: { v1Schema?: boolean } = {} - ) => { - const data = await this.get( - `/products/${productId}?${v1Schema ? "schemaVersion=1" : ""}` - ); - return data; - }, - - create: async (product: any) => { - const data = await this.post(`/products`, product); - return data; - }, - - delete: async (productId: string) => { - const data = await this.delete(`/products/${productId}`); - return data; - }, - }; - - rewards = { - create: async (reward: any) => { - const data = await this.post(`/rewards`, reward); - return data; - }, - }; - - rewardPrograms = { - create: async (rewardProgram: CreateRewardProgram) => { - const data = await this.post(`/reward_programs`, rewardProgram); - return data; - }, - }; - - referrals = { - createCode: async ({ - customerId, - referralId, - }: { - customerId: string; - referralId: string; - }) => { - const data = await this.post(`/referrals/code`, { - customer_id: customerId, - program_id: referralId, - }); - return data; - }, - redeem: async ({ - customerId, - code, - }: { - customerId: string; - code: string; - }) => { - const data = await this.post(`/referrals/redeem`, { - customer_id: customerId, - code, - }); - return data; - }, - }; - - redemptions = { - get: async ({ redemptionId }: { redemptionId: string }) => { - const data = await this.get(`/redemptions/${redemptionId}`); - return data; - }, - }; - - events = { - send: async ({ - customerId, - featureId, - value, - properties, - }: { - customerId: string; - featureId: string; - value: number; - properties?: any; - }) => { - const data = await this.post(`/events`, { - customer_id: customerId, - feature_id: featureId, - value, - properties, - }); - return data; - }, - }; - - initStripe = async () => { - await this.post(`/products/all/init_stripe`, {}); - }; -} diff --git a/server/src/external/webhooks/clerkWebhooks.ts b/server/src/external/webhooks/clerkWebhooks.ts index ac5026c3a..f2bd3532f 100644 --- a/server/src/external/webhooks/clerkWebhooks.ts +++ b/server/src/external/webhooks/clerkWebhooks.ts @@ -258,16 +258,32 @@ export const handleClerkWebhook = async (req: any, res: any) => { }); }; -const handleOrgCreated = async (sb: SupabaseClient, eventData: any) => { +export const handleOrgCreated = async ( + sb: SupabaseClient, + eventData: { + id: string; + slug: string; + created_at: number; + } +) => { console.log( `Handling organization.created: ${eventData.slug} (${eventData.id})` ); + try { - // 1. Create svix webhoooks - const { sandboxApp, liveApp } = await initOrgSvixApps({ - slug: eventData.slug, - id: eventData.id, - }); + // // Check if org already exists + // let org = await OrgService.getFullOrg({ + // sb, + // orgId: eventData.id, + // errorIfNotFound: false, + // }); + + // if (org) { + // console.log( + // `handleOrgCreated: Org already exists: ${eventData.slug} (${eventData.id})` + // ); + // return; + // } // 2. Insert org await OrgService.insert({ @@ -282,16 +298,39 @@ const handleOrgCreated = async (sb: SupabaseClient, eventData: any) => { live_pkey: generatePublishableKey(AppEnv.Live), created_at: eventData.created_at, svix_config: { - sandbox_app_id: sandboxApp.id, - live_app_id: liveApp.id, + sandbox_app_id: "", + live_app_id: "", }, config: {} as any, }, }); console.log(`Inserted org ${eventData.id}`); - } catch (error) { + + // 1. Create svix webhoooks + const { sandboxApp, liveApp } = await initOrgSvixApps({ + slug: eventData.slug, + id: eventData.id, + }); + + await OrgService.update({ + sb, + orgId: eventData.id, + updates: { + svix_config: { sandbox_app_id: sandboxApp.id, live_app_id: liveApp.id }, + }, + }); + + console.log(`Created svix webhooks for org ${eventData.id}`); + } catch (error: any) { + if (error?.data && error.data.code == "23505") { + console.error( + `Org ${eventData.id} already exists in Supabase -- skipping creationg` + ); + return; + } console.error("Failed to insert org", error); + return; } const batch = []; diff --git a/server/src/internal/api/apiRouter.ts b/server/src/internal/api/apiRouter.ts index d439ad0b4..e31cdff26 100644 --- a/server/src/internal/api/apiRouter.ts +++ b/server/src/internal/api/apiRouter.ts @@ -53,6 +53,7 @@ apiRouter.use((req: any, res: any, next: any) => { let skipUrls = ["/v1/customers/all/search"]; res.on("finish", () => { try { + req.logtail.flush(); if (skipUrls.includes(req.originalUrl)) { return; } diff --git a/server/src/internal/mainRouter.ts b/server/src/internal/mainRouter.ts index c98c5beab..787c6a8ed 100644 --- a/server/src/internal/mainRouter.ts +++ b/server/src/internal/mainRouter.ts @@ -8,6 +8,12 @@ import { productRouter } from "./products/internalProductRouter.js"; import { devRouter } from "./dev/devRouter.js"; import { cusRouter } from "./customers/internalCusRouter.js"; import { testRouter } from "./test/testRouter.js"; +import { createClerkCli } from "../external/clerkUtils.js"; +import { ErrCode } from "@autumn/shared"; +import RecaseError, { handleRequestError } from "@/utils/errorUtils.js"; +import { STATUS_CODES } from "http"; +import { StatusCodes } from "http-status-codes"; +import { handleOrgCreated } from "@/external/webhooks/clerkWebhooks.js"; const mainRouter = Router(); @@ -15,6 +21,51 @@ mainRouter.get("", (req: any, res) => { res.status(200).json({ message: "Hello World" }); }); +mainRouter.post("/organization", withAuth, async (req: any, res) => { + try { + let { orgId } = req.body; + + let userId = req.userId; + let clerk = createClerkCli(); + + let org = await clerk.organizations.getOrganization({ + organizationId: orgId, + }); + + if (!org) { + throw new RecaseError({ + message: "Organization not found", + code: ErrCode.OrgNotFound, + statusCode: StatusCodes.NOT_FOUND, + }); + } + + const memberships = await clerk.users.getOrganizationMembershipList({ + userId: userId, + }); + + let userIsAdmin = memberships.data.some((m) => m.organization.id === orgId); + + if (!userIsAdmin) { + throw new RecaseError({ + message: "User is not an admin of this organization", + code: ErrCode.OrgNotFound, + statusCode: StatusCodes.NOT_FOUND, + }); + } + + handleOrgCreated(req.sb, { + id: orgId, + slug: org.slug || "", + created_at: Date.now(), + }); + + res.status(200).json({ message: "Success" }); + } catch (error: any) { + handleRequestError({ error, req, res, action: "create org" }); + } +}); + mainRouter.use("/users", withAuth, userRouter); mainRouter.use("/organization", withOrgAuth, orgRouter); mainRouter.use("/features", withOrgAuth, featureRouter); diff --git a/server/src/internal/orgs/orgRouter.ts b/server/src/internal/orgs/orgRouter.ts index 2d37f1dba..e3ac0439d 100644 --- a/server/src/internal/orgs/orgRouter.ts +++ b/server/src/internal/orgs/orgRouter.ts @@ -12,6 +12,7 @@ import { OrgService } from "./OrgService.js"; import { createStripeCli } from "@/external/stripe/utils.js"; import { AppEnv } from "@autumn/shared"; import { nullish } from "@/utils/genUtils.js"; +import { handleOrgCreated } from "@/external/webhooks/clerkWebhooks.js"; export const orgRouter = express.Router(); diff --git a/vite/src/views/onboarding/onboarding-steps/01_CreateOrg.tsx b/vite/src/views/onboarding/onboarding-steps/01_CreateOrg.tsx index 63e47e230..62a75a85d 100644 --- a/vite/src/views/onboarding/onboarding-steps/01_CreateOrg.tsx +++ b/vite/src/views/onboarding/onboarding-steps/01_CreateOrg.tsx @@ -1,6 +1,8 @@ import Step from "@/components/general/OnboardingStep"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { useAxiosInstance } from "@/services/useAxiosInstance"; +import { useEnv } from "@/utils/envUtils"; import { useOrganization, useOrganizationList } from "@clerk/clerk-react"; import { Building } from "lucide-react"; import { useState } from "react"; @@ -15,6 +17,8 @@ export const CreateOrgStep = ({ pollForOrg: () => Promise; }) => { const { organization: org } = useOrganization(); + let env = useEnv(); + let axios = useAxiosInstance({ env }); const { createOrganization, setActive } = useOrganizationList(); const [isExploding, setIsExploding] = useState(false); @@ -37,6 +41,11 @@ export const CreateOrgStep = ({ name: fields.name, }); + // Create org in Autumn + await axios.post("/organization", { + orgId: org.id, + }); + await setActive({ organization: org.id }); // await pollForOrg(); toast.success(`Created your organization: ${org.name}`);