From 9e47aa21b41fb9e6109de2a5c4aa25326968b19f Mon Sep 17 00:00:00 2001 From: banh0006 Date: Thu, 4 Feb 2021 18:53:29 -0500 Subject: [PATCH 1/4] charging contest owner when selecting winner and fix hardcode creater id in create contest page --- client/src/apiCalls.js | 16 +++++- client/src/components/CreateContestForm.js | 3 +- client/src/pages/ContestDetails.js | 60 +++++++++++++++++----- server/api/contest_handler.py | 8 +-- server/api/payment_handler.py | 27 ++++++---- 5 files changed, 85 insertions(+), 29 deletions(-) diff --git a/client/src/apiCalls.js b/client/src/apiCalls.js index e64e746..11d2b08 100644 --- a/client/src/apiCalls.js +++ b/client/src/apiCalls.js @@ -63,13 +63,12 @@ export const register = async (username, password, email, onSuccess, onError) => makeRequest("/api/register", "POST", {"Content-Type": "application/json"}, JSON.stringify(data), onSuccess, onError) } -export const createContest = async (title, description, prize_contest, deadline_date, contest_creator, inspirational_images, onSuccess, onError) => { +export const createContest = async (title, description, prize_contest, deadline_date, inspirational_images, onSuccess, onError) => { const data = { 'title': title, 'description': description, 'prize_contest': prize_contest, 'deadline_date': deadline_date, - 'contest_creator': contest_creator, 'inspirational_images': inspirational_images } @@ -89,6 +88,19 @@ export const getStripeID = async (onSuccess, onError) => { return true } +export const checkStripeIDExists = async (onSuccess, onError) => { + get("/api/get_stripe_id", {}, onSuccess, onError) +} + +export const createPayment = async (amount, currency, onSuccess, onError) => { + const data = { + amount: amount, + currency: currency + } + + makeRequest('/api/create_payment', 'POST', {"Content-Type": "application/json"}, JSON.stringify(data), onSuccess, onError) +} + export const getOwnedContests = async (userId, onSuccess, onError) => { get(`/contests/owned/${userId}`, {"Content-Type": "application/json"}, onSuccess, onError) } diff --git a/client/src/components/CreateContestForm.js b/client/src/components/CreateContestForm.js index cb2da6a..d850bbf 100644 --- a/client/src/components/CreateContestForm.js +++ b/client/src/components/CreateContestForm.js @@ -128,8 +128,7 @@ export default function CreateContestForm() { const minutes = time.substr(3, 5) deadline.setHours(hours) deadline.setMinutes(minutes) - const contestCreator = 1 - createContest(title, description, amount, deadline, contestCreator, checkedInspireationalImages, data => { + createContest(title, description, amount, deadline, checkedInspireationalImages, data => { alert('contest has been successfully created!') history.push(`/contest-details/${data.contest_id}`) }, error => { diff --git a/client/src/pages/ContestDetails.js b/client/src/pages/ContestDetails.js index 90372cc..e24e398 100644 --- a/client/src/pages/ContestDetails.js +++ b/client/src/pages/ContestDetails.js @@ -21,7 +21,7 @@ import { Radio, } from '@material-ui/core' -import RequestError, { getContestDetails, setContestWinner } from '../apiCalls' +import RequestError, { getContestDetails, setContestWinner, checkStripeIDExists, createPayment } from '../apiCalls' const useStyles = makeStyles((theme) => ({ pageContainer: { @@ -120,6 +120,12 @@ const useStyles = makeStyles((theme) => ({ dialogImage: { width: '100%', height: '100%' + }, + contestOverText: { + color: 'red', + backgroundColor: '#1f1f1f', + fontSize: '1.5rem', + padding: '1rem' } })) @@ -147,17 +153,44 @@ export default function ContestDetails(props) { setWinningSubmission(parseInt(e.target.value)) } - const onWinnerSubmit = e => { - setContestWinner(contestId, winningSubmission, data => { - console.log('Winner Successfully Declared') - }, error => { - if (error instanceof RequestError && error.status === 400) { - console.log(error.body) - } else { - console.log("unexpected error") + const onWinnerSubmit = async(e) => { + var intentSecret = null + checkStripeIDExists( // check if payment is set up before allow user submit a winner + (data) => { + intentSecret = data["intent_id"] + console.log(intentSecret) + if (intentSecret != null){ + let amount = contest.prize_contest + let currency = 'usd' + + createPayment(amount, currency, data => { + console.log(data.msg) + + setContestWinner(contestId, winningSubmission, data => { + alert('Winner Successfully Declared') + console.log('Winner Successfully Declared') + getContestInfo(contestId) + }, error => { + if (error instanceof RequestError && error.status === 400) { + console.log(error.body) + } else { + console.log("unexpected error") + } + }) + + }, error => { + console.log("unexpected error") + }) + + + } else { + alert("Please set up your payment before selecting a winner.") + } + }, + (error) => { + alert("Please set up your payment before selecting a winner.") } - }) - getContestInfo(contestId) + ) } const onBackButtonClick = e => { history.push('/profile') @@ -227,7 +260,9 @@ export default function ContestDetails(props) { } else { newGridListItems =
There is no images to display
} - createSubmitDesignButton() + if (Date.parse(contest.deadline_date) < Date.now() && contest.winner == null) { + createSubmitDesignButton() + } } setGridListItems(newGridListItems) } @@ -293,6 +328,7 @@ export default function ContestDetails(props) { + {contest.winner !== null ?
This contest is over
:
}
Date: Thu, 4 Feb 2021 22:02:55 -0500 Subject: [PATCH 2/4] added checkout form and call confirmCardPayment method --- client/src/components/CheckoutForm.js | 154 ++++++++++++++++++++++++++ client/src/pages/ContestDetails.js | 56 +++++++--- 2 files changed, 194 insertions(+), 16 deletions(-) create mode 100644 client/src/components/CheckoutForm.js diff --git a/client/src/components/CheckoutForm.js b/client/src/components/CheckoutForm.js new file mode 100644 index 0000000..8720b8b --- /dev/null +++ b/client/src/components/CheckoutForm.js @@ -0,0 +1,154 @@ +import React, { useState } from 'react' +import { CardNumberElement, CardExpiryElement, CardCvcElement, useElements, useStripe} from '@stripe/react-stripe-js' +import { makeStyles } from "@material-ui/core/styles" +import RequestError, { setContestWinner } from '../apiCalls' + +const CARD_ELEMENT_OPTIONS = { + showIcon: true, + style: { + base: { + color: "#32325d", + fontFamily: '"Helvetica Neue", Helvetica, sans-serif', + fontSmoothing: "antialiased", + fontSize: "16px", + "::placeholder": { + color: "#aab7c4", + }, + }, + invalid: { + color: "#fa755a", + iconColor: "#fa755a", + }, + }, +} + +const useStyles = makeStyles(theme => ({ + root: { + alignItems: "center", + }, + container: { + width:300, + marginTop: theme.spacing(2), + '& .StripeElement': { + minWidth: '400px' + } + }, + centered: { + display: "flex", + alignItems: "center", + justifyContent: "center", + }, + button: { + marginTop: theme.spacing(5), + padding: '0.5rem' + } +})) + +export default function CheckoutForm(props) { + const [numberError, setNumberError] = useState(null); + const [expiryError, setExpiryError] = useState(null); + const [cvcError, setCVCError] = useState(null); + const stripe = useStripe() + const elements = useElements() + const classes = useStyles() + + const handleSubmit = async (event) => { + event.preventDefault() + + if (!stripe || !elements) { + // Stripe.js has not yet loaded. + // Make sure to disable form submission until Stripe.js has loaded. + return + } + + const result = await stripe.confirmCardPayment(props.clientSecret, { + payment_method: { + card: elements.getElement(CardNumberElement), + billing_details: { + name: 'Jenny Rosen', + }, + } + }) + + if (result.error) { + // Show error to your customer (e.g., insufficient funds) + console.log(result.error.message) + } else { + // The payment has been processed! + if (result.paymentIntent.status === 'succeeded') { + setContestWinner(props.contestId, props.winningSubmission, data => { + console.log('Winner Successfully Declared') + alert('Winner Successfully Declared') + }, error => { + if (error instanceof RequestError && error.status === 400) { + console.log(error.body) + } else { + console.log("unexpected error") + } + }) + } else { + alert('Winner Successfully Declared') + } + } + props.setOpenPaymentDialog(false) + } + + const onChangeNumber = (event) => { + if (event.error) { + setNumberError(event.error.message); + } else { + setNumberError(null); + } + } + + const onChangeExpiry = (event) => { + if (event.error) { + setExpiryError(event.error.message); + } else { + setExpiryError(null); + } + } + + const onChangeCVC = (event) => { + if (event.error) { + setCVCError(event.error.message); + } else { + setCVCError(null); + } + } + + return ( +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ ) +} \ No newline at end of file diff --git a/client/src/pages/ContestDetails.js b/client/src/pages/ContestDetails.js index e24e398..1a0f517 100644 --- a/client/src/pages/ContestDetails.js +++ b/client/src/pages/ContestDetails.js @@ -3,6 +3,10 @@ import { Link } from 'react-router-dom' import { useHistory } from 'react-router-dom' import { makeStyles } from '@material-ui/core/styles' import TabPanel from '../components/TabPanel' +import CheckoutForm from '../components/CheckoutForm' +// import CreditCardForm from '../components/CreditCardForm' +import { loadStripe } from '@stripe/stripe-js' +import { Elements } from '@stripe/react-stripe-js' import { Button, Typography, @@ -21,8 +25,9 @@ import { Radio, } from '@material-ui/core' -import RequestError, { getContestDetails, setContestWinner, checkStripeIDExists, createPayment } from '../apiCalls' +import RequestError, { getContestDetails, checkStripeIDExists, createPayment } from '../apiCalls' +const stripePromise = loadStripe("pk_test_51IH8tAGt0nFBLozrVPDB6mwr6yEw9QOAVWhTQK0hErAcLv7F278Dz2f3P637jEaboOp7lJbAVnZNbQTxQUoqD91z00VxrG6s3T") const useStyles = makeStyles((theme) => ({ pageContainer: { height: 'calc(100vh - 100px)', @@ -126,6 +131,11 @@ const useStyles = makeStyles((theme) => ({ backgroundColor: '#1f1f1f', fontSize: '1.5rem', padding: '1rem' + }, + paymentDialog: { + '& .MuiDialog-paper': { + minWidth: '600px' + } } })) @@ -133,11 +143,13 @@ export default function ContestDetails(props) { const classes = useStyles() const [activeTab, setActiveTab] = useState(0) const [openDesignDialog, setOpenDesignDialog] = useState(false) + const [openPaymentDialog, setOpenPaymentDialog] = useState(false) const [contest, setContest] = useState(null) const [designOpening, setDesignOpening] = useState({}) const [gridListItems, setGridListItems] = useState(null) const [submitButton, setSubmitButton] = useState(null) const [winningSubmission, setWinningSubmission] = useState(null) + const [clientSecret, setClientSecret] = useState("") const contestId = props.match.params.id const history = useHistory() @@ -149,6 +161,10 @@ export default function ContestDetails(props) { setOpenDesignDialog(false); } + const handlePaymentDialogClose = () => { + setOpenPaymentDialog(false) + } + const handleWinnerChange = e => { setWinningSubmission(parseInt(e.target.value)) } @@ -165,24 +181,12 @@ export default function ContestDetails(props) { createPayment(amount, currency, data => { console.log(data.msg) - - setContestWinner(contestId, winningSubmission, data => { - alert('Winner Successfully Declared') - console.log('Winner Successfully Declared') - getContestInfo(contestId) - }, error => { - if (error instanceof RequestError && error.status === 400) { - console.log(error.body) - } else { - console.log("unexpected error") - } - }) - + console.log(data.client_secret) + setClientSecret(data.client_secret) + setOpenPaymentDialog(true) }, error => { console.log("unexpected error") }) - - } else { alert("Please set up your payment before selecting a winner.") } @@ -222,6 +226,11 @@ export default function ContestDetails(props) { // eslint-disable-next-line }, []) + useEffect(() => { + //when payment dialog close, should fetch new data + getContestInfo(contestId) + }, [openPaymentDialog]) + useEffect(() => { if (contest) { var newGridListItems = null @@ -290,6 +299,7 @@ export default function ContestDetails(props) { return ( contest !== null ? ( +
@@ -303,6 +313,19 @@ export default function ContestDetails(props) { + + + Checkout Form + + + + +
{`<`} @@ -352,6 +375,7 @@ export default function ContestDetails(props) {
+ ) : (
From bbb9df3706b774cff4d0b41dac5f78a6f497ba62 Mon Sep 17 00:00:00 2001 From: banh0006 Date: Fri, 5 Feb 2021 10:02:03 -0500 Subject: [PATCH 3/4] fixed minor issues from CR --- client/src/pages/ContestDetails.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/client/src/pages/ContestDetails.js b/client/src/pages/ContestDetails.js index 1a0f517..171b497 100644 --- a/client/src/pages/ContestDetails.js +++ b/client/src/pages/ContestDetails.js @@ -4,7 +4,6 @@ import { useHistory } from 'react-router-dom' import { makeStyles } from '@material-ui/core/styles' import TabPanel from '../components/TabPanel' import CheckoutForm from '../components/CheckoutForm' -// import CreditCardForm from '../components/CreditCardForm' import { loadStripe } from '@stripe/stripe-js' import { Elements } from '@stripe/react-stripe-js' import { @@ -170,18 +169,14 @@ export default function ContestDetails(props) { } const onWinnerSubmit = async(e) => { - var intentSecret = null checkStripeIDExists( // check if payment is set up before allow user submit a winner (data) => { - intentSecret = data["intent_id"] - console.log(intentSecret) + let intentSecret = data["intent_id"] if (intentSecret != null){ - let amount = contest.prize_contest - let currency = 'usd' + const amount = contest.prize_contest + const currency = 'usd' createPayment(amount, currency, data => { - console.log(data.msg) - console.log(data.client_secret) setClientSecret(data.client_secret) setOpenPaymentDialog(true) }, error => { @@ -210,7 +205,6 @@ export default function ContestDetails(props) { const getContestInfo = contestId => { getContestDetails(contestId, (data) => { - console.log(data) data.title ? setContest(data) : setContest(null) }, (error) => { if (error instanceof RequestError && error.status === 400) { @@ -229,7 +223,7 @@ export default function ContestDetails(props) { useEffect(() => { //when payment dialog close, should fetch new data getContestInfo(contestId) - }, [openPaymentDialog]) + }, [openPaymentDialog, contestId]) useEffect(() => { if (contest) { From c76ed9f6b9d5100207701d59870c05c75bdf6cab Mon Sep 17 00:00:00 2001 From: banh0006 Date: Fri, 5 Feb 2021 11:09:07 -0500 Subject: [PATCH 4/4] hide submit button when contest is over --- client/src/pages/ContestDetails.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/src/pages/ContestDetails.js b/client/src/pages/ContestDetails.js index 171b497..d6f13eb 100644 --- a/client/src/pages/ContestDetails.js +++ b/client/src/pages/ContestDetails.js @@ -227,6 +227,10 @@ export default function ContestDetails(props) { useEffect(() => { if (contest) { + // Hide submit button when contest is over + if (contest.winner !== null) { + setSubmitButton(null) + } var newGridListItems = null if (contest.hasOwnProperty('is_owner')) { // If contest does not have property 'designs', that means it is not the contest owner accessing the page if (contest.designs.length > 0) { // Render this if it is the contest owner and there have been designs submitted