mirror of
https://github.com/tavo-wasd-gh/conex-builder.git
synced 2025-06-06 11:43:29 -06:00
api fetch site data, es dictionary, dashboard
This commit is contained in:
parent
66791447cc
commit
e8956d2eb3
8 changed files with 373 additions and 13 deletions
126
public/client.js
126
public/client.js
|
@ -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
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
9
public/static/svg/check.svg
Normal file
9
public/static/svg/check.svg
Normal 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 |
1
public/static/svg/dash.svg
Normal file
1
public/static/svg/dash.svg
Normal 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 |
34
server/db.go
34
server/db.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue