easc-cli 1.1.30 → 1.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/charity-website/admin/index.html +299 -0
- package/charity-website/public/index.html +265 -0
- package/charity-website/src/css/admin.css +710 -0
- package/charity-website/src/css/style.css +741 -0
- package/charity-website/src/js/app.js +444 -0
- package/package.json +1 -1
- package/src/cli/cmd/tui/routes/session/index.tsx +12 -12
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
// Supabase configuration
|
|
2
|
+
const SUPABASE_URL = "https://affhpjwojmovvrwxlnmn.supabase.co"
|
|
3
|
+
const SUPABASE_ANON_KEY =
|
|
4
|
+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFmZmhwandvam1vdnZyd3hsbm1uIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkwMTgyMjMsImV4cCI6MjA4NDU5NDIyM30.fN44OO4Plw-45fL7l-G1sSfliPvveN3t-gP1Pwm0LME"
|
|
5
|
+
|
|
6
|
+
// Initialize Supabase
|
|
7
|
+
const supabase = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
|
|
8
|
+
|
|
9
|
+
// Global state
|
|
10
|
+
let charities = []
|
|
11
|
+
let categories = []
|
|
12
|
+
let currentCharityId = null
|
|
13
|
+
let offset = 0
|
|
14
|
+
const limit = 6
|
|
15
|
+
|
|
16
|
+
// DOM elements
|
|
17
|
+
const navToggle = document.getElementById("navToggle")
|
|
18
|
+
const navMenu = document.querySelector(".nav-menu")
|
|
19
|
+
const charitiesGrid = document.getElementById("charitiesGrid")
|
|
20
|
+
const categoryFilter = document.getElementById("categoryFilter")
|
|
21
|
+
const sortFilter = document.getElementById("sortFilter")
|
|
22
|
+
const searchInput = document.getElementById("searchInput")
|
|
23
|
+
const loadMoreBtn = document.getElementById("loadMoreBtn")
|
|
24
|
+
const donationModal = document.getElementById("donationModal")
|
|
25
|
+
const donationForm = document.getElementById("donationForm")
|
|
26
|
+
const contactForm = document.getElementById("contactForm")
|
|
27
|
+
|
|
28
|
+
// Initialize app
|
|
29
|
+
document.addEventListener("DOMContentLoaded", async () => {
|
|
30
|
+
await initializeApp()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
async function initializeApp() {
|
|
34
|
+
try {
|
|
35
|
+
await loadCategories()
|
|
36
|
+
await loadCharities()
|
|
37
|
+
await loadStats()
|
|
38
|
+
setupEventListeners()
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error("Error initializing app:", error)
|
|
41
|
+
showToast("Error loading application", "error")
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Event Listeners
|
|
46
|
+
function setupEventListeners() {
|
|
47
|
+
// Navigation toggle
|
|
48
|
+
navToggle.addEventListener("click", () => {
|
|
49
|
+
navMenu.classList.toggle("active")
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// Smooth scrolling for navigation links
|
|
53
|
+
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
|
|
54
|
+
anchor.addEventListener("click", function (e) {
|
|
55
|
+
e.preventDefault()
|
|
56
|
+
const target = document.querySelector(this.getAttribute("href"))
|
|
57
|
+
if (target) {
|
|
58
|
+
target.scrollIntoView({ behavior: "smooth", block: "start" })
|
|
59
|
+
navMenu.classList.remove("active")
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Filters
|
|
65
|
+
categoryFilter.addEventListener("change", filterCharities)
|
|
66
|
+
sortFilter.addEventListener("change", filterCharities)
|
|
67
|
+
searchInput.addEventListener("input", debounce(filterCharities, 300))
|
|
68
|
+
|
|
69
|
+
// Donation form
|
|
70
|
+
donationForm.addEventListener("submit", handleDonation)
|
|
71
|
+
|
|
72
|
+
// Contact form
|
|
73
|
+
contactForm.addEventListener("submit", handleContact)
|
|
74
|
+
|
|
75
|
+
// Amount buttons
|
|
76
|
+
document.querySelectorAll(".amount-btn").forEach((btn) => {
|
|
77
|
+
btn.addEventListener("click", function () {
|
|
78
|
+
document.querySelectorAll(".amount-btn").forEach((b) => b.classList.remove("active"))
|
|
79
|
+
this.classList.add("active")
|
|
80
|
+
document.getElementById("donationAmount").value = this.dataset.amount
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
// Modal close on outside click
|
|
85
|
+
donationModal.addEventListener("click", (e) => {
|
|
86
|
+
if (e.target === donationModal) {
|
|
87
|
+
closeDonationModal()
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Load categories
|
|
93
|
+
async function loadCategories() {
|
|
94
|
+
try {
|
|
95
|
+
const { data, error } = await supabase.from("categories").select("*").order("name")
|
|
96
|
+
|
|
97
|
+
if (error) throw error
|
|
98
|
+
categories = data
|
|
99
|
+
|
|
100
|
+
// Populate category filter
|
|
101
|
+
categories.forEach((category) => {
|
|
102
|
+
const option = document.createElement("option")
|
|
103
|
+
option.value = category.name
|
|
104
|
+
option.textContent = category.name
|
|
105
|
+
categoryFilter.appendChild(option)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// Populate footer categories
|
|
109
|
+
const footerCategories = document.getElementById("footerCategories")
|
|
110
|
+
categories.forEach((category) => {
|
|
111
|
+
const li = document.createElement("li")
|
|
112
|
+
const a = document.createElement("a")
|
|
113
|
+
a.href = "#charities"
|
|
114
|
+
a.textContent = category.name
|
|
115
|
+
a.addEventListener("click", (e) => {
|
|
116
|
+
e.preventDefault()
|
|
117
|
+
categoryFilter.value = category.name
|
|
118
|
+
filterCharities()
|
|
119
|
+
scrollToCharities()
|
|
120
|
+
})
|
|
121
|
+
li.appendChild(a)
|
|
122
|
+
footerCategories.appendChild(li)
|
|
123
|
+
})
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error("Error loading categories:", error)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Load charities
|
|
130
|
+
async function loadCharities(reset = false) {
|
|
131
|
+
try {
|
|
132
|
+
if (reset) {
|
|
133
|
+
offset = 0
|
|
134
|
+
charities = []
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let query = supabase
|
|
138
|
+
.from("charities")
|
|
139
|
+
.select("*")
|
|
140
|
+
.eq("is_active", true)
|
|
141
|
+
.range(offset, offset + limit - 1)
|
|
142
|
+
.order("created_at", { ascending: false })
|
|
143
|
+
|
|
144
|
+
// Apply filters
|
|
145
|
+
const category = categoryFilter.value
|
|
146
|
+
if (category) {
|
|
147
|
+
query = query.eq("category", category)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const searchTerm = searchInput.value.trim()
|
|
151
|
+
if (searchTerm) {
|
|
152
|
+
query = query.or(`name.ilike.%${searchTerm}%,description.ilike.%${searchTerm}%`)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Apply sorting
|
|
156
|
+
const sortBy = sortFilter.value
|
|
157
|
+
if (sortBy === "name") {
|
|
158
|
+
query = query.order("name", { ascending: true })
|
|
159
|
+
} else if (sortBy === "goal") {
|
|
160
|
+
query = query.order("goal_amount", { ascending: false })
|
|
161
|
+
} else if (sortBy === "progress") {
|
|
162
|
+
query = query.order("current_amount", { ascending: false })
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const { data, error } = await query
|
|
166
|
+
|
|
167
|
+
if (error) throw error
|
|
168
|
+
|
|
169
|
+
if (reset) {
|
|
170
|
+
charities = data
|
|
171
|
+
} else {
|
|
172
|
+
charities = [...charities, ...data]
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
renderCharities()
|
|
176
|
+
updateLoadMoreButton(data.length)
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error("Error loading charities:", error)
|
|
179
|
+
showToast("Error loading charities", "error")
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Render charities
|
|
184
|
+
function renderCharities() {
|
|
185
|
+
charitiesGrid.innerHTML = ""
|
|
186
|
+
|
|
187
|
+
charities.forEach((charity) => {
|
|
188
|
+
const card = createCharityCard(charity)
|
|
189
|
+
charitiesGrid.appendChild(card)
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Create charity card
|
|
194
|
+
function createCharityCard(charity) {
|
|
195
|
+
const progress = (charity.current_amount / charity.goal_amount) * 100
|
|
196
|
+
|
|
197
|
+
const card = document.createElement("div")
|
|
198
|
+
card.className = "charity-card"
|
|
199
|
+
card.onclick = () => openDonationModal(charity)
|
|
200
|
+
|
|
201
|
+
card.innerHTML = `
|
|
202
|
+
<img src="${charity.image_url || "https://images.unsplash.com/photo-1488521787991-ed7bba14b592?w=400"}"
|
|
203
|
+
alt="${charity.name}" class="charity-image">
|
|
204
|
+
<div class="charity-content">
|
|
205
|
+
<span class="charity-category">${charity.category}</span>
|
|
206
|
+
<h3 class="charity-title">${charity.name}</h3>
|
|
207
|
+
<p class="charity-description">${charity.description}</p>
|
|
208
|
+
<div class="charity-progress">
|
|
209
|
+
<div class="progress-bar">
|
|
210
|
+
<div class="progress-fill" style="width: ${Math.min(progress, 100)}%"></div>
|
|
211
|
+
</div>
|
|
212
|
+
<div class="charity-stats">
|
|
213
|
+
<span class="charity-amount">$${charity.current_amount.toLocaleString()}</span>
|
|
214
|
+
<span class="charity-goal">Goal: $${charity.goal_amount.toLocaleString()}</span>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
<div class="charity-actions">
|
|
218
|
+
<button class="btn btn-primary" onclick="event.stopPropagation(); openDonationModal(${JSON.stringify(charity).replace(/"/g, """)})">
|
|
219
|
+
Donate Now
|
|
220
|
+
</button>
|
|
221
|
+
<button class="btn btn-secondary" onclick="event.stopPropagation(); window.open('${charity.website_url}', '_blank')">
|
|
222
|
+
Learn More
|
|
223
|
+
</button>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
`
|
|
227
|
+
|
|
228
|
+
return card
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Filter charities
|
|
232
|
+
async function filterCharities() {
|
|
233
|
+
await loadCharities(true)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Search charities
|
|
237
|
+
async function searchCharities() {
|
|
238
|
+
await filterCharities()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Load more charities
|
|
242
|
+
async function loadMoreCharities() {
|
|
243
|
+
offset += limit
|
|
244
|
+
await loadCharities()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Update load more button
|
|
248
|
+
function updateLoadMoreButton(loadedCount) {
|
|
249
|
+
if (loadedCount < limit) {
|
|
250
|
+
loadMoreBtn.style.display = "none"
|
|
251
|
+
} else {
|
|
252
|
+
loadMoreBtn.style.display = "inline-block"
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Load statistics
|
|
257
|
+
async function loadStats() {
|
|
258
|
+
try {
|
|
259
|
+
// Get total charities
|
|
260
|
+
const { count: totalCharities, error: charitiesError } = await supabase
|
|
261
|
+
.from("charities")
|
|
262
|
+
.select("*", { count: "exact", head: true })
|
|
263
|
+
.eq("is_active", true)
|
|
264
|
+
|
|
265
|
+
// Get total raised
|
|
266
|
+
const { data: raisedData, error: raisedError } = await supabase
|
|
267
|
+
.from("charities")
|
|
268
|
+
.select("current_amount")
|
|
269
|
+
.eq("is_active", true)
|
|
270
|
+
|
|
271
|
+
// Get total donors
|
|
272
|
+
const { count: totalDonors, error: donorsError } = await supabase
|
|
273
|
+
.from("donations")
|
|
274
|
+
.select("*", { count: "exact", head: true })
|
|
275
|
+
.eq("payment_status", "completed")
|
|
276
|
+
|
|
277
|
+
if (charitiesError || raisedError || donorsError) throw new Error("Error loading stats")
|
|
278
|
+
|
|
279
|
+
// Update DOM
|
|
280
|
+
document.getElementById("totalCharities").textContent = totalCharities || 0
|
|
281
|
+
|
|
282
|
+
const totalRaised = raisedData?.reduce((sum, charity) => sum + parseFloat(charity.current_amount), 0) || 0
|
|
283
|
+
document.getElementById("totalRaised").textContent = `$${totalRaised.toLocaleString()}`
|
|
284
|
+
|
|
285
|
+
document.getElementById("totalDonors").textContent = totalDonors || 0
|
|
286
|
+
} catch (error) {
|
|
287
|
+
console.error("Error loading stats:", error)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Open donation modal
|
|
292
|
+
function openDonationModal(charity) {
|
|
293
|
+
currentCharityId = charity.id
|
|
294
|
+
|
|
295
|
+
const modalCharityInfo = document.getElementById("modalCharityInfo")
|
|
296
|
+
modalCharityInfo.innerHTML = `
|
|
297
|
+
<h4>${charity.name}</h4>
|
|
298
|
+
<p>${charity.description}</p>
|
|
299
|
+
<div class="charity-progress">
|
|
300
|
+
<div class="progress-bar">
|
|
301
|
+
<div class="progress-fill" style="width: ${(charity.current_amount / charity.goal_amount) * 100}%"></div>
|
|
302
|
+
</div>
|
|
303
|
+
<div class="charity-stats">
|
|
304
|
+
<span>$${charity.current_amount.toLocaleString()} raised</span>
|
|
305
|
+
<span>Goal: $${charity.goal_amount.toLocaleString()}</span>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
`
|
|
309
|
+
|
|
310
|
+
donationModal.classList.add("active")
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Close donation modal
|
|
314
|
+
function closeDonationModal() {
|
|
315
|
+
donationModal.classList.remove("active")
|
|
316
|
+
donationForm.reset()
|
|
317
|
+
document.querySelectorAll(".amount-btn").forEach((btn) => btn.classList.remove("active"))
|
|
318
|
+
currentCharityId = null
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Handle donation
|
|
322
|
+
async function handleDonation(e) {
|
|
323
|
+
e.preventDefault()
|
|
324
|
+
|
|
325
|
+
if (!currentCharityId) {
|
|
326
|
+
showToast("Please select a charity", "error")
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const formData = new FormData(donationForm)
|
|
331
|
+
const donation = {
|
|
332
|
+
charity_id: currentCharityId,
|
|
333
|
+
donor_name: formData.get("donorName") || document.getElementById("donorName").value,
|
|
334
|
+
donor_email: formData.get("donorEmail") || document.getElementById("donorEmail").value,
|
|
335
|
+
amount: parseFloat(document.getElementById("donationAmount").value),
|
|
336
|
+
message: document.getElementById("donationMessage").value,
|
|
337
|
+
is_anonymous: document.getElementById("anonymousDonation").checked,
|
|
338
|
+
payment_status: "pending",
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
// Insert donation
|
|
343
|
+
const { data, error } = await supabase.from("donations").insert([donation]).select()
|
|
344
|
+
|
|
345
|
+
if (error) throw error
|
|
346
|
+
|
|
347
|
+
// Simulate payment processing (in real app, integrate with payment gateway)
|
|
348
|
+
showToast("Processing payment...", "success")
|
|
349
|
+
|
|
350
|
+
setTimeout(async () => {
|
|
351
|
+
// Update payment status
|
|
352
|
+
const { error: updateError } = await supabase
|
|
353
|
+
.from("donations")
|
|
354
|
+
.update({ payment_status: "completed" })
|
|
355
|
+
.eq("id", data[0].id)
|
|
356
|
+
|
|
357
|
+
if (updateError) throw updateError
|
|
358
|
+
|
|
359
|
+
// Update charity amount
|
|
360
|
+
const { error: charityError } = await supabase
|
|
361
|
+
.from("charities")
|
|
362
|
+
.update({
|
|
363
|
+
current_amount: supabase.raw(`current_amount + ${donation.amount}`),
|
|
364
|
+
updated_at: new Date().toISOString(),
|
|
365
|
+
})
|
|
366
|
+
.eq("id", currentCharityId)
|
|
367
|
+
|
|
368
|
+
if (charityError) throw charityError
|
|
369
|
+
|
|
370
|
+
showToast("Thank you for your donation!", "success")
|
|
371
|
+
closeDonationModal()
|
|
372
|
+
await loadCharities(true)
|
|
373
|
+
await loadStats()
|
|
374
|
+
}, 2000)
|
|
375
|
+
} catch (error) {
|
|
376
|
+
console.error("Error processing donation:", error)
|
|
377
|
+
showToast("Error processing donation", "error")
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Handle contact form
|
|
382
|
+
async function handleContact(e) {
|
|
383
|
+
e.preventDefault()
|
|
384
|
+
|
|
385
|
+
const contact = {
|
|
386
|
+
name: document.getElementById("contactName").value,
|
|
387
|
+
email: document.getElementById("contactEmail").value,
|
|
388
|
+
message: document.getElementById("contactMessage").value,
|
|
389
|
+
created_at: new Date().toISOString(),
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
try {
|
|
393
|
+
// In a real app, send this to your backend or email service
|
|
394
|
+
console.log("Contact form submission:", contact)
|
|
395
|
+
showToast("Message sent successfully!", "success")
|
|
396
|
+
contactForm.reset()
|
|
397
|
+
} catch (error) {
|
|
398
|
+
console.error("Error sending message:", error)
|
|
399
|
+
showToast("Error sending message", "error")
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Utility functions
|
|
404
|
+
function scrollToCharities() {
|
|
405
|
+
document.getElementById("charities").scrollIntoView({ behavior: "smooth" })
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function showToast(message, type = "success") {
|
|
409
|
+
const toast = document.createElement("div")
|
|
410
|
+
toast.className = `toast ${type}`
|
|
411
|
+
toast.textContent = message
|
|
412
|
+
|
|
413
|
+
document.body.appendChild(toast)
|
|
414
|
+
|
|
415
|
+
setTimeout(() => {
|
|
416
|
+
toast.classList.add("show")
|
|
417
|
+
}, 100)
|
|
418
|
+
|
|
419
|
+
setTimeout(() => {
|
|
420
|
+
toast.classList.remove("show")
|
|
421
|
+
setTimeout(() => {
|
|
422
|
+
document.body.removeChild(toast)
|
|
423
|
+
}, 300)
|
|
424
|
+
}, 3000)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function debounce(func, wait) {
|
|
428
|
+
let timeout
|
|
429
|
+
return function executedFunction(...args) {
|
|
430
|
+
const later = () => {
|
|
431
|
+
clearTimeout(timeout)
|
|
432
|
+
func(...args)
|
|
433
|
+
}
|
|
434
|
+
clearTimeout(timeout)
|
|
435
|
+
timeout = setTimeout(later, wait)
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Export functions for global access
|
|
440
|
+
window.openDonationModal = openDonationModal
|
|
441
|
+
window.closeDonationModal = closeDonationModal
|
|
442
|
+
window.scrollToCharities = scrollToCharities
|
|
443
|
+
window.searchCharities = searchCharities
|
|
444
|
+
window.loadMoreCharities = loadMoreCharities
|
package/package.json
CHANGED
|
@@ -1570,19 +1570,19 @@ function InlineTool(props: {
|
|
|
1570
1570
|
}}
|
|
1571
1571
|
backgroundColor={hover() ? theme.backgroundElement : undefined}
|
|
1572
1572
|
>
|
|
1573
|
-
<
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
<
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
>
|
|
1573
|
+
<Show
|
|
1574
|
+
fallback={
|
|
1575
|
+
<box flexDirection="row" gap={1} paddingLeft={3}>
|
|
1576
|
+
<Spinner fg={fg()} />
|
|
1577
|
+
<text fg={fg()} attributes={denied() ? TextAttributes.STRIKETHROUGH : undefined}> {props.pending}</text>
|
|
1578
|
+
</box>
|
|
1579
|
+
}
|
|
1580
|
+
when={props.complete}
|
|
1581
|
+
>
|
|
1582
|
+
<text paddingLeft={3} fg={fg()} attributes={denied() ? TextAttributes.STRIKETHROUGH : undefined}>
|
|
1583
1583
|
<span style={{ fg: props.iconColor }}>{props.icon}</span> {props.children}
|
|
1584
|
-
</
|
|
1585
|
-
</
|
|
1584
|
+
</text>
|
|
1585
|
+
</Show>
|
|
1586
1586
|
<Show when={error() && !denied()}>
|
|
1587
1587
|
<text fg={theme.error}>{error()}</text>
|
|
1588
1588
|
</Show>
|