Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c813a36
chore: Update CODEOWNERS to include @david-bardina; remove unused ima…
AlejandroLuisHCMoeve Aug 13, 2025
3d33714
feat: Add email sending functionality with nodemailer; create .env.sa…
AlejandroLuisHCMoeve Aug 13, 2025
97b35bf
chore: Update dependencies in package.json and pnpm-lock.yaml; upgrad…
AlejandroLuisHCMoeve Aug 13, 2025
4546f8e
feat: Implement email sending API using nodemailer; handle POST reque…
AlejandroLuisHCMoeve Aug 13, 2025
e5fc104
chore: move background asset to public folder to allow render
AlejandroLuisHCMoeve Aug 13, 2025
11f0583
feat: Enhance contact form functionality; integrate inquiry type sele…
AlejandroLuisHCMoeve Aug 13, 2025
a424d6f
feat: Implement email sending API and tests; create handler for POST …
AlejandroLuisHCMoeve Aug 13, 2025
a1959f7
feat: Enhance contact form with loading state and error handling; upd…
AlejandroLuisHCMoeve Aug 13, 2025
b788cf5
fix: Update email subject formatting and improve contact form validat…
AlejandroLuisHCMoeve Aug 13, 2025
2179f28
fix: Enhance error responses in email sending API; include error code…
AlejandroLuisHCMoeve Aug 14, 2025
ed305b3
refactor: Replace email sending API implementation; migrate to React-…
AlejandroLuisHCMoeve Aug 14, 2025
06011e0
refactor: Replace sendEmail.js with TypeScript implementation in send…
AlejandroLuisHCMoeve Aug 14, 2025
d3b2f85
feat: Add tests for email sending API and ContactEmail component; imp…
AlejandroLuisHCMoeve Aug 14, 2025
4445b0b
fix: Update type casting for fetch mock in useContactForm tests to im…
AlejandroLuisHCMoeve Aug 14, 2025
cc58372
chore: Add package.json for API configuration and clean up useContact…
AlejandroLuisHCMoeve Aug 14, 2025
22c0a95
refactor: Replace ContactEmail component with a new implementation us…
AlejandroLuisHCMoeve Aug 14, 2025
e6489fb
chore: Remove package.json from API and dynamically import ContactEma…
AlejandroLuisHCMoeve Aug 14, 2025
6412d97
feat: Implement email sending API with React-based email rendering; a…
AlejandroLuisHCMoeve Aug 14, 2025
f6c3d34
feat: Enhance architecture with new API and email templates; add path…
AlejandroLuisHCMoeve Aug 14, 2025
0a23fc4
feat: Format availability labels in email API to Spanish day/time; up…
AlejandroLuisHCMoeve Aug 14, 2025
77660c6
feat: Add client-side validation to contact form; disable submit butt…
AlejandroLuisHCMoeve Aug 14, 2025
d9664ee
feat: Release version 0.6.0 with enhanced contact form featuring emai…
AlejandroLuisHCMoeve Aug 14, 2025
21697f4
docs: Update README.md to clarify project features, enhance email sen…
AlejandroLuisHCMoeve Aug 14, 2025
7e8759d
chore: Update .env.sample to replace VITE_MAILEROO_API_KEY with SMTP_…
AlejandroLuisHCMoeve Aug 14, 2025
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
9 changes: 9 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# SMTP Config
SMTP_HOST=
SMTP_PORT=
SMTP_USER=
SMTP_PASS=
MAIL_TO=

# Public
VITE_CONTACT_ENDPOINT=
3 changes: 2 additions & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
* @AlejandroLuisHC
* @AlejandroLuisHC
* @david-bardina
35 changes: 0 additions & 35 deletions .github/dependabot.yml

This file was deleted.

15 changes: 4 additions & 11 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

```
blocknroll/
├── api/ # Serverless functions API (email sending)
├── emails/ # Email templates
├── public/ # Static assets
├── src/
│ ├── assets/ # Images, docs, and other assets
Expand Down Expand Up @@ -109,6 +111,8 @@ const ROUTES = {
### Path Aliases Available:

- `@/` → `src/`
- `@api/` → `api/`
- `@emails/` → `emails/`
- `@components/` → `src/components/`
- `@pages/` → `src/pages/`
- `@layouts/` → `src/layouts/`
Expand Down Expand Up @@ -151,14 +155,3 @@ const ROUTES = {
- Automated testing
- Build verification
- Coverage reporting

## 🔄 Migration Benefits

The new architecture provides:

1. **Better separation** of concerns
2. **Easier navigation** for new developers
3. **Scalable routing** for multi-page apps
4. **Consistent layouts** across pages
5. **Cleaner imports** with path aliases
6. **Future-proof** structure for growth
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## [0.6.0] (2025-08-14)

### Features

- Add better contact form with email sending service:
- Add email templates
- Use nodemailer and maileroo to send emails
- Configure vercel serverless functions to manage email sending

## [0.5.0] (2025-07-25)

### Features
Expand Down
87 changes: 24 additions & 63 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,29 @@ A modern and welcoming landing page built with React and TypeScript.

## 🏐 About This Project

This is a responsive, multilingual website template designed specifically for Block n' Roll beach volleyball club.
This is a responsive, multilingual website designed specifically for Block n' Roll beach volleyball club.

### Key Features

- **Modern Design**: Clean, responsive layout optimized for all devices
- **Multilingual Support**: Built-in internationalization (Spanish/English/Catalan)
- **Service Showcase**: Flexible sections for training programs and pricing
- **Dynamic Gallery**: Future Instagram integration with smart fallback to sample images
- **Contact Section**: User-friendly contact and inquiry section
- **Contact Section**: User-friendly contact and inquiry section with email sending service
- **Performance Optimized**: Fast loading with modern build tools

## 🚀 Technologies Used

- **React 19** - Modern React framework
- **TypeScript** - Static type checking
- **Vite** - Fast build tool and development server
- **Bootstrap 5.3.2** - CSS framework for responsive design
- **Bootstrap** - CSS framework for responsive design
- **react-i18next** - Internationalization library
- **Lucide React** - Modern icon library
- **Instagram Basic Display API** - Social media integration
- **Vitest** - Testing framework
- **Vercel** - Serverless functions and deployment
- **Nodemailer** - Email sending library
- **Maileroo** - SMTP provider
- **React Email** - Email templates

## 📦 Installation

Expand Down Expand Up @@ -54,7 +56,7 @@ This is a responsive, multilingual website template designed specifically for Bl
4. **Open your browser**
Navigate to `http://localhost:3000`

## 🏗️ Project Structure
## 🏗️ Main Project Structure

```
src/
Expand All @@ -80,13 +82,6 @@ src/

## 🎨 Design System

### Visual Identity

- **Color Scheme**: Blue (volleyball) and Yellow (beach/sand)
- **Theme**: Rock & Volleyball - "Where sand meets music"
- **Style**: Modern, welcoming, and community-focused
- **Typography**: Custom font integration with fallbacks

### Design Principles

- **Community First**: Focus on building relationships over sales
Expand Down Expand Up @@ -139,59 +134,29 @@ Update content by modifying the translation files. No code changes required for:
- Contact details
- Service descriptions

## 📷 Instagram Integration

The gallery can automatically display Instagram posts when configured:

### Setup Instructions

1. **Create Instagram App**
## 📷 Instagram Integration *(to-do)*

- Visit [Facebook Developers](https://developers.facebook.com/apps/)
- Create a new "Consumer" type app
- Add "Instagram Basic Display" product
The gallery should automatically display Instagram posts when configured:

2. **Environment Variables**

Create `.env.local` in the project root:

```bash
VITE_INSTAGRAM_ACCESS_TOKEN=your_access_token
VITE_INSTAGRAM_USER_ID=your_user_id
```

3. **Features**
### Features:
- ✅ Automatic post loading
- ✅ Smart fallback to sample images
- ✅ Media type support (images/videos)
- ✅ Error handling and loading states
- ✅ Manual refresh capability
- ✅ Redirect to post/account

## 🚀 Deployment

The project is ready for deployment on various platforms:

### Netlify/Vercel

1. Connect your repository
2. Set build command: `npm run build`
3. Set publish directory: `dist`
## 📧 Email Sending

### GitHub Pages
Emails are sent from a Serverless Function (`api/send-email.js`) using Nodemailer over SMTP.

1. Run `npm run build`
2. Deploy the `dist` folder

### Environment Variables

Remember to configure any required environment variables on your hosting platform.
Note: Works with any SMTP provider (e.g., Maileroo, Gmail, etc.) by setting the variables accordingly.

## 🧪 Testing

The project includes comprehensive testing:

- **Unit Tests**: Component and utility function tests
- **Integration Tests**: Contact form and user interaction tests
- **Coverage Reports**: Detailed test coverage analysis

Run tests with:
Expand All @@ -201,24 +166,20 @@ npm run test
npm run test:coverage
```

## 🎯 Future Enhancements
## 🎯 To-Do List

