api fetch site data, es dictionary, dashboard

This commit is contained in:
tavo 2024-09-17 23:42:31 -06:00
parent 66791447cc
commit e8956d2eb3
8 changed files with 373 additions and 13 deletions

View file

@ -25,13 +25,19 @@ document.addEventListener("DOMContentLoaded", function() {
checkoutErrorMessage.style.display = "none";
dialog.style.display = "none";
overlay.style.display = "none";
menu.style.display = "block";
menu.style.display = "flex";
}
document.getElementById("openDialogButton").addEventListener("click", openDialog);
document.getElementById("cancelDialogButton").addEventListener("click", closeDialog);
document.getElementById('title').addEventListener('change', saveEditorData);
document.getElementById('slogan').addEventListener('change', saveEditorData);
document.getElementById('buyModeButton').addEventListener('click', buyMode);
document.getElementById('editModeButton').addEventListener('click', editModeWrapper);
document.getElementById("continueToEditMode").addEventListener('click', function() {
editMode(document.getElementById("dashEditInput").value);
});
document.getElementById('dashButton').addEventListener('click', dashboardMode);
});
function saveEditorData() {
@ -54,7 +60,6 @@ function saveEditorData() {
});
}
let typingTimeout;
let hideTimeout;
const directoryInput = document.getElementById('title');
@ -71,6 +76,20 @@ directoryInput.addEventListener('input', () => {
}, 500); // Debounce
});
const directoryBuyInput = document.getElementById('dashEditInput');
directoryBuyInput.addEventListener('input', () => {
clearTimeout(typingTimeout);
typingTimeout = setTimeout(() => {
const directoryTitle = directoryBuyInput.value.trim();
if (directoryTitle.length > 0) {
const directory = sanitizeDirectoryTitle(directoryTitle);
checkDirectoryReverse(directory);
} else {
hidePopup();
}
}, 500); // Debounce
});
function sanitizeDirectoryTitle(title) {
return title
.toLowerCase()
@ -103,6 +122,29 @@ function checkDirectory(directory) {
});
}
function checkDirectoryReverse(directory) {
if (directory.length < 4) {
return;
}
if (directory.length > 35) {
showPopup(`El título no puede exceder los 35 caracteres`, 'exists');
return;
}
fetch(`/api/directory/${encodeURIComponent(directory)}`)
.then(response => response.json())
.then(data => {
if (data.exists) {
showPopup(`Editar el sitio web conex.one/${directory}`, 'available');
} else {
showPopup(`El sitio conex.one/${directory} no está registrado`, 'exists');
}
})
.catch(error => {
console.error('Error checking directory:', error);
showPopup('Error checking directory.', 'exists');
});
}
function showPopup(message, status) {
const popup = document.querySelector('.status-popup');
@ -186,3 +228,83 @@ function loadLanguage(lang) {
})
.catch(error => console.error('Error loading language file:', error));
}
function dashboardMode() {
document.getElementById("dashOverlay").style.display = "none";
document.getElementById("dashDialog").style.display = "none";
const dashboard = document.querySelector('.dashboard');
dashboard.style.display = 'flex';
setTimeout(() => {
dashboard.style.transition = 'opacity 0.5s ease';
dashboard.style.opacity = '1';
}, 10);
}
function buyMode() {
document.getElementById("openDialogButton").style.display = "block";
document.getElementById("updateSiteButton").style.display = "none";
const dashboard = document.querySelector('.dashboard');
dashboard.style.transition = 'opacity 0.5s ease';
dashboard.style.opacity = '0';
setTimeout(() => {
dashboard.style.display = 'none';
}, 500);
}
async function editMode(dir) {
const success = await loadEditorData(dir);
if (!success) {
console.error("Data could not be loaded, aborting UI changes");
return;
}
document.getElementById("openDialogButton").style.display = "none";
document.getElementById("updateSiteButton").style.display = "block";
const dashboard = document.querySelector('.dashboard');
dashboard.style.transition = 'opacity 0.5s ease';
dashboard.style.opacity = '0';
setTimeout(() => {
dashboard.style.display = 'none';
}, 500);
}
function editModeWrapper() {
document.getElementById("dashOverlay").style.display = "block";
document.getElementById("dashDialog").style.display = "block";
document.getElementById("dashEditInput").style.display = "block";
}
function loadEditorData(dirname) {
return new Promise((resolve, reject) => {
fetch(`/api/fetch/${dirname}`)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
// Populate the data into the form elements
document.getElementById('banner').src = data.banner || '/static/svg/banner.svg';
document.getElementById('title').value = data.title || '';
document.getElementById('slogan').value = data.slogan || '';
// Load editor data if available
if (data.editor_data && typeof editor !== 'undefined') {
editor.render({
blocks: data.editor_data.blocks || []
});
}
console.log('Editor data loaded successfully');
resolve(true); // Resolve with success
})
.catch(error => {
console.error('Fetching and loading data failed:', error);
resolve(false); // Resolve with failure, but not reject to avoid unhandled errors
});
});
}

