Skip to content

Commit

Permalink
add loading state
Browse files Browse the repository at this point in the history
  • Loading branch information
jakehobbs committed Jan 19, 2024
1 parent d4ccd70 commit 3eb4c09
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 14 deletions.
60 changes: 46 additions & 14 deletions src/components/petition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from "../data/petition.ts";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useCallback, useEffect, useMemo } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import {
Form,
FormControl,
Expand All @@ -29,6 +29,10 @@ import {
SelectValue,
} from "./ui/select.tsx";
import ky from "ky";
import { Alert, AlertDescription, AlertTitle } from "./ui/alert.tsx";
import { LoaderIcon, MailCheckIcon } from "lucide-react";
import { Simulate } from "react-dom/test-utils";
import reset = Simulate.reset;

Check failure on line 35 in src/components/petition.tsx

View workflow job for this annotation

GitHub Actions / deploy_frontend

'reset' is declared but its value is never read.

const PETITION_API_URL = "https://petitions-229503.appspot.com/api/sign";

Expand All @@ -54,17 +58,21 @@ export const Petition = () => {
},
});
const {
formState: { dirtyFields },
formState: { dirtyFields, touchedFields },

Check failure on line 61 in src/components/petition.tsx

View workflow job for this annotation

GitHub Actions / deploy_frontend

'touchedFields' is declared but its value is never read.
setValue,
getValues,
watch,
handleSubmit,
control,
resetField,
} = form;
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);

const onSubmit = useMemo(
() =>
handleSubmit(async (data) => {
setIsSubmitting(true);
window.dataLayer?.push({
event: "form_submitted",
});
Expand All @@ -88,6 +96,8 @@ export const Petition = () => {
throwHttpErrors: false,
});
if (petitionResp.status !== 200) {
setIsSubmitting(false);
alert("Error submitting. Please try again.");
throw new Error("Error submitting petition");
}
const campaignMailerResp = await ky.post(CAMPAIGN_MAILER_API_URL, {
Expand All @@ -106,8 +116,12 @@ export const Petition = () => {
throwHttpErrors: false,
});
if (campaignMailerResp.status !== 200) {
setIsSubmitting(false);
alert("Error submitting. Please try again.");
throw new Error("Error submitting message");
}
setIsSubmitted(true);
setIsSubmitting(false);
}),
[handleSubmit],
);
Expand Down Expand Up @@ -136,23 +150,30 @@ export const Petition = () => {
const injectValuesIntoMessage = useCallback(
(name: string | undefined, city: string | undefined) => {
if (dirtyFields.message) {
alert(
"You've already customized the message, so it couldn't be automatically updated with your new name or city. Please be sure to double check your message before submitting.",
console.log(
"Skipped updating message with name or city since it has been customized.",
);
return;
}
setValue(
"message",
DEFAULT_MESSAGE.replace("[Your name]", name || "[Your name]").replace(
"[Your city if you live in Sonoma County]",
city || "",
),
);
resetField("message", {
defaultValue: DEFAULT_MESSAGE.replace(
"[Your name]",
name || "[Your name]",
).replace("[Your city if you live in Sonoma County]", city || ""),
});
},
[dirtyFields.message, setValue],
);

return (
return isSubmitted ? (
<Alert className="self-center w-fit bg-slate-100">
<MailCheckIcon className="h-4 w-4" />
<AlertTitle>Thank you</AlertTitle>
<AlertDescription>
Your message has been submitted. Thank you for taking action!
</AlertDescription>
</Alert>
) : (
<Form {...form}>
<form
onSubmit={onSubmit}
Expand All @@ -162,6 +183,7 @@ export const Petition = () => {
<FormField
control={control}
name="name"
disabled={isSubmitting}
render={({ field }) => (
<FormItem>
<FormLabel>Full Name</FormLabel>
Expand All @@ -183,6 +205,7 @@ export const Petition = () => {
<FormField
control={control}
name="email"
disabled={isSubmitting}
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
Expand All @@ -200,6 +223,7 @@ export const Petition = () => {
<FormField
control={control}
name="phone"
disabled={isSubmitting}
render={({ field }) => (
<FormItem>
<FormLabel>Phone Number</FormLabel>
Expand All @@ -213,7 +237,7 @@ export const Petition = () => {
<FormField
control={control}
name="zip"
disabled={outsideUS}
disabled={outsideUS || isSubmitting}
render={({ field }) => (
<FormItem>
<FormLabel>Zip Code</FormLabel>
Expand All @@ -238,6 +262,7 @@ export const Petition = () => {
<FormField
control={control}
name="city"
disabled={outsideUS || isSubmitting}
render={({ field }) => (
<FormItem className={cn({ hidden: !cities.length })}>
<FormLabel>City</FormLabel>
Expand Down Expand Up @@ -269,6 +294,7 @@ export const Petition = () => {
<FormField
control={control}
name="outsideUS"
disabled={isSubmitting}
render={({ field }) => (
<FormItem
className={cn("flex gap-2 items-center", {
Expand All @@ -294,6 +320,7 @@ export const Petition = () => {
<FormField
control={control}
name="message"
disabled={isSubmitting}
render={({ field }) => (
<FormItem>
<FormLabel>Message</FormLabel>
Expand All @@ -304,7 +331,12 @@ export const Petition = () => {
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
<Button type="submit" disabled={isSubmitting}>
{isSubmitting && (
<LoaderIcon className="mr-2 h-4 w-4 animate-spin" />
)}
Submit
</Button>
<p className="text-xs text-center">
By signing, you agree to receive email messages from Direct Action
Everywhere. You may unsubscribe at any time.
Expand Down
58 changes: 58 additions & 0 deletions src/components/ui/alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "~/utils";

const alertVariants = cva(
"relative w-full rounded-lg border border-slate-200 p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-slate-950 dark:border-slate-800 dark:[&>svg]:text-slate-50",
{
variants: {
variant: {
default: "bg-white text-slate-950 dark:bg-slate-950 dark:text-slate-50",
destructive:
"border-red-500/50 text-red-500 dark:border-red-500 [&>svg]:text-red-500 dark:border-red-900/50 dark:text-red-900 dark:dark:border-red-900 dark:[&>svg]:text-red-900",
},
},
defaultVariants: {
variant: "default",
},
},
);

const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
));
Alert.displayName = "Alert";

const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
));
AlertTitle.displayName = "AlertTitle";

const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
));
AlertDescription.displayName = "AlertDescription";

export { Alert, AlertTitle, AlertDescription };

0 comments on commit 3eb4c09

Please sign in to comment.