easc-cli 1.1.31 ā 1.1.34
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/README.md +252 -0
- package/charity-website/package.json +40 -0
- package/charity-website/src/js/admin.js +743 -0
- package/package.json +7 -101
- package/script/build.ts +2 -2
- package/script/deploy.ts +30 -2
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# CharityHub - A Modern Charity Donation Platform
|
|
2
|
+
|
|
3
|
+
A comprehensive charity website built with vanilla HTML, CSS, JavaScript, and Supabase for the backend. This platform enables users to discover charities, make donations, and provides administrators with tools to manage the platform.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### For Users
|
|
8
|
+
|
|
9
|
+
- š **Responsive Home Page** with hero section and live statistics
|
|
10
|
+
- š **Charity Discovery** with search, filtering, and categorization
|
|
11
|
+
- š **Secure Donation System** with multiple payment options
|
|
12
|
+
- š **Progress Tracking** for each charity's fundraising goals
|
|
13
|
+
- š± **Mobile-First Design** that works on all devices
|
|
14
|
+
- ā **Featured Charities** highlighting important causes
|
|
15
|
+
|
|
16
|
+
### For Administrators
|
|
17
|
+
|
|
18
|
+
- š **Dashboard Analytics** with real-time statistics and charts
|
|
19
|
+
- š¢ **Charity Management** (add, edit, delete charities)
|
|
20
|
+
- šø **Donation Tracking** and management
|
|
21
|
+
- š·ļø **Category Management** for organizing causes
|
|
22
|
+
- āļø **Settings Configuration** for platform customization
|
|
23
|
+
- š **Data Export** capabilities
|
|
24
|
+
|
|
25
|
+
## Technology Stack
|
|
26
|
+
|
|
27
|
+
- **Frontend**: HTML5, CSS3, Vanilla JavaScript
|
|
28
|
+
- **Backend**: Supabase (PostgreSQL + Real-time APIs)
|
|
29
|
+
- **Database**: PostgreSQL with Row Level Security (RLS)
|
|
30
|
+
- **Charts**: Chart.js for data visualization
|
|
31
|
+
- **Icons**: Font Awesome
|
|
32
|
+
- **Styling**: Custom CSS with CSS Grid and Flexbox
|
|
33
|
+
|
|
34
|
+
## Project Structure
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
charity-website/
|
|
38
|
+
āāā public/
|
|
39
|
+
ā āāā index.html # Main website
|
|
40
|
+
āāā admin/
|
|
41
|
+
ā āāā index.html # Admin dashboard
|
|
42
|
+
āāā src/
|
|
43
|
+
ā āāā css/
|
|
44
|
+
ā ā āāā style.css # Main website styles
|
|
45
|
+
ā ā āāā admin.css # Admin dashboard styles
|
|
46
|
+
ā āāā js/
|
|
47
|
+
ā āāā app.js # Main website JavaScript
|
|
48
|
+
ā āāā admin.js # Admin dashboard JavaScript
|
|
49
|
+
āāā data/ # Static data (if needed)
|
|
50
|
+
āāā README.md # This file
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Database Schema
|
|
54
|
+
|
|
55
|
+
### Tables
|
|
56
|
+
|
|
57
|
+
- **charities**: Information about charitable organizations
|
|
58
|
+
- **donations**: Donation records and transactions
|
|
59
|
+
- **categories**: Charity categories for organization
|
|
60
|
+
|
|
61
|
+
### Key Features
|
|
62
|
+
|
|
63
|
+
- Row Level Security (RLS) for data protection
|
|
64
|
+
- Real-time updates using Supabase subscriptions
|
|
65
|
+
- Automatic timestamp tracking
|
|
66
|
+
- Foreign key relationships for data integrity
|
|
67
|
+
|
|
68
|
+
## Getting Started
|
|
69
|
+
|
|
70
|
+
### Prerequisites
|
|
71
|
+
|
|
72
|
+
- A Supabase account and project
|
|
73
|
+
- Modern web browser
|
|
74
|
+
- Local web server (optional, for development)
|
|
75
|
+
|
|
76
|
+
### Setup Instructions
|
|
77
|
+
|
|
78
|
+
1. **Clone or download the project files**
|
|
79
|
+
|
|
80
|
+
2. **Set up Supabase Database**
|
|
81
|
+
|
|
82
|
+
```sql
|
|
83
|
+
-- Run the provided migration scripts in your Supabase project
|
|
84
|
+
-- The schema includes charities, donations, and categories tables
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
3. **Configure Supabase Connection**
|
|
88
|
+
- Update the SUPABASE_URL and SUPABASE_ANON_KEY in both JavaScript files
|
|
89
|
+
- These can be found in your Supabase project settings
|
|
90
|
+
|
|
91
|
+
4. **Run the Website**
|
|
92
|
+
- Option 1: Open `public/index.html` directly in your browser
|
|
93
|
+
- Option 2: Use a local web server like `live-server` or Python's HTTP server
|
|
94
|
+
|
|
95
|
+
5. **Access Admin Dashboard**
|
|
96
|
+
- Navigate to `admin/index.html`
|
|
97
|
+
- Use the admin interface to manage charities and donations
|
|
98
|
+
|
|
99
|
+
## Configuration
|
|
100
|
+
|
|
101
|
+
### Supabase Setup
|
|
102
|
+
|
|
103
|
+
1. Create a new Supabase project
|
|
104
|
+
2. Run the provided SQL migrations
|
|
105
|
+
3. Enable Row Level Security (RLS)
|
|
106
|
+
4. Configure authentication settings if needed
|
|
107
|
+
5. Get your project URL and anon key
|
|
108
|
+
6. Update the JavaScript files with your credentials
|
|
109
|
+
|
|
110
|
+
### Environment Variables
|
|
111
|
+
|
|
112
|
+
For production deployment, consider using environment variables:
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
const SUPABASE_URL = process.env.SUPABASE_URL || "your-url"
|
|
116
|
+
const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY || "your-key"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Features in Detail
|
|
120
|
+
|
|
121
|
+
### Charity Management
|
|
122
|
+
|
|
123
|
+
- Add/edit/delete charities
|
|
124
|
+
- Upload images and set goals
|
|
125
|
+
- Track fundraising progress
|
|
126
|
+
- Categorize by cause type
|
|
127
|
+
- Activate/deactivate charities
|
|
128
|
+
|
|
129
|
+
### Donation System
|
|
130
|
+
|
|
131
|
+
- Secure donation processing
|
|
132
|
+
- Multiple amount options
|
|
133
|
+
- Anonymous donation support
|
|
134
|
+
- Donor messages and recognition
|
|
135
|
+
- Real-time progress updates
|
|
136
|
+
|
|
137
|
+
### Search & Discovery
|
|
138
|
+
|
|
139
|
+
- Full-text search across charities
|
|
140
|
+
- Filter by category and progress
|
|
141
|
+
- Sort by various criteria
|
|
142
|
+
- Responsive grid layout
|
|
143
|
+
- Load more functionality
|
|
144
|
+
|
|
145
|
+
### Admin Dashboard
|
|
146
|
+
|
|
147
|
+
- Real-time statistics
|
|
148
|
+
- Interactive charts
|
|
149
|
+
- Recent activity feed
|
|
150
|
+
- Data management tools
|
|
151
|
+
- Export functionality
|
|
152
|
+
|
|
153
|
+
## Security Features
|
|
154
|
+
|
|
155
|
+
- Row Level Security (RLS) on all tables
|
|
156
|
+
- Input validation and sanitization
|
|
157
|
+
- XSS protection
|
|
158
|
+
- Secure API endpoints
|
|
159
|
+
- Anonymous donation options
|
|
160
|
+
- Data encryption in transit
|
|
161
|
+
|
|
162
|
+
## Performance Optimizations
|
|
163
|
+
|
|
164
|
+
- Lazy loading for images
|
|
165
|
+
- Debounced search functionality
|
|
166
|
+
- Efficient database queries
|
|
167
|
+
- Minimal external dependencies
|
|
168
|
+
- Optimized CSS and JavaScript
|
|
169
|
+
- Responsive image handling
|
|
170
|
+
|
|
171
|
+
## Browser Support
|
|
172
|
+
|
|
173
|
+
- Chrome 60+
|
|
174
|
+
- Firefox 55+
|
|
175
|
+
- Safari 12+
|
|
176
|
+
- Edge 79+
|
|
177
|
+
- Mobile browsers (iOS Safari, Chrome Mobile)
|
|
178
|
+
|
|
179
|
+
## Customization
|
|
180
|
+
|
|
181
|
+
### Adding New Features
|
|
182
|
+
|
|
183
|
+
1. Update the database schema if needed
|
|
184
|
+
2. Modify the frontend components
|
|
185
|
+
3. Update the admin dashboard
|
|
186
|
+
4. Test thoroughly
|
|
187
|
+
|
|
188
|
+
### Styling
|
|
189
|
+
|
|
190
|
+
- All styles are in `src/css/`
|
|
191
|
+
- Uses CSS custom properties for theming
|
|
192
|
+
- Responsive design with mobile-first approach
|
|
193
|
+
- Easy to customize colors and layouts
|
|
194
|
+
|
|
195
|
+
### Database Extensions
|
|
196
|
+
|
|
197
|
+
- Add new tables as needed
|
|
198
|
+
- Update RLS policies
|
|
199
|
+
- Modify the JavaScript API calls
|
|
200
|
+
- Update admin interface
|
|
201
|
+
|
|
202
|
+
## Deployment Options
|
|
203
|
+
|
|
204
|
+
### Static Hosting
|
|
205
|
+
|
|
206
|
+
- Netlify
|
|
207
|
+
- Vercel
|
|
208
|
+
- GitHub Pages
|
|
209
|
+
- AWS S3 + CloudFront
|
|
210
|
+
|
|
211
|
+
### Server Options
|
|
212
|
+
|
|
213
|
+
- Any static web server
|
|
214
|
+
- CDN for better performance
|
|
215
|
+
- Domain and SSL configuration
|
|
216
|
+
|
|
217
|
+
## Contributing
|
|
218
|
+
|
|
219
|
+
1. Fork the project
|
|
220
|
+
2. Create a feature branch
|
|
221
|
+
3. Make your changes
|
|
222
|
+
4. Test thoroughly
|
|
223
|
+
5. Submit a pull request
|
|
224
|
+
|
|
225
|
+
## Support
|
|
226
|
+
|
|
227
|
+
For questions or support:
|
|
228
|
+
|
|
229
|
+
- Check the documentation
|
|
230
|
+
- Review the code comments
|
|
231
|
+
- Test with the provided sample data
|
|
232
|
+
- Ensure Supabase is properly configured
|
|
233
|
+
|
|
234
|
+
## License
|
|
235
|
+
|
|
236
|
+
This project is open source and available under the MIT License.
|
|
237
|
+
|
|
238
|
+
## Future Enhancements
|
|
239
|
+
|
|
240
|
+
- [ ] Payment gateway integration (Stripe, PayPal)
|
|
241
|
+
- [ ] Email notifications for donors
|
|
242
|
+
- [ ] Social sharing features
|
|
243
|
+
- [ ] Advanced analytics and reporting
|
|
244
|
+
- [ ] Multi-language support
|
|
245
|
+
- [ ] Recurring donations
|
|
246
|
+
- [ ] Charity verification system
|
|
247
|
+
- [ ] Mobile app development
|
|
248
|
+
- [ ] API for third-party integrations
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
**Built with ā¤ļø for making the world a better place**
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "charity-hub",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A modern charity donation platform built with HTML, CSS, JavaScript, and Supabase",
|
|
5
|
+
"main": "public/index.html",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "live-server public --port=3000",
|
|
8
|
+
"dev": "live-server public --port=3000 --watch=.",
|
|
9
|
+
"admin": "live-server admin --port=3001",
|
|
10
|
+
"build": "echo 'Build complete - static files ready for deployment'",
|
|
11
|
+
"deploy": "echo 'Deploy to your preferred static hosting service'"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"charity",
|
|
15
|
+
"donations",
|
|
16
|
+
"nonprofit",
|
|
17
|
+
"fundraising",
|
|
18
|
+
"supabase",
|
|
19
|
+
"javascript",
|
|
20
|
+
"html",
|
|
21
|
+
"css"
|
|
22
|
+
],
|
|
23
|
+
"author": "CharityHub Team",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/your-username/charity-hub.git"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/your-username/charity-hub/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/your-username/charity-hub#readme",
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"live-server": "^1.2.2"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=14.0.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,743 @@
|
|
|
1
|
+
// Admin Dashboard JavaScript
|
|
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 donations = []
|
|
12
|
+
let categories = []
|
|
13
|
+
let currentEditCharity = null
|
|
14
|
+
let currentEditCategory = null
|
|
15
|
+
let donationsChart = null
|
|
16
|
+
|
|
17
|
+
// DOM elements
|
|
18
|
+
const navLinks = document.querySelectorAll(".nav-link")
|
|
19
|
+
const contentSections = document.querySelectorAll(".content-section")
|
|
20
|
+
const pageTitle = document.getElementById("pageTitle")
|
|
21
|
+
|
|
22
|
+
// Modal elements
|
|
23
|
+
const charityModal = document.getElementById("charityModal")
|
|
24
|
+
const charityForm = document.getElementById("charityForm")
|
|
25
|
+
const categoryModal = document.getElementById("categoryModal")
|
|
26
|
+
const categoryForm = document.getElementById("categoryForm")
|
|
27
|
+
|
|
28
|
+
// Initialize admin dashboard
|
|
29
|
+
document.addEventListener("DOMContentLoaded", async () => {
|
|
30
|
+
await initializeAdmin()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
async function initializeAdmin() {
|
|
34
|
+
try {
|
|
35
|
+
setupEventListeners()
|
|
36
|
+
await loadDashboardData()
|
|
37
|
+
showSection("dashboard")
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error("Error initializing admin:", error)
|
|
40
|
+
showToast("Error loading admin dashboard", "error")
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Event Listeners
|
|
45
|
+
function setupEventListeners() {
|
|
46
|
+
// Navigation
|
|
47
|
+
navLinks.forEach((link) => {
|
|
48
|
+
link.addEventListener("click", (e) => {
|
|
49
|
+
e.preventDefault()
|
|
50
|
+
const section = link.dataset.section
|
|
51
|
+
showSection(section)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// Forms
|
|
56
|
+
charityForm.addEventListener("submit", handleCharitySubmit)
|
|
57
|
+
categoryForm.addEventListener("submit", handleCategorySubmit)
|
|
58
|
+
|
|
59
|
+
// Filters
|
|
60
|
+
document.getElementById("donationStatusFilter")?.addEventListener("change", loadDonations)
|
|
61
|
+
document.getElementById("donationDateFilter")?.addEventListener("change", loadDonations)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Navigation
|
|
65
|
+
function showSection(sectionName) {
|
|
66
|
+
// Update nav links
|
|
67
|
+
navLinks.forEach((link) => {
|
|
68
|
+
link.classList.remove("active")
|
|
69
|
+
if (link.dataset.section === sectionName) {
|
|
70
|
+
link.classList.add("active")
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// Update content sections
|
|
75
|
+
contentSections.forEach((section) => {
|
|
76
|
+
section.classList.remove("active")
|
|
77
|
+
})
|
|
78
|
+
document.getElementById(sectionName).classList.add("active")
|
|
79
|
+
|
|
80
|
+
// Update page title
|
|
81
|
+
const titles = {
|
|
82
|
+
dashboard: "Dashboard",
|
|
83
|
+
charities: "Charities",
|
|
84
|
+
donations: "Donations",
|
|
85
|
+
categories: "Categories",
|
|
86
|
+
settings: "Settings",
|
|
87
|
+
}
|
|
88
|
+
pageTitle.textContent = titles[sectionName] || "Admin"
|
|
89
|
+
|
|
90
|
+
// Load section data
|
|
91
|
+
loadSectionData(sectionName)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Load section data
|
|
95
|
+
async function loadSectionData(section) {
|
|
96
|
+
try {
|
|
97
|
+
switch (section) {
|
|
98
|
+
case "dashboard":
|
|
99
|
+
await loadDashboardData()
|
|
100
|
+
break
|
|
101
|
+
case "charities":
|
|
102
|
+
await loadCharities()
|
|
103
|
+
break
|
|
104
|
+
case "donations":
|
|
105
|
+
await loadDonations()
|
|
106
|
+
break
|
|
107
|
+
case "categories":
|
|
108
|
+
await loadCategories()
|
|
109
|
+
break
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(`Error loading ${section} data:`, error)
|
|
113
|
+
showToast(`Error loading ${section} data`, "error")
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Load dashboard data
|
|
118
|
+
async function loadDashboardData() {
|
|
119
|
+
try {
|
|
120
|
+
// Load all data
|
|
121
|
+
const [charitiesData, donationsData, categoriesData] = await Promise.all([
|
|
122
|
+
loadCharitiesData(),
|
|
123
|
+
loadDonationsData(),
|
|
124
|
+
loadCategoriesData(),
|
|
125
|
+
])
|
|
126
|
+
|
|
127
|
+
// Update stats
|
|
128
|
+
updateDashboardStats(charitiesData, donationsData)
|
|
129
|
+
|
|
130
|
+
// Update charts
|
|
131
|
+
updateDonationsChart(donationsData)
|
|
132
|
+
updateTopCharities(charitiesData)
|
|
133
|
+
updateRecentActivity(donationsData, charitiesData)
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error("Error loading dashboard data:", error)
|
|
136
|
+
throw error
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Update dashboard stats
|
|
141
|
+
function updateDashboardStats(charitiesData, donationsData) {
|
|
142
|
+
const totalCharities = charitiesData.length
|
|
143
|
+
const totalDonations = donationsData
|
|
144
|
+
.filter((d) => d.payment_status === "completed")
|
|
145
|
+
.reduce((sum, d) => sum + parseFloat(d.amount), 0)
|
|
146
|
+
const totalDonors = new Set(donationsData.filter((d) => d.payment_status === "completed").map((d) => d.donor_email))
|
|
147
|
+
.size
|
|
148
|
+
const avgDonation = totalDonors > 0 ? totalDonations / totalDonors : 0
|
|
149
|
+
|
|
150
|
+
document.getElementById("totalCharitiesStat").textContent = totalCharities
|
|
151
|
+
document.getElementById("totalDonationsStat").textContent = `$${totalDonations.toLocaleString()}`
|
|
152
|
+
document.getElementById("totalDonorsStat").textContent = totalDonors
|
|
153
|
+
document.getElementById("avgDonationStat").textContent = `$${avgDonation.toFixed(2)}`
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Update donations chart
|
|
157
|
+
function updateDonationsChart(donationsData) {
|
|
158
|
+
const ctx = document.getElementById("donationsChart")
|
|
159
|
+
if (!ctx) return
|
|
160
|
+
|
|
161
|
+
// Group donations by date (last 7 days)
|
|
162
|
+
const last7Days = []
|
|
163
|
+
const donationAmounts = []
|
|
164
|
+
|
|
165
|
+
for (let i = 6; i >= 0; i--) {
|
|
166
|
+
const date = new Date()
|
|
167
|
+
date.setDate(date.getDate() - i)
|
|
168
|
+
const dateStr = date.toISOString().split("T")[0]
|
|
169
|
+
last7Days.push(date.toLocaleDateString("en-US", { weekday: "short" }))
|
|
170
|
+
|
|
171
|
+
const dayTotal = donationsData
|
|
172
|
+
.filter((d) => d.payment_status === "completed" && d.created_at.startsWith(dateStr))
|
|
173
|
+
.reduce((sum, d) => sum + parseFloat(d.amount), 0)
|
|
174
|
+
donationAmounts.push(dayTotal)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Destroy existing chart
|
|
178
|
+
if (donationsChart) {
|
|
179
|
+
donationsChart.destroy()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Create new chart
|
|
183
|
+
donationsChart = new Chart(ctx, {
|
|
184
|
+
type: "line",
|
|
185
|
+
data: {
|
|
186
|
+
labels: last7Days,
|
|
187
|
+
datasets: [
|
|
188
|
+
{
|
|
189
|
+
label: "Daily Donations",
|
|
190
|
+
data: donationAmounts,
|
|
191
|
+
borderColor: "#e74c3c",
|
|
192
|
+
backgroundColor: "rgba(231, 76, 60, 0.1)",
|
|
193
|
+
tension: 0.4,
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
},
|
|
197
|
+
options: {
|
|
198
|
+
responsive: true,
|
|
199
|
+
maintainAspectRatio: false,
|
|
200
|
+
plugins: {
|
|
201
|
+
legend: {
|
|
202
|
+
display: false,
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
scales: {
|
|
206
|
+
y: {
|
|
207
|
+
beginAtZero: true,
|
|
208
|
+
ticks: {
|
|
209
|
+
callback: function (value) {
|
|
210
|
+
return "$" + value.toLocaleString()
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Update top charities
|
|
220
|
+
function updateTopCharities(charitiesData) {
|
|
221
|
+
const topCharities = charitiesData
|
|
222
|
+
.sort((a, b) => parseFloat(b.current_amount) - parseFloat(a.current_amount))
|
|
223
|
+
.slice(0, 5)
|
|
224
|
+
|
|
225
|
+
const container = document.getElementById("topCharities")
|
|
226
|
+
container.innerHTML = topCharities
|
|
227
|
+
.map(
|
|
228
|
+
(charity) => `
|
|
229
|
+
<div class="top-charity">
|
|
230
|
+
<span class="top-charity-name">${charity.name}</span>
|
|
231
|
+
<span class="top-charity-amount">$${parseFloat(charity.current_amount).toLocaleString()}</span>
|
|
232
|
+
</div>
|
|
233
|
+
`,
|
|
234
|
+
)
|
|
235
|
+
.join("")
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Update recent activity
|
|
239
|
+
function updateRecentActivity(donationsData, charitiesData) {
|
|
240
|
+
const recentDonations = donationsData.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)).slice(0, 10)
|
|
241
|
+
|
|
242
|
+
const container = document.getElementById("recentActivity")
|
|
243
|
+
container.innerHTML = recentDonations
|
|
244
|
+
.map((donation) => {
|
|
245
|
+
const charity = charitiesData.find((c) => c.id === donation.charity_id)
|
|
246
|
+
const timeAgo = getTimeAgo(new Date(donation.created_at))
|
|
247
|
+
|
|
248
|
+
return `
|
|
249
|
+
<div class="activity-item">
|
|
250
|
+
<div class="activity-icon activity-donation">
|
|
251
|
+
<i class="fas fa-hand-holding-usd"></i>
|
|
252
|
+
</div>
|
|
253
|
+
<div class="activity-details">
|
|
254
|
+
<div class="activity-title">
|
|
255
|
+
${donation.is_anonymous ? "Anonymous" : donation.donor_name} donated $${donation.amount} to ${charity?.name || "Unknown"}
|
|
256
|
+
</div>
|
|
257
|
+
<div class="activity-time">${timeAgo}</div>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
`
|
|
261
|
+
})
|
|
262
|
+
.join("")
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Load charities
|
|
266
|
+
async function loadCharities() {
|
|
267
|
+
try {
|
|
268
|
+
const data = await loadCharitiesData()
|
|
269
|
+
renderCharitiesTable(data)
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error("Error loading charities:", error)
|
|
272
|
+
throw error
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Load charities data
|
|
277
|
+
async function loadCharitiesData() {
|
|
278
|
+
const { data, error } = await supabase.from("charities").select("*").order("created_at", { ascending: false })
|
|
279
|
+
|
|
280
|
+
if (error) throw error
|
|
281
|
+
charities = data
|
|
282
|
+
return data
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Render charities table
|
|
286
|
+
function renderCharitiesTable(charitiesData) {
|
|
287
|
+
const tbody = document.querySelector("#charitiesTable tbody")
|
|
288
|
+
tbody.innerHTML = charitiesData
|
|
289
|
+
.map((charity) => {
|
|
290
|
+
const progress = (parseFloat(charity.current_amount) / parseFloat(charity.goal_amount)) * 100
|
|
291
|
+
|
|
292
|
+
return `
|
|
293
|
+
<tr>
|
|
294
|
+
<td>${charity.name}</td>
|
|
295
|
+
<td>${charity.category}</td>
|
|
296
|
+
<td>$${parseFloat(charity.goal_amount).toLocaleString()}</td>
|
|
297
|
+
<td>$${parseFloat(charity.current_amount).toLocaleString()}</td>
|
|
298
|
+
<td>
|
|
299
|
+
<div class="progress-bar">
|
|
300
|
+
<div class="progress-fill" style="width: ${Math.min(progress, 100)}%"></div>
|
|
301
|
+
</div>
|
|
302
|
+
${progress.toFixed(1)}%
|
|
303
|
+
</td>
|
|
304
|
+
<td>
|
|
305
|
+
<span class="status-badge ${charity.is_active ? "status-active" : "status-inactive"}">
|
|
306
|
+
${charity.is_active ? "Active" : "Inactive"}
|
|
307
|
+
</span>
|
|
308
|
+
</td>
|
|
309
|
+
<td>
|
|
310
|
+
<button class="btn btn-sm btn-secondary" onclick="editCharity('${charity.id}')">
|
|
311
|
+
<i class="fas fa-edit"></i>
|
|
312
|
+
</button>
|
|
313
|
+
<button class="btn btn-sm btn-danger" onclick="deleteCharity('${charity.id}')">
|
|
314
|
+
<i class="fas fa-trash"></i>
|
|
315
|
+
</button>
|
|
316
|
+
</td>
|
|
317
|
+
</tr>
|
|
318
|
+
`
|
|
319
|
+
})
|
|
320
|
+
.join("")
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Load donations
|
|
324
|
+
async function loadDonations() {
|
|
325
|
+
try {
|
|
326
|
+
const data = await loadDonationsData()
|
|
327
|
+
renderDonationsTable(data)
|
|
328
|
+
} catch (error) {
|
|
329
|
+
console.error("Error loading donations:", error)
|
|
330
|
+
throw error
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Load donations data
|
|
335
|
+
async function loadDonationsData() {
|
|
336
|
+
let query = supabase.from("donations").select("*, charities(name)").order("created_at", { ascending: false })
|
|
337
|
+
|
|
338
|
+
// Apply filters
|
|
339
|
+
const statusFilter = document.getElementById("donationStatusFilter")?.value
|
|
340
|
+
if (statusFilter) {
|
|
341
|
+
query = query.eq("payment_status", statusFilter)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const dateFilter = document.getElementById("donationDateFilter")?.value
|
|
345
|
+
if (dateFilter) {
|
|
346
|
+
query = query.gte("created_at", dateFilter)
|
|
347
|
+
query = query.lt("created_at", new Date(dateFilter).getTime() + 24 * 60 * 60 * 1000)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const { data, error } = await query
|
|
351
|
+
if (error) throw error
|
|
352
|
+
donations = data
|
|
353
|
+
return data
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Render donations table
|
|
357
|
+
function renderDonationsTable(donationsData) {
|
|
358
|
+
const tbody = document.querySelector("#donationsTable tbody")
|
|
359
|
+
tbody.innerHTML = donationsData
|
|
360
|
+
.map(
|
|
361
|
+
(donation) => `
|
|
362
|
+
<tr>
|
|
363
|
+
<td>${donation.id.substring(0, 8)}...</td>
|
|
364
|
+
<td>${donation.charities?.name || "Unknown"}</td>
|
|
365
|
+
<td>${donation.is_anonymous ? "Anonymous" : donation.donor_name}</td>
|
|
366
|
+
<td>$${parseFloat(donation.amount).toLocaleString()}</td>
|
|
367
|
+
<td>
|
|
368
|
+
<span class="status-badge status-${donation.payment_status}">
|
|
369
|
+
${donation.payment_status}
|
|
370
|
+
</span>
|
|
371
|
+
</td>
|
|
372
|
+
<td>${new Date(donation.created_at).toLocaleDateString()}</td>
|
|
373
|
+
<td>
|
|
374
|
+
<button class="btn btn-sm btn-secondary" onclick="viewDonation('${donation.id}')">
|
|
375
|
+
<i class="fas fa-eye"></i>
|
|
376
|
+
</button>
|
|
377
|
+
<button class="btn btn-sm btn-danger" onclick="deleteDonation('${donation.id}')">
|
|
378
|
+
<i class="fas fa-trash"></i>
|
|
379
|
+
</button>
|
|
380
|
+
</td>
|
|
381
|
+
</tr>
|
|
382
|
+
`,
|
|
383
|
+
)
|
|
384
|
+
.join("")
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Load categories
|
|
388
|
+
async function loadCategories() {
|
|
389
|
+
try {
|
|
390
|
+
const data = await loadCategoriesData()
|
|
391
|
+
renderCategoriesGrid(data)
|
|
392
|
+
populateCategorySelect(data)
|
|
393
|
+
} catch (error) {
|
|
394
|
+
console.error("Error loading categories:", error)
|
|
395
|
+
throw error
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Load categories data
|
|
400
|
+
async function loadCategoriesData() {
|
|
401
|
+
const { data, error } = await supabase.from("categories").select("*").order("name")
|
|
402
|
+
|
|
403
|
+
if (error) throw error
|
|
404
|
+
categories = data
|
|
405
|
+
return data
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Render categories grid
|
|
409
|
+
function renderCategoriesGrid(categoriesData) {
|
|
410
|
+
const container = document.getElementById("categoriesGrid")
|
|
411
|
+
container.innerHTML = categoriesData
|
|
412
|
+
.map(
|
|
413
|
+
(category) => `
|
|
414
|
+
<div class="category-card">
|
|
415
|
+
<div class="category-header">
|
|
416
|
+
<h3 class="category-name">${category.name}</h3>
|
|
417
|
+
<div>
|
|
418
|
+
<button class="btn btn-sm btn-secondary" onclick="editCategory('${category.id}')">
|
|
419
|
+
<i class="fas fa-edit"></i>
|
|
420
|
+
</button>
|
|
421
|
+
<button class="btn btn-sm btn-danger" onclick="deleteCategory('${category.id}')">
|
|
422
|
+
<i class="fas fa-trash"></i>
|
|
423
|
+
</button>
|
|
424
|
+
</div>
|
|
425
|
+
</div>
|
|
426
|
+
<p class="category-description">${category.description || "No description"}</p>
|
|
427
|
+
<div class="category-stats">
|
|
428
|
+
<span>Created: ${new Date(category.created_at).toLocaleDateString()}</span>
|
|
429
|
+
</div>
|
|
430
|
+
</div>
|
|
431
|
+
`,
|
|
432
|
+
)
|
|
433
|
+
.join("")
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Populate category select
|
|
437
|
+
function populateCategorySelect(categoriesData) {
|
|
438
|
+
const select = document.getElementById("charityCategory")
|
|
439
|
+
if (!select) return
|
|
440
|
+
|
|
441
|
+
select.innerHTML =
|
|
442
|
+
'<option value="">Select Category</option>' +
|
|
443
|
+
categoriesData
|
|
444
|
+
.map(
|
|
445
|
+
(category) => `
|
|
446
|
+
<option value="${category.name}">${category.name}</option>
|
|
447
|
+
`,
|
|
448
|
+
)
|
|
449
|
+
.join("")
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Charity Modal Functions
|
|
453
|
+
function openCharityModal(charity = null) {
|
|
454
|
+
currentEditCharity = charity
|
|
455
|
+
const modalTitle = document.getElementById("charityModalTitle")
|
|
456
|
+
const form = charityForm
|
|
457
|
+
|
|
458
|
+
if (charity) {
|
|
459
|
+
modalTitle.textContent = "Edit Charity"
|
|
460
|
+
form.charityName.value = charity.name
|
|
461
|
+
form.charityDescription.value = charity.description
|
|
462
|
+
form.charityCategory.value = charity.category
|
|
463
|
+
form.charityGoal.value = charity.goal_amount
|
|
464
|
+
form.charityImage.value = charity.image_url || ""
|
|
465
|
+
form.charityWebsite.value = charity.website_url || ""
|
|
466
|
+
form.charityActive.checked = charity.is_active
|
|
467
|
+
} else {
|
|
468
|
+
modalTitle.textContent = "Add Charity"
|
|
469
|
+
form.reset()
|
|
470
|
+
form.charityActive.checked = true
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
charityModal.classList.add("active")
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function closeCharityModal() {
|
|
477
|
+
charityModal.classList.remove("active")
|
|
478
|
+
charityForm.reset()
|
|
479
|
+
currentEditCharity = null
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function handleCharitySubmit(e) {
|
|
483
|
+
e.preventDefault()
|
|
484
|
+
|
|
485
|
+
const formData = new FormData(charityForm)
|
|
486
|
+
const charityData = {
|
|
487
|
+
name: formData.get("charityName") || document.getElementById("charityName").value,
|
|
488
|
+
description: formData.get("charityDescription") || document.getElementById("charityDescription").value,
|
|
489
|
+
category: formData.get("charityCategory") || document.getElementById("charityCategory").value,
|
|
490
|
+
goal_amount: parseFloat(formData.get("charityGoal") || document.getElementById("charityGoal").value),
|
|
491
|
+
image_url: formData.get("charityImage") || document.getElementById("charityImage").value,
|
|
492
|
+
website_url: formData.get("charityWebsite") || document.getElementById("charityWebsite").value,
|
|
493
|
+
is_active: (formData.get("charityActive") || document.getElementById("charityActive")).checked,
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
if (currentEditCharity) {
|
|
498
|
+
// Update existing charity
|
|
499
|
+
const { error } = await supabase.from("charities").update(charityData).eq("id", currentEditCharity.id)
|
|
500
|
+
|
|
501
|
+
if (error) throw error
|
|
502
|
+
showToast("Charity updated successfully", "success")
|
|
503
|
+
} else {
|
|
504
|
+
// Insert new charity
|
|
505
|
+
const { error } = await supabase.from("charities").insert([charityData])
|
|
506
|
+
|
|
507
|
+
if (error) throw error
|
|
508
|
+
showToast("Charity added successfully", "success")
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
closeCharityModal()
|
|
512
|
+
await loadCharities()
|
|
513
|
+
await loadDashboardData()
|
|
514
|
+
} catch (error) {
|
|
515
|
+
console.error("Error saving charity:", error)
|
|
516
|
+
showToast("Error saving charity", "error")
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Category Modal Functions
|
|
521
|
+
function openCategoryModal(category = null) {
|
|
522
|
+
currentEditCategory = category
|
|
523
|
+
const modalTitle = document.getElementById("categoryModalTitle")
|
|
524
|
+
const form = categoryForm
|
|
525
|
+
|
|
526
|
+
if (category) {
|
|
527
|
+
modalTitle.textContent = "Edit Category"
|
|
528
|
+
form.categoryName.value = category.name
|
|
529
|
+
form.categoryDescription.value = category.description || ""
|
|
530
|
+
} else {
|
|
531
|
+
modalTitle.textContent = "Add Category"
|
|
532
|
+
form.reset()
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
categoryModal.classList.add("active")
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function closeCategoryModal() {
|
|
539
|
+
categoryModal.classList.remove("active")
|
|
540
|
+
categoryForm.reset()
|
|
541
|
+
currentEditCategory = null
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async function handleCategorySubmit(e) {
|
|
545
|
+
e.preventDefault()
|
|
546
|
+
|
|
547
|
+
const formData = new FormData(categoryForm)
|
|
548
|
+
const categoryData = {
|
|
549
|
+
name: formData.get("categoryName") || document.getElementById("categoryName").value,
|
|
550
|
+
description: formData.get("categoryDescription") || document.getElementById("categoryDescription").value,
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
try {
|
|
554
|
+
if (currentEditCategory) {
|
|
555
|
+
// Update existing category
|
|
556
|
+
const { error } = await supabase.from("categories").update(categoryData).eq("id", currentEditCategory.id)
|
|
557
|
+
|
|
558
|
+
if (error) throw error
|
|
559
|
+
showToast("Category updated successfully", "success")
|
|
560
|
+
} else {
|
|
561
|
+
// Insert new category
|
|
562
|
+
const { error } = await supabase.from("categories").insert([categoryData])
|
|
563
|
+
|
|
564
|
+
if (error) throw error
|
|
565
|
+
showToast("Category added successfully", "success")
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
closeCategoryModal()
|
|
569
|
+
await loadCategories()
|
|
570
|
+
await loadDashboardData()
|
|
571
|
+
} catch (error) {
|
|
572
|
+
console.error("Error saving category:", error)
|
|
573
|
+
showToast("Error saving category", "error")
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Edit functions
|
|
578
|
+
async function editCharity(charityId) {
|
|
579
|
+
try {
|
|
580
|
+
const { data, error } = await supabase.from("charities").select("*").eq("id", charityId).single()
|
|
581
|
+
|
|
582
|
+
if (error) throw error
|
|
583
|
+
openCharityModal(data)
|
|
584
|
+
} catch (error) {
|
|
585
|
+
console.error("Error loading charity:", error)
|
|
586
|
+
showToast("Error loading charity", "error")
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
async function editCategory(categoryId) {
|
|
591
|
+
try {
|
|
592
|
+
const { data, error } = await supabase.from("categories").select("*").eq("id", categoryId).single()
|
|
593
|
+
|
|
594
|
+
if (error) throw error
|
|
595
|
+
openCategoryModal(data)
|
|
596
|
+
} catch (error) {
|
|
597
|
+
console.error("Error loading category:", error)
|
|
598
|
+
showToast("Error loading category", "error")
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Delete functions
|
|
603
|
+
async function deleteCharity(charityId) {
|
|
604
|
+
if (!confirm("Are you sure you want to delete this charity?")) return
|
|
605
|
+
|
|
606
|
+
try {
|
|
607
|
+
const { error } = await supabase.from("charities").delete().eq("id", charityId)
|
|
608
|
+
|
|
609
|
+
if (error) throw error
|
|
610
|
+
showToast("Charity deleted successfully", "success")
|
|
611
|
+
await loadCharities()
|
|
612
|
+
await loadDashboardData()
|
|
613
|
+
} catch (error) {
|
|
614
|
+
console.error("Error deleting charity:", error)
|
|
615
|
+
showToast("Error deleting charity", "error")
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
async function deleteCategory(categoryId) {
|
|
620
|
+
if (!confirm("Are you sure you want to delete this category?")) return
|
|
621
|
+
|
|
622
|
+
try {
|
|
623
|
+
const { error } = await supabase.from("categories").delete().eq("id", categoryId)
|
|
624
|
+
|
|
625
|
+
if (error) throw error
|
|
626
|
+
showToast("Category deleted successfully", "success")
|
|
627
|
+
await loadCategories()
|
|
628
|
+
await loadDashboardData()
|
|
629
|
+
} catch (error) {
|
|
630
|
+
console.error("Error deleting category:", error)
|
|
631
|
+
showToast("Error deleting category", "error")
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
async function deleteDonation(donationId) {
|
|
636
|
+
if (!confirm("Are you sure you want to delete this donation?")) return
|
|
637
|
+
|
|
638
|
+
try {
|
|
639
|
+
const { error } = await supabase.from("donations").delete().eq("id", donationId)
|
|
640
|
+
|
|
641
|
+
if (error) throw error
|
|
642
|
+
showToast("Donation deleted successfully", "success")
|
|
643
|
+
await loadDonations()
|
|
644
|
+
await loadDashboardData()
|
|
645
|
+
} catch (error) {
|
|
646
|
+
console.error("Error deleting donation:", error)
|
|
647
|
+
showToast("Error deleting donation", "error")
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// View donation
|
|
652
|
+
async function viewDonation(donationId) {
|
|
653
|
+
try {
|
|
654
|
+
const { data, error } = await supabase.from("donations").select("*, charities(*)").eq("id", donationId).single()
|
|
655
|
+
|
|
656
|
+
if (error) throw error
|
|
657
|
+
|
|
658
|
+
alert(
|
|
659
|
+
`Donation Details:\n\n` +
|
|
660
|
+
`ID: ${data.id}\n` +
|
|
661
|
+
`Charity: ${data.charities?.name}\n` +
|
|
662
|
+
`Donor: ${data.is_anonymous ? "Anonymous" : data.donor_name}\n` +
|
|
663
|
+
`Email: ${data.donor_email}\n` +
|
|
664
|
+
`Amount: $${data.amount}\n` +
|
|
665
|
+
`Message: ${data.message || "None"}\n` +
|
|
666
|
+
`Status: ${data.payment_status}\n` +
|
|
667
|
+
`Date: ${new Date(data.created_at).toLocaleString()}`,
|
|
668
|
+
)
|
|
669
|
+
} catch (error) {
|
|
670
|
+
console.error("Error loading donation:", error)
|
|
671
|
+
showToast("Error loading donation", "error")
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Utility functions
|
|
676
|
+
function getTimeAgo(date) {
|
|
677
|
+
const seconds = Math.floor((new Date() - date) / 1000)
|
|
678
|
+
|
|
679
|
+
let interval = seconds / 31536000
|
|
680
|
+
if (interval > 1) return Math.floor(interval) + " years ago"
|
|
681
|
+
|
|
682
|
+
interval = seconds / 2592000
|
|
683
|
+
if (interval > 1) return Math.floor(interval) + " months ago"
|
|
684
|
+
|
|
685
|
+
interval = seconds / 86400
|
|
686
|
+
if (interval > 1) return Math.floor(interval) + " days ago"
|
|
687
|
+
|
|
688
|
+
interval = seconds / 3600
|
|
689
|
+
if (interval > 1) return Math.floor(interval) + " hours ago"
|
|
690
|
+
|
|
691
|
+
interval = seconds / 60
|
|
692
|
+
if (interval > 1) return Math.floor(interval) + " minutes ago"
|
|
693
|
+
|
|
694
|
+
return "Just now"
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function showToast(message, type = "success") {
|
|
698
|
+
const toast = document.createElement("div")
|
|
699
|
+
toast.className = `toast ${type}`
|
|
700
|
+
toast.textContent = message
|
|
701
|
+
|
|
702
|
+
document.body.appendChild(toast)
|
|
703
|
+
|
|
704
|
+
setTimeout(() => {
|
|
705
|
+
toast.classList.add("show")
|
|
706
|
+
}, 100)
|
|
707
|
+
|
|
708
|
+
setTimeout(() => {
|
|
709
|
+
toast.classList.remove("show")
|
|
710
|
+
setTimeout(() => {
|
|
711
|
+
document.body.removeChild(toast)
|
|
712
|
+
}, 300)
|
|
713
|
+
}, 3000)
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Export functions
|
|
717
|
+
function exportData() {
|
|
718
|
+
// In a real implementation, this would export data to CSV/Excel
|
|
719
|
+
showToast("Export functionality coming soon", "warning")
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
async function refreshData() {
|
|
723
|
+
try {
|
|
724
|
+
await loadDashboardData()
|
|
725
|
+
showToast("Data refreshed successfully", "success")
|
|
726
|
+
} catch (error) {
|
|
727
|
+
showToast("Error refreshing data", "error")
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// Global functions
|
|
732
|
+
window.openCharityModal = openCharityModal
|
|
733
|
+
window.closeCharityModal = closeCharityModal
|
|
734
|
+
window.openCategoryModal = openCategoryModal
|
|
735
|
+
window.closeCategoryModal = closeCategoryModal
|
|
736
|
+
window.editCharity = editCharity
|
|
737
|
+
window.editCategory = editCategory
|
|
738
|
+
window.deleteCharity = deleteCharity
|
|
739
|
+
window.deleteCategory = deleteCategory
|
|
740
|
+
window.deleteDonation = deleteDonation
|
|
741
|
+
window.viewDonation = viewDonation
|
|
742
|
+
window.exportData = exportData
|
|
743
|
+
window.refreshData = refreshData
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.34",
|
|
4
4
|
"name": "easc-cli",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,107 +23,13 @@
|
|
|
23
23
|
"exports": {
|
|
24
24
|
"./*": "./src/*.ts"
|
|
25
25
|
},
|
|
26
|
-
"devDependencies": {
|
|
27
|
-
"@babel/core": "7.28.4",
|
|
28
|
-
"@octokit/webhooks-types": "7.6.1",
|
|
29
|
-
"@eliseart.ai/script": "workspace:*",
|
|
30
|
-
"@parcel/watcher-darwin-arm64": "2.5.1",
|
|
31
|
-
"@parcel/watcher-darwin-x64": "2.5.1",
|
|
32
|
-
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
|
33
|
-
"@parcel/watcher-linux-arm64-musl": "2.5.1",
|
|
34
|
-
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
|
35
|
-
"@parcel/watcher-linux-x64-musl": "2.5.1",
|
|
36
|
-
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
|
37
|
-
"@parcel/watcher-linux-x64-musl": "2.5.1",
|
|
38
|
-
"@parcel/watcher-win32-x64": "2.5.1",
|
|
39
|
-
"@standard-schema/spec": "1.0.0",
|
|
40
|
-
"@tsconfig/bun": "catalog:",
|
|
41
|
-
"@types/babel__core": "7.20.5",
|
|
42
|
-
"@types/bun": "catalog:",
|
|
43
|
-
"@types/turndown": "5.0.5",
|
|
44
|
-
"@types/yargs": "17.0.33",
|
|
45
|
-
"@typescript/native-preview": "catalog:",
|
|
46
|
-
"typescript": "catalog:",
|
|
47
|
-
"vscode-languageserver-types": "3.17.5",
|
|
48
|
-
"why-is-node-running": "3.2.2",
|
|
49
|
-
"zod-to-json-schema": "3.24.5",
|
|
50
|
-
"@actions/core": "1.11.1",
|
|
51
|
-
"@actions/github": "6.0.1",
|
|
52
|
-
"@agentclientprotocol/sdk": "0.5.1",
|
|
53
|
-
"@ai-sdk/amazon-bedrock": "3.0.73",
|
|
54
|
-
"@ai-sdk/anthropic": "2.0.57",
|
|
55
|
-
"@ai-sdk/azure": "2.0.91",
|
|
56
|
-
"@ai-sdk/cerebras": "1.0.34",
|
|
57
|
-
"@ai-sdk/cohere": "2.0.22",
|
|
58
|
-
"@ai-sdk/deepinfra": "1.0.31",
|
|
59
|
-
"@ai-sdk/gateway": "2.0.25",
|
|
60
|
-
"@ai-sdk/google": "2.0.52",
|
|
61
|
-
"@ai-sdk/google-vertex": "3.0.97",
|
|
62
|
-
"@ai-sdk/groq": "2.0.34",
|
|
63
|
-
"@ai-sdk/mistral": "2.0.27",
|
|
64
|
-
"@ai-sdk/openai": "2.0.89",
|
|
65
|
-
"@ai-sdk/openai-compatible": "1.0.30",
|
|
66
|
-
"@ai-sdk/perplexity": "2.0.23",
|
|
67
|
-
"@ai-sdk/provider": "2.0.1",
|
|
68
|
-
"@ai-sdk/provider-utils": "3.0.20",
|
|
69
|
-
"@ai-sdk/togetherai": "1.0.31",
|
|
70
|
-
"@ai-sdk/vercel": "1.0.31",
|
|
71
|
-
"@ai-sdk/xai": "2.0.51",
|
|
72
|
-
"@clack/prompts": "1.0.0-alpha.1",
|
|
73
|
-
"@eliseart.ai/plugin": "workspace:*",
|
|
74
|
-
"@eliseart.ai/sdk": "workspace:*",
|
|
75
|
-
"@eliseart.ai/util": "workspace:*",
|
|
76
|
-
"@gitlab/gitlab-ai-provider": "3.1.2",
|
|
77
|
-
"@hono/standard-validator": "0.1.5",
|
|
78
|
-
"@hono/zod-validator": "catalog:",
|
|
79
|
-
"@modelcontextprotocol/sdk": "1.25.2",
|
|
80
|
-
"@octokit/graphql": "9.0.2",
|
|
81
|
-
"@octokit/rest": "catalog:",
|
|
82
|
-
"@openauthjs/openauth": "catalog:",
|
|
83
|
-
"@openrouter/ai-sdk-provider": "1.5.2",
|
|
84
|
-
"@opentui/core": "0.1.74",
|
|
85
|
-
"@opentui/solid": "0.1.74",
|
|
86
|
-
"@parcel/watcher": "2.5.1",
|
|
87
|
-
"@pierre/diffs": "catalog:",
|
|
88
|
-
"@solid-primitives/event-bus": "1.1.2",
|
|
89
|
-
"@solid-primitives/scheduled": "1.5.2",
|
|
90
|
-
"@zip.js/zip.js": "2.7.62",
|
|
91
|
-
"ai": "catalog:",
|
|
92
|
-
"bonjour-service": "1.3.0",
|
|
93
|
-
"bun-pty": "0.4.4",
|
|
94
|
-
"chokidar": "4.0.3",
|
|
95
|
-
"clipboardy": "4.0.0",
|
|
96
|
-
"decimal.js": "10.5.0",
|
|
97
|
-
"diff": "catalog:",
|
|
98
|
-
"fuzzysort": "3.1.0",
|
|
99
|
-
"gray-matter": "4.0.3",
|
|
100
|
-
"hono": "catalog:",
|
|
101
|
-
"hono-openapi": "catalog:",
|
|
102
|
-
"ignore": "7.0.5",
|
|
103
|
-
"jsonc-parser": "3.3.1",
|
|
104
|
-
"minimatch": "10.0.3",
|
|
105
|
-
"open": "10.1.2",
|
|
106
|
-
"opentui-spinner": "0.0.6",
|
|
107
|
-
"partial-json": "0.1.7",
|
|
108
|
-
"remeda": "catalog:",
|
|
109
|
-
"solid-js": "catalog:",
|
|
110
|
-
"strip-ansi": "7.1.2",
|
|
111
|
-
"tree-sitter-bash": "0.25.0",
|
|
112
|
-
"turndown": "7.2.0",
|
|
113
|
-
"ulid": "catalog:",
|
|
114
|
-
"vscode-jsonrpc": "8.2.1",
|
|
115
|
-
"web-tree-sitter": "0.25.10",
|
|
116
|
-
"xdg-basedir": "5.1.0",
|
|
117
|
-
"yargs": "18.0.0",
|
|
118
|
-
"zod": "catalog:",
|
|
119
|
-
"zod-to-json-schema": "3.24.5"
|
|
120
|
-
},
|
|
26
|
+
"devDependencies": {},
|
|
121
27
|
"dependencies": {},
|
|
122
28
|
"optionalDependencies": {
|
|
123
|
-
"easc-linux-x64": "0.0.0-main-
|
|
124
|
-
"easc-linux-arm64": "0.0.0-main-
|
|
125
|
-
"easc-darwin-x64": "0.0.0-main-
|
|
126
|
-
"easc-darwin-arm64": "0.0.0-main-
|
|
127
|
-
"easc-windows-x64": "0.0.0-main-
|
|
29
|
+
"easc-linux-x64": "0.0.0-main-202601241225",
|
|
30
|
+
"easc-linux-arm64": "0.0.0-main-202601241225",
|
|
31
|
+
"easc-darwin-x64": "0.0.0-main-202601241225",
|
|
32
|
+
"easc-darwin-arm64": "0.0.0-main-202601241225",
|
|
33
|
+
"easc-windows-x64": "0.0.0-main-202601241225"
|
|
128
34
|
}
|
|
129
35
|
}
|
package/script/build.ts
CHANGED
|
@@ -108,7 +108,7 @@ if (!skipInstall) {
|
|
|
108
108
|
}
|
|
109
109
|
for (const item of targets) {
|
|
110
110
|
const name = [
|
|
111
|
-
|
|
111
|
+
"easc",
|
|
112
112
|
// changing to win32 flags npm for some reason
|
|
113
113
|
item.os === "win32" ? "windows" : item.os,
|
|
114
114
|
item.arch,
|
|
@@ -138,7 +138,7 @@ for (const item of targets) {
|
|
|
138
138
|
//@ts-ignore (bun types aren't up to date)
|
|
139
139
|
autoloadTsconfig: true,
|
|
140
140
|
autoloadPackageJson: true,
|
|
141
|
-
target: name.replace(
|
|
141
|
+
target: name.replace("easc", "bun") as any,
|
|
142
142
|
outfile: `dist/${name}/bin/easc`,
|
|
143
143
|
execArgv: [`--user-agent=easc/${Script.version}`, "--use-system-ca", "--"],
|
|
144
144
|
windows: {},
|
package/script/deploy.ts
CHANGED
|
@@ -49,8 +49,36 @@ for (const pkgName of packages) {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
// 3.
|
|
53
|
-
console.log(`\n
|
|
52
|
+
// 3. Update main package.json optionalDependencies
|
|
53
|
+
console.log(`\nš Updating optionalDependencies in package.json...`)
|
|
54
|
+
const linuxPkg = JSON.parse(fs.readFileSync(path.join(distDir, "easc-linux-x64", "package.json"), "utf-8"))
|
|
55
|
+
const binaryVersion = linuxPkg.version
|
|
56
|
+
|
|
57
|
+
const mainPkgPath = path.join(root, "package.json")
|
|
58
|
+
const mainPkg = JSON.parse(fs.readFileSync(mainPkgPath, "utf-8"))
|
|
59
|
+
|
|
60
|
+
const platforms = [
|
|
61
|
+
"easc-linux-x64", "easc-linux-arm64",
|
|
62
|
+
"easc-darwin-x64", "easc-darwin-arm64",
|
|
63
|
+
"easc-windows-x64"
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
for (const p of platforms) {
|
|
67
|
+
mainPkg.optionalDependencies[p] = binaryVersion
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fs.writeFileSync(mainPkgPath, JSON.stringify(mainPkg, null, 2))
|
|
71
|
+
console.log(` Updated optionalDependencies to ${binaryVersion}`)
|
|
72
|
+
|
|
73
|
+
// 4. Publish the main wrapper package
|
|
74
|
+
console.log(`\nš¤ Publishing main wrapper package (easc-cli@${pkg.version})...`)
|
|
75
|
+
|
|
76
|
+
// Strip dependencies and devDependencies before publishing
|
|
77
|
+
// The wrapper package only needs optionalDependencies for the platform binaries
|
|
78
|
+
mainPkg.dependencies = {}
|
|
79
|
+
mainPkg.devDependencies = {}
|
|
80
|
+
fs.writeFileSync(mainPkgPath, JSON.stringify(mainPkg, null, 2))
|
|
81
|
+
|
|
54
82
|
try {
|
|
55
83
|
// Ensure we are in the root of the package
|
|
56
84
|
await $`npm publish --access public`
|