diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d8d2aab --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# Copy to .env and fill in your SMTP credentials +# Common options: +# Gmail: SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_SECURE=false +# Office365: SMTP_HOST=smtp.office365.com SMTP_PORT=587 SMTP_SECURE=false + +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_SECURE=false +SMTP_USER=your-sending-address@gmail.com +SMTP_PASS=your-app-password +CONTACT_TO=kenji@kenjim.com diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4624535 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.env +node_modules/ +frontend/node_modules/ +backend/node_modules/ +frontend/dist/ diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..aee0aaa --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,7 @@ +FROM node:20-alpine +WORKDIR /app +COPY package.json . +RUN npm install --production +COPY . . +EXPOSE 3001 +CMD ["node", "server.js"] diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..3e31c51 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,13 @@ +{ + "name": "kenjim-technologies-api", + "version": "1.0.0", + "type": "commonjs", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "cors": "^2.8.5", + "express": "^4.19.2", + "nodemailer": "^6.9.14" + } +} diff --git a/backend/server.js b/backend/server.js new file mode 100644 index 0000000..e645e77 --- /dev/null +++ b/backend/server.js @@ -0,0 +1,48 @@ +const express = require('express') +const nodemailer = require('nodemailer') +const cors = require('cors') + +const app = express() +app.use(cors()) +app.use(express.json()) + +const transporter = nodemailer.createTransport({ + host: process.env.SMTP_HOST, + port: parseInt(process.env.SMTP_PORT || '587'), + secure: process.env.SMTP_SECURE === 'true', + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASS, + }, +}) + +app.post('/contact', async (req, res) => { + const { name, email, service, message } = req.body + + if (!name || !email || !message) { + return res.status(400).json({ error: 'Name, email, and message are required.' }) + } + + try { + await transporter.sendMail({ + from: `"KenJim Technologies" <${process.env.SMTP_USER}>`, + to: process.env.CONTACT_TO, + replyTo: email, + subject: `[kenjim.com] ${service || 'General Inquiry'} — ${name}`, + text: `Name: ${name}\nEmail: ${email}\nService: ${service || 'General Inquiry'}\n\n${message}`, + html: ` +

Name: ${name}

+

Email: ${email}

+

Service: ${service || 'General Inquiry'}

+
+

${message.replace(/\n/g, '
')}

+ `, + }) + res.json({ ok: true }) + } catch (err) { + console.error('Mail error:', err) + res.status(500).json({ error: 'Failed to send message. Please try again.' }) + } +}) + +app.listen(3001, () => console.log('API listening on :3001')) diff --git a/docker-compose.yml b/docker-compose.yml index aeee399..a5937f1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,15 @@ services: - www: - image: nginx:alpine - container_name: www-kenjim + frontend: + build: ./frontend + container_name: www-kenjim-frontend ports: - "8080:80" - volumes: - - ./html:/usr/share/nginx/html:ro + depends_on: + - api + restart: always + + api: + build: ./backend + container_name: www-kenjim-api + env_file: .env restart: always diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..70f53d5 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,11 @@ +FROM node:20-alpine AS build +WORKDIR /app +COPY package.json . +RUN npm install +COPY . . +RUN npm run build + +FROM nginx:alpine +COPY --from=build /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..76ef2f0 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + KenJim Technologies + + +
+ + + diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..3599995 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,18 @@ +server { + listen 80; + root /usr/share/nginx/html; + index index.html; + + # SPA routing — return index.html for all non-file routes + location / { + try_files $uri $uri/ /index.html; + } + + # Proxy API calls to the backend container + location /api/ { + proxy_pass http://api:3001/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..52c47cc --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,18 @@ +{ + "name": "kenjim-technologies", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.1", + "vite": "^5.4.0" + } +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 0000000..9b4275a --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,19 @@ +import Nav from './components/Nav' +import Hero from './components/Hero' +import Services from './components/Services' +import Contact from './components/Contact' +import Footer from './components/Footer' + +export default function App() { + return ( + <> +