diff --git a/package.json b/package.json index 201da6e..2217d3d 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@graphql-codegen/typescript-graphql-request": "^4.4.5", "@graphql-codegen/typescript-operations": "^2.3.5", "@juggle/resize-observer": "^3.3.1", + "@notionhq/client": "^1.0.4", "@radix-ui/react-polymorphic": "^0.0.14", "@reach/dialog": "^0.17.0", "@types/lodash": "^4.14.182", @@ -52,7 +53,8 @@ "react-use-measure": "^2.1.1", "react-youtube": "^9.0.2", "sharp": "^0.30.4", - "tiny-json-http": "^7.4.2" + "tiny-json-http": "^7.4.2", + "zod": "^3.17.3" }, "devDependencies": { "@graphql-codegen/cli": "^2.6.2", diff --git a/src/components/sections/contact/form/form.module.scss b/src/components/sections/contact/form/form.module.scss index d51916a..1c8962a 100644 --- a/src/components/sections/contact/form/form.module.scss +++ b/src/components/sections/contact/form/form.module.scss @@ -5,6 +5,7 @@ margin-top: 0; display: flex; place-content: center; + padding-bottom: tovw(80px, 'default', 80px); } position: relative; @@ -49,87 +50,113 @@ } } - form { - display: flex; - flex-direction: column; + .form { + position: relative; + height: fit-content; - > div:first-child { + > div { @include respond-to('mobile') { - display: flex; - flex-direction: column; + left: 0; + bottom: tovw(-75px, 'mobile', -165px); + font-size: tovw(8px, 'mobile', 12px); + width: 90%; + text-align: center; + margin: auto; } - display: grid; - margin-top: tovw(60px, 'default', 40px); - grid-template-columns: repeat(2, 1fr); - gap: tovw(40px, 'default', 25px); + bottom: 0; + right: 0; + text-align: end; + position: absolute; + width: tovw(245px, 'default', 80px); + color: var(--color-white); + font-size: tovw(14px, 'default', 9px); + text-transform: uppercase; + font-family: var(--font-dm-mono); } - label { + form { display: flex; flex-direction: column; - font-family: var(--font-tt-hoves); - font-size: tovw(30px, 'default', 18px); - input, - textarea, - select { - appearance: none; - width: 100%; - font-size: tovw(24px, 'default', 18px); - padding: tovw(16px, 'default', 12px) tovw(12px, 'default', 10px); - margin-top: tovw(20px, 'default', 16px); - background: rgb(142 142 142 / 0.1); - border: tovw(1px, 'default', 1px) solid var(--color-grey); - border-radius: tovw(8px, 'default', 8px); - - &:focus { - border: tovw(1px, 'default', 1px) solid var(--color-accent); - background: rgb(0 0 244 / 0.1); - transition: all 250ms; + > div:first-child { + @include respond-to('mobile') { + display: flex; + flex-direction: column; } - &::placeholder { - color: var(--color-grey-light); + display: grid; + margin-top: tovw(60px, 'default', 40px); + grid-template-columns: repeat(2, 1fr); + gap: tovw(40px, 'default', 25px); + } + + label { + display: flex; + flex-direction: column; + font-family: var(--font-tt-hoves); + font-size: tovw(30px, 'default', 18px); + + input, + textarea, + select { + appearance: none; + width: 100%; + font-size: tovw(24px, 'default', 18px); + padding: tovw(16px, 'default', 12px) tovw(12px, 'default', 10px); + margin-top: tovw(20px, 'default', 16px); + background: rgb(142 142 142 / 0.1); + border: tovw(1px, 'default', 1px) solid var(--color-grey); + border-radius: tovw(8px, 'default', 8px); + + &:focus { + border: tovw(1px, 'default', 1px) solid var(--color-accent); + background: rgb(0 0 244 / 0.1); + transition: all 250ms; + } + + &::placeholder { + color: var(--color-grey-light); + } + + option { + background-color: black; + opacity: 50%; + border: none; + color: var(--color-white); + } } - option { - background-color: black; - opacity: 50%; - border: none; - color: var(--color-white); + select { + background: url('/images/dropdown.svg') no-repeat 95% 50%; + background-color: rgb(142 142 142 / 0.1); + background-size: tovw(20px, 'default', 16px); + + &:invalid { + color: var(--color-grey-light); + } + } + + textarea { + resize: none; } } - select { - background: url('/images/dropdown.svg') no-repeat 95% 50%; - background-color: rgb(142 142 142 / 0.1); - background-size: tovw(20px, 'default', 16px); + > label:nth-child(2) { + margin-top: tovw(60px, 'default', 35px); + } - &:invalid { - color: var(--color-grey-light); + button { + @include respond-to('mobile') { + width: 100%; + font-size: tovw(18px, 'default', 18px); } + + width: fit-content; + align-self: center; + font-size: tovw(18px, 'default', 14px); + margin-top: tovw(50px, 'default', 35px); } - - textarea { - resize: none; - } - } - - > label:nth-child(2) { - margin-top: tovw(60px, 'default', 35px); - } - - button { - @include respond-to('mobile') { - width: 100%; - font-size: tovw(18px, 'default', 18px); - } - - width: fit-content; - align-self: center; - font-size: tovw(18px, 'default', 14px); - margin-top: tovw(50px, 'default', 35px); } } diff --git a/src/components/sections/contact/form/index.tsx b/src/components/sections/contact/form/index.tsx index d1e64b7..61d440e 100644 --- a/src/components/sections/contact/form/index.tsx +++ b/src/components/sections/contact/form/index.tsx @@ -6,9 +6,10 @@ import Section from '~/components/layout/section' import { Button } from '~/components/primitives/button' import Heading from '~/components/primitives/heading' +// import { notion } from '~/lib/notion' import s from './form.module.scss' -interface Props { +interface DataProps { data: { formHeading: string formWarning: string @@ -26,9 +27,111 @@ interface Props { } } -const Form = ({ data }: Props) => { - const [selectedOption, setSelectedOption] = useState(null) +type FormProps = { + data: DataProps['data'] + style?: React.CSSProperties +} +const CustomForm = ({ data }: FormProps) => { + const [selectedOption, setSelectedOption] = useState(null) + const [email, setEmail] = useState('') + const [text, setText] = useState('') + const [errorMsg, setErrorMsg] = useState('') + const [isFormSent, setIsFormSent] = useState(false) + const [isSending, setIsSending] = useState(false) + + const handleSubmit = async (e: any) => { + e.preventDefault() + + try { + setErrorMsg('') + + if (!selectedOption?.value) { + setErrorMsg('Inquiry is required') + return + } + + setIsSending(true) + + const data = { + email: email, + message: text, + inquiry: selectedOption.value + } + + await fetch('/api/contact', { + method: 'POST', + mode: 'no-cors', + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json' + } + }) + + setIsSending(false) + setIsFormSent(true) + } catch (error) { + // console.log(error) + } + } + + return ( +
+ {isSending &&
sending...
} + {errorMsg !== '' && ( +
+ )} + {isFormSent && errorMsg === '' && ( +
Thanks for reaching out! We'll contact you shortly.
+ )} +
handleSubmit(e)}> +
+ +
+ + +
+
+ ) +} + +const Form = ({ data }: DataProps) => { return (
@@ -38,45 +141,7 @@ const Form = ({ data }: Props) => { {data?.formWarning}
-
-
- -
- - -
+
diff --git a/src/components/sections/partners/contact/contact.module.scss b/src/components/sections/partners/contact/contact.module.scss index 58651c3..920acaa 100644 --- a/src/components/sections/partners/contact/contact.module.scss +++ b/src/components/sections/partners/contact/contact.module.scss @@ -111,88 +111,114 @@ } } - form { - display: flex; - flex-direction: column; + .form { + position: relative; + height: fit-content; - > div:first-child { + > div { @include respond-to('mobile') { - display: flex; - flex-direction: column; + left: 0; + bottom: tovw(-75px, 'mobile', -165px); + font-size: tovw(8px, 'mobile', 12px); + width: 90%; + text-align: center; + margin: auto; } - display: grid; - margin-top: tovw(60px, 'default', 40px); - grid-template-columns: repeat(2, 1fr); - gap: tovw(40px, 'default', 25px); + bottom: 0; + right: 0; + text-align: end; + position: absolute; + width: tovw(250px, 'default', 80px); + color: var(--color-white); + font-size: tovw(14px, 'default', 9px); + text-transform: uppercase; + font-family: var(--font-dm-mono); } - label { + form { display: flex; flex-direction: column; - justify-content: space-between; - font-family: var(--font-tt-hoves); - font-size: tovw(30px, 'default', 18px); - input, - textarea, - select { - appearance: none; - width: 100%; - font-size: tovw(24px, 'default', 18px); - padding: tovw(16px, 'default', 12px) tovw(12px, 'default', 10px); - margin-top: tovw(20px, 'default', 16px); - background: rgb(142 142 142 / 0.1); - border: tovw(1px, 'default', 1px) solid var(--color-grey); - border-radius: tovw(8px, 'default', 8px); - - &:focus { - border: tovw(1px, 'default', 1px) solid var(--color-accent); - background: rgb(0 0 244 / 0.1); - transition: all 250ms; + > div:first-child { + @include respond-to('mobile') { + display: flex; + flex-direction: column; } - &::placeholder { - color: var(--color-grey-light); + display: grid; + margin-top: tovw(60px, 'default', 40px); + grid-template-columns: repeat(2, 1fr); + gap: tovw(40px, 'default', 25px); + } + + label { + display: flex; + flex-direction: column; + justify-content: space-between; + font-family: var(--font-tt-hoves); + font-size: tovw(30px, 'default', 18px); + + input, + textarea, + select { + appearance: none; + width: 100%; + font-size: tovw(24px, 'default', 18px); + padding: tovw(16px, 'default', 12px) tovw(12px, 'default', 10px); + margin-top: tovw(20px, 'default', 16px); + background: rgb(142 142 142 / 0.1); + border: tovw(1px, 'default', 1px) solid var(--color-grey); + border-radius: tovw(8px, 'default', 8px); + + &:focus { + border: tovw(1px, 'default', 1px) solid var(--color-accent); + background: rgb(0 0 244 / 0.1); + transition: all 250ms; + } + + &::placeholder { + color: var(--color-grey-light); + } + + option { + background-color: black; + opacity: 50%; + border: none; + color: var(--color-white); + } } - option { - background-color: black; - opacity: 50%; - border: none; - color: var(--color-white); + select { + background: url('/images/dropdown.svg') no-repeat 95% 52%; + background-color: rgb(142 142 142 / 0.1); + background-size: tovw(20px, 'default', 16px); + + &:invalid { + color: var(--color-grey-light); + } + } + + textarea { + resize: none; } } - select { - background: url('/images/dropdown.svg') no-repeat 95% 52%; - background-color: rgb(142 142 142 / 0.1); - background-size: tovw(20px, 'default', 16px); + > label:nth-child(2) { + margin-top: tovw(60px, 'default', 35px); + } - &:invalid { - color: var(--color-grey-light); + button { + @include respond-to('mobile') { + width: 100%; + font-size: tovw(18px, 'default', 18px); } + + width: fit-content; + align-self: center; + font-size: tovw(18px, 'default', 14px); + margin-top: tovw(50px, 'default', 35px); } - - textarea { - resize: none; - } - } - - > label:nth-child(2) { - margin-top: tovw(60px, 'default', 35px); - } - - button { - @include respond-to('mobile') { - width: 100%; - font-size: tovw(18px, 'default', 18px); - } - - width: fit-content; - align-self: center; - font-size: tovw(18px, 'default', 14px); - margin-top: tovw(50px, 'default', 35px); } } diff --git a/src/components/sections/partners/contact/index.tsx b/src/components/sections/partners/contact/index.tsx index 68c440e..322bbbe 100644 --- a/src/components/sections/partners/contact/index.tsx +++ b/src/components/sections/partners/contact/index.tsx @@ -9,7 +9,7 @@ import Heading from '~/components/primitives/heading' import s from './contact.module.scss' -interface Props { +interface DataProps { data: { contactHeading: string contactDescription: string @@ -37,9 +37,168 @@ interface Props { } } -const Contact = ({ data }: Props) => { +type FormProps = { + data: DataProps['data'] +} + +const CustomForm = ({ data }: FormProps) => { const [selectedOption, setSelectedOption] = useState(null) + const [name, setName] = useState('') + const [email, setEmail] = useState('') + const [role, setRole] = useState('') + const [company, setCompany] = useState('') + const [jurisdiction, setJurisdiction] = useState('') + const [text, setText] = useState('') + + const [errorMsg, setErrorMsg] = useState('') + const [isFormSent, setIsFormSent] = useState(false) + const [isSending, setIsSending] = useState(false) + + const handleSubmit = async (e: any) => { + e.preventDefault() + + try { + setErrorMsg('') + + if (!selectedOption?.value) { + setErrorMsg('Inquiry is required') + return + } + + setIsSending(true) + + const data = { + name: name, + email: email, + message: text, + inquiry: selectedOption.value, + jurisdiction: jurisdiction, + company: company, + role: role + } + + await fetch('/api/inquiry', { + method: 'POST', + mode: 'no-cors', + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json' + } + }) + + setIsSending(false) + setIsFormSent(true) + } catch (error) { + // console.log(error) + } + } + + return ( +
+ {isSending &&
sending...
} + {errorMsg !== '' && ( +
+ )} + {isFormSent && errorMsg === '' && ( +
Thanks for reaching out! We'll contact you shortly.
+ )} +
handleSubmit(e)}> +
+ + + + + + + + +
+ ) +} + +const Contact = ({ data }: DataProps) => { return (
@@ -57,85 +216,7 @@ const Contact = ({ data }: Props) => { {data?.contactFormWarning}
-
-
- - - - - - - - +
diff --git a/src/lib/notion/index.ts b/src/lib/notion/index.ts new file mode 100644 index 0000000..0a5a6a4 --- /dev/null +++ b/src/lib/notion/index.ts @@ -0,0 +1,9 @@ +import { Client } from '@notionhq/client' + +export const notion = new Client({ + auth: process.env.NOTION_TOKEN +}) + +export const notionAlt = new Client({ + auth: process.env.NOTION_TOKEN_ALT +}) diff --git a/src/pages/api/contact.ts b/src/pages/api/contact.ts new file mode 100644 index 0000000..0d68295 --- /dev/null +++ b/src/pages/api/contact.ts @@ -0,0 +1,33 @@ +import { NextApiRequest, NextApiResponse } from 'next' + +import { internalServerError, success } from '../../lib/api-responses' +import { notion } from '../../lib/notion' + +export default async (req: NextApiRequest, res: NextApiResponse) => { + try { + const { email, message, inquiry } = JSON.parse(req.body) + await notion.pages.create({ + parent: { + database_id: '03c225f0469d436db60bd1b225deffef' as string + }, + properties: { + Email: { + email, + type: 'email' + }, + Message: { + type: 'rich_text', + rich_text: [{ text: { content: message }, type: 'text' }] + }, + 'Type of inquiry': { + type: 'select', + select: { name: inquiry } + } + } + }) + + success(res) + } catch (error) { + internalServerError(res, error) + } +} diff --git a/src/pages/api/inquiry.ts b/src/pages/api/inquiry.ts new file mode 100644 index 0000000..4f3ebc2 --- /dev/null +++ b/src/pages/api/inquiry.ts @@ -0,0 +1,55 @@ +import { NextApiRequest, NextApiResponse } from 'next' + +import { internalServerError, success } from '../../lib/api-responses' +import { notionAlt } from '../../lib/notion' + +export default async (req: NextApiRequest, res: NextApiResponse) => { + try { + const { name, email, message, inquiry, role, company, jurisdiction } = + JSON.parse(req.body) + await notionAlt.pages.create({ + parent: { + database_id: '90a7d71e342f4b91b7ca263a14d839ea' as string + }, + properties: { + Name: { + title: [ + { + text: { + content: name + } + } + ] + }, + Email: { + email, + type: 'email' + }, + Message: { + type: 'rich_text', + rich_text: [{ text: { content: message }, type: 'text' }] + }, + Role: { + type: 'rich_text', + rich_text: [{ text: { content: role }, type: 'text' }] + }, + Company: { + type: 'rich_text', + rich_text: [{ text: { content: company }, type: 'text' }] + }, + 'Legal jurisdiction': { + type: 'rich_text', + rich_text: [{ text: { content: jurisdiction }, type: 'text' }] + }, + Inquiry: { + type: 'select', + select: { name: inquiry } + } + } + }) + + success(res) + } catch (error) { + internalServerError(res, error) + } +} diff --git a/yarn.lock b/yarn.lock index f509b35..056ca6d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1101,6 +1101,14 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@notionhq/client@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@notionhq/client/-/client-1.0.4.tgz#405e9468576baf81019db4d791f4b52d091f4a57" + integrity sha512-m7zZ5l3RUktayf1lRBV1XMb8HSKsmWTv/LZPqP7UGC1NMzOlc+bbTOPNQ4CP/c1P4cP61VWLb/zBq7a3c0nMaw== + dependencies: + "@types/node-fetch" "^2.5.10" + node-fetch "^2.6.1" + "@polka/url@^1.0.0-next.20": version "1.0.0-next.21" resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" @@ -1216,6 +1224,14 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== +"@types/node-fetch@^2.5.10": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.1.tgz#8f127c50481db65886800ef496f20bbf15518975" + integrity sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA== + dependencies: + "@types/node" "*" + form-data "^3.0.0" + "@types/node@*": version "17.0.23" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da" @@ -6674,3 +6690,8 @@ youtube-player@5.5.2: debug "^2.6.6" load-script "^1.0.0" sister "^3.0.0" + +zod@^3.17.3: + version "3.17.3" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.17.3.tgz#86abbc670ff0063a4588d85a4dcc917d6e4af2ba" + integrity sha512-4oKP5zvG6GGbMlqBkI5FESOAweldEhSOZ6LI6cG+JzUT7ofj1ZOC0PJudpQOpT1iqOFpYYtX5Pw0+o403y4bcg==