From 6830d76f0072187cbf9511d864b2f155b73653d1 Mon Sep 17 00:00:00 2001 From: Peter Barnum Date: Thu, 28 Mar 2024 18:12:08 -0600 Subject: [PATCH 01/10] specify gcloud function response type --- src/api/googleFunctions.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/api/googleFunctions.ts b/src/api/googleFunctions.ts index d680ec50..d47084bf 100644 --- a/src/api/googleFunctions.ts +++ b/src/api/googleFunctions.ts @@ -1,3 +1,4 @@ +import { GaxiosPromise } from '@googleapis/calendar'; import { GoogleAuth } from 'google-auth-library'; export type RunCloudFunctionArgs = { @@ -6,7 +7,11 @@ export type RunCloudFunctionArgs = { headers?: { [key: string]: string | undefined }; }; -export async function runCloudFunction({ url, body, headers = {} }: RunCloudFunctionArgs) { +export async function runCloudFunction({ + url, + body, + headers = {}, +}: RunCloudFunctionArgs): GaxiosPromise { const { GOOGLE_SERVICE_ACCOUNT } = process.env; const credentials = GOOGLE_SERVICE_ACCOUNT && JSON.parse(GOOGLE_SERVICE_ACCOUNT); if (!credentials) { @@ -15,7 +20,7 @@ export async function runCloudFunction({ url, body, headers = {} }: RunCloudF const auth = new GoogleAuth({ credentials }); const client = await auth.getIdTokenClient(url); - await client.request({ + return await client.request({ url, method: 'POST', headers: { From 59a9efc1a51fdbdcf44577accdab23f86f99e707 Mon Sep 17 00:00:00 2001 From: Peter Barnum Date: Thu, 28 Mar 2024 18:12:23 -0600 Subject: [PATCH 02/10] fix form `onEnter` --- src/components/Form/Form.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index 0dcf2d2b..bed20878 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -1,6 +1,6 @@ +import { MotionProps, motion } from 'framer-motion'; import { CSSProperties, FormEvent, FormEventHandler, KeyboardEvent, ReactNode } from 'react'; import styled from 'styled-components'; -import { motion, MotionProps } from 'framer-motion'; const FormStyles = styled(motion.form)` display: flex; @@ -33,6 +33,7 @@ interface FormProps { const Form = ({ children, onSubmit, style, motionProps, className, onEnter }: FormProps) => { const handleSubmit: FormEventHandler = (e) => { e.preventDefault(); + e.stopPropagation(); onSubmit && onSubmit(e); }; return ( @@ -41,7 +42,7 @@ const Form = ({ children, onSubmit, style, motionProps, className, onEnter }: Fo className={className} onSubmit={handleSubmit} style={style} - onKeyDown={onEnter} + onKeyDown={(e) => e.key === 'Enter' && onEnter?.(e)} {...motionProps} > {children} From 583dbae271ff257ac69de0a49db1a80903cae1f1 Mon Sep 17 00:00:00 2001 From: Peter Barnum Date: Thu, 28 Mar 2024 18:12:58 -0600 Subject: [PATCH 03/10] move referenceOptions to new file --- src/Forms/Form.Workforce.tsx | 52 +------------------------- src/Forms/formData/referenceOptions.ts | 50 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 51 deletions(-) create mode 100644 src/Forms/formData/referenceOptions.ts diff --git a/src/Forms/Form.Workforce.tsx b/src/Forms/Form.Workforce.tsx index 6f2d6e90..0f74847b 100644 --- a/src/Forms/Form.Workforce.tsx +++ b/src/Forms/Form.Workforce.tsx @@ -13,6 +13,7 @@ import { FormDataSignup } from '@this/pages-api/infoSession/user'; import Spinner from '../components/Elements/Spinner'; import { getStateFromZipCode } from '../components/Form/helpers'; import useKeyCombo from '../hooks/useKeyCombo'; +import { referencedByOptions } from './formData/referenceOptions'; interface WorkforceFormProps { sessionDates: ISessionDates[]; @@ -445,54 +446,3 @@ const workforceFormInputs = [ required: true, }, ]; - -const referencedByOptions = [ - { - value: 'google', - name: 'Google', - }, - { - value: 'facebook', - name: 'Facebook', - }, - { - value: 'instagram', - name: 'Instagram', - }, - { - value: 'flyer', - name: 'Flyer', - }, - { - value: 'radio', - name: 'Radio Advertising', - }, - { - value: 'tv-streaming', - name: 'T.V. or Streaming Service', - }, - { - value: 'snap', - name: 'SNAP', - }, - { - value: 'other-advertising', - name: 'Other Advertising', - additionalInfo: 'Where did you hear about us?', - }, - { - value: 'verbal', - name: 'Word of mouth', - additionalInfo: 'Who told you about us?', - }, - { - value: 'event', - name: 'Community Event', - additionalInfo: 'Which event?', - }, - { - value: 'organization', - name: 'Community Organization', - additionalInfo: 'Which organization?', - }, -]; diff --git a/src/Forms/formData/referenceOptions.ts b/src/Forms/formData/referenceOptions.ts new file mode 100644 index 00000000..d496d3b0 --- /dev/null +++ b/src/Forms/formData/referenceOptions.ts @@ -0,0 +1,50 @@ +export const referencedByOptions = [ + { + value: 'google', + name: 'Google', + }, + { + value: 'facebook', + name: 'Facebook', + }, + { + value: 'instagram', + name: 'Instagram', + }, + { + value: 'flyer', + name: 'Flyer', + }, + { + value: 'radio', + name: 'Radio Advertising', + }, + { + value: 'tv-streaming', + name: 'T.V. or Streaming Service', + }, + { + value: 'snap', + name: 'SNAP', + }, + { + value: 'other-advertising', + name: 'Other Advertising', + additionalInfo: 'Where did you hear about us?', + }, + { + value: 'verbal', + name: 'Word of mouth', + additionalInfo: 'Who told you about us?', + }, + { + value: 'event', + name: 'Community Event', + additionalInfo: 'Which event?', + }, + { + value: 'organization', + name: 'Community Organization', + additionalInfo: 'Which organization?', + }, +]; From e5d9f8a9d54bbeaf8c1b8abd938222bfa6dbddba Mon Sep 17 00:00:00 2001 From: Peter Barnum Date: Thu, 28 Mar 2024 18:14:17 -0600 Subject: [PATCH 04/10] dynamically handle referrer --- pages/programs/workforce/infoSession.tsx | 31 ++++++++++++++++++++++-- src/Forms/Form.Workforce.tsx | 21 ++++++++-------- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/pages/programs/workforce/infoSession.tsx b/pages/programs/workforce/infoSession.tsx index 0631b033..75cae9a4 100644 --- a/pages/programs/workforce/infoSession.tsx +++ b/pages/programs/workforce/infoSession.tsx @@ -11,6 +11,7 @@ import { Content, Main, Section } from '@this/components/layout'; import { TOption } from '@this/data/types/bits'; import useKeyCombo from '@this/hooks/useKeyCombo'; import { getStaticAsset } from '@this/pages-api/static/[asset]'; +import { referencedByOptions } from '@this/src/Forms/formData/referenceOptions'; import { getFormattedDateTime } from '@this/src/helpers/timeUtils'; import useInfoSession from '@this/src/hooks/useInfoSession'; import { cardShadow, cardShadowLtr, cardShadowRtl } from '@this/src/theme/styled/mixins/shadows'; @@ -34,9 +35,35 @@ const InfoSession: NextPage = ({ commonQuestions, logos }) => useEffect(() => { const { referred_by = '' } = router.query; - if (!referred_by || typeof referred_by !== 'string' || referred_by.toLowerCase() !== 'snap') + const referredByStr = Array.isArray(referred_by) ? referred_by[0] : referred_by; + if (!referredByStr) return; + const [referredType, referredValue] = referredByStr.split('@'); + const defaultOption = { + name: 'Other Advertising', + value: 'other-advertising', + additionalInfoLabel: 'Where did you hear about us?', + additionalInfo: referredType, + }; + const referredByOption = referencedByOptions.find( + (option) => option.value === referredType.toLowerCase(), + ); + + if (!referredByOption) { + setReferredBy(defaultOption); return; - setReferredBy({ name: 'SNAP', value: 'snap' }); + } + + if (referredByOption.additionalInfo) { + referredValue && + setReferredBy({ + ...referredByOption, + additionalInfoLabel: referredByOption.additionalInfo, + additionalInfo: referredValue, + }); + return; + } + + setReferredBy(referredByOption); }, [router.query]); return ( diff --git a/src/Forms/Form.Workforce.tsx b/src/Forms/Form.Workforce.tsx index 0f74847b..939953c3 100644 --- a/src/Forms/Form.Workforce.tsx +++ b/src/Forms/Form.Workforce.tsx @@ -6,6 +6,7 @@ import styled from 'styled-components'; import Button from '@this/components/Elements/Button'; import { Form, Input, useForm } from '@this/components/Form'; +import { TOption } from '@this/data/types/bits'; import { IInfoSessionFormValues } from '@this/data/types/infoSession'; import { pixel } from '@this/lib/pixel'; import { ISessionDates } from '@this/pages-api/infoSession/dates'; @@ -17,7 +18,7 @@ import { referencedByOptions } from './formData/referenceOptions'; interface WorkforceFormProps { sessionDates: ISessionDates[]; - referredBy?: { name: string; value: string }; + referredBy?: TOption; } const WorkforceForm = ({ sessionDates, referredBy }: WorkforceFormProps) => { @@ -25,12 +26,10 @@ const WorkforceForm = ({ sessionDates, referredBy }: WorkforceFormProps) => { const [isSubmitting, setIsSubmitting] = useState(false); const [locationMessage, setLocationMessage] = useState(''); - const isKeyComboActive = useKeyCombo('o', 's'); - const currentValues = form.values(); - const handleSubmit = () => { + const handleSubmit = async () => { const hasErrors = form.hasErrors(); if (hasErrors) { @@ -172,12 +171,14 @@ const WorkforceForm = ({ sessionDates, referredBy }: WorkforceFormProps) => { }, [currentValues.sessionDate, currentValues.attendingLocation, sessionDates]); useEffect(() => { - if (referredBy) { - form.onSelectChange('referencedBy')({ - option: referredBy, - isValid: true, - }); - } + if (!referredBy) return; + form.onSelectChange('referencedBy')({ + option: referredBy, + additionalInfo: referredBy.additionalInfo, + additionalInfoLabel: referredBy.additionalInfoLabel, + isValid: true, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps -- Ignore form change }, [referredBy]); From 8c55d3766ed0451ac150b9164be95f04161a7126 Mon Sep 17 00:00:00 2001 From: Peter Barnum Date: Thu, 28 Mar 2024 18:14:44 -0600 Subject: [PATCH 05/10] display signup details, success, and renderer url --- pages/api/infoSession/user.ts | 5 +- src/Forms/Form.Workforce.tsx | 131 ++++++++++++++++++++++++++++------ 2 files changed, 111 insertions(+), 25 deletions(-) diff --git a/pages/api/infoSession/user.ts b/pages/api/infoSession/user.ts index 2db6d5fa..59355339 100644 --- a/pages/api/infoSession/user.ts +++ b/pages/api/infoSession/user.ts @@ -71,7 +71,8 @@ export default async function handleInfoSessionForm(req: ISessionUser, res: Next try { const payload: ISessionSignup = formatPayload(req.body); - await runCloudFunction({ + type SignupResult = { url: string }; + const result = await runCloudFunction({ url: SIGNUP_API_ENDPOINT, body: payload, headers: { @@ -79,7 +80,7 @@ export default async function handleInfoSessionForm(req: ISessionUser, res: Next }, }); - res.status(200).end(); + res.status(200).json(result.data); } catch (error) { console.error('Could not POST to signup service', error); res.status(500).end(); diff --git a/src/Forms/Form.Workforce.tsx b/src/Forms/Form.Workforce.tsx index 939953c3..4beb38fe 100644 --- a/src/Forms/Form.Workforce.tsx +++ b/src/Forms/Form.Workforce.tsx @@ -1,9 +1,12 @@ import axios from 'axios'; import moment from 'moment'; import { Fragment, useEffect, useState } from 'react'; -import { AiOutlineInfoCircle } from 'react-icons/ai'; import styled from 'styled-components'; +import { AiOutlineInfoCircle } from 'react-icons/ai'; +import { IoMdCloseCircleOutline as CloseIcon } from 'react-icons/io'; +import { MdOpenInNew as NewTabIcon } from 'react-icons/md'; + import Button from '@this/components/Elements/Button'; import { Form, Input, useForm } from '@this/components/Form'; import { TOption } from '@this/data/types/bits'; @@ -24,6 +27,7 @@ interface WorkforceFormProps { const WorkforceForm = ({ sessionDates, referredBy }: WorkforceFormProps) => { const form = useForm(); const [isSubmitting, setIsSubmitting] = useState(false); + const [renderUrl, setRenderUrl] = useState(null); const [locationMessage, setLocationMessage] = useState(''); const isKeyComboActive = useKeyCombo('o', 's'); @@ -64,21 +68,22 @@ const WorkforceForm = ({ sessionDates, referredBy }: WorkforceFormProps) => { sessionTime: session?.times?.start?.dateTime || null, }); - axios - .post('/api/infoSession/user', body) - .then(() => { - form.notifySuccess({ - msg: 'Info session registration for submitted. You should receive an email and text message shortly.', - }); - form.clear(); - }) - .catch(() => { - form.notifyError({ - title: 'Error', - msg: 'There was an error signing you up\nPlease reach out to us at "admissions@operationspark.org" or give us a call at 504-233-3838', - }); - }) - .finally(() => setIsSubmitting(false)); + try { + const { data } = await axios.post('/api/infoSession/user', body); + + form.notifySuccess({ + msg: 'Info session registration for submitted. You should receive an email and text message shortly.', + }); + + setRenderUrl(data.url); + } catch (error) { + form.notifyError({ + title: 'Error', + msg: 'There was an error signing you up\nPlease reach out to us at "admissions@operationspark.org" or give us a call at 504-233-3838', + }); + } finally { + setIsSubmitting(false); + } }; const getLocationType = (session: ISessionDates) => { const locationType = session.locationType @@ -104,6 +109,11 @@ const WorkforceForm = ({ sessionDates, referredBy }: WorkforceFormProps) => { }, ]; + const closeDetails = () => { + setRenderUrl(null); + form.clear(); + }; + useEffect(() => { const zipChange = form.onSelectChange('userLocation'); const zipCode = Number(currentValues.zipCode); @@ -184,12 +194,14 @@ const WorkforceForm = ({ sessionDates, referredBy }: WorkforceFormProps) => { return ( -
- {isSubmitting ? ( -
- -
- ) : null} + { + e.preventDefault(); + e.stopPropagation(); + handleSubmit(); + }} + > {workforceFormInputs.map((field, i) => ( { > Register! + + {isSubmitting ? ( +
+ +
+ ) : null} + {renderUrl && !isSubmitting ? ( +
+
+

Success!

+

+ You have successfully registered for an info session on{' '} + {currentValues.sessionDate.name}. You will + receive an email {currentValues.smsOptIn === 'true' ? 'and text message' : ''}{' '} + shortly. +

+ + View your registration details + + +
+
+ ) : null}
); @@ -363,13 +400,61 @@ const WorkforceFormStyles = styled.div` .form-overlay { position: absolute; inset: 0; - z-index: 10; + z-index: 100; backdrop-filter: blur(1.5px); background: rgba(125, 125, 125, 0.2); display: flex; align-items: center; justify-content: center; } + + .form-complete-response { + position: relative; + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + background: ${({ theme }) => theme.bg}; + padding: 1.5rem; + border-radius: 0.5rem; + max-width: 90%; + text-align: center; + h2 { + font-size: 2rem; + font-weight: 600; + margin-bottom: 1rem; + } + p { + font-size: 1.1rem; + font-weight: 300; + margin-bottom: 1rem; + } + a { + display: flex; + align-items: center; + font-weight: 600; + margin-top: 1rem; + gap: 0.5rem; + } + .close-button { + position: absolute; + top: 0.5rem; + right: 0.5rem; + font-size: 1.5rem; + cursor: pointer; + color: ${({ theme }) => theme.red[300]}; + transition: all 225ms; + :hover { + color: ${({ theme }) => theme.red[500]}; + transform: scale(1.1); + } + + :active { + transform: scale(0.9); + } + } + } + .user-location-row { display: flex; width: 100%; From 63426e576647be0320ec5bc8310f16632a76fbfb Mon Sep 17 00:00:00 2001 From: Peter Barnum Date: Thu, 28 Mar 2024 18:24:19 -0600 Subject: [PATCH 06/10] disable showcase --- data/gradShowcase.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/gradShowcase.json b/data/gradShowcase.json index fc8bcd31..8d25245f 100644 --- a/data/gradShowcase.json +++ b/data/gradShowcase.json @@ -2,5 +2,5 @@ "startDateTime": "3/28/2024 18:00", "cohortName": "Golf", "eventbriteUrl": "https://showcase.operationspark.org/opspark%20website", - "isActive": true + "isActive": false } From 41df330df508377fbafd407aea727591b0262b34 Mon Sep 17 00:00:00 2001 From: Peter Barnum Date: Thu, 28 Mar 2024 19:30:03 -0600 Subject: [PATCH 07/10] make success toast dynamic --- src/Forms/Form.Workforce.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Forms/Form.Workforce.tsx b/src/Forms/Form.Workforce.tsx index 4beb38fe..340cfcaa 100644 --- a/src/Forms/Form.Workforce.tsx +++ b/src/Forms/Form.Workforce.tsx @@ -70,9 +70,9 @@ const WorkforceForm = ({ sessionDates, referredBy }: WorkforceFormProps) => { try { const { data } = await axios.post('/api/infoSession/user', body); - + const textMessage = currentValues.smsOptIn === 'true' ? ' and text message' : ''; form.notifySuccess({ - msg: 'Info session registration for submitted. You should receive an email and text message shortly.', + msg: `Info session registration for submitted. You will receive an email${textMessage} shortly.`, }); setRenderUrl(data.url); From 9b01010d21347cfd5d3bddd7c75f77604b8c433e Mon Sep 17 00:00:00 2001 From: Peter Barnum Date: Thu, 28 Mar 2024 20:33:57 -0600 Subject: [PATCH 08/10] handle message for no session (future) --- src/Forms/Form.Workforce.tsx | 44 ++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/Forms/Form.Workforce.tsx b/src/Forms/Form.Workforce.tsx index 340cfcaa..8e3a717e 100644 --- a/src/Forms/Form.Workforce.tsx +++ b/src/Forms/Form.Workforce.tsx @@ -69,10 +69,24 @@ const WorkforceForm = ({ sessionDates, referredBy }: WorkforceFormProps) => { }); try { - const { data } = await axios.post('/api/infoSession/user', body); + const { data } = true + ? { data: { url: 'https://ospk.org/dvKqYqEJBb' } } + : await axios.post('/api/infoSession/user', body); + const textMessage = currentValues.smsOptIn === 'true' ? ' and text message' : ''; + + if (currentValues.sessionDate.value === 'future') { + form.notifySuccess({ + msg: `Thank you for signing up! We will reach out soon. You will receive an email ${textMessage} shortly.`, + }); + setRenderUrl(data.url); + return setIsSubmitting(false); + } + + const sessionDate = currentValues.sessionDate.name; + console.log(currentValues.sessionDate.value); form.notifySuccess({ - msg: `Info session registration for submitted. You will receive an email${textMessage} shortly.`, + msg: `You have successfully registered for an info session on ${sessionDate}. You will receive an email ${textMessage} shortly.`, }); setRenderUrl(data.url); @@ -111,7 +125,7 @@ const WorkforceForm = ({ sessionDates, referredBy }: WorkforceFormProps) => { const closeDetails = () => { setRenderUrl(null); - form.clear(); + // form.clear(); // TODO: Add back }; useEffect(() => { @@ -374,14 +388,24 @@ const WorkforceForm = ({ sessionDates, referredBy }: WorkforceFormProps) => {

Success!

-

- You have successfully registered for an info session on{' '} - {currentValues.sessionDate.name}. You will - receive an email {currentValues.smsOptIn === 'true' ? 'and text message' : ''}{' '} - shortly. -

+ {currentValues.sessionDate.value === 'future' ? ( +

+ Thank you for signing up! We will reach out soon. You will receive an email{' '} + {currentValues.smsOptIn === 'true' ? 'and text message' : ''} shortly. +

+ ) : ( +

+ You have successfully registered for an info session on{' '} + {currentValues.sessionDate.name}. You will + receive an email {currentValues.smsOptIn === 'true' ? 'and text message' : ''}{' '} + shortly. +

+ )} - View your registration details + {currentValues.sessionDate.value === 'future' + ? 'View details' + : 'View your registration details'} +