Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 7 additions & 8 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ APP_ENV=production
APP_DEBUG=false
APP_BASE_URL=http://localhost

# NGINX (internal port 80, mapped to host port 3000 for Caddy reverse proxy)
NGINX_PORT=3000
# NGINX
NGINX_PORT=80
NGINX_PORT_SSL=443

# Vite
VITE_PORT=5173
Expand All @@ -16,12 +17,10 @@ VITE_BASE_URL=/
VITE_DEV_SERVER_URL=http://localhost:5173

# Mailing list
MAIL_LIST_URL=https://localhost:9000
MAIL_LIST_API_KEY=the_api_key

# Analytics
ANALYTICS_SCRIPT_URL=https://analytics.example.com/script.js
ANALYTICS_WEBSITE_ID=your-website-id
MAIL_LIST_URL=https://api.brevo.com/v3
MAIL_LIST_API_KEY=your-brevo-api-key
MAIL_LIST_IDS=1
MAIL_LIST_UPDATE_ENABLED=true

# Error tracking
ERROR_TRACKING_DSN=
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ jobs:
[ -f /tmp/compose.override.yml.bak ] && mv /tmp/compose.override.yml.bak compose.override.yml || true

echo ">>> Running deployment script..."
export DEPLOY_ENV=production
bash scripts/deploy.sh

echo ">>> Deployment complete!"
2 changes: 2 additions & 0 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ import * as nav from "./nav.js";
import * as theme from "./theme.js";
import * as reveal from "./reveal.js";
import * as signup from "./signup.js";
import * as unsubscribe from "./unsubscribe.js";
import * as copy from "./copy.js";

highlight.init();
nav.init();
theme.init();
reveal.init();
signup.init();
unsubscribe.init();
copy.init();
135 changes: 135 additions & 0 deletions assets/js/unsubscribe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// ================================================================
// NEWSLETTER UNSUBSCRIBE / DATA DELETION FORM
// ================================================================
// Handles client-side validation and submission of the data
// deletion request form to POST /api/unsubscribe.
//
// Required HTML elements (all inside #unsubscribeForm):
// #uEmail — email input field
// #emailErr — error message container
// #unsubscribeSuccess — success message (shown after submit)
//
// Validation rules:
// - Email: required, must contain @ and .
//
// API response handling:
// - 200 + { success: true } → show success
// - 422 + { errors: {...} } → show field-level errors from server
// - Other / network error → generic alert
//
// CSS classes toggled:
// .--error on input — red border highlight
// .--show on <span> — error message visible
// ================================================================

export function init() {
var form = document.getElementById("unsubscribeForm");
if (!form) return;

// --- DOM references ---
var eIn = document.getElementById("uEmail");
var eErr = document.getElementById("emailErr");
var succ = document.getElementById("unsubscribeSuccess");

// Toggle error state on a single field + its error message
function err(inp, el, show) {
inp.classList.toggle("--error", show);
el.classList.toggle("--show", show);
}

// Reset all field errors and clear message text
function clearFieldErrors() {
err(eIn, eErr, false);
eErr.textContent = "";
}

// Toggle submit button text and disabled state during fetch
function setSubmitting(submitting, btn, originalText) {
btn.textContent = submitting ? "Deleting..." : originalText;
btn.disabled = submitting;
}

// Hide form and show the success message block
function showSuccess() {
form.style.display = "none";
succ.style.display = "block";
}

// Display server-returned validation errors next to each field.
function showFieldErrors(errors) {
clearFieldErrors();

if (errors.email) {
eIn.classList.add("--error");
eErr.textContent = errors.email[0];
eErr.classList.add("--show");
}
}

// --- Form submission ---
form.addEventListener("submit", function (e) {
e.preventDefault();
clearFieldErrors();

var ev = eIn.value.trim();
var ok = true;

// Client-side validation
if (!ev || !ev.includes("@") || !ev.includes(".")) {
err(eIn, eErr, true);
ok = false;
}
if (!ok) return;

var btn = form.querySelector('button[type="submit"]');
var originalText = btn.textContent;

setSubmitting(true, btn, originalText);

// POST to the unsubscribe API endpoint
fetch("/api/unsubscribe", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest",
},
body: JSON.stringify({ email: ev }),
})
.then(function (res) {
return res.json().then(function (data) {
return { ok: res.ok, status: res.status, data: data };
});
})
.then(function (result) {
var data = result.data;

// Success (includes case where contact was not found - treated as success)
if (result.ok && data.success) {
showSuccess();
return;
}

// Server-side validation failed — show field errors
if (422 === result.status && data.errors) {
showFieldErrors(data.errors);
setSubmitting(false, btn, originalText);
return;
}

// Unexpected response — generic error
setSubmitting(false, btn, originalText);
alert(data.message || "Something went wrong. Please try again later.");
})
.catch(function () {
// Network error or JSON parse failure
setSubmitting(false, btn, originalText);
alert("Something went wrong. Please try again later.");
});
});

// --- Inline error clearing ---
// Clear a field's error state as soon as the user starts typing
eIn.addEventListener("input", function () {
err(eIn, eErr, false);
});
}
2 changes: 2 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ services:
volumes:
- app-data:/var/www/html:ro
- ./docker/nginx/nginx.caddy.conf:/etc/nginx/conf.d/default.conf
- ./docker/nginx/ssl:/etc/nginx/ssl:ro
ports:
- "${NGINX_PORT}:80"
- "${NGINX_PORT_SSL}:443"
restart: always
networks:
- webifycms-site
Loading
Loading