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 Image from 'next/image'
 | 
				
			||||||
import Link from 'next/link'
 | 
					import Link from 'next/link'
 | 
				
			||||||
 | 
					import { useState } from 'react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Button } from '@/components/Button'
 | 
					import { Button } from '@/components/Button'
 | 
				
			||||||
import { Container } from '@/components/Container'
 | 
					import { Container } from '@/components/Container'
 | 
				
			||||||
@@ -9,6 +12,39 @@ import github from '@/images/github.svg'
 | 
				
			|||||||
import logomark from '@/images/logomark.svg'
 | 
					import logomark from '@/images/logomark.svg'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Footer() {
 | 
					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 (
 | 
					  return (
 | 
				
			||||||
    <footer className="border-t border-gray-200">
 | 
					    <footer className="border-t border-gray-200">
 | 
				
			||||||
      <Container>
 | 
					      <Container>
 | 
				
			||||||
@@ -43,20 +79,24 @@ export function Footer() {
 | 
				
			|||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </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">
 | 
					        <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>
 | 
				
			||||||
            <TextField
 | 
					            <form className="flex w-full justify-center md:w-auto" onSubmit={handleSubmit}>
 | 
				
			||||||
              type="email"
 | 
					              <TextField
 | 
				
			||||||
              aria-label="Email address"
 | 
					                type="email"
 | 
				
			||||||
              placeholder="Email address"
 | 
					                aria-label="Email address"
 | 
				
			||||||
              autoComplete="email"
 | 
					                placeholder="Email address"
 | 
				
			||||||
              required
 | 
					                autoComplete="email"
 | 
				
			||||||
              className="w-60 min-w-0 shrink"
 | 
					                required
 | 
				
			||||||
            />
 | 
					                className="w-60 min-w-0 shrink"
 | 
				
			||||||
            <Button type="submit" color="cyan" className="ml-4 flex-none">
 | 
					                value={email}
 | 
				
			||||||
              <span className="hidden lg:inline">Join our newsletter</span>
 | 
					                onChange={(e) => setEmail(e.target.value)}
 | 
				
			||||||
              <span className="lg:hidden">Join newsletter</span>
 | 
					              />
 | 
				
			||||||
            </Button>
 | 
					              <Button type="submit" color="cyan" className="ml-4 flex-none" disabled={loading}>
 | 
				
			||||||
          </form>
 | 
					                {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">
 | 
					          <p className="mt-6 text-sm text-gray-500 md:mt-0">
 | 
				
			||||||
            © Copyright{' '}
 | 
					            © Copyright{' '}
 | 
				
			||||||
            <a href="https://www.threefold.io" target="_blank" rel="noopener noreferrer" className="hover:text-cyan-500 transition-colors">
 | 
					            <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