Skip to content

Commit

Permalink
added: waitlist section with api recovery email
Browse files Browse the repository at this point in the history
  • Loading branch information
BabylooPro committed May 24, 2024
1 parent 5108bd5 commit b8ae5af
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 0 deletions.
9 changes: 9 additions & 0 deletions frontend/app/(landing)/waitlist/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { WaitListSection } from "@/features/landing/waitlist/WaitListSection";

export default function WaitList() {
return (
<div className="flex flex-col gap-4">
<WaitListSection />
</div>
);
}
10 changes: 10 additions & 0 deletions frontend/app/api/waitlist/recovery-email.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"email": "maxremy.dev@gmail.com",
"date": "2024-05-23 20:07:51"
},
{
"email": "babyloopro@gmail.com",
"date": "2024-05-24 00:41:52"
}
]
47 changes: 47 additions & 0 deletions frontend/app/api/waitlist/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { NextResponse } from "next/server";
import fs from "fs";
import path from "path";
import { format } from "date-fns";

// HANDLE POST REQUESTS
export async function POST(request: {
json: () => PromiseLike<{ email: string }> | { email: string };
}) {
const { email } = await request.json(); // EXTRACT EMAIL FROM REQUEST BODY

// CHECK IF EMAIL IS PROVIDED
if (!email) {
return NextResponse.json({ message: "Email is required" }, { status: 400 }); // RETURN STATUS 400 IF EMAIL IS MISSING
}

const filePath = path.resolve("./app/api/waitlist/recovery-email.json");
let currentData = []; // INITIALIZE EMPTY ARRAY

// READ FILE CONTENT IF EXISTS
try {
const fileContent = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf-8") : "[]";

if (fileContent.trim() === "") {
currentData = [];
} else {
currentData = JSON.parse(fileContent);
}
} catch (error) {
console.error("Error reading or parsing file:", error);
return NextResponse.json({ message: "Error processing request" }, { status: 500 });
}

const formattedDate = format(new Date(), "yyyy-MM-dd HH:mm:ss"); // FORMAT DATE
currentData.push({ email, date: formattedDate }); // ADD NEW EMAIL ENTRY

// WRITE UPDATED DATA BACK TO FILE
try {
fs.writeFileSync(filePath, JSON.stringify(currentData, null, 2));
} catch (error) {
console.error("Error writing to file:", error);
return NextResponse.json({ message: "Error processing request" }, { status: 500 });
}

// RETURN STATUS 200 INDICATING SUCCESSFUL ADDITION OF EMAIL
return NextResponse.json({ message: "Email added to waitlist" }, { status: 200 });
}
181 changes: 181 additions & 0 deletions frontend/src/features/landing/waitlist/WaitListSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
"use client";

import { BackgroundBeams } from "@/components/decoration/background-beams";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useToast } from "@/components/ui/use-toast";
import Link from "next/link";
import React, { useState } from "react";
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
} from "@/components/ui/card";
import { OneClickModeToggle } from "@/features/theme/OneClickModeToggle";
import ShaderGradientComponent from "@/components/decoration/shader-gradient";

// WAITLIST SECTION COMPONENT
export function WaitListSection() {
const [email, setEmail] = useState(""); // STATE FOR EMAIL INPUT
const [isSubmitted, setIsSubmitted] = useState(false); // STATE FOR SUBMISSION STATUS
const { toast } = useToast(); // TOAST FOR NOTIFICATIONS

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

// HANDLE FORM SUBMISSION
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();

if (!validateEmail(email)) {
toast({
title: "Invalid email",
description: "Please enter a valid email address.",
variant: "destructive",
});
return;
}

// SEND EMAIL TO WAITLIST API WITH TOAST NOTIFICATIONS
try {
const response = await fetch("/api/waitlist", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
});

const data = await response.json();
if (response.ok) {
toast({
title: "Success",
description: data.message,
variant: "default",
});
setIsSubmitted(true);
} else {
toast({
title: "Error",
description: data.message,
variant: "destructive",
});
}
} catch (error) {
toast({
title: "Error",
description: "An error occurred. Please try again.",
variant: "destructive",
});
}
};

return (
<div className="h-screen w-full rounded-md bg-background relative flex flex-col items-center justify-center antialiased">
<div className="flex flex-row absolute top-4 left-5 z-10 w-full items-center justify-between ">
<Link href="/" legacyBehavior>
<Button
shadow="xxl"
borderRadius="xl"
className="bg-black dark:bg-white hover:bg-background-900 dark:hover:bg-background-900 dark:text-black"
>
Back to Home
</Button>
</Link>
<div className="absolute right-5 mr-5">
<OneClickModeToggle />
</div>
</div>

<Card
className="z-10 max-w-2xl mx-auto p-4 rounded-xl border-none shadow-2xl max-sm:shadow-none max-sm:bg-transparent max-sm:dark:bg-transparent bg-transparent/10 dark:bg-transparent/30"
style={{ backdropFilter: "blur(2px)", WebkitBackdropFilter: "blur(2px)" }}
>
{!isSubmitted ? (
<>
<CardHeader>
<CardTitle className="relative z-10 text-6xl max-sm:text-4xl bg-clip-text text-center font-sans font-bold pb-5">
JOIN THE WAITLIST
</CardTitle>
</CardHeader>

<CardContent>
<CardDescription className="text-foreground-800 dark:text-foreground-400 max-w-2xl mx-auto my-2 text-sm relative z-10 break-words hyphens-auto text-justify">
<strong>WELCOME TO SHOWCALENDAR</strong>, your ultimate solution for
managing personal and professional appointments is currently under
development (WIP). Join our waitlist by entering your email address
below. An invitation will be sent to you as soon as ShowCalendar is
ready, with a limited technical demo scheduled for the 3rd quarter
of 2024 and public deployment in the 4th quarter.
<br />
<br />
Don&apos;t miss this exclusive opportunity to discover our
innovative platform as soon as it launches. Sign up now to be among
the first to benefit from our advanced features.
</CardDescription>
</CardContent>

<CardFooter className="z-20 flex w-full max-w-sm items-center space-x-2 mx-auto">
<Input
className="z-20"
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<Button className="z-20" type="submit" onClick={handleSubmit}>
Join Now
</Button>
</CardFooter>
</>
) : (
<>
<CardHeader>
<CardTitle>
You are on the{" "}
<span className="text-primary font-extrabold">WAITLIST</span>
</CardTitle>
</CardHeader>

<CardContent>
<CardDescription className="text-foreground-800 dark:text-foreground-400">
Thanks for joining ShowCalendar waitlist. We would notify you for
beta release. <br /> Stay tuned :)
</CardDescription>
</CardContent>
</>
)}
</Card>

<BackgroundBeams className="hidden sm:block" />

{/* <ShaderGradientComponent
color1="#ff7f50"
color2="#1e90ff"
color3="#32cd32"
type="plane"
animate="on"
uTime={1.0}
uSpeed={0.4}
uStrength={4}
uDensity={1.3}
uFrequency={5.5}
positionX={0}
positionY={0}
positionZ={0}
rotationX={0}
rotationY={0}
rotationZ={0}
reflection={0.5}
wireframe={false}
shader="default"
/> */}
</div>
);
}

0 comments on commit b8ae5af

Please sign in to comment.