Skip to content

Commit

Permalink
🍦 Integrate with voucher claiming api (#54)
Browse files Browse the repository at this point in the history
  • Loading branch information
b-tarczynski authored May 2, 2024
1 parent 0aefe81 commit 7d26659
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 439 deletions.
2 changes: 1 addition & 1 deletion packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"react-dom": "^18",
"styled-components": "^6.1.8",
"viem": "^2.9.3",
"wagmi": "^2.5.13",
"wagmi": "^2.7.1",
"zod": "^3.22.4"
},
"devDependencies": {
Expand Down
73 changes: 73 additions & 0 deletions packages/frontend/src/backend/useClaimVoucher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { useMutation } from '@tanstack/react-query'
import { GetVoucherNonceResponseSchema, GetVoucherResponseSchema, GetVoucherWithSigRequest } from '@/types/api/voucher'
import { isApiErrorResponse } from '@/types/api/error'
import { buildVoucherClaimMessage } from '@/utils/buildVoucherClaimMessage'
import { useAccount, useChainId, useSignMessage } from 'wagmi'
import { voucherCodeJwt } from '@/constants/jwt'

export const useClaimVoucher = (setVoucher: (voucher: string) => void) => {
const { address } = useAccount()
const chainId = useChainId()
const { signMessageAsync } = useSignMessage()

return useMutation({
mutationFn: async () => {
if (!address) throw new Error('Wallet not connected')
if (getVoucherCodeJwt()) {
return await getVoucherCodeUsingJwt()
}

const nonce = await getVoucherNonce()
const signature = await signMessageAsync({ message: buildVoucherClaimMessage(chainId, address, nonce) })
return await getVoucherCode({
nonce,
chainId,
signature,
userAddress: address,
})
},
onSuccess: setVoucher,
})
}

const getVoucherNonce = async (): Promise<string> => {
const response = await fetch('/api/voucher/nonce')
const data = GetVoucherNonceResponseSchema.parse(await response.json())
if (isApiErrorResponse(data)) throw new Error(data.error)

return data.nonce
}

const getVoucherCode = async (requestData: GetVoucherWithSigRequest): Promise<string> => {
const voucherFetchResponse = await fetch('/api/voucher', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestData),
})
return parseVoucherCodeResponse(await voucherFetchResponse.json())
}

const getVoucherCodeUsingJwt = async () => {
const voucherFetchResponse = await fetch('/api/voucher')
return parseVoucherCodeResponse(await voucherFetchResponse.json())
}

const parseVoucherCodeResponse = (response: unknown) => {
const voucherResponse = GetVoucherResponseSchema.parse(response)
if (isApiErrorResponse(voucherResponse)) throw new Error(voucherResponse.error)

return voucherResponse.voucherCode
}

