Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions client/src/apiCalls.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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)
}
Expand Down
154 changes: 154 additions & 0 deletions client/src/components/CheckoutForm.js
Original file line number Diff line number Diff line change
@@ -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 (
<form onSubmit={handleSubmit} className={classes.root}>
<div className={classes.container}>
<label>
Card number
<CardNumberElement
onChange={onChangeNumber}
options={CARD_ELEMENT_OPTIONS}
/>
</label>
</div>
<div className={classes.container}>
<label>
Expiration date
<CardExpiryElement
onChange={onChangeExpiry}
options={CARD_ELEMENT_OPTIONS}
/>
</label>
</div>
<div className={classes.container}>
<label>
CVC
<CardCvcElement
onChange={onChangeCVC}
options={CARD_ELEMENT_OPTIONS}
/>
</label>
</div>
<div className={classes.centered}>
<button className={classes.button}>Confirm order</button>
</div>
</form>
)
}
3 changes: 1 addition & 2 deletions client/src/components/CreateContestForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,7 @@ export default function CreateContestForm() {
const minutes = time.substr(3, 5)
deadline.setHours(hours)
deadline.setMinutes(minutes)
const contestCreator = user.username
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 => {
Expand Down
89 changes: 74 additions & 15 deletions client/src/pages/ContestDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ 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 { loadStripe } from '@stripe/stripe-js'
import { Elements } from '@stripe/react-stripe-js'
import {
Button,
Typography,
Expand All @@ -21,9 +24,10 @@ import {
Radio,
} from '@material-ui/core'

import RequestError, { getContestDetails, setContestWinner } from '../apiCalls'
import RequestError, { getContestDetails, checkStripeIDExists, createPayment } from '../apiCalls'
import { UserContext } from '../App'

const stripePromise = loadStripe("pk_test_51IH8tAGt0nFBLozrVPDB6mwr6yEw9QOAVWhTQK0hErAcLv7F278Dz2f3P637jEaboOp7lJbAVnZNbQTxQUoqD91z00VxrG6s3T")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to change this to a config var so that @edyhtan can replace accordingly

const useStyles = makeStyles((theme) => ({
pageContainer: {
height: 'calc(100vh - 100px)',
Expand Down Expand Up @@ -127,6 +131,17 @@ const useStyles = makeStyles((theme) => ({
dialogImage: {
width: '100%',
height: '100%'
},
contestOverText: {
color: 'red',
backgroundColor: '#1f1f1f',
fontSize: '1.5rem',
padding: '1rem'
},
paymentDialog: {
'& .MuiDialog-paper': {
minWidth: '600px'
}
}
}))

Expand All @@ -135,6 +150,7 @@ export default function ContestDetails(props) {
const {user, setUser} = useContext(UserContext)
const [activeTab, setActiveTab] = useState(0)
const [openDesignDialog, setOpenDesignDialog] = useState(false)
const [openPaymentDialog, setOpenPaymentDialog] = useState(false)
const [openInspirationalDialog, setOpenInspirationalDialog] = useState(false)
const [contest, setContest] = useState(null)
const [designOpening, setDesignOpening] = useState({})
Expand All @@ -143,6 +159,7 @@ export default function ContestDetails(props) {
const [inspirationalGridListItems, setInspirationalGridListItems] = 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()

Expand All @@ -155,21 +172,36 @@ export default function ContestDetails(props) {
setOpenInspirationalDialog(false);
}

const handlePaymentDialogClose = () => {
setOpenPaymentDialog(false)
}

const handleWinnerChange = e => {
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) => {
checkStripeIDExists( // check if payment is set up before allow user submit a winner
(data) => {
let intentSecret = data["intent_id"]
if (intentSecret != null){
const amount = contest.prize_contest
const currency = 'usd'

createPayment(amount, currency, data => {
setClientSecret(data.client_secret)
setOpenPaymentDialog(true)
}, 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 => {
Expand Down Expand Up @@ -198,7 +230,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) {
Expand All @@ -214,8 +245,17 @@ export default function ContestDetails(props) {
// eslint-disable-next-line
}, [])

useEffect(() => {
//when payment dialog close, should fetch new data
getContestInfo(contestId)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you getting a warning that contestId needs to be part of this dependency ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, there was a warning, I just add contestId into the dependency array

}, [openPaymentDialog, contestId])

useEffect(() => {
if (contest) {
// Hide submit button when contest is over
if (contest.winner !== null) {
setSubmitButton(null)
}
var newGridListItems = null
const newInspirationalGridListItems = contest.attached_inspirational_images.map((design, index) => (
<GridListTile key={index}>
Expand Down Expand Up @@ -258,7 +298,9 @@ export default function ContestDetails(props) {
} else {
newGridListItems = <div>There is no submitted designs</div>
}
createSubmitDesignButton()
if (Date.parse(contest.deadline_date) < Date.now() && contest.winner == null) {
createSubmitDesignButton()
}
}
setDesignGridListItems(newGridListItems)
}
Expand Down Expand Up @@ -286,6 +328,7 @@ export default function ContestDetails(props) {

return (
contest !== null ? (
<Elements stripe={stripePromise}>
<div className={classes.pageContainer}>
<div className={classes.containerWrapper}>
<Dialog open={openDesignDialog} onClose={handleClose} className={classes.dialog}>
Expand All @@ -299,13 +342,27 @@ export default function ContestDetails(props) {
<Button onClick={handleClose} color="primary">Close</Button>
</DialogActions>
</Dialog>
<Dialog open={openPaymentDialog} onClose={handlePaymentDialogClose} className={classes.paymentDialog}>
<DialogTitle>
Checkout Form
</DialogTitle>
<DialogContent>
<CheckoutForm
clientSecret={clientSecret}
contestId={contestId}
winningSubmission={winningSubmission}
setOpenPaymentDialog={setOpenPaymentDialog}
/>
</DialogContent>
</Dialog>

<Dialog open={openInspirationalDialog} onClose={handleClose} className={classes.dialog}>
<DialogContent>
<img src={inspirationalOpening} className={classes.dialogImage} />
<img src={inspirationalOpening} className={classes.dialogImage} alt="" />
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">Close</Button>
</DialogActions>
</DialogActions>
</Dialog>
<div className={classes.backButtonDiv}>
<div className={classes.backButton} onClick={onBackButtonClick}>
Expand All @@ -332,6 +389,7 @@ export default function ContestDetails(props) {
</Grid>
</Grid>
</div>
{contest.winner !== null ? <div className={classes.contestOverText}>This contest is over</div> : <div></div>}
<div className={classes.contestDesigns}>
<Paper>
<Tabs
Expand Down Expand Up @@ -363,6 +421,7 @@ export default function ContestDetails(props) {
</div>
</div>
</div>
</Elements>
) : (
<div className={classes.pageContainer}>
<div className={classes.backButtonDiv}>
Expand Down
Loading