- [ ] Advanced booking system
- [ ] Payment integration
- [ ] Member dashboard
- [ ] Event management
- [ ] Blog functionality
- [ ] SEO optimizations
- [ ] Analytics integration
- [ ] Instagram app integration
- [ ] SEO optimization
- [ ] Blog functionality
- [ ] Payment integration (?)
- [ ] Member dashboard (?)
- [ ] Events calendar/management (?)
- [ ] Analytics integration (?)

## 📄 License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## 🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

---

Built with ❤️ for the beach volleyball community
Built with love ❤️ for the Block n' Roll club and the beach volleyball community.
109 changes: 109 additions & 0 deletions api/send-email.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React from "react";
import nodemailer from "nodemailer";
import { render } from "@react-email/render";
import ContactEmail from "../emails/ContactEmail.js";

export default async function handler(req, res) {
if (req.method !== "POST") {
res.status(405).json({ ok: false, error: "Method Not Allowed" });
return;
}

try {
const smtpHost = process.env.SMTP_HOST;
const smtpPort = Number(process.env.SMTP_PORT || 587);
const smtpUser = process.env.SMTP_USER;
const smtpPass = process.env.SMTP_PASS;
const mailTo = process.env.MAIL_TO;

if (!smtpHost || !smtpUser || !smtpPass || !mailTo) {
res.status(500).json({ ok: false, error: "Missing SMTP configuration", code: "MISSING_SMTP_CONFIG" });
return;
}

const { name, email, phone, message, meta } = (req.body || {});

if (!message || !name) {
res.status(400).json({ ok: false, error: "Missing message or name", code: "BAD_REQUEST" });
return;
}

const transporter = nodemailer.createTransport({
host: smtpHost,
port: smtpPort,
secure: smtpPort === 465,
auth: { user: smtpUser, pass: smtpPass },
});

const inquiryType = (meta?.inquiryType === "talk" ? "talk" : "join");
const subject = `BnR Web - ${inquiryType === "join" ? "Join" : "Info"} - ${name || "Web user"} - ${email || ""}`;

const summaryRows = [
["Tipo de consulta", inquiryType === "join" ? "Apuntarse a entrenamientos" : "Información"],
["Nombre", name || "-"],
["Email", email || "-"],
];
if (phone) summaryRows.push(["Teléfono", phone]);

const detailsRows = [];
// Format availability keys like "mon_18_1930" to human-readable Spanish labels
const formatAvailabilityLabel = (key) => {
if (typeof key !== "string") return String(key);
const [dayKey, ...rest] = key.split("_");
const slotKey = rest.join("_");
const dayMap = { mon: "Lun", tue: "Mar", wed: "Mié", thu: "Jue", fri: "Vie" };
const slotMap = { "18_1930": "18:00-19:30", "1930_21": "19:30-21:00", "21_2230": "21:00-22:30" };
const day = dayMap[dayKey] || dayKey;
const slot = slotMap[slotKey] || slotKey?.replace("_", ":")?.replace("_", ":");
return slot ? `${day} ${slot}` : day;
};
if (inquiryType === "join") {
if (meta?.players != null) detailsRows.push(["Número de jugadores", String(meta.players)]);
if (meta?.level) detailsRows.push(["Nivel estimado", String(meta.level)]);
if (meta?.packageType) detailsRows.push(["Paquete", String(meta.packageType) === "one_per_week" ? "Una vez por semana" : String(meta.packageType) === "two_per_week" ? "Dos veces por semana" : "Privados"]);
if (Array.isArray(meta?.availability)) {
const formatted = meta.availability.map(formatAvailabilityLabel);
detailsRows.push(["Disponibilidad", formatted.length ? formatted : ["Sin preferencia"]]);
}
}

let html;
try {
html = await render(
React.createElement(ContactEmail, {
title: "New contact message",
summary: summaryRows,
details: detailsRows.length ? detailsRows : undefined,
message,
})
);
} catch (renderError) {
console.error("render error:", renderError);
const esc = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
const line = (k, v) => (v ? `<p><strong>${esc(k)}:</strong> ${esc(v)}</p>` : "");
const details = detailsRows.map(([k, v]) => line(k, v)).join("");
html = `
<h2>New contact message</h2>
${summaryRows.map(([k, v]) => line(k, v)).join("")}
${details}
<hr />
<div>${esc(message)}</div>
`;
}

const info = await transporter.sendMail({
from: smtpUser,
to: mailTo,
replyTo: email || undefined,
subject,
html,
});

res.status(200).json({ ok: true, id: info.messageId });
} catch (error) {
console.error("send-email error:", error);
res.status(500).json({ ok: false, error: "Email send failed", code: "SEND_FAILED" });
}
}


Loading