Skip to content

Commit

Permalink
Merge pull request #58 from haseebzaki-07/new_branch
Browse files Browse the repository at this point in the history
Add subscribe
  • Loading branch information
Suraj-kumar00 authored Oct 28, 2024
2 parents cb736dc + 8eaadc7 commit ed6830d
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 20 deletions.
7 changes: 5 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ NEXT_PUBLIC_APP_URL='http://localhost:3000'
# Clerk Secret keys
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="your nextjs publishable key"
CLERK_SECRET_KEY="your clerk secret key"

# Clerk URLs
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
Expand All @@ -24,4 +23,8 @@ OPENROUTER_API_KEY="your openrouter api key"

# Stripe api key
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="your stripe publishable key"
STRIPE_SECRET_KEY="your stripe secret key"
STRIPE_SECRET_KEY="your stripe secret key"

# email for sending mails to
EMAIL_USER=
EMAIL_PASS=
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"@shadcn/ui": "^0.0.4",
"@stripe/stripe-js": "^4.8.0",
"@tailwindcss/typography": "^0.5.14",
"@types/nodemailer": "^6.4.16",
"@types/react-toastify": "^4.1.0",
"axios": "^1.7.4",
"button": "^1.1.1",
"class-variance-authority": "^0.7.0",
Expand All @@ -34,10 +36,12 @@
"lucide-react": "^0.428.0",
"next": "14.2.5",
"next-themes": "^0.3.0",
"nodemailer": "^6.9.15",
"openai": "^4.68.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.52.2",
"react-toastify": "^10.0.6",
"stripe": "^16.8.0",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
Expand Down
48 changes: 48 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

72 changes: 72 additions & 0 deletions src/app/api/subscribe/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { NextRequest, NextResponse } from "next/server";
import nodemailer from "nodemailer";

type Data = {
message: string;
};

const transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST || "smtp.gmail.com",
port: Number(process.env.EMAIL_PORT) || 587,
secure: process.env.EMAIL_PORT === "465",
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});

// Helper function to send a confirmation email
const sendConfirmationEmail = async (email: string) => {
try {
await transporter.sendMail({
from: `"FlashFathom AI" <${process.env.EMAIL_USER}>`,
to: email,
subject: "Thank you for subscribing!",
text: "Thank you for subscribing to our platform!",
html: `<p>Thank you for subscribing to our platform!</p>`,
});
} catch (error) {
console.error("Error sending email:", error);
throw new Error("Failed to send confirmation email");
}
};

export async function GET() {
return NextResponse.json({ message: "Welcome to the subscribe API!" });
}

export async function POST(req: NextRequest) {
try {
const { email } = await req.json();

if (
!email ||
typeof email !== "string" ||
!/^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/.test(email)
) {
return NextResponse.json(
{ message: "Invalid email address" },
{ status: 400 }
);
}

await sendConfirmationEmail(email);
return NextResponse.json(
{ message: "Subscription confirmation email sent" },
{ status: 200 }
);
} catch (error) {
console.error("Error processing request:", error);

if (error instanceof SyntaxError) {
return NextResponse.json(
{ message: "Invalid JSON format" },
{ status: 400 }
);
}
return NextResponse.json(
{ message: "Error sending email, please try again later" },
{ status: 500 }
);
}
}
3 changes: 3 additions & 0 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import { ArrowRight } from "lucide-react";
import { buttonVariants } from "@/components/ui/button";
import FAQ from "@/components/FAQ";
import Noise from "@/components/noise";
import { ToastContainer } from "react-toastify";

export default function Home() {
return (
<div className="relative w-full h-full overflow-hidden">
{/* Toast Notification Container */}
<ToastContainer />
<Noise />
<div className="z-10">
<MaxWidthWrapper className="mb-12 mt-28 sm:mt-40 flex flex-col items-center justify-center text-center">
Expand Down
28 changes: 11 additions & 17 deletions src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,41 @@
"use client";

import * as React from "react";
import Link from "next/link";
import { Github, Twitter, Linkedin } from "lucide-react";
import MaxWidthWrapper from "./MaxWidthWrapper";
import { buttonVariants } from "./ui/button";
import StayUpdated from "./ui/StayUpdated";

const Footer = () => {
return (
<footer className="w-full border-t border-gray-200 bg-white/75 backdrop-blur-lg dark:bg-gray-900 dark:border-gray-700 py-4 z-10">
<MaxWidthWrapper>
<div className="flex flex-col items-center justify-between sm:flex-row">
<div className="flex flex-col items-center justify-between sm:flex-row space-y-4 sm:space-y-0">
{/* Copyright Text */}
<span className="text-lg text-gray-500 dark:text-gray-400">
&copy; {new Date().getFullYear()} @flashfathom ai. 2024 All rights
reserved.
&copy; {new Date().getFullYear()} @flashfathom ai. All rights reserved.
</span>

{/* Social Media Icons */}
<div className="flex space-x-4 mt-2 sm:mt-0">
<Link
href="https://github.com/Suraj-kumar00/Flash-Fathom-AI"
className={buttonVariants({ variant: "ghost", size: "icon" })}
>
<div className="flex items-center gap-10">
<div className="flex space-x-4">
<Link href="https://github.com/Suraj-kumar00/Flash-Fathom-AI" className={buttonVariants({ variant: "ghost", size: "icon" })}>
<Github className="h-5 w-5 text-purple-600 dark:text-purple-400" />
<span className="sr-only">GitHub</span>
</Link>
<Link
href="https://x.com/surajk_umar01"
className={buttonVariants({ variant: "ghost", size: "icon" })}
>
<Link href="https://x.com/surajk_umar01" className={buttonVariants({ variant: "ghost", size: "icon" })}>
<Twitter className="h-5 w-5 text-purple-600 dark:text-purple-400" />
<span className="sr-only">Twitter</span>
</Link>
<Link
href="https://www.linkedin.com/in/surajkumar00/"
className={buttonVariants({ variant: "ghost", size: "icon" })}
>
<Link href="https://www.linkedin.com/in/surajkumar00/" className={buttonVariants({ variant: "ghost", size: "icon" })}>
<Linkedin className="h-5 w-5 text-purple-600 dark:text-purple-400" />
<span className="sr-only">LinkedIn</span>
</Link>
</div>
<StayUpdated />
</div>
</div>

</MaxWidthWrapper>
</footer>
);
Expand Down
67 changes: 67 additions & 0 deletions src/components/ui/StayUpdated.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"use client";
import React, { useState } from "react";
import axios from "axios";
import { toast} from "react-toastify";
import 'react-toastify/dist/ReactToastify.css';

const StayUpdated = () => {
const [email, setEmail] = useState<string>("");
const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle");
const apiUrl = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000";

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setStatus("loading");

try {
const response = await axios.post(`${apiUrl}/api/subscribe`, { email });

setStatus("success");


toast.success(response.data.message || "Thank you for subscribing!", {
position: "top-right",
autoClose: 3000,
});

setEmail("");
} catch (error: any) {
setStatus("error");


toast.error(
error.response?.data?.message || "Failed to subscribe. Please try again.",
{
position: "top-right",
autoClose: 3000,
}
);
}
};

return (
<div className="w-full flex flex-col items-center">
<form onSubmit={handleSubmit} className="flex flex-col sm:flex-row items-center space-y-2 sm:space-y-0 sm:space-x-2">
<input
type="email"
placeholder="Enter your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="border border-gray-300 rounded px-4 py-2 w-full sm:w-auto dark:border-gray-600 dark:bg-gray-900 dark:text-gray-100"
/>
<button
type="submit"
disabled={status === "loading"}
className="bg-purple-600 text-white rounded px-4 py-2 hover:bg-purple-700 dark:bg-purple-500 dark:hover:bg-purple-600"
>
{status === "loading" ? "Subscribing..." : "Subscribe"}
</button>
</form>


</div>
);
};

export default StayUpdated;
2 changes: 1 addition & 1 deletion src/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'


const isPublicRoute = createRouteMatcher(["/flashcards(.*)","/generate-pro(.*)",'/','/sign-in(.*)', '/sign-up(.*)','/pricing'])
const isPublicRoute = createRouteMatcher(["/flashcards(.*)","/generate-pro(.*)",'/','/sign-in(.*)', '/sign-up(.*)','/pricing' , '/api/subscribe'])

export default clerkMiddleware((auth, request) => {
if (!isPublicRoute(request)) {
Expand Down

0 comments on commit ed6830d

Please sign in to comment.