From 8dfc16302b39e3446003c1a1c3e6e09c4b39be8b Mon Sep 17 00:00:00 2001 From: KrisJohnson Date: Tue, 8 Jun 2021 21:47:41 -0500 Subject: [PATCH 01/13] implemented donate mock --- .gitignore | 1 + components/Blog/blogList.js | 2 +- components/donateform.js | 224 +- components/icons/book.js | 2 +- components/icons/gps.js | 2 +- components/icons/percent.js | 3 +- components/icons/school.js | 2 +- components/icons/teacher.js | 2 +- components/icons/x.js | 3 +- components/taxReceiptButton.js | 62 +- components/taxReceiptDocument.js | 3 +- components/team.js | 5 +- mocks/browser.js | 4 + mocks/data/mockToken.js | 33 + mocks/handlers.js | 17 + mocks/index.js | 7 + mocks/public/mockServiceWorker.js | 323 + mocks/server.js | 4 + package-lock.json | 23090 +++++++++++++++++++++++++++- package.json | 6 +- pages/_app.js | 5 + pages/account.js | 45 +- pages/donate.js | 61 +- pages/index.js | 2 +- pages/pending-login.js | 4 +- pages/signin.js | 3 +- pages/success.js | 2 +- public/mockServiceWorker.js | 323 + 28 files changed, 23928 insertions(+), 312 deletions(-) create mode 100644 mocks/browser.js create mode 100644 mocks/data/mockToken.js create mode 100644 mocks/handlers.js create mode 100644 mocks/index.js create mode 100644 mocks/public/mockServiceWorker.js create mode 100644 mocks/server.js create mode 100644 public/mockServiceWorker.js diff --git a/.gitignore b/.gitignore index 259ecb0..5499b45 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ npm-debug.log* yarn-debug.log* yarn-error.log* node_modules/ +.vscode diff --git a/components/Blog/blogList.js b/components/Blog/blogList.js index 871e549..7449fee 100644 --- a/components/Blog/blogList.js +++ b/components/Blog/blogList.js @@ -6,7 +6,7 @@ const Blog = ({ content }) => { const [showAll, setShowAll] = useState(false) return ( - + diff --git a/components/donateform.js b/components/donateform.js index b79bf21..f6d4b2d 100644 --- a/components/donateform.js +++ b/components/donateform.js @@ -4,6 +4,8 @@ import { CardElement } from '@stripe/react-stripe-js' import DonationFrequency from './donationFrequency' import Router from 'next/router' +import { mockToken } from '../mocks/data/mockToken' + class DonateForm extends Component { constructor (props) { super(props) @@ -20,100 +22,162 @@ class DonateForm extends Component { } } - setLocalState = (state) => { - if (!state.error) state.error = '' - this.setState(state) - } +setLocalState = (state) => { + if (!state.error) state.error = '' + this.setState(state) +}; - updateFirstName = (e) => { - this.setLocalState({ firstName: e.target.value }) - } +updateFirstName = (e) => { + this.setLocalState({ firstName: e.target.value }) +}; - updateFrequency = (ev) => { - this.setLocalState({ frequency: ev.target.value }) - } +updateFrequency = (ev) => { + this.setLocalState({ frequency: ev.target.value }) +}; - updateLastName = (e) => { - this.setLocalState({ lastName: e.target.value }) - } +updateLastName = (e) => { + this.setLocalState({ lastName: e.target.value }) +}; - updateEmail = (e) => { - this.setLocalState({ email: e.target.value }) - } +updateEmail = (e) => { + this.setLocalState({ email: e.target.value }) +}; - updateAmount = (e) => { - this.setLocalState({ amount: `${e.target.value.includes('$') ? '' : '$'}${e.target.value}` }) - } +updateAmount = (e) => { + this.setLocalState({ + amount: `${e.target.value.includes('$') ? '' : '$'}${e.target.value}` + }) +}; - donate = async (ev) => { - ev.preventDefault() - this.setLocalState({ loading: true }) - let token - try { - const cardElement = this.props.elements.getElement(CardElement) - const res = await this.props.stripe.createToken(cardElement) - token = res.token - } catch (e) { - this.setState({ error: e.message, loading: false }) - return +donate = async (ev) => { + ev.preventDefault() + this.setLocalState({ loading: true }) + let token + try { + const cardElement = this.props.elements.getElement(CardElement) + // const res = await this.props.stripe.createToken(cardElement); + console.log('cardElement: ', cardElement) + let res + if (process.env.STRIPE_PUBLIC_KEY === 'test') { + res = { + error: 'error', + token: mockToken + } + } else { + res = await this.props.stripe.createToken(cardElement) } - if (!token) { - this.setState({ error: 'Invalid CC info!', loading: false }) - return - } - try { - const stripDollarSignAmount = parseInt(this.state.amount.replace('$', '')) - const responseStream = await fetch('/api/donate', { - method: 'POST', - body: JSON.stringify({ - source: token, - firstName: this.state.firstName, - frequency: this.state.frequency, - lastName: this.state.lastName, - amount: stripDollarSignAmount * 100, - email: this.state.email - }) + console.log('createToken res: ', res) + token = res.token + console.log('token: ', token) + } catch (e) { + this.setState({ error: e.message, loading: false }) + return + } + + if (!token) { + this.setState({ error: 'Invalid CC info!', loading: false }) + return + } + try { + const stripDollarSignAmount = parseInt( + this.state.amount.replace('$', '') + ) + const responseStream = await fetch('/api/donate', { + method: 'POST', + body: JSON.stringify({ + source: token, + firstName: this.state.firstName, + frequency: this.state.frequency, + lastName: this.state.lastName, + amount: stripDollarSignAmount * 100, + email: this.state.email + }) + }) + const response = await responseStream.json() + if (response.success) { + this.setState({ redirectSuccess: true, loading: false }) + } else { + this.setState({ + error: `Donation failed: ${response.message}`, + loading: false }) - const response = await responseStream.json() - if (response.success) { - this.setState({ redirectSuccess: true, loading: false }) - } else { - this.setState({ error: `Donation failed: ${response.message}`, loading: false }) - } - } catch (e) { - this.setState({ error: e.message, loading: false }) } + } catch (e) { + this.setState({ error: e.message, loading: false }) } +}; - render () { - const { redirectSuccess, loading } = this.state - - if (redirectSuccess) { - Router.push('/success') - } +render () { + const { redirectSuccess, loading } = this.state - if (redirectSuccess) return (
) - - return ( -
-
-

{this.state.error}

-
- - - - - - -
- -
- { loading &&

Loading...

} - - - ) + if (redirectSuccess) { + Router.push('/success') } + + if (redirectSuccess) return
+ + return ( +
+
+

+ {this.state.error} +

+
+ + + + + + +
+ +
+ {loading &&

Loading...

} + + + ) +} } export default DonateForm diff --git a/components/icons/book.js b/components/icons/book.js index d0f3c11..978200a 100644 --- a/components/icons/book.js +++ b/components/icons/book.js @@ -2,7 +2,7 @@ const Book = () => (
- + + + `} +
) diff --git a/components/icons/school.js b/components/icons/school.js index adafb8b..30474c9 100644 --- a/components/icons/school.js +++ b/components/icons/school.js @@ -2,7 +2,7 @@ const School = () => (
- + + + `} +
) diff --git a/components/taxReceiptButton.js b/components/taxReceiptButton.js index 7f06750..ea66b35 100644 --- a/components/taxReceiptButton.js +++ b/components/taxReceiptButton.js @@ -9,7 +9,7 @@ const GetTaxReceiptsButton = ({ taxYears, handleSelectTaxYear, selectedTaxYear } taxYears && taxYears.length > 1 ? - Get Tax Receipt + Get Tax Receipt {taxYears.map(year => ( @@ -18,18 +18,18 @@ const GetTaxReceiptsButton = ({ taxYears, handleSelectTaxYear, selectedTaxYear } ))} - + : ) @@ -100,7 +100,7 @@ const TaxReceiptButton = () => { } const getSaveFileName = () => - `Teacher_Fund_Tax_Receipt_${formatTimestamp(new Date())}` + `Teacher_Fund_Tax_Receipt_${formatTimestamp(new Date())}` const getDownloadLinkText = ({ loading, error }) => loading @@ -110,28 +110,28 @@ const TaxReceiptButton = () => { return ( donationsLoading ?
- { user && user.customerId && -
- -
- } - { user && user.customerId && } + {user && user.customerId && +
+ +
} + {user && user.customerId && }
)} diff --git a/pages/donate.js b/pages/donate.js index f4d60c6..d256eb9 100644 --- a/pages/donate.js +++ b/pages/donate.js @@ -25,15 +25,15 @@ class Donate extends Component { render () { return ( - + <>

- Fund Teachers. Help Students. + Fund Teachers. Help Students.

- With 100 percent of your donation funding public school teachers in need, you can - give knowing that your entire gift will help equip classrooms and help students. + With 100 percent of your donation funding public school teachers in need, you can + give knowing that your entire gift will help equip classrooms and help students.

@@ -43,32 +43,33 @@ class Donate extends Component {
{this.state.showPaypalButton && -
-

- Donate With PayPal -

-
-
- - - - -
-
-
- } -
+
+

+ Donate With PayPal +

+
+
+ + + + +
+
+
} +
) } diff --git a/pages/index.js b/pages/index.js index f94c63a..c598d6d 100644 --- a/pages/index.js +++ b/pages/index.js @@ -249,7 +249,7 @@ class IndexPage extends Component { _hover={{ bg: 'white', color: 'red.300' }} _focus={{ boxShadow: 'outline' }} > - Apply Today + Apply Today diff --git a/pages/pending-login.js b/pages/pending-login.js index 927e897..4f074ca 100644 --- a/pages/pending-login.js +++ b/pages/pending-login.js @@ -9,10 +9,10 @@ const PendingLogin = (props) => (

- Thank you. If you've signed up, you will receive an email shortly. You can close this tab. + Thank you. If you've signed up, you will receive an email shortly. You can close this tab.

- If you haven't signed up yet you will not receive an email. + If you haven't signed up yet you will not receive an email.

diff --git a/pages/signin.js b/pages/signin.js index 36d31eb..72e07a3 100644 --- a/pages/signin.js +++ b/pages/signin.js @@ -16,7 +16,8 @@ const SignIn = (props) => ( padding={{ base: '3rem 1.5rem', lg: '6rem 7.5rem' }} backgroundColor='gray.50' height='100vh' - {...props}> + {...props} + >

Sign in

diff --git a/pages/success.js b/pages/success.js index 0947060..266647b 100644 --- a/pages/success.js +++ b/pages/success.js @@ -14,7 +14,7 @@ const Success = () => {

Thank you!

Your donation helps teachers more than can be put in words.

- { user ? ( + {user ? ( If you made a monthly donation you can visit your {' account '} at any time to cancel or modify your donation. diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js new file mode 100644 index 0000000..4601441 --- /dev/null +++ b/public/mockServiceWorker.js @@ -0,0 +1,323 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const INTEGRITY_CHECKSUM = '82ef9b96d8393b6da34527d1d6e19187' +const bypassHeaderName = 'x-msw-bypass' +const activeClientIds = new Set() + +self.addEventListener('install', function () { + return self.skipWaiting() +}) + +self.addEventListener('activate', async function (event) { + return self.clients.claim() +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll() + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: INTEGRITY_CHECKSUM, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: true, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +// Resolve the "master" client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMasterClient(event) { + const client = await self.clients.get(event.clientId) + + if (client.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll() + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function handleRequest(event, requestId) { + const client = await resolveMasterClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const clonedResponse = response.clone() + sendToClient(client, { + type: 'RESPONSE', + payload: { + requestId, + type: clonedResponse.type, + ok: clonedResponse.ok, + status: clonedResponse.status, + statusText: clonedResponse.statusText, + body: + clonedResponse.body === null ? null : await clonedResponse.text(), + headers: serializeHeaders(clonedResponse.headers), + redirected: clonedResponse.redirected, + }, + }) + })() + } + + return response +} + +async function getResponse(event, client, requestId) { + const { request } = event + const requestClone = request.clone() + const getOriginalResponse = () => fetch(requestClone) + + // Bypass mocking when the request client is not active. + if (!client) { + return getOriginalResponse() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return await getOriginalResponse() + } + + // Bypass requests with the explicit bypass header + if (requestClone.headers.get(bypassHeaderName) === 'true') { + const cleanRequestHeaders = serializeHeaders(requestClone.headers) + + // Remove the bypass header to comply with the CORS preflight check. + delete cleanRequestHeaders[bypassHeaderName] + + const originalRequest = new Request(requestClone, { + headers: new Headers(cleanRequestHeaders), + }) + + return fetch(originalRequest) + } + + // Send the request to the client-side MSW. + const reqHeaders = serializeHeaders(request.headers) + const body = await request.text() + + const clientMessage = await sendToClient(client, { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + method: request.method, + headers: reqHeaders, + cache: request.cache, + mode: request.mode, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body, + bodyUsed: request.bodyUsed, + keepalive: request.keepalive, + }, + }) + + switch (clientMessage.type) { + case 'MOCK_SUCCESS': { + return delayPromise( + () => respondWithMock(clientMessage), + clientMessage.payload.delay, + ) + } + + case 'MOCK_NOT_FOUND': { + return getOriginalResponse() + } + + case 'NETWORK_ERROR': { + const { name, message } = clientMessage.payload + const networkError = new Error(message) + networkError.name = name + + // Rejecting a request Promise emulates a network error. + throw networkError + } + + case 'INTERNAL_ERROR': { + const parsedBody = JSON.parse(clientMessage.payload.body) + + console.error( + `\ +[MSW] Request handler function for "%s %s" has thrown the following exception: + +${parsedBody.errorType}: ${parsedBody.message} +(see more detailed error stack trace in the mocked response body) + +This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error. +If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses\ +`, + request.method, + request.url, + ) + + return respondWithMock(clientMessage) + } + } + + return getOriginalResponse() +} + +self.addEventListener('fetch', function (event) { + const { request } = event + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + const requestId = uuidv4() + + return event.respondWith( + handleRequest(event, requestId).catch((error) => { + console.error( + '[MSW] Failed to mock a "%s" request to "%s": %s', + request.method, + request.url, + error, + ) + }), + ) +}) + +function serializeHeaders(headers) { + const reqHeaders = {} + headers.forEach((value, name) => { + reqHeaders[name] = reqHeaders[name] + ? [].concat(reqHeaders[name]).concat(value) + : value + }) + return reqHeaders +} + +function sendToClient(client, message) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage(JSON.stringify(message), [channel.port2]) + }) +} + +function delayPromise(cb, duration) { + return new Promise((resolve) => { + setTimeout(() => resolve(cb()), duration) + }) +} + +function respondWithMock(clientMessage) { + return new Response(clientMessage.payload.body, { + ...clientMessage.payload, + headers: clientMessage.payload.headers, + }) +} + +function uuidv4() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (Math.random() * 16) | 0 + const v = c == 'x' ? r : (r & 0x3) | 0x8 + return v.toString(16) + }) +} From bde3e8e9b307e9ad637225b14a67d3825b59342d Mon Sep 17 00:00:00 2001 From: KrisJohnson Date: Wed, 9 Jun 2021 07:16:58 -0500 Subject: [PATCH 02/13] updated to pass standard checks --- components/taxReceiptButton.js | 60 +++++++++++++++++----------------- mocks/handlers.js | 16 ++++----- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/components/taxReceiptButton.js b/components/taxReceiptButton.js index ea66b35..f06059e 100644 --- a/components/taxReceiptButton.js +++ b/components/taxReceiptButton.js @@ -9,7 +9,7 @@ const GetTaxReceiptsButton = ({ taxYears, handleSelectTaxYear, selectedTaxYear } taxYears && taxYears.length > 1 ? - Get Tax Receipt + Get Tax Receipt {taxYears.map(year => ( @@ -18,17 +18,17 @@ const GetTaxReceiptsButton = ({ taxYears, handleSelectTaxYear, selectedTaxYear } ))} - + : ) @@ -100,7 +100,7 @@ const TaxReceiptButton = () => { } const getSaveFileName = () => - `Teacher_Fund_Tax_Receipt_${formatTimestamp(new Date())}` + `Teacher_Fund_Tax_Receipt_${formatTimestamp(new Date())}` const getDownloadLinkText = ({ loading, error }) => loading @@ -110,28 +110,28 @@ const TaxReceiptButton = () => { return ( donationsLoading ?