Add Stripe membership form
This commit is contained in:
parent
9a32b8dfc0
commit
896bb5242a
150
package-lock.json
generated
150
package-lock.json
generated
@ -8,12 +8,15 @@
|
|||||||
"name": "wscc-website",
|
"name": "wscc-website",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^1.7.13",
|
"@headlessui/react": "1.7.17",
|
||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.0.18",
|
||||||
"@mapbox/rehype-prism": "^0.8.0",
|
"@mapbox/rehype-prism": "^0.8.0",
|
||||||
"@mdx-js/loader": "^2.3.0",
|
"@mdx-js/loader": "^2.3.0",
|
||||||
"@mdx-js/react": "^2.3.0",
|
"@mdx-js/react": "^2.3.0",
|
||||||
"@next/mdx": "13.4.16",
|
"@next/mdx": "13.4.16",
|
||||||
|
"@stripe/react-stripe-js": "2.4.0",
|
||||||
|
"@stripe/stripe-js": "2.2.1",
|
||||||
|
"@tailwindcss/forms": "0.5.7",
|
||||||
"@tailwindcss/typography": "^0.5.4",
|
"@tailwindcss/typography": "^0.5.4",
|
||||||
"@types/node": "20.4.7",
|
"@types/node": "20.4.7",
|
||||||
"@types/react": "18.2.18",
|
"@types/react": "18.2.18",
|
||||||
@ -29,6 +32,7 @@
|
|||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
|
"stripe": "14.9.0",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"typescript": "5.1.6"
|
"typescript": "5.1.6"
|
||||||
},
|
},
|
||||||
@ -129,9 +133,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@headlessui/react": {
|
"node_modules/@headlessui/react": {
|
||||||
"version": "1.7.15",
|
"version": "1.7.17",
|
||||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.15.tgz",
|
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.17.tgz",
|
||||||
"integrity": "sha512-OTO0XtoRQ6JPB1cKNFYBZv2Q0JMqMGNhYP1CjPvcJvjz8YGokz8oAj89HIYZGN0gZzn/4kk9iUpmMF4Q21Gsqw==",
|
"integrity": "sha512-4am+tzvkqDSSgiwrsEpGWqgGo9dz8qU5M3znCkC4PgkpY4HcCZzEDEvozltGGGHIKl9jbXbZPSH5TWn4sWJdow==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"client-only": "^0.0.1"
|
"client-only": "^0.0.1"
|
||||||
},
|
},
|
||||||
@ -565,6 +569,24 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@stripe/react-stripe-js": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-1jVQEL3OuhuzNlf4OdfqovHt+MkWh8Uh8xpLxx/xUFUDdF+7/kDOrGKy+xJO3WLCfZUL7NAy+/ypwXbbYZi0tg==",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@stripe/stripe-js": "^1.44.1 || ^2.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@stripe/stripe-js": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-Wd81A0u8EwT3cf+Xv1mpMI18RbXVhgh19MtPcF9ojNTlG3kl36B1+XFe1KQfnJxD3WRnVfDuI0rNCK53mcGm6g=="
|
||||||
|
},
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz",
|
||||||
@ -584,6 +606,17 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tailwindcss/forms": {
|
||||||
|
"version": "0.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz",
|
||||||
|
"integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==",
|
||||||
|
"dependencies": {
|
||||||
|
"mini-svg-data-uri": "^1.2.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tailwindcss/typography": {
|
"node_modules/@tailwindcss/typography": {
|
||||||
"version": "0.5.9",
|
"version": "0.5.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.9.tgz",
|
||||||
@ -1683,7 +1716,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.1",
|
"function-bind": "^1.1.1",
|
||||||
"get-intrinsic": "^1.0.2"
|
"get-intrinsic": "^1.0.2"
|
||||||
@ -3524,7 +3556,6 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
||||||
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
|
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.1",
|
"function-bind": "^1.1.1",
|
||||||
"has": "^1.0.3",
|
"has": "^1.0.3",
|
||||||
@ -3836,7 +3867,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
|
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
@ -3848,7 +3878,6 @@
|
|||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
@ -6714,6 +6743,14 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mini-svg-data-uri": {
|
||||||
|
"version": "1.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
||||||
|
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
|
||||||
|
"bin": {
|
||||||
|
"mini-svg-data-uri": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
@ -7129,7 +7166,6 @@
|
|||||||
"version": "1.12.3",
|
"version": "1.12.3",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
||||||
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
|
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
|
||||||
"dev": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
@ -7863,7 +7899,6 @@
|
|||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
@ -7912,6 +7947,20 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/qs": {
|
||||||
|
"version": "6.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
|
||||||
|
"integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
|
||||||
|
"dependencies": {
|
||||||
|
"side-channel": "^1.0.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/queue-microtask": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
@ -7994,8 +8043,7 @@
|
|||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@ -8635,7 +8683,6 @@
|
|||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.0",
|
"call-bind": "^1.0.0",
|
||||||
"get-intrinsic": "^1.0.2",
|
"get-intrinsic": "^1.0.2",
|
||||||
@ -8994,6 +9041,18 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stripe": {
|
||||||
|
"version": "14.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/stripe/-/stripe-14.9.0.tgz",
|
||||||
|
"integrity": "sha512-t2XdpNbRH4x3MYEoxNWhwUPl9D80aUd5OHds0zhDiwRYPZ0H7MrUI/dj9wOSYlzycD3xdvjn0q7pWeFWljtMUQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": ">=8.1.0",
|
||||||
|
"qs": "^6.11.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/style-to-object": {
|
"node_modules/style-to-object": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.1.tgz",
|
||||||
@ -10419,9 +10478,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@headlessui/react": {
|
"@headlessui/react": {
|
||||||
"version": "1.7.15",
|
"version": "1.7.17",
|
||||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.15.tgz",
|
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.17.tgz",
|
||||||
"integrity": "sha512-OTO0XtoRQ6JPB1cKNFYBZv2Q0JMqMGNhYP1CjPvcJvjz8YGokz8oAj89HIYZGN0gZzn/4kk9iUpmMF4Q21Gsqw==",
|
"integrity": "sha512-4am+tzvkqDSSgiwrsEpGWqgGo9dz8qU5M3znCkC4PgkpY4HcCZzEDEvozltGGGHIKl9jbXbZPSH5TWn4sWJdow==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"client-only": "^0.0.1"
|
"client-only": "^0.0.1"
|
||||||
}
|
}
|
||||||
@ -10695,6 +10754,19 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
|
||||||
"integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ=="
|
"integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ=="
|
||||||
},
|
},
|
||||||
|
"@stripe/react-stripe-js": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-1jVQEL3OuhuzNlf4OdfqovHt+MkWh8Uh8xpLxx/xUFUDdF+7/kDOrGKy+xJO3WLCfZUL7NAy+/ypwXbbYZi0tg==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@stripe/stripe-js": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-Wd81A0u8EwT3cf+Xv1mpMI18RbXVhgh19MtPcF9ojNTlG3kl36B1+XFe1KQfnJxD3WRnVfDuI0rNCK53mcGm6g=="
|
||||||
|
},
|
||||||
"@swc/helpers": {
|
"@swc/helpers": {
|
||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz",
|
||||||
@ -10711,6 +10783,14 @@
|
|||||||
"defer-to-connect": "^1.0.1"
|
"defer-to-connect": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@tailwindcss/forms": {
|
||||||
|
"version": "0.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz",
|
||||||
|
"integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==",
|
||||||
|
"requires": {
|
||||||
|
"mini-svg-data-uri": "^1.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@tailwindcss/typography": {
|
"@tailwindcss/typography": {
|
||||||
"version": "0.5.9",
|
"version": "0.5.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.9.tgz",
|
||||||
@ -11550,7 +11630,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"function-bind": "^1.1.1",
|
"function-bind": "^1.1.1",
|
||||||
"get-intrinsic": "^1.0.2"
|
"get-intrinsic": "^1.0.2"
|
||||||
@ -12895,7 +12974,6 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
||||||
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
|
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"function-bind": "^1.1.1",
|
"function-bind": "^1.1.1",
|
||||||
"has": "^1.0.3",
|
"has": "^1.0.3",
|
||||||
@ -13125,14 +13203,12 @@
|
|||||||
"has-proto": {
|
"has-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
|
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"has-symbols": {
|
"has-symbols": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"has-tostringtag": {
|
"has-tostringtag": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@ -15094,6 +15170,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
|
||||||
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="
|
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="
|
||||||
},
|
},
|
||||||
|
"mini-svg-data-uri": {
|
||||||
|
"version": "1.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
||||||
|
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg=="
|
||||||
|
},
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
@ -15390,8 +15471,7 @@
|
|||||||
"object-inspect": {
|
"object-inspect": {
|
||||||
"version": "1.12.3",
|
"version": "1.12.3",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
||||||
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
|
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"object-keys": {
|
"object-keys": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
@ -15843,7 +15923,6 @@
|
|||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
@ -15882,6 +15961,14 @@
|
|||||||
"escape-goat": "^2.0.0"
|
"escape-goat": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"qs": {
|
||||||
|
"version": "6.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
|
||||||
|
"integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
|
||||||
|
"requires": {
|
||||||
|
"side-channel": "^1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"queue-microtask": {
|
"queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
@ -15940,8 +16027,7 @@
|
|||||||
"react-is": {
|
"react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"read-cache": {
|
"read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@ -16394,7 +16480,6 @@
|
|||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"call-bind": "^1.0.0",
|
"call-bind": "^1.0.0",
|
||||||
"get-intrinsic": "^1.0.2",
|
"get-intrinsic": "^1.0.2",
|
||||||
@ -16643,6 +16728,15 @@
|
|||||||
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
|
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"stripe": {
|
||||||
|
"version": "14.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/stripe/-/stripe-14.9.0.tgz",
|
||||||
|
"integrity": "sha512-t2XdpNbRH4x3MYEoxNWhwUPl9D80aUd5OHds0zhDiwRYPZ0H7MrUI/dj9wOSYlzycD3xdvjn0q7pWeFWljtMUQ==",
|
||||||
|
"requires": {
|
||||||
|
"@types/node": ">=8.1.0",
|
||||||
|
"qs": "^6.11.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"style-to-object": {
|
"style-to-object": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.1.tgz",
|
||||||
|
@ -10,12 +10,15 @@
|
|||||||
},
|
},
|
||||||
"browserslist": "defaults, not ie <= 11",
|
"browserslist": "defaults, not ie <= 11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^1.7.13",
|
"@headlessui/react": "1.7.17",
|
||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.0.18",
|
||||||
"@mapbox/rehype-prism": "^0.8.0",
|
"@mapbox/rehype-prism": "^0.8.0",
|
||||||
"@mdx-js/loader": "^2.3.0",
|
"@mdx-js/loader": "^2.3.0",
|
||||||
"@mdx-js/react": "^2.3.0",
|
"@mdx-js/react": "^2.3.0",
|
||||||
"@next/mdx": "13.4.16",
|
"@next/mdx": "13.4.16",
|
||||||
|
"@stripe/react-stripe-js": "2.4.0",
|
||||||
|
"@stripe/stripe-js": "2.2.1",
|
||||||
|
"@tailwindcss/forms": "0.5.7",
|
||||||
"@tailwindcss/typography": "^0.5.4",
|
"@tailwindcss/typography": "^0.5.4",
|
||||||
"@types/node": "20.4.7",
|
"@types/node": "20.4.7",
|
||||||
"@types/react": "18.2.18",
|
"@types/react": "18.2.18",
|
||||||
@ -31,6 +34,7 @@
|
|||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
|
"stripe": "14.9.0",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"typescript": "5.1.6"
|
"typescript": "5.1.6"
|
||||||
},
|
},
|
||||||
|
60
src/app/api/stripe_intent/route.ts
Normal file
60
src/app/api/stripe_intent/route.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import Stripe from 'stripe';
|
||||||
|
|
||||||
|
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '');
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest): Promise<NextResponse> {
|
||||||
|
const { amount, payment_intent_id } = await request.json();
|
||||||
|
|
||||||
|
if (payment_intent_id) {
|
||||||
|
try {
|
||||||
|
// If a payment_intent_id is passed, retrieve the paymentIntent
|
||||||
|
const current_intent = await stripe.paymentIntents.retrieve(
|
||||||
|
payment_intent_id,
|
||||||
|
);
|
||||||
|
// If a paymentIntent is retrieved update its amount
|
||||||
|
if (current_intent) {
|
||||||
|
const updated_intent = await stripe.paymentIntents.update(
|
||||||
|
payment_intent_id,
|
||||||
|
{
|
||||||
|
amount: amount,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return NextResponse.json(updated_intent, { status: 200 });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Catch any error and return a status 500
|
||||||
|
if ((e as any).code !== 'resource_missing') {
|
||||||
|
const errorMessage =
|
||||||
|
e instanceof Error ? e.message : 'Internal server error';
|
||||||
|
return NextResponse.json(
|
||||||
|
{ statusCode: 500, message: errorMessage },
|
||||||
|
{ status: 500 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create PaymentIntent
|
||||||
|
const params = {
|
||||||
|
amount: amount,
|
||||||
|
currency: 'usd',
|
||||||
|
payment_method_types: ['card'],
|
||||||
|
automatic_payment_methods: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const payment_intent = await stripe.paymentIntents.create(params);
|
||||||
|
//Return the payment_intent object
|
||||||
|
return NextResponse.json(payment_intent, { status: 200 });
|
||||||
|
} catch (err) {
|
||||||
|
const errorMessage =
|
||||||
|
err instanceof Error ? err.message : 'Internal server error';
|
||||||
|
return NextResponse.json(
|
||||||
|
{ statusCode: 500, message: errorMessage },
|
||||||
|
{ status: 500 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import { type Metadata } from 'next'
|
import { type Metadata } from 'next'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
@ -6,6 +7,7 @@ import clsx from 'clsx'
|
|||||||
import { Container } from '@/components/Container'
|
import { Container } from '@/components/Container'
|
||||||
import { UserPlusIcon, GiftIcon, EnvelopeIcon } from '@heroicons/react/24/solid'
|
import { UserPlusIcon, GiftIcon, EnvelopeIcon } from '@heroicons/react/24/solid'
|
||||||
import interiorEmptyImage from '@/images/photos/interior-empty.jpg'
|
import interiorEmptyImage from '@/images/photos/interior-empty.jpg'
|
||||||
|
import { ClubPayment } from './payment';
|
||||||
|
|
||||||
function SocialLink({
|
function SocialLink({
|
||||||
className,
|
className,
|
||||||
@ -37,6 +39,7 @@ export const metadata: Metadata = {
|
|||||||
'The West Sound Community Club on Orcas Island.',
|
'The West Sound Community Club on Orcas Island.',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: Replace interiorEmptyImage with a photo from a potluck
|
// TODO: Replace interiorEmptyImage with a photo from a potluck
|
||||||
|
|
||||||
export default function Club() {
|
export default function Club() {
|
||||||
@ -97,6 +100,9 @@ export default function Club() {
|
|||||||
</SocialLink>
|
</SocialLink>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<ClubPayment />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
395
src/app/club/payment.tsx
Normal file
395
src/app/club/payment.tsx
Normal file
@ -0,0 +1,395 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import React, { useEffect, useState, FormEvent } from 'react';
|
||||||
|
import { Elements } from '@stripe/react-stripe-js';
|
||||||
|
import { Appearance, StripeElementsOptions, loadStripe } from '@stripe/stripe-js';
|
||||||
|
import {
|
||||||
|
PaymentElement,
|
||||||
|
useStripe,
|
||||||
|
useElements,
|
||||||
|
} from '@stripe/react-stripe-js';
|
||||||
|
import { RadioGroup } from '@headlessui/react'
|
||||||
|
|
||||||
|
const stripe = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY || '');
|
||||||
|
|
||||||
|
const membershipLevels = [
|
||||||
|
{ id: 1, title: 'Individual', description: 'One vote', price: 20 },
|
||||||
|
{ id: 2, title: 'Household', description: 'Two votes', price: 30 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const additionalDonationLevels = [10, 50, 100, -1];
|
||||||
|
|
||||||
|
function classNames(...classes: string[]) {
|
||||||
|
return classes.filter(Boolean).join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function Spinner(): React.JSX.Element {
|
||||||
|
return (<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M10.72,19.9a8,8,0,0,1-6.5-9.79A7.77,7.77,0,0,1,10.4,4.16a8,8,0,0,1,9.49,6.52A1.54,1.54,0,0,0,21.38,12h.13a1.37,1.37,0,0,0,1.38-1.54,11,11,0,1,0-12.7,12.39A1.54,1.54,0,0,0,12,21.34h0A1.47,1.47,0,0,0,10.72,19.9Z">
|
||||||
|
<animateTransform attributeName="transform" dur="1s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12" />
|
||||||
|
</path>
|
||||||
|
</svg>);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CheckoutForm({
|
||||||
|
paymentIntentID
|
||||||
|
}: {
|
||||||
|
paymentIntentID: string
|
||||||
|
}): React.JSX.Element {
|
||||||
|
const [selectedMembershipLevel, setSelectedMembershipLevel] = useState(membershipLevels[0]);
|
||||||
|
const [selectedAdditionalDonation, setSelectedAdditionalDonation] = useState<number | null>(null);
|
||||||
|
const [customAmount, setCustomAmount] = useState('');
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [phone, setPhone] = useState('');
|
||||||
|
const [street, setStreet] = useState('');
|
||||||
|
const [zip, setZip] = useState('');
|
||||||
|
const [totalAmount, setTotalAmount] = useState(300);
|
||||||
|
const [message, setMessage] = useState<string>('');
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const stripe = useStripe();
|
||||||
|
const elements = useElements();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!stripe) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Grab the client secret from url params
|
||||||
|
const clientSecret = new URLSearchParams(window.location.search).get(
|
||||||
|
'payment_intent_client_secret'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!clientSecret) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => {
|
||||||
|
switch (paymentIntent?.status) {
|
||||||
|
case 'succeeded':
|
||||||
|
setMessage('Payment succeeded!');
|
||||||
|
break;
|
||||||
|
case 'processing':
|
||||||
|
setMessage('Your payment is processing.');
|
||||||
|
break;
|
||||||
|
case 'requires_payment_method':
|
||||||
|
setMessage('Your payment was not successful, please try again.');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setMessage('Something went wrong.');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [stripe]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let subtotal = selectedMembershipLevel.price
|
||||||
|
|
||||||
|
if (selectedAdditionalDonation && selectedAdditionalDonation !== -1) {
|
||||||
|
subtotal += selectedAdditionalDonation
|
||||||
|
} else if (customAmount !== '') {
|
||||||
|
try {
|
||||||
|
subtotal += parseFloat(customAmount)
|
||||||
|
} catch {
|
||||||
|
console.error('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTotalAmount(subtotal);
|
||||||
|
|
||||||
|
fetch('api/stripe_intent', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
amount: subtotal * 100,
|
||||||
|
payment_intent_id: paymentIntentID,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}, [paymentIntentID, selectedMembershipLevel, selectedAdditionalDonation, customAmount])
|
||||||
|
|
||||||
|
const handleSubmit = async (e: FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!stripe || !elements) {
|
||||||
|
// Stripe.js has not yet loaded.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const { error } = await stripe.confirmPayment({
|
||||||
|
elements,
|
||||||
|
confirmParams: {
|
||||||
|
return_url: window.location.origin + '/thank-you',
|
||||||
|
receipt_email: email,
|
||||||
|
payment_method_data: {
|
||||||
|
billing_details: {
|
||||||
|
name: 'Billing user',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error.type === 'card_error' || error.type === 'validation_error') {
|
||||||
|
setMessage(error.message || '');
|
||||||
|
} else {
|
||||||
|
setMessage('An unexpected error occured.');
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<form id="payment-form" onSubmit={handleSubmit} className="m-auto space-y-4">
|
||||||
|
|
||||||
|
{/* Membership Type */}
|
||||||
|
<RadioGroup value={selectedMembershipLevel} onChange={setSelectedMembershipLevel} className="space-y-3">
|
||||||
|
<RadioGroup.Label className="text-base font-semibold leading-6 text-gray-900">
|
||||||
|
Select a membership type
|
||||||
|
</RadioGroup.Label>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-y-6 sm:grid-cols-2 sm:gap-x-4">
|
||||||
|
{membershipLevels.map((membership) => (
|
||||||
|
<RadioGroup.Option
|
||||||
|
key={membership.id}
|
||||||
|
value={membership}
|
||||||
|
className={({ active }) =>
|
||||||
|
classNames(
|
||||||
|
active ? 'border-indigo-600 ring-2 ring-indigo-600' : 'border-gray-300',
|
||||||
|
'relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{({ checked, active }) => (
|
||||||
|
<>
|
||||||
|
<span className="flex flex-1 items-center justify-between ">
|
||||||
|
<span className="flex flex-col">
|
||||||
|
<RadioGroup.Label as="span" className="block text-sm font-medium text-gray-900">
|
||||||
|
{membership.title}
|
||||||
|
</RadioGroup.Label>
|
||||||
|
<RadioGroup.Description as="span" className="mt-1 flex items-center text-sm text-gray-500">
|
||||||
|
{membership.description}
|
||||||
|
</RadioGroup.Description>
|
||||||
|
</span>
|
||||||
|
<RadioGroup.Description as="span" className="ml-8 text-sm font-medium">
|
||||||
|
<span className="text-gray-900">${membership.price}</span>
|
||||||
|
<span className="text-gray-500">/yr</span>
|
||||||
|
</RadioGroup.Description>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
active ? 'border' : 'border-2',
|
||||||
|
checked ? 'border-indigo-600' : 'border-transparent',
|
||||||
|
'pointer-events-none absolute -inset-px rounded-lg'
|
||||||
|
)}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</RadioGroup.Option>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
|
||||||
|
{/* Additional donation */}
|
||||||
|
<RadioGroup value={selectedAdditionalDonation} onChange={setSelectedAdditionalDonation} className="space-y-3">
|
||||||
|
<RadioGroup.Label className="text-base font-semibold leading-6 text-gray-900">
|
||||||
|
Additional donation
|
||||||
|
</RadioGroup.Label>
|
||||||
|
<div className="grid grid-cols-3 gap-3 sm:grid-cols-5">
|
||||||
|
{additionalDonationLevels.map((option) => (
|
||||||
|
<RadioGroup.Option
|
||||||
|
key={option}
|
||||||
|
value={option}
|
||||||
|
className={({ active, checked }) =>
|
||||||
|
classNames(
|
||||||
|
'cursor-pointer focus:outline-none',
|
||||||
|
option === -1 ? 'col-span-2' : '',
|
||||||
|
checked
|
||||||
|
? 'ring-2 ring-indigo-600 ring-offset-2'
|
||||||
|
: 'ring-1 ring-inset ring-gray-300 bg-white text-gray-900 hover:bg-gray-50',
|
||||||
|
'flex items-center justify-center rounded-md py-3 px-3 text-sm font-semibold sm:flex-1'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
|
{option === -1
|
||||||
|
? (
|
||||||
|
<RadioGroup.Label as="span" className="flex flex-col items-center">
|
||||||
|
<div className="relative rounded-md shadow-sm">
|
||||||
|
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||||
|
<span className="text-gray-500 sm:text-sm">$</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="custom-amount"
|
||||||
|
id="custom-amount"
|
||||||
|
value={customAmount}
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
onChange={(e) => setCustomAmount(e.target.value)}
|
||||||
|
className="block w-full rounded-md border-0 py-1.5 pl-7 pr-12 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-1 focus:ring-inset focus:ring-gray-500 sm:text-sm sm:leading-6"
|
||||||
|
placeholder="Custom"
|
||||||
|
aria-describedby="price-currency"
|
||||||
|
/>
|
||||||
|
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
|
||||||
|
<span className="text-gray-500 sm:text-sm" id="price-currency">
|
||||||
|
/yr
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</RadioGroup.Label>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<RadioGroup.Label as="span">
|
||||||
|
<span>${option}</span>
|
||||||
|
<span className="text-gray-500">/yr</span>
|
||||||
|
</RadioGroup.Label>
|
||||||
|
)}
|
||||||
|
</RadioGroup.Option>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h2 className="text-base font-semibold leading-6 text-gray-900">
|
||||||
|
About you
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="rounded-md mb-3 px-3 pb-1.5 pt-2.5 shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-indigo-600">
|
||||||
|
<label htmlFor="name" className="block text-xs font-medium text-gray-900">
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
id="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
className="block w-full border-0 p-0 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
|
||||||
|
placeholder="you@example.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-md mb-3 px-3 pb-1.5 pt-2.5 shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-indigo-600">
|
||||||
|
<label htmlFor="name" className="block text-xs font-medium text-gray-900">
|
||||||
|
Phone
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="tel"
|
||||||
|
name="phone"
|
||||||
|
id="phone"
|
||||||
|
value={phone}
|
||||||
|
onChange={(e) => setPhone(e.target.value)}
|
||||||
|
className="block w-full border-0 p-0 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
|
||||||
|
placeholder="360-376-XXXX"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<fieldset>
|
||||||
|
<div className="mb-3 -space-y-px rounded-md bg-white shadow-sm">
|
||||||
|
<div className="flex -space-x-px">
|
||||||
|
<div className="w-2/3 min-w-0 flex-1">
|
||||||
|
<label htmlFor="street-address" className="sr-only">
|
||||||
|
Street Address
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="street-address"
|
||||||
|
id="street-address"
|
||||||
|
value={street}
|
||||||
|
onChange={(e) => setStreet(e.target.value)}
|
||||||
|
className="relative block w-full rounded-none rounded-l-md border-0 bg-transparent py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||||
|
placeholder="Street"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<label htmlFor="zipcode" className="sr-only">
|
||||||
|
Zipcode
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="zipcode"
|
||||||
|
id="zipcode"
|
||||||
|
value={zip}
|
||||||
|
onChange={(e) => setZip(e.target.value)}
|
||||||
|
className="relative block w-full rounded-none rounded-r-md border-0 bg-transparent py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||||
|
placeholder="98XXX"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h2 className="text-base font-semibold leading-6 text-gray-900">
|
||||||
|
Payment
|
||||||
|
</h2>
|
||||||
|
<div className="mt-1 text-sm text-gray-500">
|
||||||
|
If you would like to pay by cash or check, please instead
|
||||||
|
<a className="underline mx-1" href="/WSCC-Membership-Form.pdf">fill out a paper form</a>
|
||||||
|
and mail to the address on the form.
|
||||||
|
</div>
|
||||||
|
<PaymentElement id="payment-element" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* TODO: Automatically renew toggle? */}
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="mt-6 w-full rounded-md flex justify-center border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||||
|
disabled={isLoading || !stripe || !elements}
|
||||||
|
id="submit"
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<Spinner />
|
||||||
|
) : (
|
||||||
|
'Pay $' + totalAmount
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Show any error or success messages */}
|
||||||
|
{message && <div id="payment-message">{message}</div>}
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ClubPayment() {
|
||||||
|
const [clientSecret, setClientSecret] = useState('');
|
||||||
|
const [paymentIntent, setPaymentIntent] = useState('');
|
||||||
|
useEffect(() => {
|
||||||
|
// Create PaymentIntent as soon as the page loads using our local API
|
||||||
|
fetch('api/stripe_intent', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
amount: 30000,
|
||||||
|
payment_intent_id: '',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
setClientSecret(data.client_secret);
|
||||||
|
setPaymentIntent(data.id);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const appearance: Appearance = {
|
||||||
|
theme: 'stripe',
|
||||||
|
labels: 'floating',
|
||||||
|
};
|
||||||
|
|
||||||
|
const options: StripeElementsOptions = {
|
||||||
|
clientSecret,
|
||||||
|
appearance
|
||||||
|
}
|
||||||
|
return <>
|
||||||
|
{clientSecret && (
|
||||||
|
<Elements options={options}
|
||||||
|
stripe={stripe}>
|
||||||
|
<CheckoutForm paymentIntentID={paymentIntent} />
|
||||||
|
</Elements>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -3,15 +3,19 @@ import { type Metadata } from 'next'
|
|||||||
import { SimpleLayout } from '@/components/SimpleLayout'
|
import { SimpleLayout } from '@/components/SimpleLayout'
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'You’re subscribed',
|
title: 'Thank You',
|
||||||
description: 'Thanks for subscribing to my newsletter.',
|
description: 'Thanks for becoming a member.',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ThankYou() {
|
export default function ThankYou() {
|
||||||
return (
|
return (
|
||||||
<SimpleLayout
|
<SimpleLayout
|
||||||
title="Thanks for subscribing."
|
title="Thanks for becoming a member."
|
||||||
intro="I’ll send you an email any time I publish a new blog post, release a new project, or have anything interesting to share that I think you’d want to hear about. You can unsubscribe at any time, no hard feelings."
|
intro="Thank you for joining the West Sound Community Club."
|
||||||
/>
|
>
|
||||||
|
<p className="mt-6 text-base text-zinc-600 dark:text-zinc-400">
|
||||||
|
Your financial support helps us perserve this historic building and to host events for the community. We'll add you to our member mailing list so you receive announcement emails about upcoming events.
|
||||||
|
</p>
|
||||||
|
</SimpleLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import typographyPlugin from '@tailwindcss/typography'
|
import typographyPlugin from '@tailwindcss/typography'
|
||||||
|
import formsPlugin from '@tailwindcss/forms';
|
||||||
import { type Config } from 'tailwindcss'
|
import { type Config } from 'tailwindcss'
|
||||||
|
|
||||||
import typographyStyles from './typography'
|
import typographyStyles from './typography'
|
||||||
@ -6,7 +7,10 @@ import typographyStyles from './typography'
|
|||||||
export default {
|
export default {
|
||||||
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
||||||
darkMode: 'class',
|
darkMode: 'class',
|
||||||
plugins: [typographyPlugin],
|
plugins: [
|
||||||
|
typographyPlugin,
|
||||||
|
formsPlugin,
|
||||||
|
],
|
||||||
theme: {
|
theme: {
|
||||||
fontSize: {
|
fontSize: {
|
||||||
xs: ['0.8125rem', { lineHeight: '1.5rem' }],
|
xs: ['0.8125rem', { lineHeight: '1.5rem' }],
|
||||||
|
Loading…
Reference in New Issue
Block a user