Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🍦 Integrate with voucher claiming api #54

Merged
merged 8 commits into from
May 2, 2024
Merged
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
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