Created
July 27, 2025 15:48
-
-
Save thinkphp/4756c1623125e91e68bb3670824eb0d9 to your computer and use it in GitHub Desktop.
newsletter js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class SimpleNewsletter extends HTMLElement { | |
constructor() { | |
super(); | |
this.attachShadow({ mode: 'open' }); | |
// Get attributes or set defaults | |
this.title = this.getAttribute('title') || 'Newsletter'; | |
this.description = this.getAttribute('description') || 'Abonează-te pentru noutăți!'; | |
this.apiUrl = this.getAttribute('api-url') || 'newsletter.php'; | |
this.variant = this.getAttribute('variant') || 'default'; // default, compact, footer | |
this.placeholder = this.getAttribute('placeholder') || 'Adresa ta de email'; | |
this.buttonText = this.getAttribute('button-text') || 'Abonează-te'; | |
this.render(); | |
this.attachEventListeners(); | |
} | |
render() { | |
const variantStyles = this.getVariantStyles(); | |
this.shadowRoot.innerHTML = ` | |
<style> | |
:host { | |
display: block; | |
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
} | |
.newsletter-container { | |
${variantStyles.container} | |
} | |
.newsletter-container:hover { | |
transform: translateY(-2px); | |
} | |
.newsletter-header { | |
text-align: center; | |
margin-bottom: 25px; | |
} | |
.newsletter-title { | |
${variantStyles.title} | |
margin-bottom: 10px; | |
font-weight: 700; | |
} | |
.newsletter-description { | |
${variantStyles.description} | |
font-size: 1rem; | |
line-height: 1.5; | |
} | |
.newsletter-form { | |
${variantStyles.form} | |
} | |
.newsletter-input { | |
${variantStyles.input} | |
} | |
.newsletter-input:focus { | |
outline: none; | |
border-color: #667eea; | |
box-shadow: 0 0 15px rgba(102, 126, 234, 0.2); | |
} | |
.newsletter-button { | |
${variantStyles.button} | |
} | |
.newsletter-button:hover:not(:disabled) { | |
transform: translateY(-2px); | |
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4); | |
} | |
.newsletter-button:disabled { | |
opacity: 0.7; | |
cursor: not-allowed; | |
transform: none; | |
} | |
.loading { | |
display: none; | |
width: 16px; | |
height: 16px; | |
border: 2px solid transparent; | |
border-top: 2px solid currentColor; | |
border-radius: 50%; | |
animation: spin 1s linear infinite; | |
margin-right: 8px; | |
} | |
@keyframes spin { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
.message { | |
margin-top: 15px; | |
padding: 12px; | |
border-radius: 8px; | |
font-weight: 500; | |
text-align: center; | |
display: none; | |
animation: slideIn 0.3s ease; | |
} | |
.message.success { | |
background: #d4edda; | |
color: #155724; | |
border: 1px solid #c3e6cb; | |
} | |
.message.error { | |
background: #f8d7da; | |
color: #721c24; | |
border: 1px solid #f5c6cb; | |
} | |
.message.debug { | |
background: #fff3cd; | |
color: #856404; | |
border: 1px solid #ffeaa7; | |
font-size: 0.85rem; | |
text-align: left; | |
} | |
@keyframes slideIn { | |
from { | |
opacity: 0; | |
transform: translateY(-10px); | |
} | |
to { | |
opacity: 1; | |
transform: translateY(0); | |
} | |
} | |
/* Responsive */ | |
@media (max-width: 768px) { | |
.newsletter-form { | |
flex-direction: column; | |
gap: 15px; | |
} | |
.newsletter-form.horizontal { | |
flex-direction: column; | |
} | |
.newsletter-input { | |
width: 100% !important; | |
flex: none !important; | |
} | |
.newsletter-button { | |
width: 100% !important; | |
} | |
} | |
</style> | |
<div class="newsletter-container"> | |
<div class="newsletter-header"> | |
<h3 class="newsletter-title">${this.title}</h3> | |
<p class="newsletter-description">${this.description}</p> | |
</div> | |
<form class="newsletter-form" id="newsletterForm"> | |
<input | |
type="email" | |
class="newsletter-input" | |
id="emailInput" | |
placeholder="${this.placeholder}" | |
required> | |
<button type="submit" class="newsletter-button" id="submitButton"> | |
<div class="loading" id="loading"></div> | |
<span id="buttonText">${this.buttonText}</span> | |
</button> | |
</form> | |
<div class="message" id="message"></div> | |
</div> | |
`; | |
} | |
getVariantStyles() { | |
switch (this.variant) { | |
case 'compact': | |
return { | |
container: ` | |
background: linear-gradient(135deg, #667eea, #764ba2); | |
color: white; | |
padding: 20px; | |
border-radius: 12px; | |
text-align: center; | |
transition: all 0.3s ease; | |
`, | |
title: ` | |
color: white; | |
font-size: 1.3rem; | |
`, | |
description: ` | |
color: rgba(255, 255, 255, 0.9); | |
`, | |
form: ` | |
display: flex; | |
gap: 10px; | |
max-width: 350px; | |
margin: 0 auto; | |
`, | |
input: ` | |
flex: 1; | |
padding: 12px 15px; | |
border: none; | |
border-radius: 25px; | |
font-size: 0.9rem; | |
background: rgba(255, 255, 255, 0.9); | |
`, | |
button: ` | |
padding: 12px 20px; | |
background: rgba(255, 255, 255, 0.2); | |
color: white; | |
border: 2px solid white; | |
border-radius: 25px; | |
font-size: 0.9rem; | |
font-weight: 600; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
white-space: nowrap; | |
` | |
}; | |
case 'footer': | |
return { | |
container: ` | |
background: #2c3e50; | |
color: white; | |
padding: 30px; | |
border-radius: 12px; | |
text-align: center; | |
transition: all 0.3s ease; | |
`, | |
title: ` | |
color: #ecf0f1; | |
font-size: 1.5rem; | |
`, | |
description: ` | |
color: #bdc3c7; | |
`, | |
form: ` | |
display: flex; | |
gap: 10px; | |
max-width: 400px; | |
margin: 0 auto; | |
`, | |
input: ` | |
flex: 1; | |
padding: 15px 20px; | |
border: 2px solid rgba(255, 255, 255, 0.3); | |
border-radius: 25px; | |
font-size: 1rem; | |
background: rgba(255, 255, 255, 0.1); | |
color: white; | |
`, | |
button: ` | |
padding: 15px 25px; | |
background: rgba(255, 255, 255, 0.2); | |
color: white; | |
border: 2px solid white; | |
border-radius: 25px; | |
font-size: 1rem; | |
font-weight: 600; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
white-space: nowrap; | |
` | |
}; | |
default: | |
return { | |
container: ` | |
background: rgba(255, 255, 255, 0.95); | |
backdrop-filter: blur(10px); | |
border-radius: 15px; | |
padding: 30px; | |
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); | |
border: 1px solid rgba(255, 255, 255, 0.2); | |
text-align: center; | |
transition: all 0.3s ease; | |
`, | |
title: ` | |
color: #2c3e50; | |
font-size: 1.8rem; | |
background: linear-gradient(135deg, #667eea, #764ba2); | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
background-clip: text; | |
`, | |
description: ` | |
color: #7f8c8d; | |
`, | |
form: ` | |
display: flex; | |
max-width: 400px; | |
margin: 0 auto; | |
gap: 10px; | |
background: white; | |
padding: 8px; | |
border-radius: 50px; | |
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1); | |
`, | |
input: ` | |
flex: 1; | |
border: none; | |
padding: 15px 20px; | |
font-size: 1rem; | |
border-radius: 50px; | |
background: transparent; | |
`, | |
button: ` | |
padding: 15px 25px; | |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
color: white; | |
border: none; | |
border-radius: 50px; | |
font-size: 1rem; | |
font-weight: 600; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
white-space: nowrap; | |
` | |
}; | |
} | |
} | |
attachEventListeners() { | |
const form = this.shadowRoot.getElementById('newsletterForm'); | |
form.addEventListener('submit', (e) => this.handleSubmit(e)); | |
} | |
async handleSubmit(event) { | |
event.preventDefault(); | |
const emailInput = this.shadowRoot.getElementById('emailInput'); | |
const submitButton = this.shadowRoot.getElementById('submitButton'); | |
const loading = this.shadowRoot.getElementById('loading'); | |
const buttonText = this.shadowRoot.getElementById('buttonText'); | |
const messageEl = this.shadowRoot.getElementById('message'); | |
const email = emailInput.value.trim(); | |
// Validate email | |
if (!this.isValidEmail(email)) { | |
this.showMessage(messageEl, 'Te rugăm să introduci o adresă de email validă.', 'error'); | |
return; | |
} | |
// Show loading state | |
submitButton.disabled = true; | |
loading.style.display = 'inline-block'; | |
buttonText.textContent = 'Se abonează...'; | |
messageEl.style.display = 'none'; | |
try { | |
console.log('Sending request to:', this.apiUrl); | |
console.log('Request data:', { email: email }); | |
const response = await fetch(this.apiUrl, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
'Accept': 'application/json' | |
}, | |
body: JSON.stringify({ email: email }) | |
}); | |
console.log('Response status:', response.status); | |
console.log('Response headers:', Object.fromEntries(response.headers.entries())); | |
// Get response text first | |
const responseText = await response.text(); | |
console.log('Response text:', responseText); | |
// Check if response is empty | |
if (!responseText) { | |
throw new Error('Server-ul a returnat un răspuns gol'); | |
} | |
// Try to parse JSON, but handle cases where it's not JSON | |
let result; | |
try { | |
result = JSON.parse(responseText); | |
} catch (jsonError) { | |
console.error('JSON parse error:', jsonError); | |
// If it's an HTML error page, show a debug message | |
if (responseText.includes('<html>') || responseText.includes('<!DOCTYPE')) { | |
this.showMessage(messageEl, | |
`Eroare server (${response.status}). Verifică că fișierul PHP funcționează corect. ` + | |
`<details style="margin-top: 10px;"><summary>Detalii tehnice</summary><pre style="font-size: 0.8em; margin-top: 5px;">${responseText.substring(0, 500)}...</pre></details>`, | |
'debug' | |
); | |
return; | |
} else { | |
throw new Error(`Răspuns invalid de la server: ${responseText.substring(0, 100)}...`); | |
} | |
} | |
// Handle the JSON response | |
if (result.success) { | |
this.showMessage(messageEl, result.message || 'Te-ai abonat cu succes!', 'success'); | |
emailInput.value = ''; | |
// Dispatch custom event for parent components | |
this.dispatchEvent(new CustomEvent('newsletter-subscribed', { | |
detail: { email: email, subscriberId: result.subscriber_id }, | |
bubbles: true | |
})); | |
} else { | |
// Show debug info if available | |
let errorMessage = result.message || 'A apărut o eroare necunoscută.'; | |
if (result.debug_info) { | |
errorMessage += `\n\nInfo debug:\n${JSON.stringify(result.debug_info, null, 2)}`; | |
this.showMessage(messageEl, errorMessage, 'debug'); | |
} else { | |
this.showMessage(messageEl, errorMessage, 'error'); | |
} | |
} | |
} catch (error) { | |
console.error('Newsletter subscription error:', error); | |
let errorMessage = 'Eroare de conexiune. '; | |
if (error.name === 'TypeError' && error.message.includes('fetch')) { | |
errorMessage += 'Nu se poate conecta la server. Verifică URL-ul API-ului.'; | |
} else if (error.message.includes('JSON')) { | |
errorMessage += 'Răspuns invalid de la server.'; | |
} else { | |
errorMessage += error.message; | |
} | |
this.showMessage(messageEl, errorMessage + ' Încearcă din nou.', 'error'); | |
} finally { | |
// Reset button state | |
submitButton.disabled = false; | |
loading.style.display = 'none'; | |
buttonText.textContent = this.buttonText; | |
} | |
} | |
showMessage(messageEl, text, type) { | |
// Handle HTML content for debug messages | |
if (type === 'debug') { | |
messageEl.innerHTML = text; | |
} else { | |
messageEl.textContent = text; | |
} | |
messageEl.className = `message ${type}`; | |
messageEl.style.display = 'block'; | |
// Auto-hide success messages after 5 seconds | |
if (type === 'success') { | |
setTimeout(() => { | |
messageEl.style.display = 'none'; | |
}, 5000); | |
} | |
} | |
isValidEmail(email) { | |
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | |
return re.test(email); | |
} | |
// Observed attributes for dynamic updates | |
static get observedAttributes() { | |
return ['title', 'description', 'api-url', 'variant', 'placeholder', 'button-text']; | |
} | |
attributeChangedCallback(name, oldValue, newValue) { | |
if (oldValue !== newValue) { | |
switch(name) { | |
case 'title': | |
this.title = newValue; | |
break; | |
case 'description': | |
this.description = newValue; | |
break; | |
case 'api-url': | |
this.apiUrl = newValue; | |
break; | |
case 'variant': | |
this.variant = newValue; | |
break; | |
case 'placeholder': | |
this.placeholder = newValue; | |
break; | |
case 'button-text': | |
this.buttonText = newValue; | |
break; | |
} | |
this.render(); | |
this.attachEventListeners(); | |
} | |
} | |
} | |
// Register the custom element | |
customElements.define('simple-newsletter', SimpleNewsletter); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment