Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions src/components/Footer.css
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,101 @@
font-size: 0.95rem;
}

/* Newsletter Section */
.newsletter-section {
margin-bottom: 1.5rem;
padding: 1rem;
background: rgba(160, 160, 255, 0.05);
border-radius: 8px;
border: 1px solid rgba(160, 160, 255, 0.1);
}

.newsletter-title {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 1rem;
font-weight: 600;
color: rgb(160, 160, 255);
margin-bottom: 0.5rem;
}

.newsletter-icon {
font-size: 0.9rem;
}

.newsletter-description {
color: #9ca3af;
font-size: 0.85rem;
line-height: 1.4;
margin-bottom: 1rem;
}

.newsletter-form {
width: 100%;
}

.newsletter-input-group {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}

.newsletter-input {
flex: 1;
min-width: 200px;
padding: 0.75rem;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(160, 160, 255, 0.2);
border-radius: 6px;
color: #f9fafb;
font-size: 0.9rem;
font-family: 'Google Code', monospace;
transition: all 0.3s ease;
}

.newsletter-input::placeholder {
color: #9ca3af;
}

.newsletter-input:focus {
outline: none;
border-color: rgb(160, 160, 255);
background: rgba(255, 255, 255, 0.15);
box-shadow: 0 0 0 3px rgba(160, 160, 255, 0.1);
}

.newsletter-input:disabled {
opacity: 0.6;
cursor: not-allowed;
}

.newsletter-button {
padding: 0.75rem 1.25rem;
background: linear-gradient(135deg, rgb(160, 160, 255) 0%, rgb(120, 120, 255) 100%);
color: rgb(20, 20, 48);
border: none;
border-radius: 6px;
font-size: 0.9rem;
font-weight: 600;
font-family: 'Google Code', monospace;
cursor: pointer;
transition: all 0.3s ease;
white-space: nowrap;
}

.newsletter-button:hover:not(:disabled) {
background: linear-gradient(135deg, rgb(140, 140, 255) 0%, rgb(100, 100, 255) 100%);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(160, 160, 255, 0.3);
}

.newsletter-button:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}

.footer-social {
display: flex;
gap: 1rem;
Expand Down Expand Up @@ -231,4 +326,22 @@
font-size: 0.75rem;
padding: 0.2rem 0.6rem;
}

/* Newsletter responsive */
.newsletter-section {
padding: 0.75rem;
}

.newsletter-input-group {
flex-direction: column;
gap: 0.75rem;
}

.newsletter-input {
min-width: auto;
}

.newsletter-button {
width: 100%;
}
}
112 changes: 112 additions & 0 deletions src/components/Footer.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Link } from "react-router-dom";
import { useState } from "react";
import {
FaGithub,
FaReact,
Expand All @@ -9,10 +10,81 @@ import {
FaHeart,
FaCode,
FaLeaf,
FaEnvelope,
} from "react-icons/fa";
import Toast from "./ui/Toast";
import "./Footer.css";

const Footer = () => {
const [email, setEmail] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const [toast, setToast] = useState({
isVisible: false,
message: "",
type: "info"
});

const validateEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};

const handleNewsletterSubmit = async (e) => {
e.preventDefault();

if (!email.trim()) {
setToast({
isVisible: true,
message: "❌ Please enter your email address.",
type: "error"
});
return;
}

if (!validateEmail(email)) {
setToast({
isVisible: true,
message: "❌ Please enter a valid email address.",
type: "error"
});
return;
}

setIsSubmitting(true);

try {
// Simulate API call - replace with actual backend integration
await new Promise(resolve => setTimeout(resolve, 1000));

// Here you would typically make an API call to your backend
// const response = await fetch('/api/newsletter/subscribe', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ email })
// });

setToast({
isVisible: true,
message: "✅ You have successfully subscribed to our newsletter!",
type: "success"
});

setEmail(""); // Clear the form
} catch (error) {
setToast({
isVisible: true,
message: "❌ Something went wrong. Please try again later.",
type: "error"
});
} finally {
setIsSubmitting(false);
}
};

const closeToast = () => {
setToast(prev => ({ ...prev, isVisible: false }));
};

return (
<footer className="footer font-google-code">
<div className="footer-container">
Expand All @@ -29,6 +101,38 @@ const Footer = () => {
<p className="footer-description">
Where developers share stories, insights, and code - no cap! 🔥
</p>

{/* Newsletter Subscription */}
<div className="newsletter-section">
<h4 className="newsletter-title">
<FaEnvelope className="newsletter-icon" />
Stay Updated
</h4>
<p className="newsletter-description">
Get the latest developer stories and insights delivered to your inbox.
</p>
<form onSubmit={handleNewsletterSubmit} className="newsletter-form">
<div className="newsletter-input-group">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
className="newsletter-input"
disabled={isSubmitting}
required
/>
<button
type="submit"
className="newsletter-button"
disabled={isSubmitting}
>
{isSubmitting ? "Subscribing..." : "Subscribe"}
</button>
</div>
</form>
</div>

<div className="footer-social">
<a
href="https://github.com/HacktoberBlog"
Expand Down Expand Up @@ -197,6 +301,14 @@ const Footer = () => {
</div>
</div>
</div>

{/* Toast Notification */}
<Toast
message={toast.message}
type={toast.type}
isVisible={toast.isVisible}
onClose={closeToast}
/>
</footer>
);
};
Expand Down
100 changes: 100 additions & 0 deletions src/components/ui/Toast.css
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use TailwindCSS

Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/* Toast Notifications */
.toast {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
min-width: 300px;
max-width: 400px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
animation: slideIn 0.3s ease-out;
font-family: 'Google Code', monospace;
}

.toast-content {
display: flex;
align-items: center;
padding: 1rem;
gap: 0.75rem;
}

.toast-icon {
font-size: 1.2rem;
flex-shrink: 0;
}

.toast-message {
flex: 1;
font-size: 0.9rem;
font-weight: 500;
}

.toast-close {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: background-color 0.2s ease;
}

.toast-close:hover {
background-color: rgba(0, 0, 0, 0.1);
}

/* Toast Types */
.toast-success {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
border-left: 4px solid #047857;
}

.toast-error {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: white;
border-left: 4px solid #b91c1c;
}

.toast-info {
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
color: white;
border-left: 4px solid #1d4ed8;
}

/* Animation */
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}

/* Responsive Design */
@media (max-width: 480px) {
.toast {
top: 10px;
right: 10px;
left: 10px;
min-width: auto;
max-width: none;
}

.toast-content {
padding: 0.75rem;
}

.toast-message {
font-size: 0.85rem;
}
}
Loading