From 1e28d7b1d3408f5ce377238e1e615becfdcb2ed8 Mon Sep 17 00:00:00 2001 From: Marek Miklaszewski Date: Mon, 24 Apr 2023 14:23:28 +0200 Subject: [PATCH 1/8] [RAB-4] Configure SVGR, axios. Create Checkmark, Crossmark --- apps/next-app/next.config.js | 27 + apps/next-app/package.json | 2 + apps/next-app/src/assets/check-mark.svg | 12 + apps/next-app/src/assets/cross-mark.svg | 12 + .../Checkmark/checkmark.component.tsx | 7 + .../src/shared/components/Checkmark/index.ts | 1 + .../Crossmark/crossmark.component.tsx | 7 + .../src/shared/components/Crossmark/index.ts | 1 + .../src/shared/services/axios/index.ts | 5 + pnpm-lock.yaml | 1062 ++++++++++++++++- 10 files changed, 1124 insertions(+), 12 deletions(-) create mode 100644 apps/next-app/src/assets/check-mark.svg create mode 100644 apps/next-app/src/assets/cross-mark.svg create mode 100644 apps/next-app/src/shared/components/Checkmark/checkmark.component.tsx create mode 100644 apps/next-app/src/shared/components/Checkmark/index.ts create mode 100644 apps/next-app/src/shared/components/Crossmark/crossmark.component.tsx create mode 100644 apps/next-app/src/shared/components/Crossmark/index.ts create mode 100644 apps/next-app/src/shared/services/axios/index.ts diff --git a/apps/next-app/next.config.js b/apps/next-app/next.config.js index eae2851..c41ec80 100644 --- a/apps/next-app/next.config.js +++ b/apps/next-app/next.config.js @@ -1,6 +1,33 @@ /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, + webpack(config) { + // Grab the existing rule that handles SVG imports + const fileLoaderRule = config.module.rules.find((rule) => + rule.test?.test?.('.svg') + ); + + config.module.rules.push( + // Reapply the existing rule, but only for svg imports ending in ?url + { + ...fileLoaderRule, + test: /\.svg$/i, + resourceQuery: /url/, // *.svg?url + }, + // Convert all other *.svg imports to React components + { + test: /\.svg$/i, + issuer: /\.[jt]sx?$/, + resourceQuery: { not: /url/ }, // exclude if *.svg?url + use: ['@svgr/webpack'], + } + ); + + // Modify the file loader rule to ignore *.svg, since we have it handled now. + fileLoaderRule.exclude = /\.svg$/i; + + return config; + }, images: { remotePatterns: [ { diff --git a/apps/next-app/package.json b/apps/next-app/package.json index 8f2d9ba..a04abc9 100644 --- a/apps/next-app/package.json +++ b/apps/next-app/package.json @@ -25,6 +25,7 @@ "@types/react": "18.0.37", "@types/react-dom": "18.0.11", "autoprefixer": "10.4.14", + "axios": "^1.3.6", "eslint": "8.38.0", "eslint-config-next": "13.3.0", "graphql": "^16.6.0", @@ -40,6 +41,7 @@ "devDependencies": { "@graphql-codegen/cli": "^3.3.0", "@graphql-codegen/client-preset": "^3.0.0", + "@svgr/webpack": "^7.0.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", "@types/jest": "^29.5.0", diff --git a/apps/next-app/src/assets/check-mark.svg b/apps/next-app/src/assets/check-mark.svg new file mode 100644 index 0000000..cb11675 --- /dev/null +++ b/apps/next-app/src/assets/check-mark.svg @@ -0,0 +1,12 @@ + + + \ No newline at end of file diff --git a/apps/next-app/src/assets/cross-mark.svg b/apps/next-app/src/assets/cross-mark.svg new file mode 100644 index 0000000..73713a7 --- /dev/null +++ b/apps/next-app/src/assets/cross-mark.svg @@ -0,0 +1,12 @@ + + + \ No newline at end of file diff --git a/apps/next-app/src/shared/components/Checkmark/checkmark.component.tsx b/apps/next-app/src/shared/components/Checkmark/checkmark.component.tsx new file mode 100644 index 0000000..9023755 --- /dev/null +++ b/apps/next-app/src/shared/components/Checkmark/checkmark.component.tsx @@ -0,0 +1,7 @@ +import CheckmarkSVG from 'assets/check-mark.svg'; + +export const Checkmark = () => { + return ( + + ); +}; diff --git a/apps/next-app/src/shared/components/Checkmark/index.ts b/apps/next-app/src/shared/components/Checkmark/index.ts new file mode 100644 index 0000000..dde5ad8 --- /dev/null +++ b/apps/next-app/src/shared/components/Checkmark/index.ts @@ -0,0 +1 @@ +export { Checkmark } from './checkmark.component'; diff --git a/apps/next-app/src/shared/components/Crossmark/crossmark.component.tsx b/apps/next-app/src/shared/components/Crossmark/crossmark.component.tsx new file mode 100644 index 0000000..de07503 --- /dev/null +++ b/apps/next-app/src/shared/components/Crossmark/crossmark.component.tsx @@ -0,0 +1,7 @@ +import CrossmarkSVG from 'assets/cross-mark.svg'; + +export const Crossmark = () => { + return ( + + ); +}; diff --git a/apps/next-app/src/shared/components/Crossmark/index.ts b/apps/next-app/src/shared/components/Crossmark/index.ts new file mode 100644 index 0000000..5e49486 --- /dev/null +++ b/apps/next-app/src/shared/components/Crossmark/index.ts @@ -0,0 +1 @@ +export { Crossmark } from './crossmark.component'; diff --git a/apps/next-app/src/shared/services/axios/index.ts b/apps/next-app/src/shared/services/axios/index.ts new file mode 100644 index 0000000..c62d2b8 --- /dev/null +++ b/apps/next-app/src/shared/services/axios/index.ts @@ -0,0 +1,5 @@ +import axios from 'axios'; + +export const axiosApi = axios.create({ + baseURL: process.env.NEXT_PUBLIC_API_URL, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43083e7..995e15f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,6 +39,9 @@ importers: autoprefixer: specifier: 10.4.14 version: 10.4.14(postcss@8.4.22) + axios: + specifier: ^1.3.6 + version: 1.3.6 eslint: specifier: 8.38.0 version: 8.38.0 @@ -79,6 +82,9 @@ importers: '@graphql-codegen/client-preset': specifier: ^3.0.0 version: 3.0.0(graphql@16.6.0) + '@svgr/webpack': + specifier: ^7.0.0 + version: 7.0.0 '@testing-library/jest-dom': specifier: ^5.16.5 version: 5.16.5 @@ -242,6 +248,14 @@ packages: '@babel/types': 7.21.4 dev: true + /@babel/helper-builder-binary-assignment-operator-visitor@7.18.9: + resolution: {integrity: sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-explode-assignable-expression': 7.18.6 + '@babel/types': 7.21.4 + dev: true + /@babel/helper-compilation-targets@7.21.4(@babel/core@7.21.4): resolution: {integrity: sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==} engines: {node: '>=6.9.0'} @@ -274,10 +288,44 @@ packages: - supports-color dev: true + /@babel/helper-create-regexp-features-plugin@7.21.4(@babel/core@7.21.4): + resolution: {integrity: sha512-M00OuhU+0GyZ5iBBN9czjugzWrEq2vDpf/zCYHxxf93ul/Q5rv+a5h+/+0WnI1AebHNVtl5bFV0qsJoH23DbfA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-annotate-as-pure': 7.18.6 + regexpu-core: 5.3.2 + dev: true + + /@babel/helper-define-polyfill-provider@0.3.3(@babel/core@7.21.4): + resolution: {integrity: sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==} + peerDependencies: + '@babel/core': ^7.4.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.4) + '@babel/helper-plugin-utils': 7.20.2 + debug: 4.3.4 + lodash.debounce: 4.0.8 + resolve: 1.22.2 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-environment-visitor@7.18.9: resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} engines: {node: '>=6.9.0'} + /@babel/helper-explode-assignable-expression@7.18.6: + resolution: {integrity: sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.21.4 + dev: true + /@babel/helper-function-name@7.21.0: resolution: {integrity: sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==} engines: {node: '>=6.9.0'} @@ -331,6 +379,21 @@ packages: engines: {node: '>=6.9.0'} dev: true + /@babel/helper-remap-async-to-generator@7.18.9(@babel/core@7.21.4): + resolution: {integrity: sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-wrap-function': 7.20.5 + '@babel/types': 7.21.4 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-replace-supers@7.20.7: resolution: {integrity: sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==} engines: {node: '>=6.9.0'} @@ -376,6 +439,18 @@ packages: resolution: {integrity: sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==} engines: {node: '>=6.9.0'} + /@babel/helper-wrap-function@7.20.5: + resolution: {integrity: sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-function-name': 7.21.0 + '@babel/template': 7.20.7 + '@babel/traverse': 7.21.4 + '@babel/types': 7.21.4 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helpers@7.21.0: resolution: {integrity: sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==} engines: {node: '>=6.9.0'} @@ -401,6 +476,43 @@ packages: dependencies: '@babel/types': 7.21.4 + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.20.7(@babel/core@7.21.4): + resolution: {integrity: sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.21.4) + dev: true + + /@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.21.4): + resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.21.4) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.21.4) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.21.4): resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} engines: {node: '>=6.9.0'} @@ -414,6 +526,86 @@ packages: - supports-color dev: true + /@babel/plugin-proposal-class-static-block@7.21.0(@babel/core@7.21.4): + resolution: {integrity: sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-create-class-features-plugin': 7.21.4(@babel/core@7.21.4) + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.21.4) + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-proposal-dynamic-import@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.21.4) + dev: true + + /@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.21.4): + resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.21.4) + dev: true + + /@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.21.4) + dev: true + + /@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.21.4): + resolution: {integrity: sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.21.4) + dev: true + + /@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.21.4) + dev: true + + /@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.21.4) + dev: true + /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.21.4): resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==} engines: {node: '>=6.9.0'} @@ -428,6 +620,68 @@ packages: '@babel/plugin-transform-parameters': 7.21.3(@babel/core@7.21.4) dev: true + /@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.21.4) + dev: true + + /@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.21.4): + resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.21.4) + dev: true + + /@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-create-class-features-plugin': 7.21.4(@babel/core@7.21.4) + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-proposal-private-property-in-object@7.21.0(@babel/core@7.21.4): + resolution: {integrity: sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-create-class-features-plugin': 7.21.4(@babel/core@7.21.4) + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.21.4) + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-proposal-unicode-property-regex@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==} + engines: {node: '>=4'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-create-regexp-features-plugin': 7.21.4(@babel/core@7.21.4) + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.21.4): resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: @@ -455,6 +709,34 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.21.4): + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.21.4): + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.21.4): + resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-flow@7.21.4(@babel/core@7.21.4): resolution: {integrity: sha512-l9xd3N+XG4fZRxEP3vXdK6RW7vN1Uf5dxzRC/09wV86wqZ/YYQooBIGNsiRdfNR3/q2/5pPzV4B54J/9ctX5jw==} engines: {node: '>=6.9.0'} @@ -557,6 +839,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.21.4): + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.21.4): resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} @@ -587,6 +879,20 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-async-to-generator@7.20.7(@babel/core@7.21.4): + resolution: {integrity: sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-module-imports': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.21.4) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-block-scoped-functions@7.18.6(@babel/core@7.21.4): resolution: {integrity: sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==} engines: {node: '>=6.9.0'} @@ -648,6 +954,38 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-dotall-regex@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-create-regexp-features-plugin': 7.21.4(@babel/core@7.21.4) + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-duplicate-keys@7.18.9(@babel/core@7.21.4): + resolution: {integrity: sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-exponentiation-operator@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.18.9 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-flow-strip-types@7.21.0(@babel/core@7.21.4): resolution: {integrity: sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w==} engines: {node: '>=6.9.0'} @@ -701,6 +1039,19 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-modules-amd@7.20.11(@babel/core@7.21.4): + resolution: {integrity: sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-module-transforms': 7.21.2 + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-modules-commonjs@7.21.2(@babel/core@7.21.4): resolution: {integrity: sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==} engines: {node: '>=6.9.0'} @@ -715,6 +1066,55 @@ packages: - supports-color dev: true + /@babel/plugin-transform-modules-systemjs@7.20.11(@babel/core@7.21.4): + resolution: {integrity: sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-module-transforms': 7.21.2 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-validator-identifier': 7.19.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-modules-umd@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-module-transforms': 7.21.2 + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-named-capturing-groups-regex@7.20.5(@babel/core@7.21.4): + resolution: {integrity: sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-create-regexp-features-plugin': 7.21.4(@babel/core@7.21.4) + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-new-target@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-object-super@7.18.6(@babel/core@7.21.4): resolution: {integrity: sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==} engines: {node: '>=6.9.0'} @@ -748,6 +1148,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-react-constant-elements@7.21.3(@babel/core@7.21.4): + resolution: {integrity: sha512-4DVcFeWe/yDYBLp0kBmOGFJ6N2UYg7coGid1gdxb4co62dy/xISDMaYBXBVXEDhfgMk7qkbcYiGtwd5Q/hwDDQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-react-display-name@7.18.6(@babel/core@7.21.4): resolution: {integrity: sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==} engines: {node: '>=6.9.0'} @@ -758,6 +1168,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-react-jsx-development@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/plugin-transform-react-jsx': 7.21.0(@babel/core@7.21.4) + dev: true + /@babel/plugin-transform-react-jsx@7.21.0(@babel/core@7.21.4): resolution: {integrity: sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==} engines: {node: '>=6.9.0'} @@ -772,6 +1192,38 @@ packages: '@babel/types': 7.21.4 dev: true + /@babel/plugin-transform-react-pure-annotations@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-regenerator@7.20.5(@babel/core@7.21.4): + resolution: {integrity: sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + regenerator-transform: 0.15.1 + dev: true + + /@babel/plugin-transform-reserved-words@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-shorthand-properties@7.18.6(@babel/core@7.21.4): resolution: {integrity: sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==} engines: {node: '>=6.9.0'} @@ -782,25 +1234,215 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-spread@7.20.7(@babel/core@7.21.4): - resolution: {integrity: sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==} + /@babel/plugin-transform-spread@7.20.7(@babel/core@7.21.4): + resolution: {integrity: sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 + dev: true + + /@babel/plugin-transform-sticky-regex@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-template-literals@7.18.9(@babel/core@7.21.4): + resolution: {integrity: sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-typeof-symbol@7.18.9(@babel/core@7.21.4): + resolution: {integrity: sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-typescript@7.21.3(@babel/core@7.21.4): + resolution: {integrity: sha512-RQxPz6Iqt8T0uw/WsJNReuBpWpBqs/n7mNo18sKLoTbMp+UrEekhH+pKSVC7gWz+DNjo9gryfV8YzCiT45RgMw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-create-class-features-plugin': 7.21.4(@babel/core@7.21.4) + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-typescript': 7.21.4(@babel/core@7.21.4) + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-unicode-escapes@7.18.10(@babel/core@7.21.4): + resolution: {integrity: sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-unicode-regex@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-create-regexp-features-plugin': 7.21.4(@babel/core@7.21.4) + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/preset-env@7.21.4(@babel/core@7.21.4): + resolution: {integrity: sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.21.4 + '@babel/core': 7.21.4 + '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.4) + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-validator-option': 7.21.0 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.20.7(@babel/core@7.21.4) + '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.21.4) + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-proposal-class-static-block': 7.21.0(@babel/core@7.21.4) + '@babel/plugin-proposal-dynamic-import': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-proposal-export-namespace-from': 7.18.9(@babel/core@7.21.4) + '@babel/plugin-proposal-json-strings': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-proposal-logical-assignment-operators': 7.20.7(@babel/core@7.21.4) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.21.4) + '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.21.4) + '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-proposal-private-property-in-object': 7.21.0(@babel/core@7.21.4) + '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.21.4) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.21.4) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.21.4) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.21.4) + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.21.4) + '@babel/plugin-syntax-import-assertions': 7.20.0(@babel/core@7.21.4) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.21.4) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.21.4) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.21.4) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.21.4) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.21.4) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.21.4) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.21.4) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.21.4) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.21.4) + '@babel/plugin-transform-arrow-functions': 7.20.7(@babel/core@7.21.4) + '@babel/plugin-transform-async-to-generator': 7.20.7(@babel/core@7.21.4) + '@babel/plugin-transform-block-scoped-functions': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-block-scoping': 7.21.0(@babel/core@7.21.4) + '@babel/plugin-transform-classes': 7.21.0(@babel/core@7.21.4) + '@babel/plugin-transform-computed-properties': 7.20.7(@babel/core@7.21.4) + '@babel/plugin-transform-destructuring': 7.21.3(@babel/core@7.21.4) + '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-duplicate-keys': 7.18.9(@babel/core@7.21.4) + '@babel/plugin-transform-exponentiation-operator': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-for-of': 7.21.0(@babel/core@7.21.4) + '@babel/plugin-transform-function-name': 7.18.9(@babel/core@7.21.4) + '@babel/plugin-transform-literals': 7.18.9(@babel/core@7.21.4) + '@babel/plugin-transform-member-expression-literals': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-modules-amd': 7.20.11(@babel/core@7.21.4) + '@babel/plugin-transform-modules-commonjs': 7.21.2(@babel/core@7.21.4) + '@babel/plugin-transform-modules-systemjs': 7.20.11(@babel/core@7.21.4) + '@babel/plugin-transform-modules-umd': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-named-capturing-groups-regex': 7.20.5(@babel/core@7.21.4) + '@babel/plugin-transform-new-target': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-object-super': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-parameters': 7.21.3(@babel/core@7.21.4) + '@babel/plugin-transform-property-literals': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-regenerator': 7.20.5(@babel/core@7.21.4) + '@babel/plugin-transform-reserved-words': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-shorthand-properties': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-spread': 7.20.7(@babel/core@7.21.4) + '@babel/plugin-transform-sticky-regex': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-template-literals': 7.18.9(@babel/core@7.21.4) + '@babel/plugin-transform-typeof-symbol': 7.18.9(@babel/core@7.21.4) + '@babel/plugin-transform-unicode-escapes': 7.18.10(@babel/core@7.21.4) + '@babel/plugin-transform-unicode-regex': 7.18.6(@babel/core@7.21.4) + '@babel/preset-modules': 0.1.5(@babel/core@7.21.4) + '@babel/types': 7.21.4 + babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.21.4) + babel-plugin-polyfill-corejs3: 0.6.0(@babel/core@7.21.4) + babel-plugin-polyfill-regenerator: 0.4.1(@babel/core@7.21.4) + core-js-compat: 3.30.1 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/preset-modules@0.1.5(@babel/core@7.21.4): + resolution: {integrity: sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.21.4) + '@babel/types': 7.21.4 + esutils: 2.0.3 + dev: true + + /@babel/preset-react@7.18.6(@babel/core@7.21.4): + resolution: {integrity: sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 + '@babel/helper-validator-option': 7.21.0 + '@babel/plugin-transform-react-display-name': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-react-jsx': 7.21.0(@babel/core@7.21.4) + '@babel/plugin-transform-react-jsx-development': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-react-pure-annotations': 7.18.6(@babel/core@7.21.4) dev: true - /@babel/plugin-transform-template-literals@7.18.9(@babel/core@7.21.4): - resolution: {integrity: sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==} + /@babel/preset-typescript@7.21.4(@babel/core@7.21.4): + resolution: {integrity: sha512-sMLNWY37TCdRH/bJ6ZeeOH1nPuanED7Ai9Y/vH31IPqalioJ6ZNFUWONsakhv4r4n+I6gm5lmoE0olkgib/j/A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-validator-option': 7.21.0 + '@babel/plugin-syntax-jsx': 7.21.4(@babel/core@7.21.4) + '@babel/plugin-transform-modules-commonjs': 7.21.2(@babel/core@7.21.4) + '@babel/plugin-transform-typescript': 7.21.3(@babel/core@7.21.4) + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/regjsgen@0.8.0: + resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==} dev: true /@babel/runtime@7.21.0: @@ -2006,6 +2648,155 @@ packages: - supports-color dev: false + /@svgr/babel-plugin-add-jsx-attribute@7.0.0(@babel/core@7.21.4): + resolution: {integrity: sha512-khWbXesWIP9v8HuKCl2NU2HNAyqpSQ/vkIl36Nbn4HIwEYSRWL0H7Gs6idJdha2DkpFDWlsqMELvoCE8lfFY6Q==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + dev: true + + /@svgr/babel-plugin-remove-jsx-attribute@7.0.0(@babel/core@7.21.4): + resolution: {integrity: sha512-iiZaIvb3H/c7d3TH2HBeK91uI2rMhZNwnsIrvd7ZwGLkFw6mmunOCoVnjdYua662MqGFxlN9xTq4fv9hgR4VXQ==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + dev: true + + /@svgr/babel-plugin-remove-jsx-empty-expression@7.0.0(@babel/core@7.21.4): + resolution: {integrity: sha512-sQQmyo+qegBx8DfFc04PFmIO1FP1MHI1/QEpzcIcclo5OAISsOJPW76ZIs0bDyO/DBSJEa/tDa1W26pVtt0FRw==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + dev: true + + /@svgr/babel-plugin-replace-jsx-attribute-value@7.0.0(@babel/core@7.21.4): + resolution: {integrity: sha512-i6MaAqIZXDOJeikJuzocByBf8zO+meLwfQ/qMHIjCcvpnfvWf82PFvredEZElErB5glQFJa2KVKk8N2xV6tRRA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + dev: true + + /@svgr/babel-plugin-svg-dynamic-title@7.0.0(@babel/core@7.21.4): + resolution: {integrity: sha512-BoVSh6ge3SLLpKC0pmmN9DFlqgFy4NxNgdZNLPNJWBUU7TQpDWeBuyVuDW88iXydb5Cv0ReC+ffa5h3VrKfk1w==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + dev: true + + /@svgr/babel-plugin-svg-em-dimensions@7.0.0(@babel/core@7.21.4): + resolution: {integrity: sha512-tNDcBa+hYn0gO+GkP/AuNKdVtMufVhU9fdzu+vUQsR18RIJ9RWe7h/pSBY338RO08wArntwbDk5WhQBmhf2PaA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + dev: true + + /@svgr/babel-plugin-transform-react-native-svg@7.0.0(@babel/core@7.21.4): + resolution: {integrity: sha512-qw54u8ljCJYL2KtBOjI5z7Nzg8LnSvQOP5hPKj77H4VQL4+HdKbAT5pnkkZLmHKYwzsIHSYKXxHouD8zZamCFQ==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + dev: true + + /@svgr/babel-plugin-transform-svg-component@7.0.0(@babel/core@7.21.4): + resolution: {integrity: sha512-CcFECkDj98daOg9jE3Bh3uyD9kzevCAnZ+UtzG6+BQG/jOQ2OA3jHnX6iG4G1MCJkUQFnUvEv33NvQfqrb/F3A==} + engines: {node: '>=12'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + dev: true + + /@svgr/babel-preset@7.0.0(@babel/core@7.21.4): + resolution: {integrity: sha512-EX/NHeFa30j5UjldQGVQikuuQNHUdGmbh9kEpBKofGUtF0GUPJ4T4rhoYiqDAOmBOxojyot36JIFiDUHUK1ilQ==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@svgr/babel-plugin-add-jsx-attribute': 7.0.0(@babel/core@7.21.4) + '@svgr/babel-plugin-remove-jsx-attribute': 7.0.0(@babel/core@7.21.4) + '@svgr/babel-plugin-remove-jsx-empty-expression': 7.0.0(@babel/core@7.21.4) + '@svgr/babel-plugin-replace-jsx-attribute-value': 7.0.0(@babel/core@7.21.4) + '@svgr/babel-plugin-svg-dynamic-title': 7.0.0(@babel/core@7.21.4) + '@svgr/babel-plugin-svg-em-dimensions': 7.0.0(@babel/core@7.21.4) + '@svgr/babel-plugin-transform-react-native-svg': 7.0.0(@babel/core@7.21.4) + '@svgr/babel-plugin-transform-svg-component': 7.0.0(@babel/core@7.21.4) + dev: true + + /@svgr/core@7.0.0: + resolution: {integrity: sha512-ztAoxkaKhRVloa3XydohgQQCb0/8x9T63yXovpmHzKMkHO6pkjdsIAWKOS4bE95P/2quVh1NtjSKlMRNzSBffw==} + engines: {node: '>=14'} + dependencies: + '@babel/core': 7.21.4 + '@svgr/babel-preset': 7.0.0(@babel/core@7.21.4) + camelcase: 6.3.0 + cosmiconfig: 8.1.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@svgr/hast-util-to-babel-ast@7.0.0: + resolution: {integrity: sha512-42Ej9sDDEmsJKjrfQ1PHmiDiHagh/u9AHO9QWbeNx4KmD9yS5d1XHmXUNINfUcykAU+4431Cn+k6Vn5mWBYimQ==} + engines: {node: '>=14'} + dependencies: + '@babel/types': 7.21.4 + entities: 4.5.0 + dev: true + + /@svgr/plugin-jsx@7.0.0: + resolution: {integrity: sha512-SWlTpPQmBUtLKxXWgpv8syzqIU8XgFRvyhfkam2So8b3BE0OS0HPe5UfmlJ2KIC+a7dpuuYovPR2WAQuSyMoPw==} + engines: {node: '>=14'} + dependencies: + '@babel/core': 7.21.4 + '@svgr/babel-preset': 7.0.0(@babel/core@7.21.4) + '@svgr/hast-util-to-babel-ast': 7.0.0 + svg-parser: 2.0.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@svgr/plugin-svgo@7.0.0(@svgr/core@7.0.0): + resolution: {integrity: sha512-263znzlu3qTKj71/ot5G9l2vpL4CW+pr2IexBFIwwB+fRAXE9Xnw2rUFgE6P4+37N9siOuC4lKkgBfUCOLFRKQ==} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + dependencies: + '@svgr/core': 7.0.0 + cosmiconfig: 8.1.3 + deepmerge: 4.3.1 + svgo: 3.0.2 + dev: true + + /@svgr/webpack@7.0.0: + resolution: {integrity: sha512-XWzIhLTr5WYns/cNFXpXrmFy+LFf2xp60VnNUBZCpM1CGTx47FCDuUj2DQjxirMf2L6CP2jTRELK8ef01TecFQ==} + engines: {node: '>=14'} + dependencies: + '@babel/core': 7.21.4 + '@babel/plugin-transform-react-constant-elements': 7.21.3(@babel/core@7.21.4) + '@babel/preset-env': 7.21.4(@babel/core@7.21.4) + '@babel/preset-react': 7.18.6(@babel/core@7.21.4) + '@babel/preset-typescript': 7.21.4(@babel/core@7.21.4) + '@svgr/core': 7.0.0 + '@svgr/plugin-jsx': 7.0.0 + '@svgr/plugin-svgo': 7.0.0(@svgr/core@7.0.0) + transitivePeerDependencies: + - supports-color + dev: true + /@swc/helpers@0.4.14: resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==} dependencies: @@ -2060,6 +2851,11 @@ packages: engines: {node: '>= 10'} dev: true + /@trysound/sax@0.2.0: + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} + engines: {node: '>=10.13.0'} + dev: true + /@types/aria-query@5.0.1: resolution: {integrity: sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==} dev: true @@ -2514,7 +3310,6 @@ packages: /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - dev: true /auto-bind@4.0.0: resolution: {integrity: sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==} @@ -2546,6 +3341,16 @@ packages: engines: {node: '>=4'} dev: false + /axios@1.3.6: + resolution: {integrity: sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==} + dependencies: + follow-redirects: 1.15.2 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /axobject-query@3.1.1: resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==} dependencies: @@ -2593,6 +3398,42 @@ packages: '@types/babel__traverse': 7.18.3 dev: true + /babel-plugin-polyfill-corejs2@0.3.3(@babel/core@7.21.4): + resolution: {integrity: sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.21.4 + '@babel/core': 7.21.4 + '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.21.4) + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-polyfill-corejs3@0.6.0(@babel/core@7.21.4): + resolution: {integrity: sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.21.4) + core-js-compat: 3.30.1 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-polyfill-regenerator@0.4.1(@babel/core@7.21.4): + resolution: {integrity: sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.4 + '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.21.4) + transitivePeerDependencies: + - supports-color + dev: true + /babel-plugin-syntax-trailing-function-commas@7.0.0-beta.0: resolution: {integrity: sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==} dev: true @@ -2685,6 +3526,10 @@ packages: readable-stream: 3.6.2 dev: true + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: true + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -2973,7 +3818,6 @@ packages: engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 - dev: true /commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} @@ -2985,6 +3829,11 @@ packages: engines: {node: '>= 6'} dev: false + /commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + dev: true + /common-tags@1.8.2: resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} engines: {node: '>=4.0.0'} @@ -3008,6 +3857,12 @@ packages: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true + /core-js-compat@3.30.1: + resolution: {integrity: sha512-d690npR7MC6P0gq4npTl5n2VQeNAmUrJ90n+MHiKS7W2+xno4o3F5GDEuylSdi6EJ3VssibSGXOa1r3YXD3Mhw==} + dependencies: + browserslist: 4.21.5 + dev: true + /cosmiconfig@7.1.0: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} @@ -3029,6 +3884,16 @@ packages: path-type: 4.0.0 dev: true + /cosmiconfig@8.1.3: + resolution: {integrity: sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==} + engines: {node: '>=14'} + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + dev: true + /cross-fetch@3.1.5: resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==} dependencies: @@ -3044,6 +3909,37 @@ packages: shebang-command: 2.0.0 which: 2.0.2 + /css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.0.1 + nth-check: 2.1.1 + dev: true + + /css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.0.2 + dev: true + + /css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.0.2 + dev: true + + /css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + dev: true + /css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} dev: true @@ -3054,6 +3950,13 @@ packages: hasBin: true dev: false + /csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + dependencies: + css-tree: 2.2.1 + dev: true + /cssom@0.3.8: resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} dev: true @@ -3196,7 +4099,6 @@ packages: /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - dev: true /dependency-graph@0.11.0: resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==} @@ -3249,6 +4151,18 @@ packages: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} dev: true + /dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + dev: true + + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: true + /domexception@4.0.0: resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} engines: {node: '>=12'} @@ -3256,6 +4170,21 @@ packages: webidl-conversions: 7.0.0 dev: true + /domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: true + + /domutils@3.0.1: + resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==} + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dev: true + /dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} dependencies: @@ -3899,6 +4828,16 @@ packages: /flatted@3.2.7: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + /follow-redirects@1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: @@ -3911,7 +4850,6 @@ packages: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 - dev: true /fraction.js@4.2.0: resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} @@ -5141,6 +6079,11 @@ packages: - utf-8-validate dev: true + /jsesc@0.5.0: + resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} + hasBin: true + dev: true + /jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} @@ -5310,6 +6253,10 @@ packages: dependencies: p-locate: 5.0.0 + /lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + dev: true + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -5391,6 +6338,14 @@ packages: engines: {node: '>=0.10.0'} dev: true + /mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + dev: true + + /mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + dev: true + /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true @@ -5421,14 +6376,12 @@ packages: /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - dev: true /mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 - dev: true /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} @@ -5616,6 +6569,12 @@ packages: path-key: 4.0.0 dev: true + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 + dev: true + /nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} dev: true @@ -6051,6 +7010,10 @@ packages: react-is: 16.13.1 dev: false + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + /psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} dev: true @@ -6156,9 +7119,26 @@ packages: strip-indent: 3.0.0 dev: true + /regenerate-unicode-properties@10.1.0: + resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==} + engines: {node: '>=4'} + dependencies: + regenerate: 1.4.2 + dev: true + + /regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + dev: true + /regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + /regenerator-transform@0.15.1: + resolution: {integrity: sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==} + dependencies: + '@babel/runtime': 7.21.0 + dev: true + /regexp.prototype.flags@1.4.3: resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} engines: {node: '>= 0.4'} @@ -6167,6 +7147,25 @@ packages: define-properties: 1.2.0 functions-have-names: 1.2.3 + /regexpu-core@5.3.2: + resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} + engines: {node: '>=4'} + dependencies: + '@babel/regjsgen': 0.8.0 + regenerate: 1.4.2 + regenerate-unicode-properties: 10.1.0 + regjsparser: 0.9.1 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.1.0 + dev: true + + /regjsparser@0.9.1: + resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} + hasBin: true + dependencies: + jsesc: 0.5.0 + dev: true + /relay-runtime@12.0.0: resolution: {integrity: sha512-QU6JKr1tMsry22DXNy9Whsq5rmvwr3LSZiiWV/9+DFpuTWvp+WFhobWMc8TC4OjKFfNhEZy7mOiqUAn5atQtug==} dependencies: @@ -6420,7 +7419,6 @@ packages: /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} - dev: false /source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} @@ -6639,6 +7637,23 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + /svg-parser@2.0.4: + resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + dev: true + + /svgo@3.0.2: + resolution: {integrity: sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==} + engines: {node: '>=14.0.0'} + hasBin: true + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 5.1.0 + css-tree: 2.3.1 + csso: 5.0.5 + picocolors: 1.0.0 + dev: true + /swap-case@2.0.2: resolution: {integrity: sha512-kc6S2YS/2yXbtkSMunBtKdah4VFETZ8Oh6ONSmSd9bRxhqTrtARUCBUiWXH3xVPpvR7tz2CSnkuXVE42EcGnMw==} dependencies: @@ -6903,6 +7918,29 @@ packages: engines: {node: '>=0.10.0'} dev: true + /unicode-canonical-property-names-ecmascript@2.0.0: + resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} + engines: {node: '>=4'} + dev: true + + /unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.0 + unicode-property-aliases-ecmascript: 2.1.0 + dev: true + + /unicode-match-property-value-ecmascript@2.1.0: + resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} + engines: {node: '>=4'} + dev: true + + /unicode-property-aliases-ecmascript@2.1.0: + resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} + engines: {node: '>=4'} + dev: true + /universalify@0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} From 94d5487f76eb7f5fc221f23f6ac282855f13aa8d Mon Sep 17 00:00:00 2001 From: Marek Miklaszewski Date: Tue, 25 Apr 2023 10:54:41 +0200 Subject: [PATCH 2/8] [RAB-4] Install stripe, create api routes for checkout --- apps/next-app/.env.example | 5 +- apps/next-app/package.json | 5 +- .../src/constants/SUBSCRIPTION_PLAN.ts | 4 + .../api/subscription/checkout_session.ts | 42 +++++ .../src/pages/api/subscription/get_plans.ts | 23 +++ .../api/subscription/retrieve_session.ts | 27 +++ pnpm-lock.yaml | 155 ++++++++++++++++++ 7 files changed, 259 insertions(+), 2 deletions(-) create mode 100644 apps/next-app/src/constants/SUBSCRIPTION_PLAN.ts create mode 100644 apps/next-app/src/pages/api/subscription/checkout_session.ts create mode 100644 apps/next-app/src/pages/api/subscription/get_plans.ts create mode 100644 apps/next-app/src/pages/api/subscription/retrieve_session.ts diff --git a/apps/next-app/.env.example b/apps/next-app/.env.example index bf0d87e..ab62596 100644 --- a/apps/next-app/.env.example +++ b/apps/next-app/.env.example @@ -1,3 +1,6 @@ +NEXT_PUBLIC_API_URL=http://localhost:3000/api NEXT_PUBLIC_SUPABASE_URL= NEXT_PUBLIC_SUPABASE_GRAPHQL_URL= -NEXT_PUBLIC_SUPABASE_ANON_KEY= \ No newline at end of file +NEXT_PUBLIC_SUPABASE_ANON_KEY= +NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY= +STRIPE_SECRET_KEY= \ No newline at end of file diff --git a/apps/next-app/package.json b/apps/next-app/package.json index a04abc9..a34886c 100644 --- a/apps/next-app/package.json +++ b/apps/next-app/package.json @@ -18,6 +18,7 @@ "@graphql-typed-document-node/core": "^3.2.0", "@headlessui/react": "^1.7.14", "@next/env": "^13.3.0", + "@stripe/stripe-js": "^1.52.1", "@supabase/auth-helpers-nextjs": "^0.6.1", "@supabase/auth-helpers-react": "^0.3.1", "@supabase/supabase-js": "^2.21.0", @@ -34,6 +35,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-hook-form": "^7.43.9", + "stripe": "^12.2.0", "tailwind-merge": "^1.12.0", "tailwindcss": "3.3.1", "typescript": "5.0.4" @@ -50,6 +52,7 @@ "husky": "^8.0.3", "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", - "lint-staged": "^13.2.1" + "lint-staged": "^13.2.1", + "supabase": "^1.50.12" } } diff --git a/apps/next-app/src/constants/SUBSCRIPTION_PLAN.ts b/apps/next-app/src/constants/SUBSCRIPTION_PLAN.ts new file mode 100644 index 0000000..1633766 --- /dev/null +++ b/apps/next-app/src/constants/SUBSCRIPTION_PLAN.ts @@ -0,0 +1,4 @@ +export enum SUBSCRIPTION_PLAN { + BASIC = 'basic', + PRO = 'pro', +} diff --git a/apps/next-app/src/pages/api/subscription/checkout_session.ts b/apps/next-app/src/pages/api/subscription/checkout_session.ts new file mode 100644 index 0000000..e71142f --- /dev/null +++ b/apps/next-app/src/pages/api/subscription/checkout_session.ts @@ -0,0 +1,42 @@ +import { ROUTES } from 'constants/ROUTES'; +import { SUBSCRIPTION_PLAN } from 'constants/SUBSCRIPTION_PLAN'; +import { NextApiRequest, NextApiResponse } from 'next'; +import Stripe from 'stripe'; + +const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { + apiVersion: '2022-11-15', +}); + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method === 'POST') { + const { plan, email } = req.body; + try { + // Create Checkout Sessions from body params. + const session = await stripe.checkout.sessions.create({ + line_items: [ + { + price: + plan === SUBSCRIPTION_PLAN.BASIC + ? 'price_1N0MCfIqBsaLLecGvKVULmt9' + : 'price_1N0MDSIqBsaLLecG74B1hm1f', + quantity: 1, + }, + ], + customer_email: email, + mode: 'subscription', + + success_url: `${req.headers.origin}${ROUTES.SUBSCRIPTION_SUCCESS}?session_id={CHECKOUT_SESSION_ID}`, + cancel_url: `${req.headers.origin}${ROUTES.SUBSCRIPTION_CANCEL}?session_id={CHECKOUT_SESSION_ID}`, + }); + res.status(200).json(session); + } catch (err: any) { + res.status(err.statusCode || 500).json(err.message); + } + } else { + res.setHeader('Allow', 'POST'); + res.status(405).end('Method Not Allowed'); + } +} diff --git a/apps/next-app/src/pages/api/subscription/get_plans.ts b/apps/next-app/src/pages/api/subscription/get_plans.ts new file mode 100644 index 0000000..ea1d142 --- /dev/null +++ b/apps/next-app/src/pages/api/subscription/get_plans.ts @@ -0,0 +1,23 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import Stripe from 'stripe'; + +const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { + apiVersion: '2022-11-15', +}); + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method === 'GET') { + try { + const plans = await stripe.plans.list(); + res.status(200).json({ plans }); + } catch (err: any) { + res.status(err.statusCode || 500).json(err.message); + } + } else { + res.setHeader('Allow', 'GET'); + res.status(405).end('Method Not Allowed'); + } +} diff --git a/apps/next-app/src/pages/api/subscription/retrieve_session.ts b/apps/next-app/src/pages/api/subscription/retrieve_session.ts new file mode 100644 index 0000000..d04c6c4 --- /dev/null +++ b/apps/next-app/src/pages/api/subscription/retrieve_session.ts @@ -0,0 +1,27 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import Stripe from 'stripe'; + +const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { + apiVersion: '2022-11-15', +}); + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method === 'GET') { + try { + const { session_id } = req.query; + const session = await stripe.checkout.sessions.retrieve( + session_id as string + ); + + res.status(200).json({ session }); + } catch (err: any) { + res.status(err.statusCode || 500).json(err.message); + } + } else { + res.setHeader('Allow', 'GET'); + res.status(405).end('Method Not Allowed'); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 995e15f..2116df6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,6 +18,9 @@ importers: '@next/env': specifier: ^13.3.0 version: 13.3.0 + '@stripe/stripe-js': + specifier: ^1.52.1 + version: 1.52.1 '@supabase/auth-helpers-nextjs': specifier: ^0.6.1 version: 0.6.1(@supabase/supabase-js@2.21.0) @@ -66,6 +69,9 @@ importers: react-hook-form: specifier: ^7.43.9 version: 7.43.9(react@18.2.0) + stripe: + specifier: ^12.2.0 + version: 12.2.0 tailwind-merge: specifier: ^1.12.0 version: 1.12.0 @@ -112,6 +118,9 @@ importers: lint-staged: specifier: ^13.2.1 version: 13.2.1 + supabase: + specifier: ^1.50.12 + version: 1.50.12 packages: @@ -2566,6 +2575,10 @@ packages: '@sinonjs/commons': 2.0.0 dev: true + /@stripe/stripe-js@1.52.1: + resolution: {integrity: sha512-fza40OPSpGQlFxc5TZWiYC/6Lk89Sep1fLuv9ss33YS6lCAF8UZbfA1E6W+lwO4c7WRKZIZumHIEbPJfP/O9uw==} + dev: false + /@supabase/auth-helpers-nextjs@0.6.1(@supabase/supabase-js@2.21.0): resolution: {integrity: sha512-ffDAR4pW2Cosj/CuuGRAhzOFfHtqzdgax7zqRGGyq334gxxPplsmJFGaLPRJ3Z/4mTSIJHyFd6LYdHMN0a+5Bw==} peerDependencies: @@ -3513,6 +3526,16 @@ packages: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: true + /bin-links@4.0.1: + resolution: {integrity: sha512-bmFEM39CyX336ZGGRsGPlc6jZHriIoHacOQcTt72MktIjpPhZoP4te2jOyUXF3BLILmJ8aNLncoPVeIIFlrDeA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + cmd-shim: 6.0.1 + npm-normalize-package-bin: 3.0.0 + read-cmd-shim: 4.0.0 + write-file-atomic: 5.0.0 + dev: true + /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -3710,6 +3733,11 @@ packages: fsevents: 2.3.2 dev: false + /chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + dev: true + /ci-info@3.8.0: resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} engines: {node: '>=8'} @@ -3783,6 +3811,11 @@ packages: engines: {node: '>=0.8'} dev: true + /cmd-shim@6.0.1: + resolution: {integrity: sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: true + /co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -3986,6 +4019,11 @@ packages: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} dev: false + /data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + dev: true + /data-urls@3.0.2: resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} engines: {node: '>=12'} @@ -4784,6 +4822,14 @@ packages: - encoding dev: true + /fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.2.1 + dev: true + /figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -4851,10 +4897,24 @@ packages: combined-stream: 1.0.8 mime-types: 2.1.35 + /formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + dependencies: + fetch-blob: 3.2.0 + dev: true + /fraction.js@4.2.0: resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} dev: false + /fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 + dev: true + /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -6414,6 +6474,32 @@ packages: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} dev: false + /minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + dependencies: + yallist: 4.0.0 + dev: true + + /minipass@4.2.8: + resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} + engines: {node: '>=8'} + dev: true + + /minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + dev: true + + /mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + dev: true + /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} dev: false @@ -6505,6 +6591,11 @@ packages: resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} dev: true + /node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + dev: true + /node-fetch@2.6.7: resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} engines: {node: 4.x || >=6.0.0} @@ -6528,6 +6619,15 @@ packages: whatwg-url: 5.0.0 dev: true + /node-fetch@3.3.1: + resolution: {integrity: sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + dev: true + /node-gyp-build@4.6.0: resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} hasBin: true @@ -6555,6 +6655,11 @@ packages: engines: {node: '>=0.10.0'} dev: false + /npm-normalize-package-bin@3.0.0: + resolution: {integrity: sha512-g+DPQSkusnk7HYXr75NtzkIP4+N81i3RPsGFidF3DzHd9MT9wWngmqoeg/fnHFz5MNdtG4w03s+QnhewSLTT2Q==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: true + /npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} @@ -7041,6 +7146,13 @@ packages: engines: {node: '>=6.0.0'} dev: true + /qs@6.11.1: + resolution: {integrity: sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: false + /querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} dev: true @@ -7095,6 +7207,11 @@ packages: pify: 2.3.0 dev: false + /read-cmd-shim@4.0.0: + resolution: {integrity: sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: true + /readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -7582,6 +7699,14 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + /stripe@12.2.0: + resolution: {integrity: sha512-OVqmUuHvDL8z9pBhrhL0HkjPduBM6x1KJCSnVC8R3m+nWpTb7xRpqNuETEkvkYrek1QpfzbcS+UlOCTpv7SC/w==} + engines: {node: '>=12.*'} + dependencies: + '@types/node': 18.15.11 + qs: 6.11.1 + dev: false + /styled-jsx@5.1.1(@babel/core@7.21.4)(react@18.2.0): resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} @@ -7614,6 +7739,16 @@ packages: ts-interface-checker: 0.1.13 dev: false + /supabase@1.50.12: + resolution: {integrity: sha512-D0YmA3+ACfN0UGJ7vJ80wBAwrSjqXmMjvOPoLIlFBoQofGoEgmfGppuuGYzwKmjRdP//XrQHJkZoratccsL6AA==} + hasBin: true + requiresBuild: true + dependencies: + bin-links: 4.0.1 + node-fetch: 3.3.1 + tar: 6.1.13 + dev: true + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -7721,6 +7856,18 @@ packages: engines: {node: '>=6'} dev: false + /tar@6.1.13: + resolution: {integrity: sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==} + engines: {node: '>=10'} + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 4.2.8 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + dev: true + /test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -8172,6 +8319,14 @@ packages: signal-exit: 3.0.7 dev: true + /write-file-atomic@5.0.0: + resolution: {integrity: sha512-R7NYMnHSlV42K54lwY9lvW6MnSm1HSJqZL3xiSgi9E7//FYaI74r2G0rd+/X6VAMkHEdzxQaU5HUOXWUz5kA/w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + dev: true + /ws@8.13.0: resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} engines: {node: '>=10.0.0'} From fa86bc135407e3c97e7dcc75e03202a98d2017c9 Mon Sep 17 00:00:00 2001 From: Marek Miklaszewski Date: Tue, 25 Apr 2023 10:55:23 +0200 Subject: [PATCH 3/8] [RAB-4] Create Subscription pages, create SubscriptionCard --- apps/next-app/src/constants/ROUTES.ts | 4 + apps/next-app/src/middleware.ts | 2 +- .../src/pages/subscription/cancel.tsx | 55 +++++++++++++ .../next-app/src/pages/subscription/index.tsx | 77 +++++++++++++++++++ .../src/pages/subscription/success.tsx | 53 +++++++++++++ .../subscriptions/SubscriptionCard/index.ts | 1 + .../subscriptionCard.component.tsx | 64 +++++++++++++++ .../subscriptionCard.constants.ts | 15 ++++ apps/next-app/src/utils/getStripe.ts | 9 +++ .../src/utils/getSubscriptionPrice.ts | 1 + 10 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 apps/next-app/src/pages/subscription/cancel.tsx create mode 100644 apps/next-app/src/pages/subscription/index.tsx create mode 100644 apps/next-app/src/pages/subscription/success.tsx create mode 100644 apps/next-app/src/shared/components/subscriptions/SubscriptionCard/index.ts create mode 100644 apps/next-app/src/shared/components/subscriptions/SubscriptionCard/subscriptionCard.component.tsx create mode 100644 apps/next-app/src/shared/components/subscriptions/SubscriptionCard/subscriptionCard.constants.ts create mode 100644 apps/next-app/src/utils/getStripe.ts create mode 100644 apps/next-app/src/utils/getSubscriptionPrice.ts diff --git a/apps/next-app/src/constants/ROUTES.ts b/apps/next-app/src/constants/ROUTES.ts index 5c38844..d0ecf49 100644 --- a/apps/next-app/src/constants/ROUTES.ts +++ b/apps/next-app/src/constants/ROUTES.ts @@ -1,6 +1,10 @@ export enum ROUTES { LOGIN = '/auth/login', PROVIDER = '/auth/provider', + HOME = '/', DASHBOARD = '/dashboard', PROFILE = '/profile', + SUBSCRIPTION = '/subscription', + SUBSCRIPTION_SUCCESS = '/subscription/success', + SUBSCRIPTION_CANCEL = '/subscription/cancel', } diff --git a/apps/next-app/src/middleware.ts b/apps/next-app/src/middleware.ts index 10b5985..2cdff00 100644 --- a/apps/next-app/src/middleware.ts +++ b/apps/next-app/src/middleware.ts @@ -21,5 +21,5 @@ export async function middleware(req: NextRequest) { } export const config = { - matcher: ['/dashboard', '/profile'], + matcher: ['/dashboard', '/profile', '/subscription'], }; diff --git a/apps/next-app/src/pages/subscription/cancel.tsx b/apps/next-app/src/pages/subscription/cancel.tsx new file mode 100644 index 0000000..f30c1f7 --- /dev/null +++ b/apps/next-app/src/pages/subscription/cancel.tsx @@ -0,0 +1,55 @@ +import { ROUTES } from 'constants/ROUTES'; +import CrossmarkSVG from 'assets/cross-mark.svg'; +import Link from 'next/link'; +import { GetServerSidePropsContext } from 'next'; +import { axiosApi } from 'shared/services/axios'; + +const Cancel = () => { + return ( +
+
+ +

+ We see that you changed your mind +

+

+ Lorem ipsum dolor, sit amet consectetur adipisicing elit. Corporis, + delectus aperiam. Sapiente vero distinctio magni, natus obcaecati, + esse perspiciatis voluptatem ipsa, illum earum culpa neque! +

+
+
+ + Go back to Subscriptions + + + Go to Home page + +
+
+ ); +}; + +export default Cancel; + +export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { + try { + const { session_id } = ctx.query; + await axiosApi.get( + `/subscription/retrieve_session?session_id=${session_id}` + ); + return { props: {} }; + } catch (err) { + return { + redirect: { + destination: ROUTES.HOME, + }, + }; + } +}; diff --git a/apps/next-app/src/pages/subscription/index.tsx b/apps/next-app/src/pages/subscription/index.tsx new file mode 100644 index 0000000..bbd9f3d --- /dev/null +++ b/apps/next-app/src/pages/subscription/index.tsx @@ -0,0 +1,77 @@ +import { useUser } from '@supabase/auth-helpers-react'; +import { SUBSCRIPTION_PLAN } from 'constants/SUBSCRIPTION_PLAN'; +import { SubscriptionCard } from 'shared/components/subscriptions/SubscriptionCard'; +import { axiosApi } from 'shared/services/axios'; +import { getStripe } from 'utils/getStripe'; +import { getSubscriptionPrice } from 'utils/getSubscriptionPrice'; + +interface SubscriptionProps { + plans: any[]; +} + +const Subscription = ({ plans }: SubscriptionProps) => { + const user = useUser(); + + const handleBasicPlan = async () => { + try { + const { data } = await axiosApi.post('/subscription/checkout_session', { + plan: SUBSCRIPTION_PLAN.BASIC, + email: user?.email, + }); + const stripe = await getStripe(); + await stripe?.redirectToCheckout({ sessionId: data.id }); + } catch (err) {} + }; + + const handleProPlan = async () => { + try { + const { data } = await axiosApi.post('/subscription/checkout_session', { + plan: SUBSCRIPTION_PLAN.PRO, + email: user?.email, + }); + const stripe = await getStripe(); + await stripe?.redirectToCheckout({ sessionId: data.id }); + } catch (err) {} + }; + + const basicPlan = plans.find( + ({ metadata }) => metadata.type === SUBSCRIPTION_PLAN.BASIC + ); + const proPlan = plans.find( + ({ metadata }) => metadata.type === SUBSCRIPTION_PLAN.PRO + ); + + return ( +
+

App Boilerplate Subscription

+
+ + +
+
+ ); +}; + +export default Subscription; + +export const getServerSideProps = async () => { + const { data } = await axiosApi.get('/subscription/get_plans'); + + const plans = data.plans.data.map((plan: any) => ({ + ...plan, + amount: getSubscriptionPrice(plan.amount), + })); + return { props: { plans } }; +}; diff --git a/apps/next-app/src/pages/subscription/success.tsx b/apps/next-app/src/pages/subscription/success.tsx new file mode 100644 index 0000000..009cb5d --- /dev/null +++ b/apps/next-app/src/pages/subscription/success.tsx @@ -0,0 +1,53 @@ +import { ROUTES } from 'constants/ROUTES'; +import CheckmarkSVG from 'assets/check-mark.svg'; +import Link from 'next/link'; +import { axiosApi } from 'shared/services/axios'; +import { GetServerSidePropsContext } from 'next'; + +const Success = () => { + return ( +
+
+ +

Payment successful!

+

+ Lorem ipsum dolor, sit amet consectetur adipisicing elit. Corporis, + delectus aperiam. Sapiente vero distinctio magni, natus obcaecati, + esse perspiciatis voluptatem ipsa, illum earum culpa neque! +

+
+
+ + Go to Dashboard + + + Go to your Profile + +
+
+ ); +}; + +export default Success; + +export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { + try { + const { session_id } = ctx.query; + await axiosApi.get( + `/subscription/retrieve_session?session_id=${session_id}` + ); + return { props: {} }; + } catch (err) { + return { + redirect: { + destination: ROUTES.HOME, + }, + }; + } +}; diff --git a/apps/next-app/src/shared/components/subscriptions/SubscriptionCard/index.ts b/apps/next-app/src/shared/components/subscriptions/SubscriptionCard/index.ts new file mode 100644 index 0000000..f372050 --- /dev/null +++ b/apps/next-app/src/shared/components/subscriptions/SubscriptionCard/index.ts @@ -0,0 +1 @@ +export { SubscriptionCard } from './subscriptionCard.component'; diff --git a/apps/next-app/src/shared/components/subscriptions/SubscriptionCard/subscriptionCard.component.tsx b/apps/next-app/src/shared/components/subscriptions/SubscriptionCard/subscriptionCard.component.tsx new file mode 100644 index 0000000..82c49ac --- /dev/null +++ b/apps/next-app/src/shared/components/subscriptions/SubscriptionCard/subscriptionCard.component.tsx @@ -0,0 +1,64 @@ +import { SUBSCRIPTION_PLAN } from 'constants/SUBSCRIPTION_PLAN'; +import { Button } from 'shared/components/Button'; +import { Checkmark } from 'shared/components/Checkmark'; +import { Crossmark } from 'shared/components/Crossmark'; +import { BASIC_FEATURES, FEATURES } from './subscriptionCard.constants'; + +interface ProCardProps { + type: SUBSCRIPTION_PLAN; + name: string; + price: number; + currency: string; + handleClick: () => Promise; +} + +export const SubscriptionCard = ({ + type, + name, + price, + currency, + handleClick, +}: ProCardProps) => { + return ( +
+ {type === SUBSCRIPTION_PLAN.PRO && ( +
+ Recommended +
+ )} +

{name}

+

+ Lorem ipsum dolor, sit amet consectetur adipisicing elit! +

+
+ + {currency} {price} + + / per month +
+ +
    + {type === SUBSCRIPTION_PLAN.BASIC + ? FEATURES.map((feature) => ( +
  • + {BASIC_FEATURES.includes(feature) ? ( + + ) : ( + + )} + {feature} +
  • + )) + : type === SUBSCRIPTION_PLAN.PRO && + FEATURES.map((feature) => ( +
  • + + {feature} +
  • + ))} +
+ + +
+ ); +}; diff --git a/apps/next-app/src/shared/components/subscriptions/SubscriptionCard/subscriptionCard.constants.ts b/apps/next-app/src/shared/components/subscriptions/SubscriptionCard/subscriptionCard.constants.ts new file mode 100644 index 0000000..5b8ea75 --- /dev/null +++ b/apps/next-app/src/shared/components/subscriptions/SubscriptionCard/subscriptionCard.constants.ts @@ -0,0 +1,15 @@ +export const FEATURES = [ + 'Access to all features', + 'Checking accounts', + 'API Integration', + 'Cancel anytime', + 'Financial Analytics', + 'Account manager', +]; + +export const BASIC_FEATURES = [ + 'Access to all features', + 'Checking accounts', + 'API Integration', + 'Cancel anytime', +]; diff --git a/apps/next-app/src/utils/getStripe.ts b/apps/next-app/src/utils/getStripe.ts new file mode 100644 index 0000000..388ce25 --- /dev/null +++ b/apps/next-app/src/utils/getStripe.ts @@ -0,0 +1,9 @@ +import { Stripe, loadStripe } from '@stripe/stripe-js'; + +let stripePromise: Promise; +export const getStripe = () => { + if (!stripePromise) { + stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!); + } + return stripePromise; +}; diff --git a/apps/next-app/src/utils/getSubscriptionPrice.ts b/apps/next-app/src/utils/getSubscriptionPrice.ts new file mode 100644 index 0000000..ab58f90 --- /dev/null +++ b/apps/next-app/src/utils/getSubscriptionPrice.ts @@ -0,0 +1 @@ +export const getSubscriptionPrice = (amount: number) => amount / 100; From 30528579fea2bca41f0ab94258fa6081b21820ff Mon Sep 17 00:00:00 2001 From: Marek Miklaszewski Date: Tue, 25 Apr 2023 12:28:16 +0200 Subject: [PATCH 4/8] [RAB-4] Fix getting user profile --- .../src/graphql/__generated/gql/gql.ts | 6 +-- .../src/graphql/__generated/gql/graphql.ts | 45 ++++++++++++++++++- apps/next-app/src/pages/dashboard/index.tsx | 5 ++- apps/next-app/src/pages/profile/index.tsx | 5 ++- .../src/shared/queries/index.graphql.ts | 4 +- 5 files changed, 57 insertions(+), 8 deletions(-) diff --git a/apps/next-app/src/graphql/__generated/gql/gql.ts b/apps/next-app/src/graphql/__generated/gql/gql.ts index 2e01793..d1d8e56 100644 --- a/apps/next-app/src/graphql/__generated/gql/gql.ts +++ b/apps/next-app/src/graphql/__generated/gql/gql.ts @@ -13,7 +13,7 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/ * Therefore it is highly recommended to use the babel or swc plugin for production. */ const documents = { - '\n query getProfile {\n profilesCollection {\n edges {\n node {\n id\n full_name\n avatar_url\n }\n }\n }\n }\n': + '\n query getProfile($profileId: UUID!) {\n profilesCollection(filter: { id: { eq: $profileId } }) {\n edges {\n node {\n id\n full_name\n avatar_url\n }\n }\n }\n }\n': types.GetProfileDocument, '\n mutation updateAvatar($input: profilesUpdateInput!) {\n updateprofilesCollection(set: $input) {\n records {\n avatar_url\n }\n }\n }\n': types.UpdateAvatarDocument, @@ -39,8 +39,8 @@ export function graphql(source: string): unknown; * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: '\n query getProfile {\n profilesCollection {\n edges {\n node {\n id\n full_name\n avatar_url\n }\n }\n }\n }\n' -): (typeof documents)['\n query getProfile {\n profilesCollection {\n edges {\n node {\n id\n full_name\n avatar_url\n }\n }\n }\n }\n']; + source: '\n query getProfile($profileId: UUID!) {\n profilesCollection(filter: { id: { eq: $profileId } }) {\n edges {\n node {\n id\n full_name\n avatar_url\n }\n }\n }\n }\n' +): (typeof documents)['\n query getProfile($profileId: UUID!) {\n profilesCollection(filter: { id: { eq: $profileId } }) {\n edges {\n node {\n id\n full_name\n avatar_url\n }\n }\n }\n }\n']; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/apps/next-app/src/graphql/__generated/gql/graphql.ts b/apps/next-app/src/graphql/__generated/gql/graphql.ts index fd319e6..c8e770d 100644 --- a/apps/next-app/src/graphql/__generated/gql/graphql.ts +++ b/apps/next-app/src/graphql/__generated/gql/graphql.ts @@ -439,7 +439,9 @@ export type ProfilesUpdateResponse = { records: Array; }; -export type GetProfileQueryVariables = Exact<{ [key: string]: never }>; +export type GetProfileQueryVariables = Exact<{ + profileId: Scalars['UUID']; +}>; export type GetProfileQuery = { __typename?: 'Query'; @@ -488,12 +490,53 @@ export const GetProfileDocument = { kind: 'OperationDefinition', operation: 'query', name: { kind: 'Name', value: 'getProfile' }, + variableDefinitions: [ + { + kind: 'VariableDefinition', + variable: { + kind: 'Variable', + name: { kind: 'Name', value: 'profileId' }, + }, + type: { + kind: 'NonNullType', + type: { kind: 'NamedType', name: { kind: 'Name', value: 'UUID' } }, + }, + }, + ], selectionSet: { kind: 'SelectionSet', selections: [ { kind: 'Field', name: { kind: 'Name', value: 'profilesCollection' }, + arguments: [ + { + kind: 'Argument', + name: { kind: 'Name', value: 'filter' }, + value: { + kind: 'ObjectValue', + fields: [ + { + kind: 'ObjectField', + name: { kind: 'Name', value: 'id' }, + value: { + kind: 'ObjectValue', + fields: [ + { + kind: 'ObjectField', + name: { kind: 'Name', value: 'eq' }, + value: { + kind: 'Variable', + name: { kind: 'Name', value: 'profileId' }, + }, + }, + ], + }, + }, + ], + }, + }, + ], selectionSet: { kind: 'SelectionSet', selections: [ diff --git a/apps/next-app/src/pages/dashboard/index.tsx b/apps/next-app/src/pages/dashboard/index.tsx index 98fe7e3..e87032b 100644 --- a/apps/next-app/src/pages/dashboard/index.tsx +++ b/apps/next-app/src/pages/dashboard/index.tsx @@ -56,6 +56,9 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { const session = await supabase.auth.getSession(); const client = getApolloServerClient(session.data.session?.access_token); - const { data } = await client.query({ query: GET_PROFILE }); + const { data } = await client.query({ + query: GET_PROFILE, + variables: { profileId: session.data.session?.user.id }, + }); return { props: { profile: data.profilesCollection } }; }; diff --git a/apps/next-app/src/pages/profile/index.tsx b/apps/next-app/src/pages/profile/index.tsx index eac5e10..8faa536 100644 --- a/apps/next-app/src/pages/profile/index.tsx +++ b/apps/next-app/src/pages/profile/index.tsx @@ -30,6 +30,9 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { const session = await supabase.auth.getSession(); const client = getApolloServerClient(session.data.session?.access_token); - const { data } = await client.query({ query: GET_PROFILE }); + const { data } = await client.query({ + query: GET_PROFILE, + variables: { profileId: session.data.session?.user.id }, + }); return { props: { profile: data.profilesCollection } }; }; diff --git a/apps/next-app/src/shared/queries/index.graphql.ts b/apps/next-app/src/shared/queries/index.graphql.ts index 0e98e6c..6bc0327 100644 --- a/apps/next-app/src/shared/queries/index.graphql.ts +++ b/apps/next-app/src/shared/queries/index.graphql.ts @@ -1,8 +1,8 @@ import { graphql } from 'graphql/__generated/gql'; export const GET_PROFILE = graphql(` - query getProfile { - profilesCollection { + query getProfile($profileId: UUID!) { + profilesCollection(filter: { id: { eq: $profileId } }) { edges { node { id From ce5dde077c3730da5dc65800ed5355233963da81 Mon Sep 17 00:00:00 2001 From: Marek Miklaszewski Date: Wed, 26 Apr 2023 10:49:04 +0200 Subject: [PATCH 5/8] [RAB-4] Create supabase edge functions, add vscode deno support --- apps/next-app/.vscode/settings.json | 7 ++ apps/next-app/supabase/.env.example | 4 + apps/next-app/supabase/.gitignore | 3 + apps/next-app/supabase/config.toml | 82 +++++++++++++++++++ .../supabase/functions/_shared/cors.ts | 5 ++ .../supabase/functions/_shared/stripe.ts | 6 ++ .../functions/_shared/supabaseClient.ts | 24 ++++++ .../functions/checkout-session/index.ts | 47 +++++++++++ .../functions/create-customer/index.ts | 34 ++++++++ .../functions/get-checkout-session/index.ts | 33 ++++++++ .../functions/get-stripe-charges/index.ts | 38 +++++++++ .../functions/get-subscription-plans/index.ts | 23 ++++++ .../supabase/functions/import_map.json | 7 ++ .../functions/stripe-webhooks/index.ts | 70 ++++++++++++++++ apps/next-app/supabase/seed.sql | 0 15 files changed, 383 insertions(+) create mode 100644 apps/next-app/.vscode/settings.json create mode 100644 apps/next-app/supabase/.env.example create mode 100644 apps/next-app/supabase/.gitignore create mode 100644 apps/next-app/supabase/config.toml create mode 100644 apps/next-app/supabase/functions/_shared/cors.ts create mode 100644 apps/next-app/supabase/functions/_shared/stripe.ts create mode 100644 apps/next-app/supabase/functions/_shared/supabaseClient.ts create mode 100644 apps/next-app/supabase/functions/checkout-session/index.ts create mode 100644 apps/next-app/supabase/functions/create-customer/index.ts create mode 100644 apps/next-app/supabase/functions/get-checkout-session/index.ts create mode 100644 apps/next-app/supabase/functions/get-stripe-charges/index.ts create mode 100644 apps/next-app/supabase/functions/get-subscription-plans/index.ts create mode 100644 apps/next-app/supabase/functions/import_map.json create mode 100644 apps/next-app/supabase/functions/stripe-webhooks/index.ts create mode 100644 apps/next-app/supabase/seed.sql diff --git a/apps/next-app/.vscode/settings.json b/apps/next-app/.vscode/settings.json new file mode 100644 index 0000000..20afb98 --- /dev/null +++ b/apps/next-app/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "deno.enable": true, + "deno.lint": true, + "deno.unstable": true, + "deno.enablePaths": ["./supabase/functions"], + "deno.importMap": "./supabase/functions/import_map.json" +} diff --git a/apps/next-app/supabase/.env.example b/apps/next-app/supabase/.env.example new file mode 100644 index 0000000..c2bad1f --- /dev/null +++ b/apps/next-app/supabase/.env.example @@ -0,0 +1,4 @@ +STRIPE_SECRET_KEY= +STRIPE_WEBHOOK_SIGNING_SECRET= +WEBAPP_CHECKOUT_SUCCESS_URL= +WEBAPP_CHECKOUT_CANCEL_URL= \ No newline at end of file diff --git a/apps/next-app/supabase/.gitignore b/apps/next-app/supabase/.gitignore new file mode 100644 index 0000000..773c7c3 --- /dev/null +++ b/apps/next-app/supabase/.gitignore @@ -0,0 +1,3 @@ +# Supabase +.branches +.temp diff --git a/apps/next-app/supabase/config.toml b/apps/next-app/supabase/config.toml new file mode 100644 index 0000000..db841db --- /dev/null +++ b/apps/next-app/supabase/config.toml @@ -0,0 +1,82 @@ +# A string used to distinguish different Supabase projects on the same host. Defaults to the working +# directory name when running `supabase init`. +project_id = "next-app" + +[api] +# Port to use for the API URL. +port = 54321 +# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API +# endpoints. public and storage are always included. +schemas = ["public", "storage", "graphql_public"] +# Extra schemas to add to the search_path of every request. public is always included. +extra_search_path = ["public", "extensions"] +# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size +# for accidental or malicious requests. +max_rows = 1000 + +[db] +# Port to use for the local database URL. +port = 54322 +# The database major version to use. This has to be the same as your remote database's. Run `SHOW +# server_version;` on the remote database to check. +major_version = 15 + +[studio] +# Port to use for Supabase Studio. +port = 54323 + +# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they +# are monitored, and you can view the emails that would have been sent from the web interface. +[inbucket] +# Port to use for the email testing server web interface. +port = 54324 +smtp_port = 54325 +pop3_port = 54326 + +[storage] +# The maximum file size allowed (e.g. "5MB", "500KB"). +file_size_limit = "50MiB" + +[auth] +# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used +# in emails. +site_url = "http://localhost:3000" +# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. +additional_redirect_urls = ["https://localhost:3000"] +# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one +# week). +jwt_expiry = 3600 +# Allow/disallow new user signups to your project. +enable_signup = true + +[auth.email] +# Allow/disallow new user signups via email to your project. +enable_signup = true +# If enabled, a user will be required to confirm any email change on both the old, and new email +# addresses. If disabled, only the new email is required to confirm. +double_confirm_changes = true +# If enabled, users need to confirm their email address before signing in. +enable_confirmations = false + +# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, +# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin`, `notion`, `twitch`, +# `twitter`, `slack`, `spotify`, `workos`, `zoom`. +[auth.external.apple] +enabled = false +client_id = "" +secret = "" +# Overrides the default auth redirectUrl. +redirect_uri = "" +# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, +# or any other third-party OIDC providers. +url = "" + +[analytics] +enabled = false +port = 54327 +vector_port = 54328 +# Setup BigQuery project to enable log viewer on local development stack. +# See: https://logflare.app/guides/bigquery-setup +gcp_project_id = "" +gcp_project_number = "" +gcp_jwt_path = "supabase/gcloud.json" diff --git a/apps/next-app/supabase/functions/_shared/cors.ts b/apps/next-app/supabase/functions/_shared/cors.ts new file mode 100644 index 0000000..5e97a4c --- /dev/null +++ b/apps/next-app/supabase/functions/_shared/cors.ts @@ -0,0 +1,5 @@ +export const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': + 'authorization, x-client-info, apikey, content-type', +}; diff --git a/apps/next-app/supabase/functions/_shared/stripe.ts b/apps/next-app/supabase/functions/_shared/stripe.ts new file mode 100644 index 0000000..dccd365 --- /dev/null +++ b/apps/next-app/supabase/functions/_shared/stripe.ts @@ -0,0 +1,6 @@ +import Stripe from 'stripe'; + +export const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY') ?? '', { + httpClient: Stripe.createFetchHttpClient(), + apiVersion: '2022-11-15', +}); diff --git a/apps/next-app/supabase/functions/_shared/supabaseClient.ts b/apps/next-app/supabase/functions/_shared/supabaseClient.ts new file mode 100644 index 0000000..53e8852 --- /dev/null +++ b/apps/next-app/supabase/functions/_shared/supabaseClient.ts @@ -0,0 +1,24 @@ +import { createClient } from '@supabase/supabase-js'; + +export const getSupabaseAuthClient = (req: Request) => + createClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_ANON_KEY')!, + { + global: { + headers: { Authorization: req.headers.get('Authorization')! }, + }, + } + ); + +export const getSupabaseClient = () => + createClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_ANON_KEY')! + ); + +export const getSupabaseServiceClient = () => + createClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! + ); diff --git a/apps/next-app/supabase/functions/checkout-session/index.ts b/apps/next-app/supabase/functions/checkout-session/index.ts new file mode 100644 index 0000000..adfab9e --- /dev/null +++ b/apps/next-app/supabase/functions/checkout-session/index.ts @@ -0,0 +1,47 @@ +import { serve } from 'std/server'; +import { corsHeaders } from '../_shared/cors.ts'; +import { stripe } from '../_shared/stripe.ts'; +import { getSupabaseAuthClient } from '../_shared/supabaseClient.ts'; + +serve(async (req: Request) => { + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }); + } + + const { plan } = await req.json(); + try { + const supabaseClient = getSupabaseAuthClient(req); + + const { + data: { user }, + } = await supabaseClient.auth.getUser(); + + const { data } = await supabaseClient + .from('profiles') + .select('stripe_customer_id') + .match({ id: user?.id }) + .single(); + + const session = await stripe.checkout.sessions.create({ + line_items: [{ price: plan, quantity: 1 }], + customer: data?.stripe_customer_id, + mode: 'subscription', + success_url: `${Deno.env.get( + 'WEBAPP_CHECKOUT_SUCCESS_URL' + )}?session_id={CHECKOUT_SESSION_ID}`, + cancel_url: `${Deno.env.get( + 'WEBAPP_CHECKOUT_CANCEL_URL' + )}?session_id={CHECKOUT_SESSION_ID}`, + }); + + return new Response(JSON.stringify({ session }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 200, + }); + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 400, + }); + } +}); diff --git a/apps/next-app/supabase/functions/create-customer/index.ts b/apps/next-app/supabase/functions/create-customer/index.ts new file mode 100644 index 0000000..0d9a8ba --- /dev/null +++ b/apps/next-app/supabase/functions/create-customer/index.ts @@ -0,0 +1,34 @@ +import { serve } from 'std/server'; +import { stripe } from '../_shared/stripe.ts'; +import { getSupabaseClient } from '../_shared/supabaseClient.ts'; + +const supabaseClient = getSupabaseClient(); + +serve(async (req: Request) => { + const { + record: { email, id }, + } = await req.json(); + try { + const customer = await stripe.customers.create({ + email, + metadata: { supabase_id: id }, + }); + + await supabaseClient + .from('profiles') + .update({ + stripe_customer_id: customer.id, + }) + .match({ id }); + + return new Response(JSON.stringify({ customerId: customer.id }), { + headers: { 'Content-Type': 'application/json' }, + status: 200, + }); + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { + headers: { 'Content-Type': 'application/json' }, + status: 400, + }); + } +}); diff --git a/apps/next-app/supabase/functions/get-checkout-session/index.ts b/apps/next-app/supabase/functions/get-checkout-session/index.ts new file mode 100644 index 0000000..9c5bfa2 --- /dev/null +++ b/apps/next-app/supabase/functions/get-checkout-session/index.ts @@ -0,0 +1,33 @@ +import { serve } from 'std/server'; +import { corsHeaders } from '../_shared/cors.ts'; +import { stripe } from '../_shared/stripe.ts'; + +serve(async (req: Request) => { + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }); + } + + try { + const url = new URL(req.url); + const sessionId = url.searchParams.get('session_id'); + + if (!sessionId) { + return new Response(JSON.stringify({}), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 400, + }); + } + + const session = await stripe.checkout.sessions.retrieve(sessionId); + + return new Response(JSON.stringify({ session }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 200, + }); + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 400, + }); + } +}); diff --git a/apps/next-app/supabase/functions/get-stripe-charges/index.ts b/apps/next-app/supabase/functions/get-stripe-charges/index.ts new file mode 100644 index 0000000..1d24db7 --- /dev/null +++ b/apps/next-app/supabase/functions/get-stripe-charges/index.ts @@ -0,0 +1,38 @@ +import { serve } from 'std/server'; +import { corsHeaders } from '../_shared/cors.ts'; +import { stripe } from '../_shared/stripe.ts'; +import { getSupabaseAuthClient } from '../_shared/supabaseClient.ts'; + +serve(async (req: Request) => { + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }); + } + + try { + const supabaseClient = getSupabaseAuthClient(req); + + const { + data: { user }, + } = await supabaseClient.auth.getUser(); + + const { data } = await supabaseClient + .from('profiles') + .select('stripe_customer_id') + .match({ id: user?.id }) + .single(); + + const charges = await stripe.charges.list({ + customer: data?.stripe_customer_id, + }); + + return new Response(JSON.stringify({ charges }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 200, + }); + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 400, + }); + } +}); diff --git a/apps/next-app/supabase/functions/get-subscription-plans/index.ts b/apps/next-app/supabase/functions/get-subscription-plans/index.ts new file mode 100644 index 0000000..34690e2 --- /dev/null +++ b/apps/next-app/supabase/functions/get-subscription-plans/index.ts @@ -0,0 +1,23 @@ +import { serve } from 'std/server'; +import { corsHeaders } from '../_shared/cors.ts'; +import { stripe } from '../_shared/stripe.ts'; + +serve(async (req: Request) => { + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }); + } + + try { + const plans = await stripe.plans.list(); + + return new Response(JSON.stringify({ plans }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 200, + }); + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 400, + }); + } +}); diff --git a/apps/next-app/supabase/functions/import_map.json b/apps/next-app/supabase/functions/import_map.json new file mode 100644 index 0000000..d6986c4 --- /dev/null +++ b/apps/next-app/supabase/functions/import_map.json @@ -0,0 +1,7 @@ +{ + "imports": { + "std/server": "https://deno.land/std@0.177.0/http/server.ts", + "stripe": "https://esm.sh/stripe@12.2.0?target=deno", + "@supabase/supabase-js": "https://esm.sh/@supabase/supabase-js@2.7.1" + } +} diff --git a/apps/next-app/supabase/functions/stripe-webhooks/index.ts b/apps/next-app/supabase/functions/stripe-webhooks/index.ts new file mode 100644 index 0000000..ea18cc7 --- /dev/null +++ b/apps/next-app/supabase/functions/stripe-webhooks/index.ts @@ -0,0 +1,70 @@ +import { serve } from 'std/server'; +import Stripe from 'stripe'; +import { stripe } from '../_shared/stripe.ts'; +import { getSupabaseServiceClient } from '../_shared/supabaseClient.ts'; + +const supabase = getSupabaseServiceClient(); +// This is needed in order to use the Web Crypto API in Deno. +const cryptoProvider = Stripe.createSubtleCryptoProvider(); + +serve(async (req: Request) => { + const signature = req.headers.get('Stripe-Signature'); + + // First step is to verify the event. The .text() method must be used as the + // verification relies on the raw req body rather than the parsed JSON. + const body = await req.text(); + let receivedEvent; + try { + receivedEvent = await stripe.webhooks.constructEventAsync( + body, + signature!, + Deno.env.get('STRIPE_WEBHOOK_SIGNING_SECRET')!, + undefined, + cryptoProvider + ); + } catch (err) { + return new Response(err.message, { status: 400 }); + } + + const requestOptions = + receivedEvent.request && receivedEvent.request.idempotency_key + ? { idempotencyKey: receivedEvent.request.idempotency_key } + : {}; + + let retrievedEvent; + try { + retrievedEvent = await stripe.events.retrieve( + receivedEvent.id, + requestOptions + ); + } catch (err) { + return new Response(err.message, { status: 400 }); + } + + const subscription = retrievedEvent.data.object; + + const updateUserSubscription = async (plan: string | null) => { + await supabase + .from('profiles') + .update({ + subscription: plan, + }) + .match({ stripe_customer_id: subscription.customer }); + }; + + if (retrievedEvent) { + switch (retrievedEvent.type) { + case 'customer.subscription.updated': + await updateUserSubscription( + subscription.items.data[0].plan.metadata.type + ); + break; + case 'customer.subscription.deleted': + await updateUserSubscription(null); + break; + default: + } + } + + return new Response(JSON.stringify({ ok: true }), { status: 200 }); +}); diff --git a/apps/next-app/supabase/seed.sql b/apps/next-app/supabase/seed.sql new file mode 100644 index 0000000..e69de29 From f6550f669bcc34e0705f6be8e8af580a569c39e3 Mon Sep 17 00:00:00 2001 From: Marek Miklaszewski Date: Wed, 26 Apr 2023 10:49:49 +0200 Subject: [PATCH 6/8] [RAB-4] Refactor app logic to connect to edge functions --- apps/next-app/.env.example | 4 +- .../src/constants/EDGE_FUNCTION_NAMES.ts | 7 +++ .../src/graphql/__generated/gql/gql.ts | 10 ++--- .../src/graphql/__generated/gql/graphql.ts | 28 ++++++++++++ .../api/subscription/checkout_session.ts | 42 ----------------- .../src/pages/api/subscription/get_plans.ts | 23 ---------- .../api/subscription/retrieve_session.ts | 27 ----------- .../src/pages/subscription/cancel.tsx | 16 +------ .../next-app/src/pages/subscription/index.tsx | 45 +++++++++---------- .../src/pages/subscription/success.tsx | 16 +------ .../src/shared/queries/index.graphql.ts | 2 + .../src/shared/services/axios/index.ts | 5 --- .../utils/handleUnactiveCheckoutRedirect.ts | 33 ++++++++++++++ 13 files changed, 101 insertions(+), 157 deletions(-) create mode 100644 apps/next-app/src/constants/EDGE_FUNCTION_NAMES.ts delete mode 100644 apps/next-app/src/pages/api/subscription/checkout_session.ts delete mode 100644 apps/next-app/src/pages/api/subscription/get_plans.ts delete mode 100644 apps/next-app/src/pages/api/subscription/retrieve_session.ts delete mode 100644 apps/next-app/src/shared/services/axios/index.ts create mode 100644 apps/next-app/src/utils/handleUnactiveCheckoutRedirect.ts diff --git a/apps/next-app/.env.example b/apps/next-app/.env.example index ab62596..e3baf50 100644 --- a/apps/next-app/.env.example +++ b/apps/next-app/.env.example @@ -1,6 +1,4 @@ -NEXT_PUBLIC_API_URL=http://localhost:3000/api NEXT_PUBLIC_SUPABASE_URL= NEXT_PUBLIC_SUPABASE_GRAPHQL_URL= NEXT_PUBLIC_SUPABASE_ANON_KEY= -NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY= -STRIPE_SECRET_KEY= \ No newline at end of file +NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY= \ No newline at end of file diff --git a/apps/next-app/src/constants/EDGE_FUNCTION_NAMES.ts b/apps/next-app/src/constants/EDGE_FUNCTION_NAMES.ts new file mode 100644 index 0000000..bd6ebcb --- /dev/null +++ b/apps/next-app/src/constants/EDGE_FUNCTION_NAMES.ts @@ -0,0 +1,7 @@ +export enum EDGE_FUNCTION_NAMES { + GET_SUBSCRIPTION_PLANS = '/get-subscription-plans', + CHECKOUT_SESSION = '/checkout-session', + CREATE_CUSTOMER = '/create-customer', + GET_STRIPE_CHARGES = '/get-stripe-charges', + RETRIEVE_CHECKOUT_SESSION = '/get-checkout-session', +} diff --git a/apps/next-app/src/graphql/__generated/gql/gql.ts b/apps/next-app/src/graphql/__generated/gql/gql.ts index d1d8e56..8cdf841 100644 --- a/apps/next-app/src/graphql/__generated/gql/gql.ts +++ b/apps/next-app/src/graphql/__generated/gql/gql.ts @@ -13,7 +13,7 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/ * Therefore it is highly recommended to use the babel or swc plugin for production. */ const documents = { - '\n query getProfile($profileId: UUID!) {\n profilesCollection(filter: { id: { eq: $profileId } }) {\n edges {\n node {\n id\n full_name\n avatar_url\n }\n }\n }\n }\n': + '\n query getProfile($profileId: UUID!) {\n profilesCollection(filter: { id: { eq: $profileId } }) {\n edges {\n node {\n id\n full_name\n avatar_url\n stripe_customer_id\n subscription\n }\n }\n }\n }\n': types.GetProfileDocument, '\n mutation updateAvatar($input: profilesUpdateInput!) {\n updateprofilesCollection(set: $input) {\n records {\n avatar_url\n }\n }\n }\n': types.UpdateAvatarDocument, @@ -39,20 +39,20 @@ export function graphql(source: string): unknown; * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: '\n query getProfile($profileId: UUID!) {\n profilesCollection(filter: { id: { eq: $profileId } }) {\n edges {\n node {\n id\n full_name\n avatar_url\n }\n }\n }\n }\n' -): (typeof documents)['\n query getProfile($profileId: UUID!) {\n profilesCollection(filter: { id: { eq: $profileId } }) {\n edges {\n node {\n id\n full_name\n avatar_url\n }\n }\n }\n }\n']; + source: '\n query getProfile($profileId: UUID!) {\n profilesCollection(filter: { id: { eq: $profileId } }) {\n edges {\n node {\n id\n full_name\n avatar_url\n stripe_customer_id\n subscription\n }\n }\n }\n }\n' +): typeof documents['\n query getProfile($profileId: UUID!) {\n profilesCollection(filter: { id: { eq: $profileId } }) {\n edges {\n node {\n id\n full_name\n avatar_url\n stripe_customer_id\n subscription\n }\n }\n }\n }\n']; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( source: '\n mutation updateAvatar($input: profilesUpdateInput!) {\n updateprofilesCollection(set: $input) {\n records {\n avatar_url\n }\n }\n }\n' -): (typeof documents)['\n mutation updateAvatar($input: profilesUpdateInput!) {\n updateprofilesCollection(set: $input) {\n records {\n avatar_url\n }\n }\n }\n']; +): typeof documents['\n mutation updateAvatar($input: profilesUpdateInput!) {\n updateprofilesCollection(set: $input) {\n records {\n avatar_url\n }\n }\n }\n']; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( source: '\n mutation updateProfile($input: profilesUpdateInput!) {\n updateprofilesCollection(set: $input) {\n records {\n full_name\n }\n }\n }\n' -): (typeof documents)['\n mutation updateProfile($input: profilesUpdateInput!) {\n updateprofilesCollection(set: $input) {\n records {\n full_name\n }\n }\n }\n']; +): typeof documents['\n mutation updateProfile($input: profilesUpdateInput!) {\n updateprofilesCollection(set: $input) {\n records {\n full_name\n }\n }\n }\n']; export function graphql(source: string) { return (documents as any)[source] ?? {}; diff --git a/apps/next-app/src/graphql/__generated/gql/graphql.ts b/apps/next-app/src/graphql/__generated/gql/graphql.ts index c8e770d..b55460d 100644 --- a/apps/next-app/src/graphql/__generated/gql/graphql.ts +++ b/apps/next-app/src/graphql/__generated/gql/graphql.ts @@ -362,10 +362,13 @@ export type CountriesUpdateResponse = { export type Profiles = Node & { __typename?: 'profiles'; avatar_url?: Maybe; + email: Scalars['String']; full_name?: Maybe; id: Scalars['UUID']; /** Globally Unique Record Identifier */ nodeId: Scalars['ID']; + stripe_customer_id?: Maybe; + subscription?: Maybe; updated_at?: Maybe; username?: Maybe; }; @@ -392,17 +395,23 @@ export type ProfilesEdge = { export type ProfilesFilter = { avatar_url?: InputMaybe; + email?: InputMaybe; full_name?: InputMaybe; id?: InputMaybe; nodeId?: InputMaybe; + stripe_customer_id?: InputMaybe; + subscription?: InputMaybe; updated_at?: InputMaybe; username?: InputMaybe; }; export type ProfilesInsertInput = { avatar_url?: InputMaybe; + email?: InputMaybe; full_name?: InputMaybe; id?: InputMaybe; + stripe_customer_id?: InputMaybe; + subscription?: InputMaybe; updated_at?: InputMaybe; username?: InputMaybe; }; @@ -417,16 +426,22 @@ export type ProfilesInsertResponse = { export type ProfilesOrderBy = { avatar_url?: InputMaybe; + email?: InputMaybe; full_name?: InputMaybe; id?: InputMaybe; + stripe_customer_id?: InputMaybe; + subscription?: InputMaybe; updated_at?: InputMaybe; username?: InputMaybe; }; export type ProfilesUpdateInput = { avatar_url?: InputMaybe; + email?: InputMaybe; full_name?: InputMaybe; id?: InputMaybe; + stripe_customer_id?: InputMaybe; + subscription?: InputMaybe; updated_at?: InputMaybe; username?: InputMaybe; }; @@ -454,6 +469,8 @@ export type GetProfileQuery = { id: any; full_name?: string | null; avatar_url?: string | null; + stripe_customer_id?: string | null; + subscription?: string | null; }; }>; } | null; @@ -564,6 +581,17 @@ export const GetProfileDocument = { kind: 'Field', name: { kind: 'Name', value: 'avatar_url' }, }, + { + kind: 'Field', + name: { + kind: 'Name', + value: 'stripe_customer_id', + }, + }, + { + kind: 'Field', + name: { kind: 'Name', value: 'subscription' }, + }, ], }, }, diff --git a/apps/next-app/src/pages/api/subscription/checkout_session.ts b/apps/next-app/src/pages/api/subscription/checkout_session.ts deleted file mode 100644 index e71142f..0000000 --- a/apps/next-app/src/pages/api/subscription/checkout_session.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ROUTES } from 'constants/ROUTES'; -import { SUBSCRIPTION_PLAN } from 'constants/SUBSCRIPTION_PLAN'; -import { NextApiRequest, NextApiResponse } from 'next'; -import Stripe from 'stripe'; - -const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { - apiVersion: '2022-11-15', -}); - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - if (req.method === 'POST') { - const { plan, email } = req.body; - try { - // Create Checkout Sessions from body params. - const session = await stripe.checkout.sessions.create({ - line_items: [ - { - price: - plan === SUBSCRIPTION_PLAN.BASIC - ? 'price_1N0MCfIqBsaLLecGvKVULmt9' - : 'price_1N0MDSIqBsaLLecG74B1hm1f', - quantity: 1, - }, - ], - customer_email: email, - mode: 'subscription', - - success_url: `${req.headers.origin}${ROUTES.SUBSCRIPTION_SUCCESS}?session_id={CHECKOUT_SESSION_ID}`, - cancel_url: `${req.headers.origin}${ROUTES.SUBSCRIPTION_CANCEL}?session_id={CHECKOUT_SESSION_ID}`, - }); - res.status(200).json(session); - } catch (err: any) { - res.status(err.statusCode || 500).json(err.message); - } - } else { - res.setHeader('Allow', 'POST'); - res.status(405).end('Method Not Allowed'); - } -} diff --git a/apps/next-app/src/pages/api/subscription/get_plans.ts b/apps/next-app/src/pages/api/subscription/get_plans.ts deleted file mode 100644 index ea1d142..0000000 --- a/apps/next-app/src/pages/api/subscription/get_plans.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { NextApiRequest, NextApiResponse } from 'next'; -import Stripe from 'stripe'; - -const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { - apiVersion: '2022-11-15', -}); - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - if (req.method === 'GET') { - try { - const plans = await stripe.plans.list(); - res.status(200).json({ plans }); - } catch (err: any) { - res.status(err.statusCode || 500).json(err.message); - } - } else { - res.setHeader('Allow', 'GET'); - res.status(405).end('Method Not Allowed'); - } -} diff --git a/apps/next-app/src/pages/api/subscription/retrieve_session.ts b/apps/next-app/src/pages/api/subscription/retrieve_session.ts deleted file mode 100644 index d04c6c4..0000000 --- a/apps/next-app/src/pages/api/subscription/retrieve_session.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { NextApiRequest, NextApiResponse } from 'next'; -import Stripe from 'stripe'; - -const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { - apiVersion: '2022-11-15', -}); - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - if (req.method === 'GET') { - try { - const { session_id } = req.query; - const session = await stripe.checkout.sessions.retrieve( - session_id as string - ); - - res.status(200).json({ session }); - } catch (err: any) { - res.status(err.statusCode || 500).json(err.message); - } - } else { - res.setHeader('Allow', 'GET'); - res.status(405).end('Method Not Allowed'); - } -} diff --git a/apps/next-app/src/pages/subscription/cancel.tsx b/apps/next-app/src/pages/subscription/cancel.tsx index f30c1f7..022c48b 100644 --- a/apps/next-app/src/pages/subscription/cancel.tsx +++ b/apps/next-app/src/pages/subscription/cancel.tsx @@ -2,7 +2,7 @@ import { ROUTES } from 'constants/ROUTES'; import CrossmarkSVG from 'assets/cross-mark.svg'; import Link from 'next/link'; import { GetServerSidePropsContext } from 'next'; -import { axiosApi } from 'shared/services/axios'; +import { handleUnactiveCheckoutRedirect } from 'utils/handleUnactiveCheckoutRedirect'; const Cancel = () => { return ( @@ -39,17 +39,5 @@ const Cancel = () => { export default Cancel; export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { - try { - const { session_id } = ctx.query; - await axiosApi.get( - `/subscription/retrieve_session?session_id=${session_id}` - ); - return { props: {} }; - } catch (err) { - return { - redirect: { - destination: ROUTES.HOME, - }, - }; - } + return handleUnactiveCheckoutRedirect(ctx); }; diff --git a/apps/next-app/src/pages/subscription/index.tsx b/apps/next-app/src/pages/subscription/index.tsx index bbd9f3d..0e7f688 100644 --- a/apps/next-app/src/pages/subscription/index.tsx +++ b/apps/next-app/src/pages/subscription/index.tsx @@ -1,7 +1,9 @@ -import { useUser } from '@supabase/auth-helpers-react'; +import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'; +import { useSupabaseClient } from '@supabase/auth-helpers-react'; +import { EDGE_FUNCTION_NAMES } from 'constants/EDGE_FUNCTION_NAMES'; import { SUBSCRIPTION_PLAN } from 'constants/SUBSCRIPTION_PLAN'; +import { GetServerSidePropsContext } from 'next'; import { SubscriptionCard } from 'shared/components/subscriptions/SubscriptionCard'; -import { axiosApi } from 'shared/services/axios'; import { getStripe } from 'utils/getStripe'; import { getSubscriptionPrice } from 'utils/getSubscriptionPrice'; @@ -10,27 +12,18 @@ interface SubscriptionProps { } const Subscription = ({ plans }: SubscriptionProps) => { - const user = useUser(); + const supabase = useSupabaseClient(); - const handleBasicPlan = async () => { + const handleBuyPlan = async (plan: string) => { try { - const { data } = await axiosApi.post('/subscription/checkout_session', { - plan: SUBSCRIPTION_PLAN.BASIC, - email: user?.email, - }); + const { data } = await supabase.functions.invoke( + EDGE_FUNCTION_NAMES.CHECKOUT_SESSION, + { + body: { plan }, + } + ); const stripe = await getStripe(); - await stripe?.redirectToCheckout({ sessionId: data.id }); - } catch (err) {} - }; - - const handleProPlan = async () => { - try { - const { data } = await axiosApi.post('/subscription/checkout_session', { - plan: SUBSCRIPTION_PLAN.PRO, - email: user?.email, - }); - const stripe = await getStripe(); - await stripe?.redirectToCheckout({ sessionId: data.id }); + await stripe?.redirectToCheckout({ sessionId: data.session.id }); } catch (err) {} }; @@ -50,14 +43,14 @@ const Subscription = ({ plans }: SubscriptionProps) => { name={basicPlan.metadata.name} price={basicPlan.amount} currency={basicPlan.currency.toUpperCase()} - handleClick={handleBasicPlan} + handleClick={() => handleBuyPlan(basicPlan.id)} /> handleBuyPlan(proPlan.id)} /> @@ -66,8 +59,12 @@ const Subscription = ({ plans }: SubscriptionProps) => { export default Subscription; -export const getServerSideProps = async () => { - const { data } = await axiosApi.get('/subscription/get_plans'); +export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { + const supabase = createServerSupabaseClient(ctx); + + const { data } = await supabase.functions.invoke( + EDGE_FUNCTION_NAMES.GET_SUBSCRIPTION_PLANS + ); const plans = data.plans.data.map((plan: any) => ({ ...plan, diff --git a/apps/next-app/src/pages/subscription/success.tsx b/apps/next-app/src/pages/subscription/success.tsx index 009cb5d..3bc2c7b 100644 --- a/apps/next-app/src/pages/subscription/success.tsx +++ b/apps/next-app/src/pages/subscription/success.tsx @@ -1,8 +1,8 @@ import { ROUTES } from 'constants/ROUTES'; import CheckmarkSVG from 'assets/check-mark.svg'; import Link from 'next/link'; -import { axiosApi } from 'shared/services/axios'; import { GetServerSidePropsContext } from 'next'; +import { handleUnactiveCheckoutRedirect } from 'utils/handleUnactiveCheckoutRedirect'; const Success = () => { return ( @@ -37,17 +37,5 @@ const Success = () => { export default Success; export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { - try { - const { session_id } = ctx.query; - await axiosApi.get( - `/subscription/retrieve_session?session_id=${session_id}` - ); - return { props: {} }; - } catch (err) { - return { - redirect: { - destination: ROUTES.HOME, - }, - }; - } + return handleUnactiveCheckoutRedirect(ctx); }; diff --git a/apps/next-app/src/shared/queries/index.graphql.ts b/apps/next-app/src/shared/queries/index.graphql.ts index 6bc0327..bd300fc 100644 --- a/apps/next-app/src/shared/queries/index.graphql.ts +++ b/apps/next-app/src/shared/queries/index.graphql.ts @@ -8,6 +8,8 @@ export const GET_PROFILE = graphql(` id full_name avatar_url + stripe_customer_id + subscription } } } diff --git a/apps/next-app/src/shared/services/axios/index.ts b/apps/next-app/src/shared/services/axios/index.ts deleted file mode 100644 index c62d2b8..0000000 --- a/apps/next-app/src/shared/services/axios/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import axios from 'axios'; - -export const axiosApi = axios.create({ - baseURL: process.env.NEXT_PUBLIC_API_URL, -}); diff --git a/apps/next-app/src/utils/handleUnactiveCheckoutRedirect.ts b/apps/next-app/src/utils/handleUnactiveCheckoutRedirect.ts new file mode 100644 index 0000000..9876a8f --- /dev/null +++ b/apps/next-app/src/utils/handleUnactiveCheckoutRedirect.ts @@ -0,0 +1,33 @@ +import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'; +import { EDGE_FUNCTION_NAMES } from 'constants/EDGE_FUNCTION_NAMES'; +import { ROUTES } from 'constants/ROUTES'; +import { GetServerSidePropsContext } from 'next'; + +export const handleUnactiveCheckoutRedirect = async ( + ctx: GetServerSidePropsContext +) => { + try { + const sessionId = ctx.query.session_id as string; + const supabase = createServerSupabaseClient(ctx); + + const { error } = await supabase.functions.invoke( + `${EDGE_FUNCTION_NAMES.RETRIEVE_CHECKOUT_SESSION}?session_id=${sessionId}` + ); + + if (error) { + return { + redirect: { + destination: ROUTES.HOME, + }, + }; + } + + return { props: {} }; + } catch (err) { + return { + redirect: { + destination: ROUTES.HOME, + }, + }; + } +}; From 85f45c3c53b513c590262b9e38ba76235621cf76 Mon Sep 17 00:00:00 2001 From: Marek Miklaszewski Date: Wed, 26 Apr 2023 11:01:01 +0200 Subject: [PATCH 7/8] [RAB-4] Move supabase to separate package --- .vscode/settings.json | 7 +++++++ apps/next-app/.vscode/settings.json | 7 ------- .../supabase/.env.example | 0 .../next-app => packages}/supabase/.gitignore | 3 +++ .../supabase/config.toml | 0 .../supabase/functions/_shared/cors.ts | 0 .../supabase/functions/_shared/stripe.ts | 0 .../functions/_shared/supabaseClient.ts | 0 .../functions/checkout-session/index.ts | 0 .../functions/create-customer/index.ts | 0 .../functions/get-checkout-session/index.ts | 0 .../functions/get-stripe-charges/index.ts | 20 +++++++++---------- .../functions/get-subscription-plans/index.ts | 0 .../supabase/functions/import_map.json | 0 .../functions/stripe-webhooks/index.ts | 0 {apps/next-app => packages}/supabase/seed.sql | 0 16 files changed, 20 insertions(+), 17 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 apps/next-app/.vscode/settings.json rename {apps/next-app => packages}/supabase/.env.example (100%) rename {apps/next-app => packages}/supabase/.gitignore (61%) rename {apps/next-app => packages}/supabase/config.toml (100%) rename {apps/next-app => packages}/supabase/functions/_shared/cors.ts (100%) rename {apps/next-app => packages}/supabase/functions/_shared/stripe.ts (100%) rename {apps/next-app => packages}/supabase/functions/_shared/supabaseClient.ts (100%) rename {apps/next-app => packages}/supabase/functions/checkout-session/index.ts (100%) rename {apps/next-app => packages}/supabase/functions/create-customer/index.ts (100%) rename {apps/next-app => packages}/supabase/functions/get-checkout-session/index.ts (100%) rename {apps/next-app => packages}/supabase/functions/get-stripe-charges/index.ts (53%) rename {apps/next-app => packages}/supabase/functions/get-subscription-plans/index.ts (100%) rename {apps/next-app => packages}/supabase/functions/import_map.json (100%) rename {apps/next-app => packages}/supabase/functions/stripe-webhooks/index.ts (100%) rename {apps/next-app => packages}/supabase/seed.sql (100%) diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..78f9b00 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "deno.enable": true, + "deno.lint": true, + "deno.unstable": true, + "deno.enablePaths": ["./packages/supabase/functions"], + "deno.importMap": "./packages/supabase/functions/import_map.json" +} diff --git a/apps/next-app/.vscode/settings.json b/apps/next-app/.vscode/settings.json deleted file mode 100644 index 20afb98..0000000 --- a/apps/next-app/.vscode/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "deno.enable": true, - "deno.lint": true, - "deno.unstable": true, - "deno.enablePaths": ["./supabase/functions"], - "deno.importMap": "./supabase/functions/import_map.json" -} diff --git a/apps/next-app/supabase/.env.example b/packages/supabase/.env.example similarity index 100% rename from apps/next-app/supabase/.env.example rename to packages/supabase/.env.example diff --git a/apps/next-app/supabase/.gitignore b/packages/supabase/.gitignore similarity index 61% rename from apps/next-app/supabase/.gitignore rename to packages/supabase/.gitignore index 773c7c3..45a4371 100644 --- a/apps/next-app/supabase/.gitignore +++ b/packages/supabase/.gitignore @@ -1,3 +1,6 @@ # Supabase .branches .temp + +.env +.env*.local \ No newline at end of file diff --git a/apps/next-app/supabase/config.toml b/packages/supabase/config.toml similarity index 100% rename from apps/next-app/supabase/config.toml rename to packages/supabase/config.toml diff --git a/apps/next-app/supabase/functions/_shared/cors.ts b/packages/supabase/functions/_shared/cors.ts similarity index 100% rename from apps/next-app/supabase/functions/_shared/cors.ts rename to packages/supabase/functions/_shared/cors.ts diff --git a/apps/next-app/supabase/functions/_shared/stripe.ts b/packages/supabase/functions/_shared/stripe.ts similarity index 100% rename from apps/next-app/supabase/functions/_shared/stripe.ts rename to packages/supabase/functions/_shared/stripe.ts diff --git a/apps/next-app/supabase/functions/_shared/supabaseClient.ts b/packages/supabase/functions/_shared/supabaseClient.ts similarity index 100% rename from apps/next-app/supabase/functions/_shared/supabaseClient.ts rename to packages/supabase/functions/_shared/supabaseClient.ts diff --git a/apps/next-app/supabase/functions/checkout-session/index.ts b/packages/supabase/functions/checkout-session/index.ts similarity index 100% rename from apps/next-app/supabase/functions/checkout-session/index.ts rename to packages/supabase/functions/checkout-session/index.ts diff --git a/apps/next-app/supabase/functions/create-customer/index.ts b/packages/supabase/functions/create-customer/index.ts similarity index 100% rename from apps/next-app/supabase/functions/create-customer/index.ts rename to packages/supabase/functions/create-customer/index.ts diff --git a/apps/next-app/supabase/functions/get-checkout-session/index.ts b/packages/supabase/functions/get-checkout-session/index.ts similarity index 100% rename from apps/next-app/supabase/functions/get-checkout-session/index.ts rename to packages/supabase/functions/get-checkout-session/index.ts diff --git a/apps/next-app/supabase/functions/get-stripe-charges/index.ts b/packages/supabase/functions/get-stripe-charges/index.ts similarity index 53% rename from apps/next-app/supabase/functions/get-stripe-charges/index.ts rename to packages/supabase/functions/get-stripe-charges/index.ts index 1d24db7..dafe20e 100644 --- a/apps/next-app/supabase/functions/get-stripe-charges/index.ts +++ b/packages/supabase/functions/get-stripe-charges/index.ts @@ -1,11 +1,11 @@ -import { serve } from 'std/server'; -import { corsHeaders } from '../_shared/cors.ts'; -import { stripe } from '../_shared/stripe.ts'; -import { getSupabaseAuthClient } from '../_shared/supabaseClient.ts'; +import { serve } from "std/server"; +import { corsHeaders } from "../_shared/cors.ts"; +import { stripe } from "../_shared/stripe.ts"; +import { getSupabaseAuthClient } from "../_shared/supabaseClient.ts"; serve(async (req: Request) => { - if (req.method === 'OPTIONS') { - return new Response('ok', { headers: corsHeaders }); + if (req.method === "OPTIONS") { + return new Response("ok", { headers: corsHeaders }); } try { @@ -16,8 +16,8 @@ serve(async (req: Request) => { } = await supabaseClient.auth.getUser(); const { data } = await supabaseClient - .from('profiles') - .select('stripe_customer_id') + .from("profiles") + .select("stripe_customer_id") .match({ id: user?.id }) .single(); @@ -26,12 +26,12 @@ serve(async (req: Request) => { }); return new Response(JSON.stringify({ charges }), { - headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + headers: { ...corsHeaders, "Content-Type": "application/json" }, status: 200, }); } catch (error) { return new Response(JSON.stringify({ error: error.message }), { - headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + headers: { ...corsHeaders, "Content-Type": "application/json" }, status: 400, }); } diff --git a/apps/next-app/supabase/functions/get-subscription-plans/index.ts b/packages/supabase/functions/get-subscription-plans/index.ts similarity index 100% rename from apps/next-app/supabase/functions/get-subscription-plans/index.ts rename to packages/supabase/functions/get-subscription-plans/index.ts diff --git a/apps/next-app/supabase/functions/import_map.json b/packages/supabase/functions/import_map.json similarity index 100% rename from apps/next-app/supabase/functions/import_map.json rename to packages/supabase/functions/import_map.json diff --git a/apps/next-app/supabase/functions/stripe-webhooks/index.ts b/packages/supabase/functions/stripe-webhooks/index.ts similarity index 100% rename from apps/next-app/supabase/functions/stripe-webhooks/index.ts rename to packages/supabase/functions/stripe-webhooks/index.ts diff --git a/apps/next-app/supabase/seed.sql b/packages/supabase/seed.sql similarity index 100% rename from apps/next-app/supabase/seed.sql rename to packages/supabase/seed.sql From 23a011a97b52f787ee649aff80c92da90d767106 Mon Sep 17 00:00:00 2001 From: Marek Miklaszewski Date: Wed, 26 Apr 2023 11:22:24 +0200 Subject: [PATCH 8/8] [RAB-4] Change functions directory --- packages/functions/package.json | 15 +++++++++++++++ packages/{ => functions}/supabase/.env.example | 0 packages/{ => functions}/supabase/.gitignore | 0 packages/{ => functions}/supabase/config.toml | 2 +- .../supabase/functions/_shared/cors.ts | 0 .../supabase/functions/_shared/stripe.ts | 0 .../supabase/functions/_shared/supabaseClient.ts | 0 .../supabase/functions/checkout-session/index.ts | 0 .../supabase/functions/create-customer/index.ts | 0 .../functions/get-checkout-session/index.ts | 0 .../functions/get-stripe-charges/index.ts | 0 .../functions/get-subscription-plans/index.ts | 0 .../supabase/functions/import_map.json | 0 .../supabase/functions/stripe-webhooks/index.ts | 0 packages/{ => functions}/supabase/seed.sql | 0 pnpm-lock.yaml | 6 ++++++ 16 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 packages/functions/package.json rename packages/{ => functions}/supabase/.env.example (100%) rename packages/{ => functions}/supabase/.gitignore (100%) rename packages/{ => functions}/supabase/config.toml (99%) rename packages/{ => functions}/supabase/functions/_shared/cors.ts (100%) rename packages/{ => functions}/supabase/functions/_shared/stripe.ts (100%) rename packages/{ => functions}/supabase/functions/_shared/supabaseClient.ts (100%) rename packages/{ => functions}/supabase/functions/checkout-session/index.ts (100%) rename packages/{ => functions}/supabase/functions/create-customer/index.ts (100%) rename packages/{ => functions}/supabase/functions/get-checkout-session/index.ts (100%) rename packages/{ => functions}/supabase/functions/get-stripe-charges/index.ts (100%) rename packages/{ => functions}/supabase/functions/get-subscription-plans/index.ts (100%) rename packages/{ => functions}/supabase/functions/import_map.json (100%) rename packages/{ => functions}/supabase/functions/stripe-webhooks/index.ts (100%) rename packages/{ => functions}/supabase/seed.sql (100%) diff --git a/packages/functions/package.json b/packages/functions/package.json new file mode 100644 index 0000000..133d696 --- /dev/null +++ b/packages/functions/package.json @@ -0,0 +1,15 @@ +{ + "name": "functions", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "supabase": "^1.50.12" + } +} diff --git a/packages/supabase/.env.example b/packages/functions/supabase/.env.example similarity index 100% rename from packages/supabase/.env.example rename to packages/functions/supabase/.env.example diff --git a/packages/supabase/.gitignore b/packages/functions/supabase/.gitignore similarity index 100% rename from packages/supabase/.gitignore rename to packages/functions/supabase/.gitignore diff --git a/packages/supabase/config.toml b/packages/functions/supabase/config.toml similarity index 99% rename from packages/supabase/config.toml rename to packages/functions/supabase/config.toml index db841db..32091e1 100644 --- a/packages/supabase/config.toml +++ b/packages/functions/supabase/config.toml @@ -1,6 +1,6 @@ # A string used to distinguish different Supabase projects on the same host. Defaults to the working # directory name when running `supabase init`. -project_id = "next-app" +project_id = "functions" [api] # Port to use for the API URL. diff --git a/packages/supabase/functions/_shared/cors.ts b/packages/functions/supabase/functions/_shared/cors.ts similarity index 100% rename from packages/supabase/functions/_shared/cors.ts rename to packages/functions/supabase/functions/_shared/cors.ts diff --git a/packages/supabase/functions/_shared/stripe.ts b/packages/functions/supabase/functions/_shared/stripe.ts similarity index 100% rename from packages/supabase/functions/_shared/stripe.ts rename to packages/functions/supabase/functions/_shared/stripe.ts diff --git a/packages/supabase/functions/_shared/supabaseClient.ts b/packages/functions/supabase/functions/_shared/supabaseClient.ts similarity index 100% rename from packages/supabase/functions/_shared/supabaseClient.ts rename to packages/functions/supabase/functions/_shared/supabaseClient.ts diff --git a/packages/supabase/functions/checkout-session/index.ts b/packages/functions/supabase/functions/checkout-session/index.ts similarity index 100% rename from packages/supabase/functions/checkout-session/index.ts rename to packages/functions/supabase/functions/checkout-session/index.ts diff --git a/packages/supabase/functions/create-customer/index.ts b/packages/functions/supabase/functions/create-customer/index.ts similarity index 100% rename from packages/supabase/functions/create-customer/index.ts rename to packages/functions/supabase/functions/create-customer/index.ts diff --git a/packages/supabase/functions/get-checkout-session/index.ts b/packages/functions/supabase/functions/get-checkout-session/index.ts similarity index 100% rename from packages/supabase/functions/get-checkout-session/index.ts rename to packages/functions/supabase/functions/get-checkout-session/index.ts diff --git a/packages/supabase/functions/get-stripe-charges/index.ts b/packages/functions/supabase/functions/get-stripe-charges/index.ts similarity index 100% rename from packages/supabase/functions/get-stripe-charges/index.ts rename to packages/functions/supabase/functions/get-stripe-charges/index.ts diff --git a/packages/supabase/functions/get-subscription-plans/index.ts b/packages/functions/supabase/functions/get-subscription-plans/index.ts similarity index 100% rename from packages/supabase/functions/get-subscription-plans/index.ts rename to packages/functions/supabase/functions/get-subscription-plans/index.ts diff --git a/packages/supabase/functions/import_map.json b/packages/functions/supabase/functions/import_map.json similarity index 100% rename from packages/supabase/functions/import_map.json rename to packages/functions/supabase/functions/import_map.json diff --git a/packages/supabase/functions/stripe-webhooks/index.ts b/packages/functions/supabase/functions/stripe-webhooks/index.ts similarity index 100% rename from packages/supabase/functions/stripe-webhooks/index.ts rename to packages/functions/supabase/functions/stripe-webhooks/index.ts diff --git a/packages/supabase/seed.sql b/packages/functions/supabase/seed.sql similarity index 100% rename from packages/supabase/seed.sql rename to packages/functions/supabase/seed.sql diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2116df6..adb1782 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -122,6 +122,12 @@ importers: specifier: ^1.50.12 version: 1.50.12 + packages/functions: + devDependencies: + supabase: + specifier: ^1.50.12 + version: 1.50.12 + packages: /@adobe/css-tools@4.2.0: