forked from sashaastiadi/www_mycelium_net
		
	feat: add newsletter subscription form with loading and error states in Footer component
This commit is contained in:
		
							
								
								
									
										48
									
								
								src/app/api/subscribe/route.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/app/api/subscribe/route.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
import { NextRequest, NextResponse } from 'next/server';
 | 
			
		||||
 | 
			
		||||
export async function POST(req: NextRequest) {
 | 
			
		||||
  const { email } = await req.json();
 | 
			
		||||
 | 
			
		||||
  if (!email) {
 | 
			
		||||
    return NextResponse.json({ error: 'Email is required' }, { status: 400 });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const MAILERLITE_API_KEY = process.env.MAILERLITE_API_KEY;
 | 
			
		||||
  const MAILERLITE_GROUP_ID = process.env.MAILERLITE_GROUP_ID;
 | 
			
		||||
 | 
			
		||||
  if (!MAILERLITE_API_KEY || !MAILERLITE_GROUP_ID) {
 | 
			
		||||
    return NextResponse.json(
 | 
			
		||||
      { error: 'MailerLite API key or Group ID are not configured' },
 | 
			
		||||
      { status: 500 },
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    const response = await fetch(
 | 
			
		||||
      `https://api.mailerlite.com/api/v2/groups/${MAILERLITE_GROUP_ID}/subscribers`,
 | 
			
		||||
      {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Content-Type': 'application/json',
 | 
			
		||||
          'X-MailerLite-ApiKey': MAILERLITE_API_KEY,
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify({ email }),
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (!response.ok) {
 | 
			
		||||
      const errorData = await response.json();
 | 
			
		||||
      return NextResponse.json(
 | 
			
		||||
        { error: errorData.error.message || 'Something went wrong' },
 | 
			
		||||
        { status: response.status },
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return NextResponse.json({ success: true }, { status: 200 });
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    return NextResponse.json(
 | 
			
		||||
      { error: 'An unexpected error occurred' },
 | 
			
		||||
      { status: 500 },
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,8 @@
 | 
			
		||||
'use client'
 | 
			
		||||
 | 
			
		||||
import Image from 'next/image'
 | 
			
		||||
import Link from 'next/link'
 | 
			
		||||
import { useState } from 'react'
 | 
			
		||||
 | 
			
		||||
import { Button } from '@/components/Button'
 | 
			
		||||
import { Container } from '@/components/Container'
 | 
			
		||||
@@ -9,6 +12,39 @@ import github from '@/images/github.svg'
 | 
			
		||||
import logomark from '@/images/logomark.svg'
 | 
			
		||||
 | 
			
		||||
export function Footer() {
 | 
			
		||||
  const [email, setEmail] = useState('');
 | 
			
		||||
  const [loading, setLoading] = useState(false);
 | 
			
		||||
  const [message, setMessage] = useState('');
 | 
			
		||||
 | 
			
		||||
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    setLoading(true);
 | 
			
		||||
    setMessage('');
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const response = await fetch('/api/subscribe', {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Content-Type': 'application/json',
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify({ email }),
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      const data = await response.json();
 | 
			
		||||
 | 
			
		||||
      if (!response.ok) {
 | 
			
		||||
        throw new Error(data.error || 'Something went wrong');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      setMessage('Thanks for subscribing!');
 | 
			
		||||
      setEmail('');
 | 
			
		||||
    } catch (error: any) {
 | 
			
		||||
      setMessage(error.message);
 | 
			
		||||
    } finally {
 | 
			
		||||
      setLoading(false);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <footer className="border-t border-gray-200">
 | 
			
		||||
      <Container>
 | 
			
		||||
@@ -43,7 +79,8 @@ export function Footer() {
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div className="flex flex-col items-center border-t border-gray-200 pt-8 pb-12 md:flex-row-reverse md:justify-between md:pt-6">
 | 
			
		||||
          <form className="flex w-full justify-center md:w-auto">
 | 
			
		||||
          <div>
 | 
			
		||||
            <form className="flex w-full justify-center md:w-auto" onSubmit={handleSubmit}>
 | 
			
		||||
              <TextField
 | 
			
		||||
                type="email"
 | 
			
		||||
                aria-label="Email address"
 | 
			
		||||
@@ -51,12 +88,15 @@ export function Footer() {
 | 
			
		||||
                autoComplete="email"
 | 
			
		||||
                required
 | 
			
		||||
                className="w-60 min-w-0 shrink"
 | 
			
		||||
                value={email}
 | 
			
		||||
                onChange={(e) => setEmail(e.target.value)}
 | 
			
		||||
              />
 | 
			
		||||
            <Button type="submit" color="cyan" className="ml-4 flex-none">
 | 
			
		||||
              <span className="hidden lg:inline">Join our newsletter</span>
 | 
			
		||||
              <span className="lg:hidden">Join newsletter</span>
 | 
			
		||||
              <Button type="submit" color="cyan" className="ml-4 flex-none" disabled={loading}>
 | 
			
		||||
                {loading ? 'Joining...' : <><span className="hidden lg:inline">Join our newsletter</span><span className="lg:hidden">Join newsletter</span></>}
 | 
			
		||||
              </Button>
 | 
			
		||||
            </form>
 | 
			
		||||
            {message && <p className="mt-2 text-sm text-gray-600">{message}</p>}
 | 
			
		||||
          </div>
 | 
			
		||||
          <p className="mt-6 text-sm text-gray-500 md:mt-0">
 | 
			
		||||
            © Copyright{' '}
 | 
			
		||||
            <a href="https://www.threefold.io" target="_blank" rel="noopener noreferrer" className="hover:text-cyan-500 transition-colors">
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user