djs-builder 0.6.401 → 0.7.0
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/README.md +292 -43
- package/function/dash.js +638 -0
- package/function/giveaway.js +47 -10
- package/function/security.js +0 -0
- package/handler/starter.js +73 -38
- package/package.json +9 -4
- package/views/404.ejs +40 -0
- package/views/dashboard.ejs +894 -0
- package/views/giveaways.ejs +306 -0
- package/views/guild.ejs +576 -0
- package/views/index.ejs +419 -0
- package/views/levels.ejs +326 -0
- package/views/login.ejs +437 -0
package/views/levels.ejs
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="ar" dir="rtl">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>المستويات - <%= guild.name %></title>
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Noto+Color+Emoji&display=swap" rel="stylesheet">
|
|
8
|
+
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
|
|
9
|
+
<style>
|
|
10
|
+
:root {
|
|
11
|
+
--bg-primary: #030305;
|
|
12
|
+
--bg-secondary: #0a0a0f;
|
|
13
|
+
--bg-card: #12121a;
|
|
14
|
+
--bg-card-hover: #1a1a25;
|
|
15
|
+
--accent: #5865F2;
|
|
16
|
+
--accent-light: #7289DA;
|
|
17
|
+
--accent-glow: rgba(88, 101, 242, 0.3);
|
|
18
|
+
--purple: #9b59b6;
|
|
19
|
+
--pink: #e91e63;
|
|
20
|
+
--success: #10b981;
|
|
21
|
+
--warning: #f59e0b;
|
|
22
|
+
--danger: #ef4444;
|
|
23
|
+
--text-primary: #ffffff;
|
|
24
|
+
--text-secondary: #94a3b8;
|
|
25
|
+
--text-muted: #64748b;
|
|
26
|
+
--border: rgba(255,255,255,0.06);
|
|
27
|
+
--radius: 16px;
|
|
28
|
+
--radius-sm: 10px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
32
|
+
body { font-family: 'Inter', 'Noto Color Emoji', sans-serif; background: var(--bg-primary); color: var(--text-primary); min-height: 100vh; overflow-x: hidden; }
|
|
33
|
+
a { color: inherit; text-decoration: none; }
|
|
34
|
+
::-webkit-scrollbar { width: 6px; }
|
|
35
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
36
|
+
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 6px; }
|
|
37
|
+
|
|
38
|
+
.bg-effects { position: fixed; inset: 0; pointer-events: none; overflow: hidden; z-index: 0; }
|
|
39
|
+
.bg-gradient-1 { position: absolute; top: -20%; right: -15%; width: 50%; height: 50%; background: radial-gradient(circle, rgba(88,101,242,0.12) 0%, transparent 60%); filter: blur(80px); }
|
|
40
|
+
.bg-gradient-2 { position: absolute; bottom: -20%; left: -15%; width: 40%; height: 40%; background: radial-gradient(circle, rgba(155,89,182,0.08) 0%, transparent 60%); filter: blur(80px); }
|
|
41
|
+
.bg-grid { position: absolute; inset: 0; background-image: linear-gradient(rgba(255,255,255,0.015) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.015) 1px, transparent 1px); background-size: 50px 50px; }
|
|
42
|
+
|
|
43
|
+
.btn { display: inline-flex; align-items: center; gap: 8px; padding: 12px 24px; border-radius: var(--radius-sm); font-weight: 600; font-size: 14px; cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); border: none; }
|
|
44
|
+
.btn-primary { background: linear-gradient(135deg, var(--accent), var(--accent-light)); color: white; box-shadow: 0 4px 15px var(--accent-glow); }
|
|
45
|
+
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 8px 25px var(--accent-glow); }
|
|
46
|
+
.btn-secondary { background: rgba(255,255,255,0.03); color: var(--text-primary); border: 1px solid var(--border); }
|
|
47
|
+
.btn-secondary:hover { background: rgba(255,255,255,0.06); border-color: var(--accent); }
|
|
48
|
+
.btn-success { background: linear-gradient(135deg, var(--success), #059669); color: white; }
|
|
49
|
+
.btn-sm { padding: 8px 16px; font-size: 13px; }
|
|
50
|
+
|
|
51
|
+
.sidebar { width: 280px; height: 100vh; position: fixed; right: 0; top: 0; background: rgba(10,10,15,0.95); backdrop-filter: blur(20px); border-left: 1px solid var(--border); display: flex; flex-direction: column; z-index: 100; }
|
|
52
|
+
.sidebar-header { padding: 24px; border-bottom: 1px solid var(--border); background: rgba(0,0,0,0.2); }
|
|
53
|
+
.guild-card { display: flex; align-items: center; gap: 14px; }
|
|
54
|
+
.guild-icon-wrapper { position: relative; }
|
|
55
|
+
.guild-icon { width: 52px; height: 52px; border-radius: var(--radius-sm); border: 2px solid transparent; background: linear-gradient(var(--bg-card), var(--bg-card)) padding-box, linear-gradient(135deg, var(--accent), var(--purple)) border-box; }
|
|
56
|
+
.guild-icon-placeholder { width: 52px; height: 52px; border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center; font-size: 22px; font-weight: 700; color: var(--accent); border: 2px solid transparent; background: linear-gradient(var(--bg-card), var(--bg-card)) padding-box, linear-gradient(135deg, var(--accent), var(--purple)) border-box; }
|
|
57
|
+
.guild-status { position: absolute; bottom: -2px; left: -2px; width: 16px; height: 16px; background: var(--success); border-radius: 50%; border: 3px solid var(--bg-secondary); }
|
|
58
|
+
.guild-info { flex: 1; min-width: 0; }
|
|
59
|
+
.guild-name { font-size: 16px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 4px; }
|
|
60
|
+
.guild-meta { font-size: 12px; color: var(--text-muted); display: flex; align-items: center; gap: 6px; }
|
|
61
|
+
|
|
62
|
+
.sidebar-nav { flex: 1; padding: 16px; overflow-y: auto; }
|
|
63
|
+
.nav-section { margin-bottom: 24px; }
|
|
64
|
+
.nav-section-title { font-size: 10px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 1px; padding: 8px 14px; }
|
|
65
|
+
.nav-link { display: flex; align-items: center; gap: 12px; padding: 12px 14px; border-radius: var(--radius-sm); color: var(--text-secondary); font-size: 14px; font-weight: 500; margin-bottom: 4px; transition: all 0.3s; position: relative; overflow: hidden; }
|
|
66
|
+
.nav-link::before { content: ''; position: absolute; right: 0; top: 50%; transform: translateY(-50%); width: 3px; height: 0; background: var(--accent); border-radius: 3px; transition: height 0.3s; }
|
|
67
|
+
.nav-link:hover { background: rgba(255,255,255,0.03); color: var(--text-primary); }
|
|
68
|
+
.nav-link.active { background: rgba(88,101,242,0.15); color: var(--accent); }
|
|
69
|
+
.nav-link.active::before { height: 60%; }
|
|
70
|
+
.nav-link i { font-size: 20px; }
|
|
71
|
+
|
|
72
|
+
.sidebar-footer { padding: 16px; border-top: 1px solid var(--border); background: rgba(0,0,0,0.2); }
|
|
73
|
+
.back-btn { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 12px; background: rgba(255,255,255,0.02); border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text-secondary); font-size: 13px; font-weight: 500; transition: all 0.3s; }
|
|
74
|
+
.back-btn:hover { background: rgba(255,255,255,0.05); color: var(--text-primary); border-color: var(--accent); }
|
|
75
|
+
|
|
76
|
+
.main { margin-right: 280px; padding: 40px; min-height: 100vh; position: relative; z-index: 1; }
|
|
77
|
+
.page-header { margin-bottom: 32px; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; }
|
|
78
|
+
.page-header h1 { font-size: 36px; font-weight: 800; background: linear-gradient(135deg, #fff, var(--text-secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; display: flex; align-items: center; }
|
|
79
|
+
.page-header h1 i { margin-left: 12px; -webkit-text-fill-color: var(--accent); }
|
|
80
|
+
.page-header p { color: var(--text-secondary); margin-top: 8px; }
|
|
81
|
+
|
|
82
|
+
.search-section { background: linear-gradient(145deg, var(--bg-card), rgba(18,18,26,0.6)); border: 1px solid var(--border); border-radius: var(--radius); padding: 24px; margin-bottom: 28px; position: relative; z-index: 100; }
|
|
83
|
+
.search-title { font-size: 16px; font-weight: 600; margin-bottom: 18px; display: flex; align-items: center; gap: 10px; }
|
|
84
|
+
.search-title i { color: var(--accent); font-size: 20px; }
|
|
85
|
+
.search-row { display: flex; gap: 14px; flex-wrap: wrap; position: relative; }
|
|
86
|
+
.search-input { flex: 1; min-width: 200px; position: relative; }
|
|
87
|
+
.search-input input { width: 100%; padding: 14px 18px 14px 46px; background: rgba(255,255,255,0.03); border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 14px; transition: all 0.3s; }
|
|
88
|
+
.search-input input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
|
|
89
|
+
.search-input i { position: absolute; left: 16px; top: 50%; transform: translateY(-50%); color: var(--text-muted); }
|
|
90
|
+
.search-results { position: absolute; top: 100%; left: 0; right: 0; margin-top: 8px; background: var(--bg-card); border: 2px solid var(--accent); border-radius: var(--radius-sm); max-height: 300px; overflow-y: auto; z-index: 99999; display: none; box-shadow: 0 20px 50px rgba(0,0,0,0.5); }
|
|
91
|
+
.search-results.show { display: block; }
|
|
92
|
+
.search-result-item { display: flex; align-items: center; gap: 14px; padding: 14px 18px; cursor: pointer; transition: all 0.2s; border-bottom: 1px solid var(--border); }
|
|
93
|
+
.search-result-item:last-child { border-bottom: none; }
|
|
94
|
+
.search-result-item:hover { background: var(--accent); }
|
|
95
|
+
.search-result-item img { width: 42px; height: 42px; border-radius: 50%; }
|
|
96
|
+
.search-result-item .name { font-weight: 600; }
|
|
97
|
+
.search-result-item .tag { font-size: 12px; color: var(--text-muted); }
|
|
98
|
+
.search-result-item:hover .tag { color: rgba(255,255,255,0.7); }
|
|
99
|
+
|
|
100
|
+
.stats-row { display: flex; gap: 18px; margin-bottom: 28px; flex-wrap: wrap; }
|
|
101
|
+
.mini-stat { background: linear-gradient(145deg, var(--bg-card), rgba(18,18,26,0.6)); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 20px 28px; display: flex; align-items: center; gap: 16px; transition: all 0.3s; }
|
|
102
|
+
.mini-stat:hover { border-color: var(--accent); transform: translateY(-3px); }
|
|
103
|
+
.mini-stat i { font-size: 28px; }
|
|
104
|
+
.mini-stat:nth-child(1) i { color: var(--accent); }
|
|
105
|
+
.mini-stat:nth-child(2) i { color: var(--purple); }
|
|
106
|
+
.mini-stat:nth-child(3) i { color: var(--warning); }
|
|
107
|
+
.mini-stat-value { font-size: 26px; font-weight: 800; }
|
|
108
|
+
.mini-stat-label { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
|
|
109
|
+
|
|
110
|
+
.leaderboard { background: linear-gradient(145deg, var(--bg-card), rgba(18,18,26,0.6)); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
|
|
111
|
+
.leaderboard-header { padding: 20px 28px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; background: rgba(0,0,0,0.2); }
|
|
112
|
+
.leaderboard-header h2 { font-size: 18px; font-weight: 600; display: flex; align-items: center; gap: 10px; }
|
|
113
|
+
.leaderboard-header h2 i { color: #FFD700; }
|
|
114
|
+
.leaderboard-table { width: 100%; }
|
|
115
|
+
.leaderboard-table th { text-align: right; padding: 16px 28px; font-size: 11px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; background: rgba(0,0,0,0.15); }
|
|
116
|
+
.leaderboard-table td { padding: 18px 28px; border-bottom: 1px solid var(--border); }
|
|
117
|
+
.leaderboard-table tr:last-child td { border-bottom: none; }
|
|
118
|
+
.leaderboard-table tr:hover td { background: rgba(255,255,255,0.02); }
|
|
119
|
+
|
|
120
|
+
.rank { font-size: 20px; font-weight: 800; width: 50px; }
|
|
121
|
+
.rank.gold { color: #FFD700; text-shadow: 0 0 20px rgba(255,215,0,0.5); }
|
|
122
|
+
.rank.silver { color: #C0C0C0; }
|
|
123
|
+
.rank.bronze { color: #CD7F32; }
|
|
124
|
+
|
|
125
|
+
.user-cell { display: flex; align-items: center; gap: 14px; }
|
|
126
|
+
.user-avatar { width: 46px; height: 46px; border-radius: 50%; background: var(--bg-secondary); border: 2px solid var(--border); }
|
|
127
|
+
.user-name { font-weight: 600; font-size: 15px; }
|
|
128
|
+
.user-id { font-size: 11px; color: var(--text-muted); font-family: monospace; margin-top: 2px; }
|
|
129
|
+
|
|
130
|
+
.level-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 36px; height: 36px; padding: 0 14px; background: linear-gradient(135deg, var(--accent), var(--accent-light)); border-radius: 50px; font-weight: 700; font-size: 15px; box-shadow: 0 4px 15px var(--accent-glow); }
|
|
131
|
+
|
|
132
|
+
.xp-bar { width: 220px; }
|
|
133
|
+
.xp-text { font-size: 13px; color: var(--text-secondary); margin-bottom: 8px; }
|
|
134
|
+
.xp-progress { height: 10px; background: rgba(255,255,255,0.05); border-radius: 10px; overflow: hidden; }
|
|
135
|
+
.xp-fill { height: 100%; background: linear-gradient(90deg, var(--accent), var(--purple)); border-radius: 10px; transition: width 0.4s ease; }
|
|
136
|
+
|
|
137
|
+
.actions-cell { display: flex; gap: 8px; }
|
|
138
|
+
.action-btn { padding: 8px 12px; background: rgba(255,255,255,0.03); border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text-secondary); cursor: pointer; font-size: 14px; transition: all 0.3s; }
|
|
139
|
+
.action-btn:hover { background: var(--accent); border-color: var(--accent); color: white; }
|
|
140
|
+
.action-btn.danger:hover { background: var(--danger); border-color: var(--danger); }
|
|
141
|
+
|
|
142
|
+
.empty-state { text-align: center; padding: 80px 20px; }
|
|
143
|
+
.empty-state i { font-size: 56px; color: var(--text-muted); margin-bottom: 20px; opacity: 0.3; }
|
|
144
|
+
.empty-state h3 { font-size: 20px; margin-bottom: 10px; }
|
|
145
|
+
.empty-state p { color: var(--text-secondary); }
|
|
146
|
+
|
|
147
|
+
.modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.8); backdrop-filter: blur(8px); align-items: center; justify-content: center; z-index: 2000; }
|
|
148
|
+
.modal.show { display: flex; }
|
|
149
|
+
.modal-content { background: linear-gradient(145deg, var(--bg-card), rgba(18,18,26,0.95)); border: 1px solid var(--border); border-radius: var(--radius); width: 480px; max-width: 90%; box-shadow: 0 25px 60px rgba(0,0,0,0.5); animation: modalIn 0.3s ease; }
|
|
150
|
+
@keyframes modalIn { from { opacity: 0; transform: scale(0.95) translateY(20px); } to { opacity: 1; transform: scale(1) translateY(0); } }
|
|
151
|
+
.modal-header { padding: 24px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; }
|
|
152
|
+
.modal-header h3 { font-size: 18px; font-weight: 600; }
|
|
153
|
+
.modal-close { background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 24px; transition: all 0.3s; }
|
|
154
|
+
.modal-close:hover { color: var(--text-primary); transform: rotate(90deg); }
|
|
155
|
+
.modal-body { padding: 24px; }
|
|
156
|
+
.modal-user { display: flex; align-items: center; gap: 14px; padding: 18px; background: rgba(255,255,255,0.02); border: 1px solid var(--border); border-radius: var(--radius-sm); margin-bottom: 24px; }
|
|
157
|
+
.modal-user img { width: 52px; height: 52px; border-radius: 50%; }
|
|
158
|
+
.modal-user-info h4 { font-weight: 600; margin-bottom: 4px; }
|
|
159
|
+
.modal-user-info p { font-size: 12px; color: var(--text-muted); font-family: monospace; }
|
|
160
|
+
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
|
|
161
|
+
.form-group { margin-bottom: 18px; }
|
|
162
|
+
.form-group label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 10px; color: var(--text-secondary); }
|
|
163
|
+
.form-group input { width: 100%; padding: 14px 16px; background: rgba(255,255,255,0.03); border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 14px; transition: all 0.3s; }
|
|
164
|
+
.form-group input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
|
|
165
|
+
.modal-footer { padding: 20px 24px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 10px; }
|
|
166
|
+
|
|
167
|
+
.toast { position: fixed; bottom: 24px; left: 24px; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 16px 24px; display: flex; align-items: center; gap: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.4); transform: translateY(120px); opacity: 0; transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); z-index: 3000; }
|
|
168
|
+
.toast.show { transform: translateY(0); opacity: 1; }
|
|
169
|
+
.toast.success { border-color: var(--success); color: var(--success); }
|
|
170
|
+
.toast.error { border-color: var(--danger); color: var(--danger); }
|
|
171
|
+
|
|
172
|
+
@keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
|
|
173
|
+
.animate-in { animation: fadeIn 0.5s ease forwards; }
|
|
174
|
+
|
|
175
|
+
@media (max-width: 992px) {
|
|
176
|
+
.sidebar { transform: translateX(100%); transition: transform 0.3s; }
|
|
177
|
+
.sidebar.open { transform: translateX(0); }
|
|
178
|
+
.main { margin-right: 0; padding: 20px; padding-top: 70px; }
|
|
179
|
+
.mobile-menu-btn { position: fixed; top: 16px; right: 16px; z-index: 101; width: 44px; height: 44px; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center; color: var(--text-primary); font-size: 22px; cursor: pointer; }
|
|
180
|
+
.xp-bar { width: 100px; }
|
|
181
|
+
.form-row { grid-template-columns: 1fr; }
|
|
182
|
+
.page-header h1 { font-size: 24px; }
|
|
183
|
+
.page-subtitle { font-size: 13px; }
|
|
184
|
+
.header-actions { flex-wrap: wrap; gap: 8px; }
|
|
185
|
+
.search-box { min-width: auto; width: 100%; }
|
|
186
|
+
.leaderboard-table th, .leaderboard-table td { padding: 14px 10px; }
|
|
187
|
+
.user-info { gap: 10px; }
|
|
188
|
+
.user-avatar { width: 36px; height: 36px; }
|
|
189
|
+
.user-name { font-size: 13px; }
|
|
190
|
+
}
|
|
191
|
+
@media (max-width: 600px) {
|
|
192
|
+
.main { padding: 16px; padding-top: 60px; }
|
|
193
|
+
.page-header h1 { font-size: 20px; }
|
|
194
|
+
.header-actions { flex-direction: column; width: 100%; }
|
|
195
|
+
.search-box { width: 100%; }
|
|
196
|
+
.btn { width: 100%; justify-content: center; }
|
|
197
|
+
.leaderboard-table { font-size: 12px; }
|
|
198
|
+
.rank-badge { width: 28px; height: 28px; font-size: 12px; }
|
|
199
|
+
.xp-bar { width: 70px; }
|
|
200
|
+
.xp-text { font-size: 10px; }
|
|
201
|
+
.user-avatar { width: 32px; height: 32px; }
|
|
202
|
+
.modal-content { width: 95%; margin: 12px; padding: 20px; }
|
|
203
|
+
}
|
|
204
|
+
@media (min-width: 993px) { .mobile-menu-btn { display: none; } }
|
|
205
|
+
</style>
|
|
206
|
+
</head>
|
|
207
|
+
<body>
|
|
208
|
+
<div class="bg-effects"><div class="bg-gradient-1"></div><div class="bg-gradient-2"></div><div class="bg-grid"></div></div>
|
|
209
|
+
|
|
210
|
+
<button class="mobile-menu-btn" onclick="document.querySelector('.sidebar').classList.toggle('open')"><i class="ri-menu-line"></i></button>
|
|
211
|
+
|
|
212
|
+
<aside class="sidebar">
|
|
213
|
+
<div class="sidebar-header">
|
|
214
|
+
<div class="guild-card">
|
|
215
|
+
<div class="guild-icon-wrapper">
|
|
216
|
+
<% if (guild.icon) { %><img src="https://cdn.discordapp.com/icons/<%= guild.id %>/<%= guild.icon %>.png?size=128" alt="<%= guild.name %>" class="guild-icon"><% } else { %><div class="guild-icon-placeholder"><%= guild.name.charAt(0).toUpperCase() %></div><% } %>
|
|
217
|
+
<div class="guild-status"></div>
|
|
218
|
+
</div>
|
|
219
|
+
<div class="guild-info">
|
|
220
|
+
<div class="guild-name"><%= guild.name %></div>
|
|
221
|
+
<div class="guild-meta"><i class="ri-group-line"></i> <%= (guild.memberCount || 0).toLocaleString() %> عضو</div>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
<nav class="sidebar-nav">
|
|
226
|
+
<div class="nav-section"><div class="nav-section-title">الرئيسية</div><a href="/dashboard/<%= guild.id %>" class="nav-link"><i class="ri-dashboard-3-line"></i><span>نظرة عامة</span></a></div>
|
|
227
|
+
<div class="nav-section"><div class="nav-section-title">الميزات</div>
|
|
228
|
+
<a href="/dashboard/<%= guild.id %>/levels" class="nav-link active"><i class="ri-bar-chart-grouped-line"></i><span>نظام المستويات</span></a>
|
|
229
|
+
<a href="/dashboard/<%= guild.id %>/giveaways" class="nav-link"><i class="ri-gift-2-line"></i><span>الهدايا</span></a>
|
|
230
|
+
</div>
|
|
231
|
+
</nav>
|
|
232
|
+
<div class="sidebar-footer"><a href="/dashboard" class="back-btn"><i class="ri-arrow-right-line"></i><span>العودة للوحة التحكم</span></a></div>
|
|
233
|
+
</aside>
|
|
234
|
+
|
|
235
|
+
<main class="main">
|
|
236
|
+
<div class="page-header animate-in">
|
|
237
|
+
<div><h1><i class="ri-bar-chart-grouped-line"></i> نظام المستويات</h1><p>إدارة مستويات ونقاط الأعضاء</p></div>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div class="search-section animate-in" style="animation-delay: 0.05s">
|
|
241
|
+
<div class="search-title"><i class="ri-search-line"></i> البحث عن عضو وإعطاء XP</div>
|
|
242
|
+
<div class="search-row">
|
|
243
|
+
<div class="search-input">
|
|
244
|
+
<i class="ri-search-line"></i>
|
|
245
|
+
<input type="text" id="memberSearch" placeholder="ابحث عن عضو بالاسم أو المعرف..." autocomplete="off">
|
|
246
|
+
<div class="search-results" id="searchResults"></div>
|
|
247
|
+
</div>
|
|
248
|
+
<button class="btn btn-primary" onclick="openAddXPModal()"><i class="ri-add-line"></i> إضافة XP لعضو</button>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<% const totalXP = leaderboard.reduce((sum, u) => sum + (u.totalXP || 0), 0); const highestLevel = leaderboard.length > 0 ? (leaderboard[0].level || 0) : 0; %>
|
|
253
|
+
|
|
254
|
+
<div class="stats-row animate-in" style="animation-delay: 0.1s">
|
|
255
|
+
<div class="mini-stat"><i class="ri-group-line"></i><div><div class="mini-stat-value"><%= leaderboard.length %></div><div class="mini-stat-label">مستخدم مسجل</div></div></div>
|
|
256
|
+
<div class="mini-stat"><i class="ri-bar-chart-grouped-line"></i><div><div class="mini-stat-value"><%= highestLevel %></div><div class="mini-stat-label">أعلى مستوى</div></div></div>
|
|
257
|
+
<div class="mini-stat"><i class="ri-star-line"></i><div><div class="mini-stat-value"><%= totalXP.toLocaleString() %></div><div class="mini-stat-label">إجمالي XP</div></div></div>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<% if (leaderboard.length > 0) { %>
|
|
261
|
+
<div class="leaderboard animate-in" style="animation-delay: 0.15s">
|
|
262
|
+
<div class="leaderboard-header"><h2><i class="ri-trophy-line"></i> المتصدرون</h2></div>
|
|
263
|
+
<table class="leaderboard-table">
|
|
264
|
+
<thead><tr><th>الترتيب</th><th>المستخدم</th><th>المستوى</th><th>نقاط XP</th><th>إجراءات</th></tr></thead>
|
|
265
|
+
<tbody id="leaderboardBody">
|
|
266
|
+
<% leaderboard.forEach((user, index) => { if (!user) return; const userXP = user.totalXP || 0; const userLevel = user.level || 0; const xpNeeded = (userLevel + 1) * 100; const progress = xpNeeded > 0 ? Math.min((userXP / xpNeeded) * 100, 100) : 0; const rankClass = index === 0 ? 'gold' : index === 1 ? 'silver' : index === 2 ? 'bronze' : ''; %>
|
|
267
|
+
<tr data-user-id="<%= user.userId %>">
|
|
268
|
+
<td><span class="rank <%= rankClass %>">#<%= index + 1 %></span></td>
|
|
269
|
+
<td><div class="user-cell"><img src="https://cdn.discordapp.com/embed/avatars/<%= index % 5 %>.png" class="user-avatar user-avatar-<%= user.userId %>" data-user-id="<%= user.userId %>" alt=""><div><div class="user-name user-name-<%= user.userId %>"><%= user.userId %></div><div class="user-id"><%= user.userId %></div></div></div></td>
|
|
270
|
+
<td><span class="level-badge"><%= userLevel %></span></td>
|
|
271
|
+
<td><div class="xp-bar"><div class="xp-text"><%= userXP.toLocaleString() %> / <%= xpNeeded.toLocaleString() %> XP</div><div class="xp-progress"><div class="xp-fill" style="width: <%= progress %>%"></div></div></div></td>
|
|
272
|
+
<td><div class="actions-cell"><button class="action-btn" onclick="openEditModal('<%= user.userId %>', <%= userXP %>, <%= userLevel %>)" title="تعديل"><i class="ri-edit-line"></i></button><button class="action-btn danger" onclick="resetUser('<%= user.userId %>')" title="إعادة تعيين"><i class="ri-refresh-line"></i></button></div></td>
|
|
273
|
+
</tr>
|
|
274
|
+
<% }); %>
|
|
275
|
+
</tbody>
|
|
276
|
+
</table>
|
|
277
|
+
</div>
|
|
278
|
+
<% } else { %>
|
|
279
|
+
<div class="empty-state animate-in"><i class="ri-bar-chart-grouped-line"></i><h3>لا توجد بيانات</h3><p>لم يكتسب أي عضو نقاط XP بعد</p></div>
|
|
280
|
+
<% } %>
|
|
281
|
+
</main>
|
|
282
|
+
|
|
283
|
+
<div class="modal" id="editModal">
|
|
284
|
+
<div class="modal-content">
|
|
285
|
+
<div class="modal-header"><h3>تعديل المستوى</h3><button class="modal-close" onclick="closeEditModal()">×</button></div>
|
|
286
|
+
<div class="modal-body">
|
|
287
|
+
<div class="modal-user"><img src="https://cdn.discordapp.com/embed/avatars/0.png" id="editUserAvatar" alt=""><div class="modal-user-info"><h4 id="editUserName">المستخدم</h4><p id="editUserId">ID</p></div></div>
|
|
288
|
+
<input type="hidden" id="editingUserId">
|
|
289
|
+
<div class="form-row"><div class="form-group"><label>XP الحالي</label><input type="number" id="editXP" min="0"></div><div class="form-group"><label>المستوى</label><input type="number" id="editLevel" min="0"></div></div>
|
|
290
|
+
</div>
|
|
291
|
+
<div class="modal-footer"><button class="btn btn-secondary" onclick="closeEditModal()">إلغاء</button><button class="btn btn-primary" onclick="saveUserEdit()">حفظ</button></div>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
<div class="modal" id="addXPModal">
|
|
296
|
+
<div class="modal-content">
|
|
297
|
+
<div class="modal-header"><h3>إضافة XP لعضو</h3><button class="modal-close" onclick="closeAddXPModal()">×</button></div>
|
|
298
|
+
<div class="modal-body">
|
|
299
|
+
<div class="form-group"><label>معرف العضو (ID)</label><input type="text" id="newUserId" placeholder="أدخل معرف العضو..."></div>
|
|
300
|
+
<div class="form-row"><div class="form-group"><label>XP للإضافة</label><input type="number" id="newUserXP" value="100" min="1"></div><div class="form-group"><label>المستوى (اختياري)</label><input type="number" id="newUserLevel" value="0" min="0"></div></div>
|
|
301
|
+
</div>
|
|
302
|
+
<div class="modal-footer"><button class="btn btn-secondary" onclick="closeAddXPModal()">إلغاء</button><button class="btn btn-success" onclick="addXPToUser()"><i class="ri-add-line"></i> إضافة</button></div>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<div class="toast" id="toast"></div>
|
|
307
|
+
|
|
308
|
+
<script>
|
|
309
|
+
const guildId = '<%= guild.id %>';
|
|
310
|
+
function showToast(message, type = 'success') { const toast = document.getElementById('toast'); toast.textContent = message; toast.className = 'toast ' + type + ' show'; setTimeout(() => toast.classList.remove('show'), 3000); }
|
|
311
|
+
async function loadUserData() { const userIds = [...document.querySelectorAll('[data-user-id]')].map(el => el.dataset.userId); const uniqueIds = [...new Set(userIds)]; for (const userId of uniqueIds) { try { const res = await fetch(`/api/${guildId}/member/${userId}`); const data = await res.json(); if (data.user) { const avatarUrl = data.user.avatar ? `https://cdn.discordapp.com/avatars/${userId}/${data.user.avatar}.png?size=64` : `https://cdn.discordapp.com/embed/avatars/${parseInt(userId) % 5}.png`; document.querySelectorAll(`.user-avatar-${userId}`).forEach(img => img.src = avatarUrl); document.querySelectorAll(`.user-name-${userId}`).forEach(el => el.textContent = data.user.username || data.user.global_name || userId); } } catch (e) { console.error('Error loading user:', userId, e); } } }
|
|
312
|
+
let searchTimeout; const searchInput = document.getElementById('memberSearch'); const searchResults = document.getElementById('searchResults');
|
|
313
|
+
searchInput.addEventListener('input', function() { clearTimeout(searchTimeout); const query = this.value.trim(); if (query.length < 2) { searchResults.classList.remove('show'); return; } searchTimeout = setTimeout(async () => { try { const res = await fetch(`/api/${guildId}/members/search?q=${encodeURIComponent(query)}`); const data = await res.json(); if (data.members && data.members.length > 0) { searchResults.innerHTML = data.members.map(m => `<div class="search-result-item" onclick="selectMember('${m.id}', '${m.username}', '${m.avatar || ''}')"><img src="${m.avatar ? `https://cdn.discordapp.com/avatars/${m.id}/${m.avatar}.png?size=64` : `https://cdn.discordapp.com/embed/avatars/${parseInt(m.id) % 5}.png`}" alt=""><div><div class="name">${m.displayName || m.username}</div><div class="tag">${m.username}</div></div></div>`).join(''); searchResults.classList.add('show'); } else { searchResults.innerHTML = '<div style="padding: 16px; text-align: center; color: var(--text-muted);">لا توجد نتائج</div>'; searchResults.classList.add('show'); } } catch (e) { searchResults.classList.remove('show'); } }, 300); });
|
|
314
|
+
document.addEventListener('click', function(e) { if (!e.target.closest('.search-input')) searchResults.classList.remove('show'); });
|
|
315
|
+
function selectMember(id, username, avatar) { document.getElementById('newUserId').value = id; searchResults.classList.remove('show'); searchInput.value = username; openAddXPModal(); }
|
|
316
|
+
function openEditModal(userId, xp, level) { document.getElementById('editingUserId').value = userId; document.getElementById('editXP').value = xp; document.getElementById('editLevel').value = level; document.getElementById('editUserId').textContent = userId; const nameEl = document.querySelector(`.user-name-${userId}`); const avatarEl = document.querySelector(`.user-avatar-${userId}`); document.getElementById('editUserName').textContent = nameEl ? nameEl.textContent : userId; document.getElementById('editUserAvatar').src = avatarEl ? avatarEl.src : 'https://cdn.discordapp.com/embed/avatars/0.png'; document.getElementById('editModal').classList.add('show'); }
|
|
317
|
+
function closeEditModal() { document.getElementById('editModal').classList.remove('show'); }
|
|
318
|
+
async function saveUserEdit() { const userId = document.getElementById('editingUserId').value; const xp = parseInt(document.getElementById('editXP').value) || 0; const level = parseInt(document.getElementById('editLevel').value) || 0; try { const res = await fetch(`/api/${guildId}/level/update`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId, xp, level }) }); const data = await res.json(); if (data.success) { showToast('تم التحديث بنجاح'); closeEditModal(); setTimeout(() => location.reload(), 1000); } else showToast(data.error || 'حدث خطأ', 'error'); } catch (e) { showToast('حدث خطأ في الاتصال', 'error'); } }
|
|
319
|
+
function openAddXPModal() { document.getElementById('addXPModal').classList.add('show'); }
|
|
320
|
+
function closeAddXPModal() { document.getElementById('addXPModal').classList.remove('show'); document.getElementById('newUserId').value = ''; searchInput.value = ''; }
|
|
321
|
+
async function addXPToUser() { const userId = document.getElementById('newUserId').value.trim(); const xp = parseInt(document.getElementById('newUserXP').value) || 100; const level = parseInt(document.getElementById('newUserLevel').value) || 0; if (!userId) return showToast('أدخل معرف العضو', 'error'); try { const res = await fetch(`/api/${guildId}/level/add`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId, xp, level }) }); const data = await res.json(); if (data.success) { showToast('تمت الإضافة بنجاح'); closeAddXPModal(); setTimeout(() => location.reload(), 1000); } else showToast(data.error || 'حدث خطأ', 'error'); } catch (e) { showToast('حدث خطأ في الاتصال', 'error'); } }
|
|
322
|
+
async function resetUser(userId) { if (!confirm('هل أنت متأكد من إعادة تعيين بيانات هذا المستخدم؟')) return; try { const res = await fetch(`/api/${guildId}/level/reset`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId }) }); const data = await res.json(); if (data.success) { showToast('تمت إعادة التعيين'); setTimeout(() => location.reload(), 1000); } else showToast(data.error || 'حدث خطأ', 'error'); } catch (e) { showToast('حدث خطأ', 'error'); } }
|
|
323
|
+
loadUserData();
|
|
324
|
+
</script>
|
|
325
|
+
</body>
|
|
326
|
+
</html>
|