RentCalc-validation/RentCalc.com
1
1import express from 'express';2import cors from 'cors';3import nodemailer from 'nodemailer';4import dotenv from 'dotenv';5import { createClient } from '@supabase/supabase-js';6 7dotenv.config();8 9const app = express();10const PORT = process.env.PORT || 10000;11 12// Supabase Configuration13const supabaseUrl = process.env.SUPABASE_URL;14const supabaseKey = process.env.SUPABASE_KEY;15const supabase = createClient(supabaseUrl, supabaseKey);16 17// Email Transporter - Matching friend's working config18const transporter = nodemailer.createTransport({19 service: 'gmail',20 host: process.env.SMTP_HOST || 'smtp.gmail.com',21 port: parseInt(process.env.SMTP_PORT) || 465,22 secure: true,23 auth: {24 user: process.env.EMAIL_USER,25 pass: process.env.EMAIL_PASS,26 },27 tls: {28 rejectUnauthorized: false29 }30});31 32// Neobrutalist Email Template33const getEmailTemplate = (name) => `34 <!DOCTYPE html>35 <html>36 <head>37 <meta charset="utf-8">38 <meta name="viewport" content="width=device-width, initial-scale=1.0">39 <link href="https://fonts.googleapis.com/css2?family=Bungee&family=Lexend+Mega:wght@900&family=JetBrains+Mono:wght@700&display=swap" rel="stylesheet">40 <style>41 .heading-font { font-family: 'Lexend Mega', 'Arial Black', sans-serif !important; }42 .code-font { font-family: 'JetBrains Mono', 'Courier New', monospace !important; }43 </style>44 </head>45 <body style="margin: 0; padding: 0; background-color: #F8F9FA; font-family: 'Helvetica', 'Arial', sans-serif;">46 <table width="100%" border="0" cellspacing="0" cellpadding="0" style="background-color: #F8F9FA; padding: 40px 10px;">47 <tr>48 <td align="center">49 <table width="100%" border="0" cellspacing="0" cellpadding="0" style="background-color: #ffffff; border: 4px solid #1A1A1A; box-shadow: 12px 12px 0px 0px #1A1A1A; max-width: 600px; width: 100%;">50 <tr>51 <td style="padding: 40px 20px 20px 20px;">52 <img src="https://raw.githubusercontent.com/gmahesh-star/rentcalc-validation-website/main/public/brand/logo.png" alt="RentCalc Logo" width="180" style="display: block; border: 0; max-width: 80%;">53 </td>54 </tr>55 <tr>56 <td style="padding: 0 20px 20px 20px;">57 <h1 class="heading-font" style="margin: 0; font-size: 28px; font-weight: 900; color: #1A1A1A; line-height: 1.2; text-transform: uppercase;">58 HEYY ${name.toUpperCase() || 'THERE'}!59 </h1>60 </td>61 </tr>62 <tr>63 <td style="padding: 0 20px;">64 <div style="height: 4px; background-color: #1A1A1A; width: 60px; margin-bottom: 25px;"></div>65 </td>66 </tr>67 <tr>68 <td style="padding: 0 20px 25px 20px;">69 <p style="margin: 0; font-family: 'Arial', sans-serif; font-size: 16px; font-weight: 700; color: #1A1A1A; line-height: 1.6;">70 Thanks for checking out RentCalc. Honestly, I really appreciate you taking a few seconds to vote. It helps me figure out if this is actually something worth building for us.71 </p>72 </td>73 </tr>74 <tr>75 <td style="padding: 0 20px 30px 20px;">76 <table width="100%" border="0" cellspacing="0" cellpadding="0" style="background-color: #00EAD3; border: 4px solid #1A1A1A; box-shadow: 6px 6px 0px 0px #1A1A1A;">77 <tr>78 <td style="padding: 20px;">79 <p class="heading-font" style="margin: 0 0 10px 0; font-size: 18px; font-weight: 900; text-transform: uppercase; color: #1A1A1A;">SO, WHAT'S THE PLAN?</p>80 <p style="margin: 0; font-family: 'Arial', sans-serif; font-size: 15px; font-weight: 700; color: #1A1A1A; line-height: 1.5;">81 I'm just talking to more people to see how many of us actually face these rental/resale struggles. If it's a big enough need, I'll get to work. You'll be the first one I'll ping when I have something real to show.82 </p>83 </td>84 </tr>85 </table>86 </td>87 </tr>88 <tr>89 <td style="padding: 0 20px 30px 20px;">90 <div style="background-color: #1A1A1A; color: #ffffff; padding: 12px 20px; display: inline-block;">91 <p class="code-font" style="margin: 0; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px;">92 NO CORPORATE FLUFF. JUST ME BUILDING FOR US.93 </p>94 </div>95 </td>96 </tr>97 <tr>98 <td style="padding: 25px 20px; border-top: 4px solid #1A1A1A;">99 <p class="heading-font" style="margin: 0; font-size: 18px; font-weight: 900; color: #1A1A1A; text-transform: uppercase;">100 — Mahesh101 </p>102 <p class="code-font" style="margin: 5px 0 0 0; font-size: 12px; font-weight: 700; color: #666666; text-transform: uppercase;">103 Creator, RentCalc104 </p>105 </td>106 </tr>107 </table>108 <p class="code-font" style="margin-top: 25px; font-size: 10px; font-weight: 700; color: #666666; text-transform: uppercase; letter-spacing: 1px;">109 No spam. No pressure. Just honest building.110 </p>111 </td>112 </tr>113 </table>114 </body>115 </html>116`;117 118// Allow requests from Hugging Face119app.use(cors());120app.use(express.json());121 122// Health check endpoint123app.get('/', (req, res) => {124 res.json({ status: 'RentCalc API is running!' });125});126 127app.post('/api/responses', async (req, res) => {128 try {129 const { pollData } = req.body;130 131 // Save to Supabase132 const { data, error: sbError } = await supabase133 .from('responses')134 .insert([{ poll_data: pollData }]);135 136 if (sbError) throw sbError;137 138 // Check if user wants notification and provided an email139 const wantsNotify = pollData?.notificationChoice === 'Yes (share contact)';140 const contactEmail = pollData?.email;141 const studentName = pollData?.name?.trim() || 'Student';142 143 if (wantsNotify && contactEmail) {144 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;145 if (emailRegex.test(contactEmail.trim())) {146 const mailOptions = {147 from: `"Mahesh from RentCalc" <${process.env.EMAIL_USER}>`,148 to: contactEmail.trim(),149 subject: `Yo ${studentName}! Thanks for the RentCalc feedback`,150 html: getEmailTemplate(studentName)151 };152 153 transporter.sendMail(mailOptions, (error, info) => {154 if (error) {155 console.error('Email sending failed:', error);156 } else {157 console.log('Email sent successfully to:', contactEmail);158 }159 });160 }161 }162 163 res.status(201).json({ message: 'Response saved successfully' });164 } catch (error) {165 console.error('Error saving response:', error);166 res.status(500).json({ error: 'Failed' });167 }168});169 170app.get('/api/stats', async (req, res) => {171 try {172 const { count, error } = await supabase173 .from('responses')174 .select('*', { count: 'exact', head: true });175 176 if (error) throw error;177 res.json({ count: count || 0 });178 } catch (error) {179 res.status(500).json({ error: 'Failed' });180 }181});182 183app.listen(PORT, '0.0.0.0', () => {184 console.log(`RentCalc API running on port ${PORT}`);185});186 