@@ -3,6 +3,7 @@ import getConfig from 'next/config'
33import moment from 'moment'
44import copy from 'copy-to-clipboard'
55import jwt from 'jsonwebtoken'
6+ import pluralize from 'pluralize'
67import { Copy } from '@styled-icons/boxicons-regular/Copy'
78import { Check } from '@styled-icons/boxicons-regular/Check'
89import { X } from '@styled-icons/boxicons-regular/X'
@@ -18,14 +19,59 @@ import List from '../components/List'
1819import { NotificationContext } from '../components/Notifications'
1920import Modal from '../components/Modal'
2021
22+ const BuyItem = ( { text, cost, wallet, handleBuy } ) => {
23+ const [ amount , setAmount ] = useState ( 1 )
24+ return (
25+ < Box
26+ display = "flex"
27+ alignItems = "center"
28+ justifyContent = "space-between"
29+ border = "1px solid"
30+ borderColor = "border"
31+ borderRadius = { 1 }
32+ p = { 3 }
33+ pl = { 4 }
34+ >
35+ < Text > { text } </ Text >
36+ < form onSubmit = { handleBuy } >
37+ < Box display = "flex" alignItems = "center" >
38+ < Text color = "grey" mr = { 4 } >
39+ Cost: { amount * cost } points
40+ </ Text >
41+ < Input
42+ type = "number"
43+ name = "amount"
44+ value = { amount }
45+ onChange = { ( e ) => setAmount ( parseInt ( e . currentTarget . value ) ) }
46+ min = { 1 }
47+ max = { Math . floor ( wallet / cost ) }
48+ width = "100px"
49+ mr = { 3 }
50+ />
51+ < Button disabled = { amount * cost > wallet } > Buy</ Button >
52+ </ Box >
53+ </ form >
54+ </ Box >
55+ )
56+ }
57+
2158const Account = ( { token, invites = [ ] , user, userRole } ) => {
59+ const [ remainingInvites , setRemainingInvites ] = useState (
60+ user . remainingInvites ?? 0
61+ )
2262 const [ invitesList , setInvitesList ] = useState ( invites )
2363 const [ showInviteModal , setShowInviteModal ] = useState ( false )
64+ const [ bonusPoints , setBonusPoints ] = useState ( user . bonusPoints ?? 0 )
2465
2566 const { addNotification } = useContext ( NotificationContext )
2667
2768 const {
28- publicRuntimeConfig : { SQ_API_URL } ,
69+ publicRuntimeConfig : {
70+ SQ_API_URL ,
71+ SQ_BP_EARNED_PER_GB ,
72+ SQ_BP_COST_PER_INVITE ,
73+ SQ_BP_COST_PER_GB ,
74+ } ,
2975 } = getConfig ( )
3076
3177 const handleGenerateInvite = async ( e ) => {
@@ -59,6 +105,8 @@ const Account = ({ token, invites = [], user, userRole }) => {
59105
60106 addNotification ( 'success' , 'Invite sent successfully' )
61107
108+ setRemainingInvites ( ( r ) => r - 1 )
109+
62110 setShowInviteModal ( false )
63111 } catch ( e ) {
64112 addNotification ( 'error' , `Could not send invite: ${ e . message } ` )
@@ -104,6 +152,48 @@ const Account = ({ token, invites = [], user, userRole }) => {
104152 }
105153 }
106154
155+ const handleBuy = async ( e , type ) => {
156+ e . preventDefault ( )
157+ const form = new FormData ( e . target )
158+
159+ try {
160+ const amount = parseInt ( form . get ( 'amount' ) )
161+
162+ const buyRes = await fetch ( `${ SQ_API_URL } /account/buy` , {
163+ method : 'POST' ,
164+ headers : {
165+ 'Content-Type' : 'application/json' ,
166+ Authorization : `Bearer ${ token } ` ,
167+ } ,
168+ body : JSON . stringify ( {
169+ type,
170+ amount,
171+ } ) ,
172+ } )
173+
174+ if ( buyRes . status !== 200 ) {
175+ const reason = await buyRes . text ( )
176+ throw new Error ( reason )
177+ }
178+
179+ addNotification ( 'success' , 'Items purchased successfully' )
180+
181+ const pointsRemaining = await buyRes . text ( )
182+ setBonusPoints ( parseInt ( pointsRemaining ) )
183+
184+ if ( type === 'invite' ) setRemainingInvites ( ( r ) => r + amount )
185+
186+ const fields = e . target . querySelectorAll ( 'input' )
187+ for ( const field of fields ) {
188+ field . value = 1
189+ field . blur ( )
190+ }
191+ } catch ( e ) {
192+ addNotification ( 'error' , `Could not buy items: ${ e . message } ` )
193+ console . error ( e )
194+ }
195+ }
196+
107197 return (
108198 < >
109199 < SEO title = "My account" />
@@ -115,6 +205,28 @@ const Account = ({ token, invites = [], user, userRole }) => {
115205 < Text > This is an admin account.</ Text >
116206 </ Infobox >
117207 ) }
208+ < Text as = "h2" mb = { 4 } >
209+ Bonus points
210+ </ Text >
211+ < Text mb = { 4 } >
212+ You currently have < strong > { bonusPoints } </ strong > bonus points. You will
213+ earn { SQ_BP_EARNED_PER_GB } { pluralize ( 'point' , SQ_BP_EARNED_PER_GB ) } for
214+ every GB you upload.
215+ </ Text >
216+ < Box _css = { { '> * + *' : { mt : 3 } } } mb = { 5 } >
217+ < BuyItem
218+ text = "Purchase invites"
219+ cost = { SQ_BP_COST_PER_INVITE }
220+ wallet = { bonusPoints }
221+ handleBuy = { ( e ) => handleBuy ( e , 'invite' ) }
222+ />
223+ < BuyItem
224+ text = "Purchase upload (1 GB)"
225+ cost = { SQ_BP_COST_PER_GB }
226+ wallet = { bonusPoints }
227+ handleBuy = { ( e ) => handleBuy ( e , 'upload' ) }
228+ />
229+ </ Box >
118230 < Box
119231 display = "flex"
120232 alignItems = "center"
@@ -131,11 +243,11 @@ const Account = ({ token, invites = [], user, userRole }) => {
131243 pl = { 4 }
132244 >
133245 < Text color = "grey" mr = { 4 } >
134- { user . remainingInvites || 0 } remaining
246+ { remainingInvites . toLocaleString ( ) } remaining
135247 </ Text >
136248 < Button
137249 onClick = { ( ) => setShowInviteModal ( true ) }
138- disabled = { ( user . remainingInvites || 0 ) < 1 }
250+ disabled = { remainingInvites < 1 }
139251 >
140252 Send invite
141253 </ Button >
0 commit comments