diff --git a/signer-service/src/api/controllers/email.controller.js b/signer-service/src/api/controllers/email.controller.js new file mode 100644 index 00000000..3d0dd6d6 --- /dev/null +++ b/signer-service/src/api/controllers/email.controller.js @@ -0,0 +1,13 @@ +const { spreadsheet } = require('../../config/vars'); +const { storeDataInGoogleSpreadsheet } = require('./googleSpreadsheet.controller'); + +// These are the headers for the Google Spreadsheet +const EMAIL_SHEET_HEADER_VALUES = [ + 'timestamp', + 'email', + 'transactionId' +]; + +exports.EMAIL_SHEET_HEADER_VALUES = EMAIL_SHEET_HEADER_VALUES; + +exports.storeEmail = async (req, res) => storeDataInGoogleSpreadsheet(req, res, spreadsheet.emailSheetId, EMAIL_SHEET_HEADER_VALUES) diff --git a/signer-service/src/api/controllers/googleSpreadSheet.controller.js b/signer-service/src/api/controllers/googleSpreadSheet.controller.js new file mode 100644 index 00000000..520a990e --- /dev/null +++ b/signer-service/src/api/controllers/googleSpreadSheet.controller.js @@ -0,0 +1,26 @@ +require('dotenv').config(); + +const { spreadsheet } = require('../../config/vars'); +const { initGoogleSpreadsheet, getOrCreateSheet, appendData } = require('../services/spreadsheet.service'); + + +exports.storeDataInGoogleSpreadsheet = async (req, res, spreadsheetId, sheetHeaderValues) => { + try { + // We expect the data to be an object that matches our schema + const data = req.body; + + // Try dumping transactions to spreadsheet + const sheet = await initGoogleSpreadsheet(spreadsheetId, spreadsheet.googleCredentials).then((doc) => getOrCreateSheet(doc, sheetHeaderValues)); + + if (sheet) { + console.log('Appending data to sheet'); + await appendData(sheet, data); + return res.status(200).json({ message: 'Data stored successfully' }); + } + + return res.status(500).json({ error: 'Failed to store data. Sheet unavailable.' }); + } catch (error) { + console.error('Error in storeData:', error); + return res.status(500).json({ error: 'Failed to store data', details: error.message }); + } +}; diff --git a/signer-service/src/api/controllers/storage.controller.js b/signer-service/src/api/controllers/storage.controller.js index f94b0bea..c859dbf1 100644 --- a/signer-service/src/api/controllers/storage.controller.js +++ b/signer-service/src/api/controllers/storage.controller.js @@ -1,10 +1,8 @@ -require('dotenv').config(); - const { spreadsheet } = require('../../config/vars'); -const { initGoogleSpreadsheet, getOrCreateSheet, appendData } = require('../services/spreadsheet.service'); +const { storeDataInGoogleSpreadsheet } = require('./googleSpreadsheet.controller'); // These are the headers for the Google Spreadsheet -exports.SHEET_HEADER_VALUES = [ +const DUMP_SHEET_HEADER_VALUES = [ 'timestamp', 'polygonAddress', 'stellarEphemeralPublicKey', @@ -16,25 +14,7 @@ exports.SHEET_HEADER_VALUES = [ 'stellarCleanupTx', ]; -exports.storeData = async (req, res, next) => { - try { - // We expect the data to be an object that matches our schema - const data = req.body; - - // Try dumping transactions to spreadsheet - const sheet = await initGoogleSpreadsheet(spreadsheet.sheetId, spreadsheet.googleCredentials).then((doc) => { - return getOrCreateSheet(doc, this.SHEET_HEADER_VALUES); - }); +exports.DUMP_SHEET_HEADER_VALUES = DUMP_SHEET_HEADER_VALUES; - if (sheet) { - console.log('Appending data to sheet'); - await appendData(sheet, data); - return res.status(200).json({ message: 'Data stored successfully' }); - } +exports.storeData = async (req, res) => storeDataInGoogleSpreadsheet(req, res, spreadsheet.storageSheetId, DUMP_SHEET_HEADER_VALUES) - return res.status(500).json({ error: 'Failed to store data. Sheet unavailable.', details: error.message }); - } catch (error) { - console.error('Error in storeData:', error); - return res.status(500).json({ error: 'Failed to store data', details: error.message }); - } -}; diff --git a/signer-service/src/api/middlewares/validators.js b/signer-service/src/api/middlewares/validators.js index a514d0be..ee5dfdba 100644 --- a/signer-service/src/api/middlewares/validators.js +++ b/signer-service/src/api/middlewares/validators.js @@ -1,5 +1,6 @@ -const { SHEET_HEADER_VALUES } = require('../controllers/storage.controller'); const { TOKEN_CONFIG } = require('../../constants/tokenConfig'); +const { DUMP_SHEET_HEADER_VALUES } = require('../controllers/storage.controller'); +const { EMAIL_SHEET_HEADER_VALUES } = require('../controllers/email.controller'); const validateCreationInput = (req, res, next) => { const { accountId, maxTime, assetCode } = req.body; @@ -33,12 +34,12 @@ const validateChangeOpInput = (req, res, next) => { next(); }; -const validateStorageInput = (req, res, next) => { +const validateInputHeaderValues = (requiredHeaders) => (req, res, next) => { const data = req.body; - // Check if the data contains values for all the headers - if (!SHEET_HEADER_VALUES.every((header) => data[header])) { - const missingItems = SHEET_HEADER_VALUES.filter((header) => !data[header]); - let errorMessage = 'Data does not match schema. Missing items: ' + missingItems.join(', '); + + if (!requiredHeaders.every((header) => data[header])) { + const missingItems = requiredHeaders.filter((header) => !data[header]); + const errorMessage = 'Data does not match schema. Missing items: ' + missingItems.join(', '); console.error(errorMessage); return res.status(400).json({ error: errorMessage }); } @@ -46,6 +47,9 @@ const validateStorageInput = (req, res, next) => { next(); }; +const validateStorageInput = validateInputHeaderValues(DUMP_SHEET_HEADER_VALUES); +const validateEmailInput = validateInputHeaderValues(EMAIL_SHEET_HEADER_VALUES); + const validatePreSwapSubsidizationInput = (req, res, next) => { const { amountRaw, address } = req.body; @@ -97,4 +101,5 @@ module.exports = { validatePreSwapSubsidizationInput, validatePostSwapSubsidizationInput, validateStorageInput, + validateEmailInput, }; diff --git a/signer-service/src/api/routes/v1/email.route.js b/signer-service/src/api/routes/v1/email.route.js new file mode 100644 index 00000000..806eae22 --- /dev/null +++ b/signer-service/src/api/routes/v1/email.route.js @@ -0,0 +1,9 @@ +const express = require('express'); +const controller = require('../../controllers/email.controller'); +const { validateEmailInput } = require('../../middlewares/validators'); + +const router = express.Router({ mergeParams: true }); + +router.route('/create').post(validateEmailInput, controller.storeEmail); + +module.exports = router; diff --git a/signer-service/src/api/routes/v1/index.js b/signer-service/src/api/routes/v1/index.js index eb82741b..67dfe5f6 100644 --- a/signer-service/src/api/routes/v1/index.js +++ b/signer-service/src/api/routes/v1/index.js @@ -3,6 +3,7 @@ const express = require('express'); const stellarRoutes = require('./stellar.route'); const pendulumRoutes = require('./pendulum.route'); const storageRoutes = require('./storage.route'); +const emailRoutes = require('./email.route'); const subsidizeRoutes = require('./subsidize.route'); const router = express.Router({ mergeParams: true }); @@ -33,6 +34,11 @@ router.get('/status', sendStatusWithPk); * POST v1/stellar */ router.use('/stellar', stellarRoutes); + + +/** + * POST v1/pendulum + */ router.use('/pendulum', pendulumRoutes); /** @@ -40,6 +46,15 @@ router.use('/pendulum', pendulumRoutes); */ router.use('/storage', storageRoutes); +/** + * POST v1/email + */ +router.use('/email', emailRoutes); + + +/** + * POST v1/subsidize + */ router.use('/subsidize', subsidizeRoutes); module.exports = router; diff --git a/signer-service/src/config/vars.js b/signer-service/src/config/vars.js index 3aa64104..ec17e533 100644 --- a/signer-service/src/config/vars.js +++ b/signer-service/src/config/vars.js @@ -19,6 +19,7 @@ module.exports = { email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL, key: process.env.GOOGLE_PRIVATE_KEY?.split(String.raw`\n`).join('\n'), }, - sheetId: process.env.GOOGLE_SPREADSHEET_ID, + storageSheetId: process.env.GOOGLE_SPREADSHEET_ID, + emailSheetId: process.env.GOOGLE_EMAIL_SPREADSHEET_ID, }, }; diff --git a/src/components/EmailForm/index.tsx b/src/components/EmailForm/index.tsx index 3fc81ef6..06c6a4d9 100644 --- a/src/components/EmailForm/index.tsx +++ b/src/components/EmailForm/index.tsx @@ -1,18 +1,70 @@ +import { useMutation } from '@tanstack/react-query'; import { useForm } from 'react-hook-form'; +import { storeUserEmailInBackend } from '../../services/storage/remote'; import { TextInput } from '../../components/TextInput'; -export const EmailForm = () => { - const { register } = useForm(); +interface EmailFormProps { + transactionId?: string; +} + +export const EmailForm = ({ transactionId }: EmailFormProps) => { + const { register, handleSubmit } = useForm(); + + const { + mutate: saveUserEmailMutation, + isPending, + isSuccess, + isError, + } = useMutation({ + mutationFn: storeUserEmailInBackend, + }); + + const onSubmit = handleSubmit((data) => { + if (!transactionId) { + console.error('Transaction ID is missing'); + return; + } + + saveUserEmailMutation({ email: data.email, transactionId }); + }); + + const FormButtonSection = () => { + if (isSuccess) { + return ( +
+ Error while saving your email. Please try again. +
+ )} + > + ); + }; return ( -If you continue to experience issues, contact support on: