fix: use private helcimApiToken for all server-side Helcim API calls
This commit is contained in:
parent
ccd1d0783a
commit
d31b5b4dac
53 changed files with 1755 additions and 572 deletions
|
|
@ -162,6 +162,25 @@
|
|||
No events found matching your criteria
|
||||
</div>
|
||||
</div>
|
||||
<!-- Confirm Delete Modal -->
|
||||
<div v-if="confirmDelete.show" class="modal-overlay" @click.self="confirmDelete.show = false">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h2>Delete Event</h2>
|
||||
<button class="modal-close" @click="confirmDelete.show = false">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to delete <strong>"{{ confirmDelete.title }}"</strong>?</p>
|
||||
<p class="help-text" style="margin-top: 8px;">This action cannot be undone.</p>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn" @click="confirmDelete.show = false">Cancel</button>
|
||||
<button class="btn btn-danger" :disabled="confirmDelete.deleting" @click="executeDelete">
|
||||
{{ confirmDelete.deleting ? 'Deleting...' : 'Delete' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -255,16 +274,25 @@ const duplicateEvent = (event) => {
|
|||
navigateTo('/admin/events/create?duplicate=true')
|
||||
}
|
||||
|
||||
const deleteEvent = async (event) => {
|
||||
if (confirm(`Are you sure you want to delete "${event.title}"?`)) {
|
||||
try {
|
||||
await $fetch(`/api/admin/events/${String(event._id)}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
await refresh()
|
||||
} catch (error) {
|
||||
console.error('Failed to delete event:', error)
|
||||
}
|
||||
const confirmDelete = reactive({ show: false, id: null, title: '', deleting: false })
|
||||
|
||||
const deleteEvent = (event) => {
|
||||
confirmDelete.id = String(event._id)
|
||||
confirmDelete.title = event.title
|
||||
confirmDelete.deleting = false
|
||||
confirmDelete.show = true
|
||||
}
|
||||
|
||||
const executeDelete = async () => {
|
||||
confirmDelete.deleting = true
|
||||
try {
|
||||
await $fetch(`/api/admin/events/${confirmDelete.id}`, { method: 'DELETE' })
|
||||
confirmDelete.show = false
|
||||
await refresh()
|
||||
} catch (error) {
|
||||
console.error('Failed to delete event:', error)
|
||||
} finally {
|
||||
confirmDelete.deleting = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -588,6 +616,77 @@ tbody td {
|
|||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* ---- MODALS ---- */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: var(--bg);
|
||||
border: 1px dashed var(--border);
|
||||
max-width: 440px;
|
||||
width: 100%;
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 24px 16px;
|
||||
border-bottom: 1px dashed var(--border);
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
font-family: 'Brygada 1918', serif;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: var(--text-bright);
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-faint);
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
padding: 0 4px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
.modal-body p {
|
||||
font-size: 13px;
|
||||
color: var(--text);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 16px 24px;
|
||||
border-top: 1px dashed var(--border);
|
||||
}
|
||||
|
||||
.help-text {
|
||||
font-size: 11px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
/* ---- RESPONSIVE ---- */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
|
|
|
|||
|
|
@ -248,6 +248,60 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Member Modal -->
|
||||
<div v-if="showEditModal" class="modal-overlay" @click.self="showEditModal = false">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h2>Edit Member</h2>
|
||||
<button class="modal-close" @click="showEditModal = false">×</button>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="submitEditMember" class="modal-body">
|
||||
<div class="field">
|
||||
<label>Name</label>
|
||||
<input v-model="editingMember.name" required />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Email</label>
|
||||
<input v-model="editingMember.email" type="email" required />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Circle</label>
|
||||
<select v-model="editingMember.circle">
|
||||
<option value="community">Community</option>
|
||||
<option value="founder">Founder</option>
|
||||
<option value="practitioner">Practitioner</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Contribution Tier</label>
|
||||
<select v-model="editingMember.contributionTier">
|
||||
<option value="0">$0/month</option>
|
||||
<option value="5">$5/month</option>
|
||||
<option value="15">$15/month</option>
|
||||
<option value="30">$30/month</option>
|
||||
<option value="50">$50/month</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Status</label>
|
||||
<select v-model="editingMember.status">
|
||||
<option value="pending_payment">Pending Payment</option>
|
||||
<option value="active">Active</option>
|
||||
<option value="suspended">Suspended</option>
|
||||
<option value="cancelled">Cancelled</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="btn" @click="showEditModal = false">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" :disabled="saving">
|
||||
{{ saving ? 'Saving...' : 'Save Changes' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Send Invites Modal -->
|
||||
<div v-if="showInviteModal" class="modal-overlay" @click.self="showInviteModal = false">
|
||||
<div class="modal modal-wide">
|
||||
|
|
@ -629,8 +683,55 @@ const sendSlackInvite = (member) => {
|
|||
console.log('Send Slack invite to:', member.email)
|
||||
}
|
||||
|
||||
// --- Edit Member ---
|
||||
const showEditModal = ref(false)
|
||||
const saving = ref(false)
|
||||
const editingMemberId = ref(null)
|
||||
const editingMember = reactive({
|
||||
name: '',
|
||||
email: '',
|
||||
circle: 'community',
|
||||
contributionTier: '0',
|
||||
status: 'pending_payment',
|
||||
})
|
||||
|
||||
const editMember = (member) => {
|
||||
console.log('Edit member:', member._id)
|
||||
editingMemberId.value = member._id
|
||||
Object.assign(editingMember, {
|
||||
name: member.name,
|
||||
email: member.email,
|
||||
circle: member.circle,
|
||||
contributionTier: String(member.contributionTier),
|
||||
status: member.status || 'pending_payment',
|
||||
})
|
||||
showEditModal.value = true
|
||||
}
|
||||
|
||||
const submitEditMember = async () => {
|
||||
saving.value = true
|
||||
try {
|
||||
await $fetch(`/api/admin/members/${editingMemberId.value}`, {
|
||||
method: 'PUT',
|
||||
body: {
|
||||
name: editingMember.name,
|
||||
email: editingMember.email,
|
||||
circle: editingMember.circle,
|
||||
contributionTier: editingMember.contributionTier,
|
||||
status: editingMember.status,
|
||||
},
|
||||
})
|
||||
showEditModal.value = false
|
||||
await refresh()
|
||||
toast.add({ title: 'Member updated', color: 'green' })
|
||||
} catch (err) {
|
||||
toast.add({
|
||||
title: 'Failed to update member',
|
||||
description: err.data?.statusMessage || err.message,
|
||||
color: 'red',
|
||||
})
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -361,6 +361,25 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Confirm Action Modal -->
|
||||
<div v-if="confirmAction.show" class="modal-overlay" @click.self="confirmAction.show = false">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h2>{{ confirmAction.heading }}</h2>
|
||||
<button class="modal-close" @click="confirmAction.show = false">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{ confirmAction.message }}</p>
|
||||
<p class="help-text" style="margin-top: 8px;">This action cannot be undone.</p>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn" @click="confirmAction.show = false">Cancel</button>
|
||||
<button class="btn btn-danger" :disabled="confirmAction.running" @click="confirmAction.execute">
|
||||
{{ confirmAction.running ? 'Working...' : confirmAction.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -496,29 +515,47 @@ const editEvent = (event) => {
|
|||
navigateTo(`/admin/events/create?edit=${event.id}`)
|
||||
}
|
||||
|
||||
const removeFromSeries = async (event) => {
|
||||
if (!confirm(`Remove "${event.title}" from its series?`)) return
|
||||
const confirmAction = reactive({
|
||||
show: false,
|
||||
heading: '',
|
||||
message: '',
|
||||
label: '',
|
||||
running: false,
|
||||
execute: () => {},
|
||||
})
|
||||
|
||||
try {
|
||||
await $fetch(`/api/admin/events/${event.id}`, {
|
||||
method: 'PUT',
|
||||
body: {
|
||||
...event,
|
||||
series: {
|
||||
isSeriesEvent: false,
|
||||
id: '',
|
||||
title: '',
|
||||
description: '',
|
||||
type: 'workshop_series',
|
||||
position: 1,
|
||||
totalEvents: null,
|
||||
const removeFromSeries = (event) => {
|
||||
confirmAction.heading = 'Remove from Series'
|
||||
confirmAction.message = `Remove "${event.title}" from its series?`
|
||||
confirmAction.label = 'Remove'
|
||||
confirmAction.running = false
|
||||
confirmAction.execute = async () => {
|
||||
confirmAction.running = true
|
||||
try {
|
||||
await $fetch(`/api/admin/events/${event.id}`, {
|
||||
method: 'PUT',
|
||||
body: {
|
||||
...event,
|
||||
series: {
|
||||
isSeriesEvent: false,
|
||||
id: '',
|
||||
title: '',
|
||||
description: '',
|
||||
type: 'workshop_series',
|
||||
position: 1,
|
||||
totalEvents: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
await refresh()
|
||||
} catch (error) {
|
||||
console.error('Failed to remove event from series:', error)
|
||||
})
|
||||
confirmAction.show = false
|
||||
await refresh()
|
||||
} catch (error) {
|
||||
console.error('Failed to remove event from series:', error)
|
||||
} finally {
|
||||
confirmAction.running = false
|
||||
}
|
||||
}
|
||||
confirmAction.show = true
|
||||
}
|
||||
|
||||
const addEventToSeries = (series) => {
|
||||
|
|
@ -572,23 +609,32 @@ const saveSeriesEdit = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
const deleteSeries = async (series) => {
|
||||
if (!confirm(`Delete the entire "${series.title}" series? This will remove the series relationship from all ${series.eventCount} events.`)) return
|
||||
|
||||
try {
|
||||
for (const event of series.events) {
|
||||
await $fetch(`/api/admin/events/${event.id}`, {
|
||||
method: 'PUT',
|
||||
body: {
|
||||
...event,
|
||||
series: { isSeriesEvent: false, id: '', title: '', description: '', type: 'workshop_series', position: 1, totalEvents: null },
|
||||
},
|
||||
})
|
||||
const deleteSeries = (series) => {
|
||||
confirmAction.heading = 'Delete Series'
|
||||
confirmAction.message = `Delete the entire "${series.title}" series? This will remove the series relationship from all ${series.eventCount} events.`
|
||||
confirmAction.label = 'Delete'
|
||||
confirmAction.running = false
|
||||
confirmAction.execute = async () => {
|
||||
confirmAction.running = true
|
||||
try {
|
||||
for (const event of series.events) {
|
||||
await $fetch(`/api/admin/events/${event.id}`, {
|
||||
method: 'PUT',
|
||||
body: {
|
||||
...event,
|
||||
series: { isSeriesEvent: false, id: '', title: '', description: '', type: 'workshop_series', position: 1, totalEvents: null },
|
||||
},
|
||||
})
|
||||
}
|
||||
confirmAction.show = false
|
||||
await refresh()
|
||||
} catch (error) {
|
||||
console.error('Failed to delete series:', error)
|
||||
} finally {
|
||||
confirmAction.running = false
|
||||
}
|
||||
await refresh()
|
||||
} catch (error) {
|
||||
console.error('Failed to delete series:', error)
|
||||
}
|
||||
confirmAction.show = true
|
||||
}
|
||||
|
||||
const manageSeriesTickets = (series) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue