extra field in update handler, frontend refactor

This commit is contained in:
tavo 2024-09-20 23:22:23 -06:00
parent b9d1620646
commit 57c7c15ec5
6 changed files with 312 additions and 62 deletions

View file

@ -21,9 +21,12 @@ document.addEventListener("DOMContentLoaded", function () {
const checkoutDialog = document.getElementById("checkoutDialog");
const editDialog = document.getElementById("editDialog");
const buyDialog = document.getElementById("buyDialog");
const updateContentDialog = document.getElementById("updateContentDialog");
checkoutDialog.style.display = "none";
editDialog.style.display = "none";
buyDialog.style.display = "none";
updateContentDialog.style.display = "none";
loadLanguage('es');
Promise.all(EditorJSComponents.map(src => loadScript(src))).then(() => {
@ -58,7 +61,7 @@ function loadEditorState() {
if (savedData) {
const parsedData = JSON.parse(savedData);
console.log('Loaded parsedData:', parsedData);
document.getElementById('title').value = parsedData.title || '';
document.getElementById('title').innerText = parsedData.title || '';
document.getElementById('slogan').value = parsedData.slogan || '';
document.getElementById('banner').src = parsedData.banner || '/static/svg/banner.svg';
const disclaimers = document.querySelectorAll(".localstorage-exists");
@ -82,7 +85,7 @@ function loadEditorState() {
})
.catch(error => console.error('Error loading translation file:', error));
} else {
document.getElementById('title').value = '';
document.getElementById('title').innerText = '';
document.getElementById('slogan').value = '';
document.getElementById('banner').src = '/static/svg/banner.svg';
document.getElementById("continueEditingModeButton").style.display = "none";
@ -104,28 +107,73 @@ function initializeEventListeners() {
});
document.getElementById("buyButton").addEventListener("click", () => openDialog(checkoutDialog));
document.getElementById("editButton").addEventListener("click", () => openDialog(updateContentDialog));
document.getElementById("closeDialogButton").addEventListener("click", () => closeDialog());
// // Editor save
document.getElementById('title').addEventListener('change', saveEditorData);
// document.getElementById('title').addEventListener('change', saveEditorData);
document.getElementById('slogan').addEventListener('change', saveEditorData);
// // Mode switching
// document.getElementById('buyModeButton').addEventListener('click', openBuyModeDialog);
document.getElementById('buyModeButton').addEventListener('click', buyMode);
document.getElementById('buyModeButton').addEventListener('click', openBuyModeDialog);
document.getElementById('editModeButton').addEventListener('click', openEditModeDialog);
document.getElementById("continueToBuyModeButton").addEventListener('click', async () => {
const directory = sanitizeDirectoryTitle(document.getElementById("buyModeDirectoryInput").value);
const exists = await checkDirectoryExists(directory);
if (exists) {
document.getElementById("checkdir-error-message").style.display = "block";
document.getElementById("checkdir-error-message").innerHTML = `El sitio https://conex.one/${directory} ya existe.`;
} else {
document.getElementById("checkdir-error-message").style.display = "none";
buyMode(directory);
}
});
document.getElementById("continueToEditModeButton").addEventListener('click', () =>
editMode(document.getElementById("editModeDirectoryInput").value)
editMode(extractSitePath(document.getElementById("editModeDirectoryInput").value))
);
document.getElementById('continueEditingModeButton').addEventListener('click', continueMode);
document.getElementById('dashButton').addEventListener('click', dashboardMode);
document.getElementById('uploadBannerBtn').addEventListener('change', handleImageUpload);
const titleElement = document.getElementById('title');
if (titleElement) {
setupDirectoryInput(titleElement);
}
document.getElementById('requestChangesButton').addEventListener('click', async () => {
const button = document.getElementById('requestChangesButton');
button.disabled = true;
button.classList.add('disabled');
await updateSiteRequest();
setTimeout(() => {
button.disabled = false;
button.classList.remove('disabled');
}, 3000);
});
document.getElementById('confirmChangesButton').addEventListener('click', function() {
successElement = document.getElementById('update-success-message');
errorElement = document.getElementById('update-error-message');
const codeInput = document.getElementById('updateContentCodeInput').value;
if (codeInput.length === 6 && !isNaN(codeInput)) {
document.getElementById('updateContentCodeInput').value = '';
errorElement.style.display = "none"
updateSiteConfirm(codeInput);
} else {
successElement.style.display = "none"
errorElement.style.display = "block"
errorElement.innerHTML = "El código es un pin numérico de 6 dígitos.";
console.error('Invalid code. Please enter a 6-digit number.');
}
});
const titleElement = document.getElementById('buyModeDirectoryInput');
titleElement.addEventListener('input', debounce(function() {
const directory = titleElement.value.trim();
if (directory.length > 0) {
validateDirectory(directory);
}
}, 500)); // 500ms debounce
}
function openDialog(content) {
@ -135,21 +183,31 @@ function openDialog(content) {
floatingButtons.style.display = "none";
}
function debounce(func, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}
function closeDialog() {
checkoutDialog.style.display = "none";
editDialog.style.display = "none";
// buyDialog.style.display = "none";
buyDialog.style.display = "none";
updateContentDialog.style.display = "none";
dialog.style.display = "none";
overlay.style.display = "none";
floatingButtons.style.display = "flex";
document.getElementById('checkout-error-message').style.display = "none";
document.getElementById('update-error-message').style.display = "none";
}
function saveEditorData() {
const titleValue = document.getElementById('title').value.trim();
const titleValue = document.getElementById('title').innerText.trim();
const dataToSave = {
banner: document.getElementById('banner').src || '/static/svg/banner.svg',
title: document.getElementById('title').value,
title: document.getElementById('title').innerText,
slogan: document.getElementById('slogan').value,
directory: sanitizeDirectoryTitle(titleValue)
};
@ -162,21 +220,37 @@ function saveEditorData() {
});
}
function setupDirectoryInput(inputElement, debounceTime = 500) {
inputElement.addEventListener('input', () => {
clearTimeout(typingTimeout);
function validateDirectory(directory) {
successMessageElement = document.getElementById('checkdir-success-message');
errorMessageElement = document.getElementById('checkdir-error-message');
successMessageElement.textContent = '';
errorMessageElement.textContent = '';
typingTimeout = setTimeout(() => {
const inputValue = inputElement.value.trim();
if (!validateDirectoryLength(directory)) {
successMessageElement.style.display = "none";
errorMessageElement.style.display = "block";
errorMessageElement.textContent = 'Directory name must be between 4 and 35 characters.';
return;
}
if (inputValue.length > 0) {
const sanitizedValue = sanitizeDirectoryTitle(inputValue); // Sanitize the input value
checkDirectory(sanitizedValue);
directory = sanitizeDirectoryTitle(directory)
checkDirectoryExists(directory)
.then(exists => {
if (exists) {
successMessageElement.style.display = "none";
errorMessageElement.style.display = "block";
errorMessageElement.textContent = `El sitio https://conex.one/${directory} ya existe.`;
} else {
hidePopup();
successMessageElement.style.display = "block";
errorMessageElement.style.display = "none";
successMessageElement.textContent = `Se publicará en https://conex.one/${directory}`;
}
}, debounceTime);
});
})
.catch(() => {
successMessageElement.style.display = "none";
errorMessageElement.style.display = "block";
errorMessageElement.textContent = 'Error occurred while checking the directory.';
});
}
function sanitizeDirectoryTitle(title) {
@ -188,11 +262,6 @@ function sanitizeDirectoryTitle(title) {
.replace(/[^a-z0-9\-]/g, '');
}
function checkDirectory(directory) {
if (!validateDirectoryLength(directory)) return;
fetchDirectoryStatus(directory, 'exists', 'available', 'El sitio web ya existe', 'Se publicará en');
}
function validateDirectoryLength(directory) {
if (directory.length < 4 || directory.length > 35) {
showPopup('El título debe tener entre 4 y 35 caracteres', 'exists');
@ -201,14 +270,6 @@ function validateDirectoryLength(directory) {
return true;
}
function fetchDirectoryStatus(directory, failureStatus, successStatus, failureMessage, successMessage) {
checkDirectoryExists(directory).then(exists => {
const message = exists ? `${failureMessage} conex.one/${directory}` : `${successMessage} conex.one/${directory}`;
const status = exists ? failureStatus : successStatus;
showPopup(message, status);
});
}
function checkDirectoryExists(directory) {
return fetch(`/api/directory/${encodeURIComponent(directory)}`)
.then(response => response.json())
@ -331,8 +392,14 @@ function dashboardMode() {
function buyMode() {
localStorage.removeItem('conex_data');
const dataToSave = {
title: document.getElementById('buyModeDirectoryInput').value.trim(),
};
localStorage.setItem('conex_data', JSON.stringify(dataToSave));
loadEditorState();
closeDialog();
document.getElementById('checkout-success-message').style.display = "none";
document.querySelector("#paypal-button-container").style.display = "block";
document.getElementById("buyButton").style.display = "block";
@ -380,6 +447,8 @@ function continueBuyMode() {
}
function openBuyModeDialog() {
document.getElementById("checkdir-error-message").style.display = "none";
document.getElementById("checkdir-success-message").style.display = "none";
overlay.style.display = "block";
dialog.style.display = "block";
buyDialog.style.display = "block";
@ -392,13 +461,23 @@ function openEditModeDialog() {
}
async function editMode(dir) {
closeDialog();
const success = await fetchAndStoreData(dir);
if (!success) {
console.error("Data could not be loaded, aborting UI changes");
return;
const errorMessageElement = document.getElementById('edit-error-message');
const conexData = JSON.parse(localStorage.getItem('conex_data'));
if (conexData?.directory === dir) {
console.log("Directory already loaded, skipping fetch.");
} else {
const success = await fetchAndStoreData(dir);
if (!success) {
errorMessageElement.innerHTML = "No se pudo cargar el sitio, asegúrate que estás digitando el enlace correcto.";
errorMessageElement.style.display = "block";
console.error("Data could not be loaded, aborting UI changes");
return;
}
}
closeDialog();
errorMessageElement.style.display = "none";
document.getElementById("buyButton").style.display = "none";
document.getElementById("editButton").style.display = "block";
const dashboard = document.getElementById('dashboard');
@ -428,3 +507,77 @@ async function fetchAndStoreData(directoryName) {
return false;
}
}
function updateSiteRequest() {
const conexData = JSON.parse(localStorage.getItem('conex_data'));
const directory = conexData?.directory;
successElement = document.getElementById('update-success-message');
errorElement = document.getElementById('update-error-message');
fetch('/api/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ directory: directory })
})
.then(response => {
if (response.status === 200) {
successElement.style.display = "block"
errorElement.style.display = "none"
successElement.innerHTML = "Se envió el código de autenticación de 6 dígitos a su correo electrónico.";
} else {
successElement.style.display = "none"
errorElement.style.display = "block"
errorElement.innerHTML = "Error enviando el código de confirmación a su correo, recuerde que puede solicitar el código solamente una vez cada minuto.";
}
})
}
function updateSiteConfirm(code) {
const conexData = JSON.parse(localStorage.getItem('conex_data'));
const directory = conexData?.directory;
const editorData = conexData?.editor_data;
const slogan = conexData?.slogan;
successElement = document.getElementById('update-success-message');
errorElement = document.getElementById('update-error-message');
if (!directory || !editorData) {
console.error('Directory or editor_data not found in localStorage');
return;
}
fetch('/api/confirm', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
directory: directory,
auth_code: code,
slogan: slogan,
editor_data: editorData
})
})
.then(response => {
if (response.status === 200) {
successElement.style.display = "block"
errorElement.style.display = "none"
successElement.innerHTML = "Se actualizó correctamente la información de su sitio, los cambios deberían verse reflejados en menos de 24 horas.";
} else {
successElement.style.display = "none"
errorElement.style.display = "block"
errorElement.innerHTML = "Error actualizando su sitio, por favor vuelva a intentarlo más tarde.";
}
})
}
function extractSitePath(url) {
if (!url.includes("conex.one")) {
return url;
}
const cleanUrl = url.replace(/^(https?:\/\/)?(www\.)?/, '').replace(/#.*$/, '');
const match = cleanUrl.match(/^conex\.one\/([^\/?#]+)\/?/);
return match ? match[1] : null;
}

View file

@ -10,10 +10,6 @@
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/editorjs-dark.css">
</head>
<div id="status-popup" class="status-popup">
<span class="close-popup" onclick="hidePopup()">×</span>
<span id="status-message"></span>
</div>
<div id="overlay"></div>
<div id="dashboard">
<div class="wave"></div>
@ -45,13 +41,21 @@
</div>
<img id="banner" src="/static/svg/banner.svg" class="banner-image"/>
<div class="desc">
<input type="text" id="title" maxlength="35" class="input-title" data-translate="sampleTitle" placeholder="" >
<h1 id="title" class="input-title"></h1>
<textarea type="text" id="slogan" maxlength="100" class="input-slogan" data-translate="sampleSlogan" placeholder=""></textarea>
</div>
</div>
<body>
<div id="editorjs"></div>
</body>
<footer class="footer">
<div class="footer-content">
<ul class="footer-links">
<li><a href="#contact">Contactar con soporte</a></li>
</ul>
<p data-translate="footer"></p>
</div>
</footer>
<div class="floating-buttons-container" id="floatingButtons">
<button class="floating-button" id="buyButton">
<img src="/static/svg/check.svg">
@ -86,9 +90,29 @@
<div class="message error-message" id="checkout-error-message"></div>
<div id="paypal-button-container"></div>
</div>
<div id="buyDialog">
<h2 data-translate="buyDialogHeader"></h2>
<p data-translate="buyDialogParagraph"></p>
<div class="message success-message" id="checkdir-success-message"></div>
<div class="message error-message" id="checkdir-error-message"></div>
<input class="input-dialog" type="text" id="buyModeDirectoryInput" maxlength="35" placeholder="Mi Sitio"><br>
<button id="continueToBuyModeButton" class="right">
<span data-translate="continueToBuyModeButton"></span>
</button>
</div>
<div id="updateContentDialog">
<h2 data-translate="updateContentDialogHeader"></h2>
<p data-translate="updateContentDialogParagraph"></p>
<div class="message success-message" id="update-success-message"></div>
<div class="message error-message" id="update-error-message"></div>
<input data-translate="tempCodePlaceholder" class="input-dialog" type="number" id="updateContentCodeInput" min="0" max="999999" placeholder=""><br>
<button data-translate="requestChangesButton" id="requestChangesButton"></button>
<button data-translate="confirmChangesButton" id="confirmChangesButton"></button>
</div>
<div id="editDialog">
<h2 data-translate="editDialogHeader"></h2>
<p data-translate="editDialogParagraph"></p>
<div class="message error-message" id="edit-error-message"></div>
<input class="input-dialog" type="text" id="editModeDirectoryInput" name="title" maxlength="35" placeholder="https://conex.one/mi-sitio"><br>
<button id="continueToEditModeButton" class="right">
<span data-translate="continueToEditModeButton"></span>

View file

@ -6,6 +6,14 @@
"editModeButton": "Editar sitio existente",
"editModeButtonDisclaimer": "Limpiará el progreso actual para cargar un sitio existente",
"continueEditingModeButton": "Continuar editando",
"buyDialogHeader": "Diseñar un nuevo sitio web",
"buyDialogParagraph": "Ingresa el nombre del sitio y revisa el enlace en el que se publicará una vez adquirido.",
"updateContentDialogHeader": "Actualizar mi sitio",
"updateContentDialogParagraph": "Para actualizar el sitio web, requerimos confirmar que el sitio es suyo. Para esto, enviaremos un correo electrónico a la cuenta de correo con la que compró este sitio web, con un código temporal de 6 dígitos que expirará en 5 minutos luego de ser enviado. Por favor, presione el botón para enviar el código y luego compruebe su identidad digitándolo en la casilla.",
"tempCodePlaceholder": "Código de 6 dígitos enviado a su correo",
"requestChangesButton": "Enviar correo con el pin",
"confirmChangesButton": "Verificar pin y actualizar sitio",
"continueToBuyModeButton": "Empezar a diseñar",
"editDialogHeader": "Editar un sitio existente",
"editDialogParagraph": "El sitio ya debe estar registrado en conex.one para poder cargarlo localmente.",
"continueToEditModeButton": "Editar este sitio",
@ -18,7 +26,7 @@
"supportEmail": "Correo electrónico: soporte@conex.one",
"supportPhone": "",
"supportSchedule": "Horario de atención: L-V: 9:00 a.m. - 6:00 p.m.",
"sampleTitle": "[Título de ejemplo]",
"sampleTitle": "Mi Sitio",
"sampleSlogan": "[Slogan o breve descripción]",
"sampleHeader": "Acerca de [Empres]",
"sampleParagraph": "En [Nombre de Tu Empresa], nos dedicamos a ofrecer [tu servicio/producto] de la más alta calidad con un servicio al cliente excepcional. Nuestro equipo de expertos se asegura de que cada aspecto de tu experiencia sea manejado con profesionalismo y cuidado.",
@ -33,5 +41,6 @@
"sampleTable23": "1000",
"sampleTable31": "Laminado",
"sampleTable32": "Breve descripción",
"sampleTable33": "2000"
"sampleTable33": "2000",
"footer": "Copyright (C) 2024 Conex"
}

View file

@ -262,7 +262,7 @@ a {
#continueEditingModeButton {
display: none;
background-image: linear-gradient(45deg, #4a3b72, #4a3b72);
background-image: linear-gradient(45deg, #4a3b72, #8a6cdf);
}
@keyframes gradient {
@ -352,18 +352,14 @@ a {
#dialog button {
margin: 0.5em 0.5em 0 0;
padding: 1em;
}
#dialog button {
padding: 0.5em;
color: var(--unemph-color);
background: var(--background-color);
border: 1px solid var(--hover-border);
border-radius: 10px;
position: absolute;
right: 1em;
bottom: 1em;
right: 1.3em;
bottom: 1.5em;
}
#dialog button:hover {
@ -382,6 +378,15 @@ a {
font-size: 0.9em;
}
#requestChangesButton,
#confirmChangesButton {
width: 45%;
}
#requestChangesButton {
left: 1.78em;
}
.input-dialog {
color: var(--color);
border-width: 0;
@ -472,3 +477,47 @@ a {
transform: rotate(360deg);
}
}
.disabled {
background-color: var(--hover-background);
color: var(--unemph-color);
cursor: not-allowed;
pointer-events: none;
}
.footer {
background-color: var(--hover-background);
color: var(--unemph-color);
padding: 0;
text-align: center;
}
.footer-content {
margin: 0 auto;
}
.footer p {
margin: 0.5em;
font-size: 0.9em;
}
.footer-links {
list-style: none;
padding: 0;
margin: 0.5em 0 0;
}
.footer-links li {
display: inline;
margin: 1em 1em;
}
.footer-links a {
font-size: 0.9em;
color: var(--hyper-color);
text-decoration: none;
}
.footer-links a:hover {
text-decoration: underline;
}

View file

@ -119,10 +119,12 @@ func RegisterSitePayment(db *sql.DB, capture Capture, cart ConexData) error {
return nil
}
func UpdateSite(db *sql.DB, pkey int, editorData json.RawMessage) error {
func UpdateSite(db *sql.DB, pkey int, editorData json.RawMessage, slogan string) error {
if _, err := db.Exec(`
UPDATE sites SET raw = $1 WHERE id = $2
`, editorData, pkey); err != nil {
UPDATE sites
SET raw = $1, slogan = $2, status = 'diff'
WHERE id = $3
`, editorData, slogan, pkey); err != nil {
return fmt.Errorf("%s: %v", errDBUpdateRaw, err)
}
@ -130,15 +132,27 @@ func UpdateSite(db *sql.DB, pkey int, editorData json.RawMessage) error {
}
func UpdateSiteAuth(db *sql.DB, folder string, code string) (string, error) {
valid := time.Now().Add(5 * time.Minute)
var valid sql.NullTime
if err := db.QueryRow(`
SELECT valid
FROM sites
WHERE folder = $1
`, folder).Scan(&valid); err != nil {
return "", fmt.Errorf("error fetching valid timestamp: %v", err)
}
if valid.Valid && valid.Time.After(time.Now().Add(4*time.Minute)) {
return "", fmt.Errorf("valid timestamp is still active, cannot update")
}
newValid := time.Now().Add(5 * time.Minute)
var email string
if err := db.QueryRow(`
UPDATE sites
SET auth = $1, valid = $2
WHERE folder = $3
RETURNING email;
`, code, valid, folder).Scan(&email); err != nil {
`, code, newValid, folder).Scan(&email); err != nil {
return "", fmt.Errorf("%s: %v", errDBUpdateSiteAuth, err)
}

View file

@ -284,6 +284,7 @@ func ConfirmChangesHandler(db *sql.DB) http.HandlerFunc {
Directory string `json:"directory"`
Code string `json:"auth_code"`
EditorData json.RawMessage `json:"editor_data"`
Slogan string `json:"slogan"`
}
if err := json.NewDecoder(r.Body).Decode(&cart); err != nil {
httpErrorAndLog(w, err, errReadBody, errClientNotice)
@ -296,7 +297,7 @@ func ConfirmChangesHandler(db *sql.DB) http.HandlerFunc {
return
}
if err := UpdateSite(db, pkey, cart.EditorData); err != nil {
if err := UpdateSite(db, pkey, cart.EditorData, cart.Slogan); err != nil {
httpErrorAndLog(w, err, errUpdateSite, errClientNotice)
return
}