Skip to content

Commit

Permalink
feat: create Attio list entry upon early access request (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
TheCatLady authored Oct 3, 2023
1 parent 0663a6d commit aaecc44
Show file tree
Hide file tree
Showing 7 changed files with 606 additions and 303 deletions.
23 changes: 11 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,44 +19,43 @@
"dependencies": {
"@headlessui/react": "1.7.17",
"@hookform/resolvers": "3.3.1",
"axios": "1.5.1",
"clsx": "2.0.0",
"graphql": "16.8.1",
"graphql-request": "6.1.0",
"next": "13.5.3",
"next-client-cookies": "1.0.3",
"next": "13.5.4",
"next-client-cookies": "1.0.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-google-recaptcha": "3.1.0",
"react-hook-form": "7.46.2",
"react-hook-form": "7.47.0",
"react-icons": "4.11.0",
"tailwind-merge": "1.14.0",
"zod": "3.22.2",
"zod": "3.22.3",
"zod-form-data": "2.0.1"
},
"devDependencies": {
"@commitlint/cli": "17.7.1",
"@commitlint/cli": "17.7.2",
"@commitlint/config-conventional": "17.7.0",
"@svgr/webpack": "8.1.0",
"@tailwindcss/forms": "0.5.6",
"@types/gtag.js": "0.0.14",
"@types/react": "18.2.23",
"@types/react": "18.2.24",
"@types/react-google-recaptcha": "2.1.6",
"@typescript-eslint/eslint-plugin": "6.7.3",
"@typescript-eslint/parser": "6.7.3",
"@typescript-eslint/eslint-plugin": "6.7.4",
"@typescript-eslint/parser": "6.7.4",
"autoprefixer": "10.4.16",
"encoding": "0.1.13",
"eslint": "8.50.0",
"eslint-config-next": "13.5.3",
"eslint-config-next": "13.5.4",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-simple-import-sort": "10.0.0",
"eslint-plugin-unused-imports": "3.0.0",
"husky": "8.0.3",
"lint-staged": "14.0.1",
"next-sitemap": "4.2.3",
"postcss": "8.4.30",
"postcss": "8.4.31",
"prettier": "3.0.3",
"prettier-plugin-tailwindcss": "0.5.4",
"prettier-plugin-tailwindcss": "0.5.5",
"svgo": "3.0.2",
"tailwindcss": "3.3.3",
"typescript": "5.2.2"
Expand Down
64 changes: 50 additions & 14 deletions src/app/api/request-early-access/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import axios from 'axios';
import type { NextRequest } from 'next/server';
import { z } from 'zod';
import { zfd } from 'zod-form-data';
Expand All @@ -9,34 +8,71 @@ import { RECAPTCHA_SECRET } from '@/constants/google';
export async function POST(req: NextRequest) {
const schema = zfd.formData({
email: zfd.text(z.string().email()),
captcha: zfd.text(z.string().optional()),
captcha: zfd.text(),
});

const { email, captcha } = schema.parse(await req.formData());
const { email, captcha } = schema.parse(await req.json());

const { data: captchaResponse } = await axios.post(
const captchaResponse = await fetch(
'https://www.google.com/recaptcha/api/siteverify',
null,
{
params: { secret: RECAPTCHA_SECRET!, response: captcha },
method: 'POST',
body: new URLSearchParams({
secret: RECAPTCHA_SECRET!,
response: captcha,
}),
},
);

if (!captchaResponse.ok) {
return new Response('Invalid captcha', { status: 500 });
}

const personResponse = await fetch(
'https://api.attio.com/v2/objects/people/records?matching_attribute=email_addresses',
{
method: 'PUT',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Bearer ${ATTIO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
data: { values: { email_addresses: [{ email_address: email }] } },
}),
},
);

if (!captchaResponse.success) {
return new Response('Invalid captcha', { status: 500 });
if (!personResponse.ok) {
return new Response('Failed to assert Attio person record', {
status: 500,
});
}

await axios.put(
'https://api.attio.com/v1/people',
{ email_addresses: [email] },
const listResponse = await fetch(
'https://api.attio.com/v2/lists/fix_early_access/entries',
{
auth: { username: ATTIO_API_KEY!, password: '' },
headers: { 'Content-Type': 'application/json' },
method: 'POST',
headers: {
Authorization: `Bearer ${ATTIO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
data: {
entry_values: {
status: [{ status: '60179743-3e4f-417e-8aa9-73623cd93715' }],
},
parent_object: 'people',
parent_record_id: (await personResponse.json()).data.id.record_id,
},
}),
},
);

if (!listResponse.ok) {
return new Response('Failed to create Attio list entry', {
status: 500,
});
}

return new Response();
}
4 changes: 2 additions & 2 deletions src/app/cookie-policy/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ export const metadata: Metadata = {
title: 'Cookie policy',
};

export default function CookiePolicy() {
export default function CookiePolicyPage() {
return (
<div className="bg-white px-6 py-32 lg:px-8">
<div className="px-6 py-32 lg:px-8">
<div className="mx-auto max-w-3xl text-base leading-7 text-gray-700">
<h1 className="mt-2 text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">
Cookie policy
Expand Down
2 changes: 1 addition & 1 deletion src/app/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const metadata: Metadata = {
title: 'Not Found',
};

export default function NotFound() {
export default function NotFoundPage() {
return (
<div className="mx-auto flex w-full max-w-7xl flex-auto flex-col justify-center px-6 py-24 sm:py-64 lg:px-8">
<p className="text-lg font-semibold leading-8 text-primary-900">404</p>
Expand Down
20 changes: 11 additions & 9 deletions src/components/RequestEarlyAccessForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import { Disclosure } from '@headlessui/react';
import { zodResolver } from '@hookform/resolvers/zod';
import axios from 'axios';
import { useRef, useState } from 'react';
import ReCAPTCHA from 'react-google-recaptcha';
import { useForm } from 'react-hook-form';
Expand Down Expand Up @@ -76,15 +75,18 @@ export function RequestEarlyAccessForm() {
<form
onSubmit={handleSubmit(async (data) => {
try {
const captcha = await captchaRef.current?.executeAsync();
const captcha =
(await captchaRef.current?.executeAsync()) ?? '';

await axios.post(
'/api/request-early-access',
{ ...data, captcha },
{
headers: { 'Content-type': 'multipart/form-data' },
},
);
const response = await fetch('/api/request-early-access', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...data, captcha }),
});

if (!response.ok) {
throw new Error(response.statusText);
}
} catch (e) {
if (e instanceof Error) {
setError('root.serverError', {
Expand Down
Loading

0 comments on commit aaecc44

Please sign in to comment.