Complete phase 5

This commit is contained in:
Eugene Burmakin
2025-09-28 13:10:07 +02:00
parent e17f732706
commit 1f67e889e3
30 changed files with 1618 additions and 71 deletions
@@ -0,0 +1,81 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["confirmButton", "cancelButton"]
static values = {
action: String,
memberEmail: String,
familyName: String
}
connect() {
this.setupConfirmationMessages()
}
setupConfirmationMessages() {
const confirmButtons = this.element.querySelectorAll('[data-confirm]')
confirmButtons.forEach(button => {
button.addEventListener('click', (event) => {
const action = button.dataset.action
const confirmMessage = this.getConfirmationMessage(action)
if (!confirm(confirmMessage)) {
event.preventDefault()
return false
}
})
})
}
getConfirmationMessage(action) {
switch(action) {
case 'leave-family':
return `Are you sure you want to leave "${this.familyNameValue}"? You'll need a new invitation to rejoin.`
case 'delete-family':
return `Are you sure you want to delete "${this.familyNameValue}"? This action cannot be undone.`
case 'remove-member':
return `Are you sure you want to remove ${this.memberEmailValue} from the family?`
case 'cancel-invitation':
return `Are you sure you want to cancel the invitation to ${this.memberEmailValue}?`
default:
return 'Are you sure you want to perform this action?'
}
}
showLoadingState(button, action) {
const originalText = button.innerHTML
button.disabled = true
const loadingText = this.getLoadingText(action)
button.innerHTML = `
<span class="loading loading-spinner loading-sm"></span>
${loadingText}
`
// Store original text to restore if needed
button.dataset.originalText = originalText
}
getLoadingText(action) {
switch(action) {
case 'leave-family':
return 'Leaving family...'
case 'delete-family':
return 'Deleting family...'
case 'remove-member':
return 'Removing member...'
case 'cancel-invitation':
return 'Cancelling invitation...'
default:
return 'Processing...'
}
}
onConfirmedAction(event) {
const button = event.currentTarget
const action = button.dataset.action
this.showLoadingState(button, action)
}
}
@@ -0,0 +1,66 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["form", "email", "submitButton", "errorMessage"]
static values = { maxMembers: Number, currentMembers: Number }
connect() {
this.validateForm()
}
validateForm() {
const email = this.emailTarget.value.trim()
const isValid = this.isValidEmail(email) && this.canInviteMoreMembers()
this.submitButtonTarget.disabled = !isValid
if (email && !this.isValidEmail(email)) {
this.showError("Please enter a valid email address")
} else if (!this.canInviteMoreMembers()) {
this.showError(`Family is full (${this.currentMembersValue}/${this.maxMembersValue} members)`)
} else {
this.hideError()
}
}
onEmailInput() {
this.validateForm()
}
isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(email)
}
canInviteMoreMembers() {
return this.currentMembersValue < this.maxMembersValue
}
showError(message) {
if (this.hasErrorMessageTarget) {
this.errorMessageTarget.textContent = message
this.errorMessageTarget.classList.remove("hidden")
}
}
hideError() {
if (this.hasErrorMessageTarget) {
this.errorMessageTarget.classList.add("hidden")
}
}
onSubmit(event) {
if (!this.isValidEmail(this.emailTarget.value.trim()) || !this.canInviteMoreMembers()) {
event.preventDefault()
this.validateForm()
return false
}
// Show loading state
this.submitButtonTarget.disabled = true
this.submitButtonTarget.innerHTML = `
<span class="loading loading-spinner loading-sm"></span>
Sending invitation...
`
}
}
@@ -0,0 +1,43 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static values = {
type: String,
autoDismiss: Boolean
}
connect() {
this.element.style.animation = 'slideInFromRight 0.3s ease-out forwards'
if (this.autoDismissValue) {
this.scheduleDismissal()
}
}
scheduleDismissal() {
// Auto-dismiss success/notice messages after 5 seconds
this.dismissTimeout = setTimeout(() => {
this.dismiss()
}, 5000)
}
dismiss() {
if (this.dismissTimeout) {
clearTimeout(this.dismissTimeout)
}
this.element.style.animation = 'slideOutToRight 0.3s ease-in forwards'
setTimeout(() => {
if (this.element.parentNode) {
this.element.parentNode.removeChild(this.element)
}
}, 300)
}
disconnect() {
if (this.dismissTimeout) {
clearTimeout(this.dismissTimeout)
}
}
}