diff --git a/app/api/send-email/route.ts b/app/api/send-email/route.ts new file mode 100644 index 0000000..b6f16f4 --- /dev/null +++ b/app/api/send-email/route.ts @@ -0,0 +1,440 @@ +import { NextResponse } from "next/server"; +import nodemailer from "nodemailer"; + +interface MailOptions { + from: string | { name: string; address: string }; + to: string; + subject: string; + html: string; + text: string; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const createAcceptanceMailOptions = (email: string): MailOptions => ({ + from: { + name: "Garuda Hacks", + address: "no-reply@garudahacks.com", + }, + to: email, + subject: "Congratulations! You're Accepted to Garuda Hacks 6.0!", + html: ` + + + + + + Congratulations! You're Accepted to Garuda Hacks 6.0! + + + + + + + + +
+ + + + + + + + + + + +
+

+ Garuda
Hacks 6.0 +

+ +

+ July 24 - 26 • 2025 +

+
+ +
+

+ 🎉 Congratulations! +

+

+ We're thrilled to inform you that your application has been accepted for Garuda Hacks 6.0! +

+

+ This year is an exciting time to to join Garuda Hacks. When we began planning for this year’s event, we collected feedback from previous years and decided to focus on the participant experience. From a career fair and a networking lunch, to a live judging round with VCs and a revamped application portal, we are determined to make this year our most engaging event yet. We hope that you will enjoy the experience. +

+
+ + +
+

+ 📅 Event Details +

+ + + + + + + + + + + + + +
Date:July 24 - 26, 2025
Location:Universitas Multimedia Nusantara (UMN)
Duration:30 Hours
+
+ + +
+

+ 🚀 Next Steps +

+
    +
  • RSVP on Portal to confirm your participation at portal.garudahacks.com
  • +
  • + Join our Discord community for updates and networking: + discord.gg/5hVnu8t4mw +
  • +
  • Add the official Garuda Hacks 6.0 Twibbon to your social media: twb.nz/garudahacks6
  • +
  • Prepare your development environment and tools
  • +
  • Form your team or find teammates in our Discord
  • +
  • Mark your calendar and get ready to hack!
  • +
+
+ + +
+ + + + + +
+ + RSVP + + + + View Guidebook + +
+
+ + +
+

+ Questions? Contact us at hiba@garudahacks.com +

+

+ Follow us on social media for the latest updates +

+
+
+ + + + + + +
+

+ © 2025 Garuda Hacks. All rights reserved. +

+

+ You received this email because you applied for Garuda Hacks 6.0. +

+
+
+ + + `, + text: `Welcome to Garuda Hacks 6.0! + +🎉 Congratulations! +We're thrilled to inform you that your application has been accepted for Garuda Hacks 6.0! You're now part of Indonesia's largest hackathon - please RSVP on our portal to get ready for an incredible experience! + +--- + +EVENT DETAILS +- Date: July 24 - 26, 2025 +- Location: Universitas Multimedia Nusantara (UMN) +- Duration: 30 Hours + +--- + +NEXT STEPS +1. RSVP on our portal to confirm your participation: https://portal.garudahacks.com/ +2. Join our Discord community for updates and networking: https://discord.gg/5hVnu8t4mw +3. Add the official Garuda Hacks 6.0 Twibbon to your social media. +4. Prepare your development environment and tools. +5. Form your team or find teammates in our Discord. +6. Mark your calendar and get ready to hack! + +--- + +Questions? Contact us at hiba@garudahacks.com +Follow us on social media for the latest updates. + +© 2025 Garuda Hacks. All rights reserved. +You received this email because you applied for Garuda Hacks 6.0. +`, +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const createRejectionMailOptions = (email: string): MailOptions => ({ + from: { + name: "Garuda Hacks", + address: "no-reply@garudahacks.com", + }, + to: email, + subject: "Your Garuda Hacks 6.0 Application Status", + html: ` + + + + + + Your Garuda Hacks 6.0 Application Status + + + + + + + +
+ + + + + + + +
+

+ Garuda
Hacks 6.0 +

+

+ Application Update +

+
+
+

+ Thank you for applying +

+

+ We appreciate your interest in Garuda Hacks 6.0. After careful consideration, we regret to inform you that we are unable to offer you a spot this year. +

+

+ The selection process was highly competitive, and we encourage you to apply again next year. Thank you for your passion and effort! +

+
+
+

+ If you have any questions, feel free to reach out to us at hiba@garudahacks.com. +

+

+ We wish you the best in your future hackathons and hope to see you at Garuda Hacks in the future! +

+
+
+ + + + +
+

+ © 2025 Garuda Hacks. All rights reserved. +

+

+ You received this email because you applied for Garuda Hacks 6.0. +

+
+
+ + + `, + text: `Your Garuda Hacks 6.0 Application Status\n\nThank you for applying to Garuda Hacks 6.0.\n\nAfter careful consideration, we regret to inform you that we are unable to offer you a spot this year.\n\nThe selection process was highly competitive, and we encourage you to apply again next year.\n\nIf you have any questions, feel free to reach out to us at hiba@garudahacks.com.\n\nWe wish you the best in your future hackathons and hope to see you at Garuda Hacks in the future!\n\n© 2025 Garuda Hacks. All rights reserved.\nYou received this email because you applied for Garuda Hacks 6.0.`, +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const createWaitlistedMailOptions = (email: string): MailOptions => ({ + from: { + name: "Garuda Hacks", + address: "no-reply@garudahacks.com", + }, + to: email, + subject: "Your Garuda Hacks 6.0 Application Status: Waitlisted", + html: ` + + + + + + Your Garuda Hacks 6.0 Application Status: Waitlisted + + + + + + + +
+ + + + + + + +
+

+ Garuda
Hacks 6.0 +

+

+ Application Update +

+
+
+

+ You're on the Waitlist! +

+

+ Thank you for your interest in Garuda Hacks 6.0. At this time, you have been placed on our waitlist. Spots may open up as the event approaches, and we will notify you immediately if a spot becomes available. +

+

+ We appreciate your patience and enthusiasm. Stay tuned for updates! +

+
+
+

+ If you have any questions, feel free to reach out to us at hiba@garudahacks.com. +

+

+ We hope to see you at Garuda Hacks 6.0! +

+
+
+ + + + +
+

+ © 2025 Garuda Hacks. All rights reserved. +

+

+ You received this email because you applied for Garuda Hacks 6.0. +

+
+
+ + + `, + text: `Your Garuda Hacks 6.0 Application Status: Waitlisted\n\nThank you for your interest in Garuda Hacks 6.0. At this time, you have been placed on our waitlist. Spots may open up as the event approaches, and we will notify you immediately if a spot becomes available.\n\nWe appreciate your patience and enthusiasm. Stay tuned for updates!\n\nIf you have any questions, feel free to reach out to us at hiba@garudahacks.com.\n\nWe hope to see you at Garuda Hacks 6.0!\n\n© 2025 Garuda Hacks. All rights reserved.\nYou received this email because you applied for Garuda Hacks 6.0.`, +}); + +export async function POST(request: Request) { + try { + const body = await request.json(); + const { email, type, rsvpDeadline, teamDeadline, eventStartDate } = body; + + if (!email) { + return NextResponse.json( + { error: "Missing required parameters" }, + { status: 400 } + ); + } + + const transporter = nodemailer.createTransport({ + host: "live.smtp.mailtrap.io", + port: 587, + auth: { + user: process.env.MAILTRAP_USER, + pass: process.env.MAILTRAP_PASS, + }, + }); + + let mailOptions; + if (type === "rejected") { + mailOptions = createRejectionMailOptions(email); + } else if (type === "waitlisted") { + mailOptions = createWaitlistedMailOptions(email); + } else { + if (!rsvpDeadline || !teamDeadline || !eventStartDate) { + return NextResponse.json( + { error: "Missing required parameters for acceptance email" }, + { status: 400 } + ); + } + mailOptions = createAcceptanceMailOptions(email); + } + + await transporter.sendMail(mailOptions); + + return NextResponse.json({ message: "Email sent successfully" }); + } catch (error) { + console.error(error); + const errorMessage = + error instanceof Error ? error.message : "An unknown error occurred"; + return NextResponse.json( + { error: "Failed to send email", details: errorMessage }, + { status: 500 } + ); + } +} diff --git a/app/applications/page.tsx b/app/applications/page.tsx index 0a85151..7a671e8 100644 --- a/app/applications/page.tsx +++ b/app/applications/page.tsx @@ -139,6 +139,26 @@ export default function Applications() { ); if (success) { + try { + const response = await fetch("/api/send-email", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email: selectedApplication.email, + type: "rejected", + }), + }); + + if (!response.ok) { + const errorData = await response.json(); + console.error("Failed to send rejection email:", errorData); + } + } catch (emailError) { + console.error("Error sending rejection email:", emailError); + } + setApplications((prev) => prev.map((app) => app.id === selectedApplication.id @@ -171,6 +191,28 @@ export default function Applications() { ); if (success) { + try { + const response = await fetch("/api/send-email", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email: selectedApplication.email, + rsvpDeadline: "2025-07-01", + teamDeadline: "2025-07-01", + eventStartDate: "2025-07-24", + }), + }); + + if (!response.ok) { + const errorData = await response.json(); + console.error("Failed to send acceptance email:", errorData); + } + } catch (emailError) { + console.error("Error sending acceptance email:", emailError); + } + setApplications((prev) => prev.map((app) => app.id === selectedApplication.id @@ -192,6 +234,58 @@ export default function Applications() { } }; + const handleWaitlistParticipant = async () => { + if (!selectedApplication) return; + + try { + setRejecting(true); + const success = await updateUserStatus( + selectedApplication.id, + APPLICATION_STATUS.WAITLISTED + ); + + if (success) { + try { + const response = await fetch("/api/send-email", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email: selectedApplication.email, + type: "waitlisted", + }), + }); + + if (!response.ok) { + const errorData = await response.json(); + console.error("Failed to send waitlist email:", errorData); + } + } catch (emailError) { + console.error("Error sending waitlist email:", emailError); + } + + setApplications((prev) => + prev.map((app) => + app.id === selectedApplication.id + ? { ...app, status: APPLICATION_STATUS.WAITLISTED } + : app + ) + ); + + setSelectedApplication((prev) => + prev ? { ...prev, status: APPLICATION_STATUS.WAITLISTED } : null + ); + } else { + console.error("Failed to waitlist participant"); + } + } catch (error) { + console.error("Error waitlisting participant:", error); + } finally { + setRejecting(false); + } + }; + const getDisplayStatus = (application: CombinedApplicationData): string => { if ( application.status === APPLICATION_STATUS.SUBMITTED && diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx index c31e05c..0ba9e4a 100644 --- a/components/Sidebar.tsx +++ b/components/Sidebar.tsx @@ -120,6 +120,8 @@ export default function Sidebar() { Garuda Hacks diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..1571804 --- /dev/null +++ b/next.config.js @@ -0,0 +1,7 @@ +module.exports = { + eslint: { + // Warning: This allows production builds to successfully complete even if + // your project has ESLint errors. + ignoreDuringBuilds: true, + }, +}; diff --git a/package-lock.json b/package-lock.json index 9acb38a..306151f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,12 +11,14 @@ "@vercel/analytics": "^1.5.0", "firebase": "^11.8.1", "next": "15.1.6", + "nodemailer": "^7.0.3", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { "@eslint/eslintrc": "^3", "@types/node": "^20", + "@types/nodemailer": "^6.4.17", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", @@ -1520,6 +1522,16 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/nodemailer": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", + "integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/react": { "version": "19.0.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz", @@ -4608,6 +4620,15 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/nodemailer": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.3.tgz", + "integrity": "sha512-Ajq6Sz1x7cIK3pN6KesGTah+1gnwMnx5gKl3piQlQQE/PwyJ4Mbc8is2psWYxK3RJTVeqsDaCv8ZzXLCDHMTZw==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", diff --git a/package.json b/package.json index fa9b36e..91d0cb3 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,14 @@ "@vercel/analytics": "^1.5.0", "firebase": "^11.8.1", "next": "15.1.6", + "nodemailer": "^7.0.3", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { "@eslint/eslintrc": "^3", "@types/node": "^20", + "@types/nodemailer": "^6.4.17", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9",