Skip to content

Commit

Permalink
fix buggy address autocomplete interaction (#11)
Browse files Browse the repository at this point in the history
* fix buggy address autocomplete interaction

* back to hybrid

* 💅
  • Loading branch information
jgravois authored Jun 3, 2024
1 parent 0ad42d3 commit 1aecac1
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 74 deletions.
157 changes: 83 additions & 74 deletions app/routes/start.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Loader } from "@googlemaps/js-api-loader";
import type { ActionFunctionArgs, MetaFunction } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { Form, useActionData, useSearchParams } from "@remix-run/react";
import { useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";
import invariant from "tiny-invariant";

import { createUser, getUserByEmail } from "~/models/user.server";
Expand All @@ -13,6 +13,29 @@ import { HeaderLeft } from "./HeaderLeft";
const HALF = "AIzaSyBI_vhCo";
const OTHER_HALF = "hiRS0dvt5Yk7sAJ-978T_mUwd8";

const ADDRESS_REQUIRED = "Street address is required";

const callStytch = async (email: string) => {
const rawResponse = await fetch(
"https://test.stytch.com/v1/magic_links/email/login_or_create",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Basic ${btoa(
`${process.env.STYTCH_PROJECT_ID}:${process.env.STYTCH_SECRET}`,
)}`,
},

body: JSON.stringify({
email,
login_magic_link_url: "http://localhost:3000/authenticate",
}),
},
);
return rawResponse.json();
};

export const action = async ({ request }: ActionFunctionArgs) => {
invariant(process.env.STYTCH_PROJECT_ID, "STYTCH_PROJECT_ID must be set");
invariant(process.env.STYTCH_SECRET, "STYTCH_SECRET must be set");
Expand All @@ -35,62 +58,41 @@ export const action = async ({ request }: ActionFunctionArgs) => {
// if valid, call stytch, create user in DB and redirect to same generic landing page
const existingUser = await getUserByEmail(email);

if (!existingUser) {
if (typeof rawCoordinates !== "string" || rawCoordinates.length === 0) {
return json(
{
errors: {
email: null,
password: null,
address: "Street address is required",
},
},
{ status: 400 },
);
}
if (existingUser) {
await callStytch(email);
return redirect("/magic");
}

const coordinates = rawCoordinates?.split(",") as unknown as [
number,
number,
];
if (!validateCoordinates(coordinates)) {
return json(
{
errors: {
email: null,
password: null,
address: "signup is only available to HOA residents",
},
if (typeof rawCoordinates !== "string" || rawCoordinates.length === 0) {
return json(
{
errors: {
email: null,
password: null,
address: ADDRESS_REQUIRED,
},
{ status: 400 },
);
}
},
{ status: 400 },
);
}

const raw = await fetch(
"https://test.stytch.com/v1/magic_links/email/login_or_create",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Basic ${btoa(
`${process.env.STYTCH_PROJECT_ID}:${process.env.STYTCH_SECRET}`,
)}`,
const coordinates = rawCoordinates?.split(",") as unknown as [number, number];
if (!validateCoordinates(coordinates)) {
return json(
{
errors: {
email: null,
password: null,
address: "Sign up is only available to HOA residents",
},
},

body: JSON.stringify({
email,
login_magic_link_url: "http://localhost:3000/authenticate",
}),
},
);

const res = await raw.json();

if (res.user_id && !existingUser) {
await createUser(email, res.user_id);
{ status: 400 },
);
}

const response = await callStytch(email);
if (response.user_id) await createUser(email, response.user_id);

return redirect("/magic");
};

Expand All @@ -103,16 +105,16 @@ export default function Start() {
const emailRef = useRef<HTMLInputElement>(null);
const addressRef = useRef<HTMLInputElement>(null);
const coordinatesRef = useRef<HTMLInputElement>(null);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const autoCompleteRef = useRef<any>(null);
const autoCompleteRef = useRef<google.maps.places.Autocomplete | null>(null);
const [showAddress, setShowAddress] = useState(false);

const options = { fields: ["geometry"] };

useEffect(() => {
if (actionData?.errors?.address) {
addressRef.current?.focus();
if (!showAddress && actionData?.errors.address === ADDRESS_REQUIRED) {
setShowAddress(true);
}
}, [actionData]);

const options = { fields: ["geometry"] };
}, [actionData?.errors.address, showAddress, setShowAddress]);

useEffect(() => {
const loader = new Loader({
Expand All @@ -121,24 +123,33 @@ export default function Start() {
});

loader.load().then(async (goo) => {
const { Autocomplete } = await goo.maps.importLibrary("places");
autoCompleteRef.current = new Autocomplete(addressRef.current, options);

autoCompleteRef.current?.addListener("place_changed", async function () {
if (autoCompleteRef.current && coordinatesRef.current) {
const { geometry } = await autoCompleteRef.current.getPlace();
coordinatesRef.current.value = `${geometry.location.lng()},${geometry.location.lat()}`;
}
});
const { Autocomplete } = (await goo.maps.importLibrary(
"places",
)) as google.maps.PlacesLibrary;

if (addressRef.current) {
autoCompleteRef.current = new Autocomplete(
addressRef.current as HTMLInputElement,
options,
);

autoCompleteRef.current?.addListener(
"place_changed",
async function () {
if (autoCompleteRef.current && coordinatesRef.current) {
const { geometry } = await autoCompleteRef.current.getPlace();
coordinatesRef.current.value = `${geometry?.location?.lng()},${geometry?.location?.lat()}`;
}
},
);
}
});

return () => {
autoCompleteRef.current = null;
};
});

const showAddress = actionData?.errors.address;

return (
<>
<header className="header">
Expand All @@ -149,9 +160,7 @@ export default function Start() {
</header>
<div className="signUp">
<div className="signUp_form">
<p className="signUp_insutructions">
Sign up or sign in to your account, no password needed!
</p>
<p>Sign up or log in to your account, no password needed!</p>
<Form method="post">
<div>
<label htmlFor="email" className="signUp_label">
Expand All @@ -162,8 +171,6 @@ export default function Start() {
ref={emailRef}
id="email"
required
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={true}
name="email"
type="email"
autoComplete="email"
Expand Down Expand Up @@ -191,13 +198,15 @@ export default function Start() {
name="street-address"
type="text"
autoComplete="off"
required
aria-invalid={
actionData?.errors?.address ? true : undefined
}
aria-describedby="street-address-error"
className="signUp_input"
/>
{actionData?.errors?.address ? (
{actionData?.errors?.address &&
actionData?.errors?.address !== ADDRESS_REQUIRED ? (
<div className="pt-1 text-red-700" id="password-error">
{actionData.errors.address}
</div>
Expand Down
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"@types/bcryptjs": "^2.4.5",
"@types/eslint": "^8.44.6",
"@types/geojson": "^7946.0.14",
"@types/google.maps": "^3.55.9",
"@types/node": "^18.18.6",
"@types/react": "^18.2.31",
"@types/react-dom": "^18.2.14",
Expand Down

0 comments on commit 1aecac1

Please sign in to comment.