|
1 | 1 | "use client";
|
2 | 2 |
|
3 |
| -import { Button } from "@/app/_components/ui/button"; |
4 |
| -import { Input } from "@/app/_components/ui/input"; |
5 |
| -import { cn } from "@/app/_utils/cn"; |
6 |
| -import { LoaderCircle } from "lucide-react"; |
7 | 3 | import { usePlausible } from "next-plausible";
|
8 |
| -import { useState } from "react"; |
9 |
| -import { ROUTES } from "../_constants/routes"; |
10 |
| - |
11 |
| -type FormStatus = "idle" | "loading" | "success" | "error"; |
| 4 | +import { LinkCustom } from "./link-custom"; |
12 | 5 |
|
13 | 6 | export const SubscribeForm = () => {
|
14 | 7 | const plausible = usePlausible()
|
15 |
| - const [formState, setFormState] = useState({ |
16 |
| - email: "", |
17 |
| - status: "idle" as FormStatus, |
18 |
| - message: "", |
19 |
| - }); |
20 |
| - |
21 |
| - const isLoading = formState.status === "loading"; |
22 |
| - |
23 |
| - const handleSubmit = async (e: React.FormEvent) => { |
24 |
| - e.preventDefault(); |
25 |
| - setFormState((prev) => ({ ...prev, status: "loading", message: "" })); |
26 |
| - |
27 |
| - try { |
28 |
| - const response = await fetch(ROUTES.email, { |
29 |
| - method: "POST", |
30 |
| - headers: { |
31 |
| - "Content-Type": "application/json", |
32 |
| - }, |
33 |
| - body: JSON.stringify({ email: formState.email }), |
34 |
| - }); |
35 |
| - |
36 |
| - const data = await response.json(); |
37 |
| - |
38 |
| - if (!response.ok) { |
39 |
| - throw new Error(data.message || "Failed to subscribe"); |
40 |
| - } |
41 |
| - |
42 |
| - if (data.success) { |
43 |
| - setFormState({ |
44 |
| - email: "", |
45 |
| - status: "success", |
46 |
| - message: "Thanks for subscribing! Please check your inbox for a confirmation email (it may be in your spam folder)." |
47 |
| - }); |
48 |
| - } else { |
49 |
| - setFormState((prev) => ({ |
50 |
| - ...prev, |
51 |
| - status: "error", |
52 |
| - message: data.message || "Failed to subscribe" |
53 |
| - })); |
54 |
| - } |
55 |
| - } catch (error) { |
56 |
| - console.error("Subscription error:", error); |
57 |
| - setFormState((prev) => ({ |
58 |
| - ...prev, |
59 |
| - status: "error", |
60 |
| - message: error instanceof Error ? error.message : "Failed to subscribe" |
61 |
| - })); |
62 |
| - } |
63 |
| - }; |
64 | 8 |
|
65 | 9 | return (
|
66 | 10 | <section className="flex flex-col mt-10 items-center justify-center py-10 px-4 text-center border border-neutral-400 dark:border-neutral-600 rounded-xl" aria-labelledby="subscribe-title">
|
67 | 11 | <h2 id="subscribe-title" className="text-2xl font-bold mb-5 text-foreground">
|
68 | 12 | Get notified when new patterns are added!
|
69 | 13 | </h2>
|
70 |
| - <form onSubmit={handleSubmit} className="space-y-4"> |
71 |
| - <div className="space-y-2"> |
72 |
| - <div className="flex flex-col md:flex-row gap-2"> |
73 |
| - <Input |
74 |
| - |
75 |
| - type="email" |
76 |
| - className="w-full md:w-[300px]" |
77 |
| - value={formState.email} |
78 |
| - onChange={(e) => setFormState((prev) => ({ ...prev, email: e.target.value }))} |
79 |
| - disabled={isLoading} |
80 |
| - required |
81 |
| - /> |
82 |
| - <Button |
83 |
| - type="submit" |
84 |
| - disabled={isLoading} |
85 |
| - data-loading={isLoading} |
86 |
| - variant="outline" |
87 |
| - onClick={() => plausible('subscribe')} |
88 |
| - > |
89 |
| - <span className="group-data-[loading=true]:text-transparent"> |
90 |
| - Notify me |
91 |
| - </span> |
92 |
| - {isLoading && ( |
93 |
| - <div className="absolute inset-0 flex items-center justify-center"> |
94 |
| - <LoaderCircle |
95 |
| - className="animate-spin" |
96 |
| - size={16} |
97 |
| - strokeWidth={2} |
98 |
| - aria-hidden="true" |
99 |
| - /> |
100 |
| - </div> |
101 |
| - )} |
102 |
| - </Button> |
103 |
| - </div> |
104 |
| - {formState.message && ( |
105 |
| - <p |
106 |
| - className={cn( |
107 |
| - "mt-2 text-sm", |
108 |
| - formState.status === "error" ? "text-destructive" : "text-muted-foreground", |
109 |
| - )} |
110 |
| - role="alert" |
111 |
| - aria-live="polite" |
112 |
| - > |
113 |
| - {formState.message} |
114 |
| - </p> |
115 |
| - )} |
116 |
| - </div> |
117 |
| - </form> |
| 14 | + <p className="text-muted-foreground max-w-xl mx-auto mb-5"> |
| 15 | + Subscribe to my newsletter "David's Dev Diary" for the latest updates on UX Patterns for Devs. |
| 16 | + </p> |
| 17 | + |
| 18 | + <LinkCustom variant="neutral" href="https://thedaviddias.substack.com/" onClick={() => plausible('subscribe')}>Subscribe</LinkCustom> |
| 19 | + |
118 | 20 | </section>
|
119 | 21 | );
|
120 | 22 | }
|
0 commit comments