const getVoucherCodeJwt = () => {
const cookies = document.cookie.split(';')
for (const cookie of cookies) {
const [key, value] = cookie.split('=')
if (key === voucherCodeJwt) {
return value
}
}
return undefined
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,15 @@ import { useAuctionState } from '@/blockchain/hooks/useAuctionState'
import { Button } from '@/components/buttons'
import { ErrorNotifications } from '@/components/notifications/ErrorNotifications'
import { Colors } from '@/styles/colors'
import { useMutation } from '@tanstack/react-query'
import { useClaimVoucher } from '@/backend/useClaimVoucher'

interface ClaimVoucherSectionProps {
setVoucher: (val: string) => void
}

export const ClaimVoucherSection = ({ setVoucher }: ClaimVoucherSectionProps) => {
const state = useAuctionState()

const { mutate, isPending, error, reset } = useMutation({
mutationFn: async () => {
await new Promise((r) => setTimeout(r, 2000))
return 'YOUR VOUCHER CODE'
},
onSuccess: (data) => setVoucher(data),
})
const { mutate, isPending, error, reset } = useClaimVoucher(setVoucher)

return (
<VoucherOption>
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/constants/jwt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const voucherCodeJwt = 'voucherCodeJwt'
3 changes: 2 additions & 1 deletion packages/frontend/src/pages/api/voucher/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { nonceStore } from '@/utils/nonceStore'
import { ApiErrorResponse } from '@/types/api/error'
import { ContractFunctionExecutionError } from 'viem'
import { getVoucherCodes } from '@/utils/getVoucherCodes'
import { voucherCodeJwt } from '@/constants/jwt'

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
switch (req.method) {
Expand Down Expand Up @@ -148,7 +149,7 @@ async function getVoucherWithSig(req: NextApiRequest, res: NextApiResponse) {
.sign(environment.authSecret)
res
.status(200)
.setHeader('Set-Cookie', `voucherCodeJwt=${jwt}; sameSite=none; secure=true;`)
.setHeader('Set-Cookie', `${voucherCodeJwt}=${jwt}; sameSite=none; secure=true; path=/`)
.json({
voucherCode,
} satisfies GetVoucherResponse)
Expand Down
3 changes: 3 additions & 0 deletions packages/frontend/src/types/api/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ export const ApiErrorResponseSchema = z.object({
})

export type ApiErrorResponse = z.infer<typeof ApiErrorResponseSchema>

export const isApiErrorResponse = <T extends object>(data: ApiErrorResponse | T): data is ApiErrorResponse =>
'error' in data
6 changes: 2 additions & 4 deletions packages/frontend/src/utils/getVoucherCodes.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import * as jose from 'jose'
import { environment } from '@/config/environment'
import { readFile } from 'node:fs/promises'
import path from 'node:path'

export async function getVoucherCodes() {
const encryptedVoucherCodes = await readFile(path.resolve(__dirname, `../voucherCodes.${process.env.NODE_ENV}`), {
const encryptedVoucherCodes = await readFile(process.cwd() + `/src/voucherCodes.${process.env.NODE_ENV}`, {
encoding: 'utf-8',
})
return decryptVoucherCodes(encryptedVoucherCodes, environment.authSecret)
Expand All @@ -20,6 +19,5 @@ export async function getVoucherCodes() {
*/
export async function decryptVoucherCodes(encryptedVoucherCodes: string, secretKey: Uint8Array) {
const { plaintext } = await jose.compactDecrypt(encryptedVoucherCodes, secretKey.slice(0, 32))
const voucherCodes = new TextDecoder().decode(plaintext).split(',')
return voucherCodes
return new TextDecoder().decode(plaintext).split(',')
}
2 changes: 1 addition & 1 deletion packages/frontend/src/voucherCodes.development
Original file line number Diff line number Diff line change
@@ -1 +1 @@
eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..1H7GnaOBGvdnpqQ4.WPJ_oQGQuE5umc4C4QXyah_9ITH90jeDR_1ZT38oG_KdvqDzJNQuI7s2v4-JWPrswu_xgID2KUqshABk2cRGubXj5eHE3gPnluMe8LUeOlImNH-LDfl43CeI5rMbi5ZjKUS-E_v-KxPfhm8G3jJLyMYjtyY4s0nUbmWfNp-QDy8ENhU5UPtUd2iA4d7aCDi9dkDICDm_6IMGHFEne-Xn-eHg0qW9QxuFDhaSYt036Dp52royS0bnIKq11RmxwSjfS8cpurS9x9egGO4ZabSF74d4zKgMfq1vzRolrhVd05VdDbX7EdiQVyF98naKg4L_LsC4124XJmp_CyUbpN6LbCJnONZ4H3G5bwVX1stH6AD-2IQXb8mGq_zNRuR1sizSYpPWVNrt6x21a16Fe7tzMyotkv8WzXDrHUolmBNP6JOjyLTyeF5uvFIxfkU1Zcl4dA3mlkrw38mfumh_bbSAlGJRFgbZY5-RQXbg14PB3ax6Uz34QBk_hBduebUAZ25E7LVRtkqyiwPRWrbm017tG0PJrY_-efmkhs7VByxtlwo1_fMiK-qulaeRfjksN2CCkV1M_K_DcRpjnQ6OckFGtQGJkR0cxRvn3zE9sLvrnht7qzDZCAOkigKdUDCKwa87LN5nmWAdO3dk33XpliZ26w1OGXWZwfo-9QO6X2rovey-YYy91JoZ6Y3Ewsg5avaEzpNu_exfVnz4lQHf_8ZgvGSVgYlDfBfNNnl5G7Z9attCM7YM4oPBvqEUtMvQ3-DXT1qjA7Zz_fFLvfW53u3hm0j5x4XUVsbgOhwpVfSwiPkvf10xqX-Y9AkzsyC4gafsMZQ0nN43YJ0dOy5rcDlFVeLWZ4dbf_47LtB3iziE2TtawLrFL9mNIzyqVjKMceGybm028L2lo0ZW30uP66t5s-VxuweAe1io3E0ZhB8gFAUX6Onm9HIN-5Edn3HLSD_GP-JAbpAk1hBTexITwaCUpeaVJLB3xb1LgCw2cEKxlvdV1oId4j5-qhOtC5KHisVzh6JUVTcnOBrnKJDQsC08WnGx-etEM7n1GEQrlQJs6ElLk1-hyfZJ4WEIJDtSoRYkwoZrb67aeNv92Lkifvu0RTLb63TYQiI7S9J07tFkM9qW5KjgYZKfTQKVGHWadPNfjzfe5acUe9FAEU4vf5dTn6_owxRmmaQ-r5xuIlsb9mc5Z_G5RM8oVHBWA8wK6k6sm2hxGvhmfN74vZHmA7uKURXi_D7a9kbtd1pZ47HNe5vekFc-iw2kLcGMwFoefTlAV62hsptPnWPBlRj48AppteZ5u53AvDzjOKu_fxO8-xf5YoWB9xPjw5Px4CA98V1M9WzrgcJ2zShiHCioZDAnXPODunfkS5T6x7gHpbk9AOmy8-SfGSf3HQXPDlUMM0QNMdsxw7JBOdOr1XZKybgiwlDRJf_MrVhkd-y1ymtv8KJMQylWrGgBB3-bpTPC1f8buxPy0Pn21hWcxXzwKInO3-cjH-IhBDw8zx2bFeKorsUEqnhlCC_nQduYw-Nh4SkeqybeAnYZ3CwduW13G4WN7JOn1dnNogSNhJ0oB1SFG5IlyOa_PjXEfAU5AV09sEfFkRwTcF9xtuEa225QS74tBBK_HziXY5sQpnQqlH9BbmH1CoY9fFW3AbncmXY8Q4KclDFR0k9P5QAKrlFxNvEq48sEgKvipNurGiBoRqUo44fbgjWlrlQi_wfDcUNu32MWSOFpOnmU3wqIm8q5blX-O6VaJl1YOPb7rs7y1wfdH_-jKdVMCimJfRBOLH66ri5DzOs6MItsbcBJnwYRZJmL1feZPxuZNHxg5yVLNk6acn87WGeZHmccCb78JSSnB8EBjxnmWf_Lu2ovKi88tkZI4CNy9g8FkVbKLYNutmPcap7W6p6Tyg3VVOH1ihVE1pgI-FXovMSSI58RCOa7w181gU7XtAO7nng-3DoHK7cRbSUNl6naUW65MKJy3InbOcAGlrEqfQxxEsA5JWeyO-xMtTwsJR3EBCSeIths88bA54MXtv0zi6sU_6N1ooaXXAZ37-06cfUebT12rHokq35vPORgOo9cpqGDP8tS8t33FoVk4wlrGM3CTbm4gM_JKdZaEgLVBX47ZrSugzRBLHlqPGeXqmh3n5emzicAqJgNEQeKPfEGISvtHxXZo3XrFwl4nYfSKAJYG4rN--_3eZdVqRcuGUw_xdybPuO5cKJLQv0AWE8WIOvDG-qG34vdcSZSiz2N7KXR--bBKFOhOFw99nTpUSi46MH12OIfBiMxqNpYd9iuf2QPmbLj07HYtMF7uGHbRk06qhXuCNM6ODoynKehtEKjMIYGtK_I3gCcmt_f0ZFhqmULlW8UQvhZKONNRxd2Ba48R8InkYwJTHkpwWlgxrOXDc2NyzyEaS9xPkM9lc58RFz03D9SHxQ1YxdIntDNpm2YezxizJ7xxxhTxR64KaybQeP9YiolfwP3c9TCHL-6hKYAjVPwzhUVbLLZf-MET9sFL9tenQVo8nml4CT9sGbHcUa9cJb0-hUSdZxotaAj3adiuyrRrcYosk1-eaA8n-jJDr1KiPniLk442WwRpBcEeq0J_zZEwkOS2Oe8G_mjcfKgPE1zzfRpIsdM4O9jchL6uqSvFlpnleIxfPshUdexVGJLszyDYREhdvBvjf06CTSoy4DHq6w1HPRAg1ziWisgXnD1Ht8CLNFyg23bBN7ZzCHi_xrJ-jN2LncChSBxFn6FtdK-1mKodqXzbAA1buMa274nbeC2OypKhxegmTNkie6vwlL0zGdUDROEGvyYQ1NTWSET0E-4dhQxCEk7asOLoHg7hmD4oYTCgLS8suqLO1qoIkJb-pfhh6e1FyN0hBsWdyh3bRin350ajfYTXv3RNwLEFBj5dEGa5sVME_dh9JnXrTgX7Ne9h0NPtUrtptHc-grWkW025BxARFIsOh2iWFSugLe8Dq1UAxFiOtAzfzFjPzTzuX2vsdKwAZA4V-iziZNj3e1-D_FHXR3m8hItzP61kDsoQ8WeWgPzOLcrLNsQ92ra-dr_ECjoiBhuWcJD6ZLl8XuYMZIIwo4X3QeLZtOHFg-CbD-9sZCGtnbVDPZoOdmKFyfjp0IVdu6Q9HEzwqOQLb2pZ7CFPO3BcYVSv3IxEVjZcBdnRMcMetM9HGJ6_uemNB_Ap_DS9GGEvTKF9Ptg13WvD5GrJfrdG8zfNQvp65bJmuY8ajqLC9wk2XepEXAwU_jH56k0nbySbDEobQoENSWn0s6HYfiP0BWa5LF075FenznDQPM-VsUV62nJFEdUNV7NkHRUxCBf4ubkaCpNkqw6qiaSvBrtOxC54MwOpD5WKFlUK_db5QyJMHWHIhWLLwZSAt-ZEPzEktBhEZ__wIB6nKYeebNLvJnAZSCbAd9VK3aNcT4I6Ic_vxRes8qjoVwPOVczoX2N-TZp65_6NUHnf9qQlduZH3voQcq94mphRsTmQ85kl2-xq3kSAXiZ09mpWPpqZbPdWLPAKx0WiF37cNdHH7PWI4og1Bbf4nLJA5ooiYx2OZDNQlaXSepFMHCrvaEQYpXHu85J0L-HjvOJVrEgO7dJ0Ui2DIPynOoSgyN5XHTT6ROS-8FEuIVHxLLYbVwYtDFGxlqTrulqriX3x_pNHsKDrDOcIqYeXeHqMCxIqDVzTXI57sg0EyDBFQ9FOlLf2YHRUprrTIZZdbuJHpkyNfRuPUHtSLRRg6mt6BkbhxQSQ7yYAke1tLB0-vdni18UgLKndg_OhslMk9SRe4NWzSeA2938KN0SfZ06kFRFdrayRAnAVXBHFDSsQZF_hSnPfr5k9Weh69fHpsOEuBU88DsSS2MsAVcEUh3ZdAkhkh-yttOceJaNNTYEqZvAjV8kEk911M5mOKa-svx0y6wSlAxF7gtZPSpU8fxFkHdgGrzYcx8szbmKPYA-snr1AEI-JGELm5MWlE25fvSsUnHYDk6U3Xtq3swionjtGKqD55c_gJ2Zm7H9y7ccTPct3zNGJQZsLjHceFOZvP0d1CJ-fBfD0D-l2LXVJ5OgBGbpQZg5cwRENCdujWuiXsf0IXQnxactw-iyn3x3UsorPeAUAGLUsIeG1fHziwqAtFHiDMz_90NgmDNVPcMNpFWAJYD27nL1pY-mliGqnMU9Wqa2fyPlxqo_6XldvD1AFl6v7dKvBVM8Wx2_hKJo2BjXFWAy665IiqZAJpYYGaVQlZ8QjGEqieQsst-5JT_Gm4KD7wsogAd4r8Ve0T0M_hxx0PmqF37A1fM5y_Ogvetc694yuO_erTbfMFYdDUZwroEvVqQCAP6ehMa6_rFD4WSxjCvNIL_u3fSgDVDLxvJDoTIIYk4iCmv9Z8SiTR1lqAzlvIoy5yXeRv9kGx1V3bD0Fc1VEvqsrxZBVZsyc5Z8DM8Vu0GvaeSv5moU9erlvilLNXtXVniMIl_MYse6U66En1bWazwljXN4csJ9ZHIsgpkBP5SsGNbOMHSsV5pdcTodg9Ng96yNLCfYcQVJSAEvSTChZi-kkG3G.UltJrjuZ5Nff6BLLrRBH0Q
eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0.._zEgb_4nu2rR-cXp.OBXpq8omnB_m_FDpV_2OpFvVR4xlEeHXPF3pT4-jk09Au46o67Js-isEGcRmW2v-vjnRgSOktFE0VoCawoSaeTtVadb5ZUKkP95D3kRadWzgD8d2IXYOVj9iFl2kDtem3tyns1GHoLjmIQdkl1M8Vh_b4vPAzW_MmQnYbGFrjAstnIQ0IEG967Wr3HvJf8CLxA2SYFmZRsIt-cnjnu82HB9hK0bVl1VDMQssYrV9OoWCtnh3M-CQE0YCy05v87siyi38KmFGmFQxYftU2ImqRTGiXKHIpSqLgBWwVlxcJV4zxlZbnQSqln05kyc5Elog8f2PecDuEaQTmdt9ALHBrq6c4n4toY64B7svRA2dTWsP9GCXJDr1rEFzMSfzMgOUMjHoXROaOKEzia4ePcod8oG8Auhu0hsCLE6NG4VOgKAottXd0LivLg-YtEADST1GdoZsIIDqpF0V1SDmUu83AZORbs_h7VhEwALpc3W-sKHx5gPOIVYLr5R9mNKpBhLnzTDttzWGca8mG7AbRiTvy0tbLzHUx2eM3H3mbceelpYYLKopmXdMbYjim-ox_lrU3av_OXiPdDoMaGYiBRdiHwse8Ab0HdUaHv9ZVTzHaE6XhnqrqfroQW1mWXJs1U8yW8g6CB03pZ7cmYSViUWphSd5trfp8ZN_bq81BWjH5KzQzn8qsLTHb1CtC6z9T-V29_rBrnHBEbKxpoFJ1iQp5AbMWSslUqv1MxJAeLNzr130uHuHPI6XUiRZqqxFWBbCZHJL3RqUDCHZ80rDj6fmwvgo2xps1GzdU-vtK12xyB1xawYdBmEoqF4bRW0yrWdsyVRT0y3jlG-ZO_ux7u3V9oB3_tGlXBvZZPytfg6CwKUVlniAzXvX_SYZ3r3IZmunNzefe7AlRHPKnioF2MjDR3drEd7wSJHGqEfU5fPCKzTvT-O4N3gtjSmtoe4GwkwEP1QWh4L47XBBGIbicg6673HsFGkxcaxk9_xFIIL3Gm_n6u97w90OVdiEtYa4siqDtUDvbSVoFSxkD3wta1OlYnD1KFfAFtBAXjUDZGi-4iYzLuxwi_5B8HOYlz7Q865xHTSzMkbUWtn6WXqne-ctyogkDsvZjI32uwr6MDCGxLnVOwVC1JAmAKMzzlZw9wBavsIT4VOrFsWBrqiKbo8-QSyBCP36ZHQq_JISKwOBfK42Ukc20PQBctGmfGEPLOgxgKeLqVlFGvGR08JZ29uJJWRXuMgaxR8928rkC8NItNBT8Cd-FoPQMAn7PyHkEvsWIbKP64hNk7D3Ow6W7SpypQ8CTRYxNE46Vz-Q8Or8USG23SKU3NIzUP2a9Vjp53SRywqNAvW_y4-wDsEG2irj-dArxboef0P8D44gW3NAwVRm50WFPEaOX6pvcL7TdssfDQC_PXFjITzdQdfAncnosvVBRv5HjowTh1Z3yi0yVYRnlAzGBxvb4hg5H5GjQOuHUHzJEyy3HcKgjvoRVI630Tfp0_Y7B5h05EU5JWy_51PHdhu4KHvlinew2myk30YlMZQEUCZa1AZ5oL22Mp28SEQBGLVpLDGDW9jcTYCsnD0LKL9ew2yMy9T2eLUAnA3tZjwz7CFw9c1S2Hlrehzo-EviMShYJnKdapVJKO000pkDKqseTi7-jClWSguGYltUMClnjHMbc0RBGsPT3P_eEIXOzST0AMF69EWSdjmuAyAuS8hfUB1az7pWoeA2C35ptek2OCDKamvcK-O-FQ2XOOenPuL39-sGgVpUeKHFok5gdFyaML_zQFQBMeaHoI1pFbUfKotcXHAyBaNE9mhPe5zwpHrNgkjlUpg1Fz2eg53zwRt2AiOSxLPB3am5BvULBJevvFAMmj2DLIYztI-SlkGaWvpzOSGF_Ou1-DfzAypAdNwuz7AcVZDcMV5EuXldHtPd2G097QZdC2jOULGS9gS5aNt_l9j6vQnfcwk2YvxPojtAuj78vwK3dPFLMuvxIwNTItNcTwQKij-ZZ4FoGaRHLybUqpEPtUWqR53-L7T3RiUsyGGWqfZ_OYiONUsWAcDoC_rBEdFUlRf9DO70EGxUgJ4QG_SyzWdans7naKYW-IEQpu9WR6oCQPychJDJUt2vzQI2lw.hiJ5zTjKKZGYQiCp9_KLhQ
Loading

0 comments on commit 7d26659

Please sign in to comment.