View file

@ -11,6 +11,27 @@
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/editorjs-dark.css">
</head>
<div class="dashboard">
<div class="wave"></div>
<div class="wave"></div>
<div class="wave"></div>
<div class="mode-button-container">
<h1 class="dashboard-text" data-translate="welcome"></h1>
<p class="dashboard-text" data-translate="about"></p>
<span data-translate="buyModeButton" id="buyModeButton" class="mode-button buyModeButton"></span>
<span data-translate="editModeButton" id="editModeButton" class="mode-button editModeButton"></span>
</div>
<div id="dashOverlay"></div>
<div id="dashDialog">
<h2 data-translate="editDialogHeader"></h2>
<p data-translate="editDialogParagraph"></p>
<input type="text" id="dashBuyInput" name="title" maxlength="35" class="input-title" data-translate="sampleTitle" placeholder="" >
<input type="text" id="dashEditInput" name="title" maxlength="35" class="input-title" data-translate="sampleTitle" placeholder="" >
<button id="continueToEditMode">
<span data-translate="continueToEditMode"></span>
</button>
</div>
</div>
<div class="banner">
<input type="file" id="imageUpload" accept="image/*">
<label for="imageUpload" class="upload-button">
@ -36,13 +57,17 @@
<div id="overlay"></div>
<div class="floating-buttons" id="floatingButtons">
<button class="floating-button" id="openDialogButton">
<img src="/static/svg/cart.svg">
<img src="/static/svg/check.svg">
<span data-translate="buyButton"></span>
</button>
<button class="floating-button" id="updateSiteButton">
<img src="/static/svg/edit.svg">
<img src="/static/svg/check.svg">
<span data-translate="editButton"></span>
</button>
<button class="floating-button" id="dashButton">
<img src="/static/svg/dash.svg">
<span data-translate="dashButton"></span>
</button>
</div>
<div id="dialog">
<h2 data-translate="checkoutHeader"></h2>

View file

@ -1,6 +1,14 @@
{
"welcome": "Editor | CONEX.one",
"about": "En editor.conex.one puede crear un sitio web de manera rápida y sencilla, por $20 al año. Para continuar editando un sitio, presione el botón \"Editar sitio existente\". Si desea crear un sitio desde cero, presione el botón \"Diseñar un nuevo sitio\"",
"buyModeButton": "Diseñar un nuevo sitio",
"editModeButton": "Editar sitio existente",
"editDialogHeader": "Editar un sitio existente",
"editDialogParagraph": "El sitio ya debe estar registrado en conex.one para poder cargarlo localmente.",
"continueToEditMode": "Editar este sitio",
"buyButton": "Guardar",
"editButton": "Aplicar cambios",
"dashButton": "Panel principal",
"checkoutHeader": "Contratar por $20 al año",
"checkoutParagraph": "Gracias por elegir nuestro servicio para la compra de sitios web. Luego de ser aprobado, su sitio será publicado en menos de 24 horas a partir de la confirmación de tu compra. Utilizaremos los medios de contacto que proporcione para comunicarnos en caso de cualquier inconveniente con la publicación. Si experimenta algún problema, no dude en ponerte en contacto con nosotros a través de los canales:",
"supportEmail": "Correo electrónico: soporte@conex.one",

View file

@ -77,7 +77,7 @@ a {
position: absolute;
top: 0;
left: 0;
z-index: -1;
z-index: -1000;
}
.input-title {
@ -151,6 +151,7 @@ footer {
padding-bottom: 1em;
}
#dashDialog,
#dialog {
width: 85%;
max-width: 30em;
@ -170,17 +171,20 @@ footer {
max-height: 70vh;
}
#dashDialog h2, #dashDialog p,
#dialog h2, #dialog p {
margin: 0;
padding: 0 0 0.5em 0;
text-align: justify;
}
#dashDialog button,
#dialog button {
margin: 0.5em 0.5em 0 0;
padding: 1em;
}
#dashOverlay,
#overlay {
display: none;
position: fixed;
@ -192,6 +196,11 @@ footer {
z-index: 999;
}
#dashOverlay {
z-index: 1000;
}
#dashDialog input,
#dialog input {
text-align: left;
font-size: 1em;
@ -205,6 +214,10 @@ footer {
margin: 1em auto;
}
#dashDialog input {
display: none;
}
#form-container {
padding-bottom: 1em;
}
@ -280,14 +293,14 @@ button {
bottom: 0.2em;
right: 0.5em;
gap: 0.4em;
z-index: 9999;
z-index: 2000;
}
.floating-button {
height: 2.5em;
cursor: pointer;
font-size: 1em;
z-index: 1001;
z-index: 2000;
border-radius: 999px;
box-sizing: border-box;
color: rgba(255, 255, 255, 0.85);
@ -319,16 +332,16 @@ button {
box-shadow: #0099c5 0 10px 20px -15px;
}
#deleteSiteButton {
background: linear-gradient(135deg, #5e2329, #bf4c58);
box-shadow: #e31300 0 10px 20px -15px;
}
#openDialogButton {
background: linear-gradient(135deg, #21532a, #4fc764);
box-shadow: #27d100 0 10px 20px -15px;
}
#dashButton {
background: linear-gradient(45deg, #9b59b6, #6f42c1);
box-shadow: #8f53b9 0 10px 20px -15px;
}
.status-popup {
position: fixed;
top: 19%;
@ -343,6 +356,7 @@ button {
opacity: 0;
visibility: hidden;
transition: opacity 0.1s ease, visibility 0.1s ease;
z-index: 10000;
}
.status-popup.show {
@ -389,10 +403,131 @@ button {
}
.upload-button:hover {
background-color: #ffffff40; /* Hover effect */
background-color: #ffffff40;
}
.upload-button .icon {
width: 1em;
height: 1em;
}
.dashboard {
background-color: var(--background-color);
height: 100%;
width: 100%;
position: fixed;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
z-index: 3000;
}
.dashboard-text {
color: white;
}
.mode-button-container {
width: var(--page-width);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
}
.mode-button {
width: 90%;
padding: 1em;
margin: 1em 0;
border-radius: 15px;
text-align: center;
text-decoration: none;
font-weight: bold;
color: white;
cursor: pointer;
}
.buyModeButton {
background: linear-gradient(45deg, #21532a, #4fc764);
}
.buyModeButton:hover {
background: linear-gradient(45deg, #173a1f, #3a9a4d);
}
.editModeButton {
background: linear-gradient(45deg, #3498db, #007bff);
}
.editModeButton:hover {
background: linear-gradient(45deg, #2980b9, #0056b3);
}
.dashboard {
background: linear-gradient(315deg, #0c4848 3%, #071832 98%);
animation: gradient 15s ease infinite;
background-size: 400% 400%;
}
@keyframes gradient {
0% {
background-position: 0% 0%;
}
50% {
background-position: 100% 100%;
}
100% {
background-position: 0% 0%;
}
}
.wave {
background: #94e1f080;
border-radius: 1000% 1000% 0 0;
position: fixed;
width: 200%;
height: 12em;
animation: wave 10s -3s linear infinite;
transform: translate3d(0, 0, 0);
opacity: 0.8;
bottom: 0;
left: 0;
z-index: -1;
}
.wave:nth-of-type(2) {
bottom: -1.25em;
animation: wave 18s linear reverse infinite;
opacity: 0.8;
}
.wave:nth-of-type(3) {
bottom: -2.5em;
animation: wave 20s -1s reverse infinite;
opacity: 0.9;
}
@keyframes wave {
2% {
transform: translateX(1);
}
25% {
transform: translateX(-25%);
}
50% {
transform: translateX(-50%);
}
75% {
transform: translateX(-25%);
}
100% {
transform: translateX(1);
}
}

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<svg height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 17.837 17.837" xml:space="preserve">
<g>
<path style="fill:#ffffff;" d="M16.145,2.571c-0.272-0.273-0.718-0.273-0.99,0L6.92,10.804l-4.241-4.27
c-0.272-0.274-0.715-0.274-0.989,0L0.204,8.019c-0.272,0.271-0.272,0.717,0,0.99l6.217,6.258c0.272,0.271,0.715,0.271,0.99,0
L17.63,5.047c0.276-0.273,0.276-0.72,0-0.994L16.145,2.571z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 539 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" fill="#ffffff" viewBox="0 0 512 481.157"><path d="M35.64 0h159.702c19.604 0 35.641 16.037 35.641 35.64v145.308c0 19.604-16.037 35.64-35.641 35.64H35.64c-19.603 0-35.64-16.036-35.64-35.64V35.64C0 16.037 16.037 0 35.64 0zm281.017 264.569h159.702c19.604 0 35.641 16.036 35.641 35.64v145.307c0 19.604-16.037 35.641-35.641 35.641H316.657c-19.603 0-35.64-16.037-35.64-35.641V300.209c0-19.604 16.037-35.64 35.64-35.64zm-281.017 0h159.702c19.604 0 35.641 16.036 35.641 35.64v145.307c0 19.604-16.037 35.641-35.641 35.641H35.64C16.037 481.157 0 465.12 0 445.516V300.209c0-19.604 16.037-35.64 35.64-35.64zM316.657 0h159.702C495.963 0 512 16.037 512 35.64v145.308c0 19.604-16.037 35.64-35.641 35.64H316.657c-19.603 0-35.64-16.036-35.64-35.64V35.64c0-19.603 16.037-35.64 35.64-35.64z"/></svg>

After

Width:  |  Height:  |  Size: 967 B

View file

@ -180,3 +180,37 @@ func ValidateSiteAuth(db *sql.DB, folder string, code string) (int, error) {
return pkey, nil
}
func FetchSite(db *sql.DB, folder string) (ConexData, error) {
var siteData ConexData
query := `
SELECT folder, banner, title, slogan, raw
FROM sites
WHERE folder = $1
`
var rawData []byte
err := db.QueryRow(query, folder).Scan(
&siteData.Directory,
&siteData.Banner,
&siteData.Title,
&siteData.Slogan,
&rawData,
)
if err != nil {
if err == sql.ErrNoRows {
return siteData, fmt.Errorf("site not found: %v", folder)
}
return siteData, fmt.Errorf("error fetching site: %v", err)
}
err = json.Unmarshal(rawData, &siteData.EditorData)
if err != nil {
return siteData, fmt.Errorf("error unmarshaling editor data: %v", err)
}
return siteData, nil
}

View file

@ -129,6 +129,7 @@ func main() {
http.HandleFunc("/api/update", UpdateSiteHandler(db))
http.HandleFunc("/api/confirm", ConfirmChangesHandler(db))
http.HandleFunc("/api/directory/", VerifyDirectoryHandler(db))
http.HandleFunc("/api/fetch/", FetchSiteHandler(db))
http.HandleFunc("/api/upload", UploadFileHandler(s3Client, endpoint, apiEndpoint, apiToken, bucketName, publicEndpoint))
http.Handle("/", http.FileServer(http.Dir("./public")))
@ -391,3 +392,28 @@ func UploadFileHandler(s3Client *s3.Client, endpoint string, apiEndpoint string,
return
}
}
func FetchSiteHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
errClientNotice := "Error fetching site from db"
path := strings.TrimPrefix(r.URL.Path, "/api/fetch/")
parts := strings.Split(path, "/")
folder := parts[0]
if folder == "" {
httpErrorAndLog(w, nil, "Error getting directory", errClientNotice)
return
}
var siteData ConexData
siteData, err := FetchSite(db, folder)
if err != nil {
httpErrorAndLog(w, err, "Error fetching site data", "Error fetching site data")
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(siteData)
return
}
}