stem-lab-toolkit 1.0.1 → 1.0.3
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/admin.html +60 -13
- package/api.js +163 -85
- package/assets/favicon.png +0 -0
- package/assets/logo.png +0 -0
- package/browse.html +121 -5
- package/bump.sh +5 -1
- package/change-password.html +168 -0
- package/control-panel.html +885 -0
- package/css/style.css +3 -1
- package/index.html +247 -98
- package/js/admin.js +144 -29
- package/js/games.js +2 -2
- package/js/main.js +14 -95
- package/js/pin-modal.js +10 -95
- package/js/theme.js +20 -0
- package/package.json +7 -2
- package/{game.html → tool.html} +31 -18
- package/users.json +62 -0
- package/version.txt +1 -1
- package/pins.json +0 -1
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<script>
|
|
6
|
+
(function(){
|
|
7
|
+
if(sessionStorage.getItem('mm_auth')!=='true'){window.location.replace('./index.html');return;}
|
|
8
|
+
if(sessionStorage.getItem('mm_force_change')!=='true'){
|
|
9
|
+
var rank=sessionStorage.getItem('mm_rank');
|
|
10
|
+
if(rank==='administrator'){window.location.replace('./admin.html');}
|
|
11
|
+
else if(rank==='admin'){window.location.replace('./control-panel.html');}
|
|
12
|
+
else{window.location.replace('./browse.html');}
|
|
13
|
+
}
|
|
14
|
+
})();
|
|
15
|
+
</script>
|
|
16
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
17
|
+
<title>Change Password — MM Games</title>
|
|
18
|
+
<link rel="icon" type="image/png" href="/assets/favicon.png">
|
|
19
|
+
<style>
|
|
20
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
21
|
+
body {
|
|
22
|
+
min-height: 100dvh;
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: center;
|
|
26
|
+
background: #f3f4f6;
|
|
27
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
28
|
+
padding: 24px;
|
|
29
|
+
}
|
|
30
|
+
.card {
|
|
31
|
+
background: #fff;
|
|
32
|
+
border-radius: 16px;
|
|
33
|
+
padding: 36px 32px 32px;
|
|
34
|
+
width: 100%;
|
|
35
|
+
max-width: 380px;
|
|
36
|
+
box-shadow: 0 4px 24px rgba(0,0,0,.09);
|
|
37
|
+
border: 1px solid #e5e7eb;
|
|
38
|
+
}
|
|
39
|
+
.icon {
|
|
40
|
+
width: 48px; height: 48px;
|
|
41
|
+
background: #fef3c7;
|
|
42
|
+
border-radius: 12px;
|
|
43
|
+
display: flex; align-items: center; justify-content: center;
|
|
44
|
+
font-size: 1.4rem;
|
|
45
|
+
margin-bottom: 16px;
|
|
46
|
+
}
|
|
47
|
+
h1 { font-size: 1.15rem; font-weight: 700; color: #111827; margin-bottom: 6px; }
|
|
48
|
+
.sub { font-size: 0.85rem; color: #6b7280; margin-bottom: 28px; line-height: 1.5; }
|
|
49
|
+
.field { margin-bottom: 14px; }
|
|
50
|
+
label {
|
|
51
|
+
display: block;
|
|
52
|
+
font-size: 0.78rem; font-weight: 600;
|
|
53
|
+
color: #6b7280;
|
|
54
|
+
text-transform: uppercase; letter-spacing: .04em;
|
|
55
|
+
margin-bottom: 5px;
|
|
56
|
+
}
|
|
57
|
+
input {
|
|
58
|
+
width: 100%;
|
|
59
|
+
padding: 10px 13px;
|
|
60
|
+
border: 1.5px solid #e5e7eb;
|
|
61
|
+
border-radius: 8px;
|
|
62
|
+
font-size: 0.9rem;
|
|
63
|
+
outline: none;
|
|
64
|
+
font-family: inherit;
|
|
65
|
+
transition: border-color .15s, box-shadow .15s;
|
|
66
|
+
color: #111827;
|
|
67
|
+
}
|
|
68
|
+
input:focus { border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37,99,235,.1); }
|
|
69
|
+
.msg { font-size: 0.82rem; min-height: 1.2em; margin: 8px 0 4px; }
|
|
70
|
+
.msg.error { color: #b91c1c; }
|
|
71
|
+
.msg.ok { color: #15803d; }
|
|
72
|
+
.submit {
|
|
73
|
+
width: 100%;
|
|
74
|
+
background: #2563eb; color: #fff;
|
|
75
|
+
border: none; border-radius: 8px;
|
|
76
|
+
padding: 12px; font-size: 0.9rem; font-weight: 600;
|
|
77
|
+
cursor: pointer; font-family: inherit;
|
|
78
|
+
margin-top: 6px; transition: background .15s;
|
|
79
|
+
}
|
|
80
|
+
.submit:hover { background: #1d4ed8; }
|
|
81
|
+
.submit:disabled { opacity: .55; cursor: not-allowed; }
|
|
82
|
+
</style>
|
|
83
|
+
</head>
|
|
84
|
+
<body>
|
|
85
|
+
<div class="card">
|
|
86
|
+
<div class="icon">🔒</div>
|
|
87
|
+
<h1>Password Change Required</h1>
|
|
88
|
+
<p class="sub">Your account requires a password change before you can continue. Please set a new password below.</p>
|
|
89
|
+
|
|
90
|
+
<div class="field">
|
|
91
|
+
<label>Current Password</label>
|
|
92
|
+
<input id="cur" type="password" autocomplete="current-password" placeholder="Current password">
|
|
93
|
+
</div>
|
|
94
|
+
<div class="field">
|
|
95
|
+
<label>New Password</label>
|
|
96
|
+
<input id="nw" type="password" autocomplete="new-password" placeholder="New password (min 6 chars)">
|
|
97
|
+
</div>
|
|
98
|
+
<div class="field">
|
|
99
|
+
<label>Confirm New Password</label>
|
|
100
|
+
<input id="con" type="password" autocomplete="new-password" placeholder="Confirm new password">
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div class="msg" id="msg"></div>
|
|
104
|
+
<button class="submit" id="submit-btn">Set New Password</button>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<script>
|
|
108
|
+
(function () {
|
|
109
|
+
var curEl = document.getElementById('cur');
|
|
110
|
+
var newEl = document.getElementById('nw');
|
|
111
|
+
var conEl = document.getElementById('con');
|
|
112
|
+
var msgEl = document.getElementById('msg');
|
|
113
|
+
var subBtn = document.getElementById('submit-btn');
|
|
114
|
+
|
|
115
|
+
curEl.focus();
|
|
116
|
+
|
|
117
|
+
[curEl, newEl, conEl].forEach(function (el) {
|
|
118
|
+
el.addEventListener('keydown', function (e) { if (e.key === 'Enter') doChange(); });
|
|
119
|
+
});
|
|
120
|
+
subBtn.addEventListener('click', doChange);
|
|
121
|
+
|
|
122
|
+
async function doChange() {
|
|
123
|
+
var username = sessionStorage.getItem('mm_username') || '';
|
|
124
|
+
var cur = curEl.value;
|
|
125
|
+
var nw = newEl.value;
|
|
126
|
+
var con = conEl.value;
|
|
127
|
+
|
|
128
|
+
msgEl.className = 'msg error';
|
|
129
|
+
if (!username) { msgEl.textContent = 'Session error — please log in again.'; return; }
|
|
130
|
+
if (!cur || !nw || !con) { msgEl.textContent = 'All fields are required.'; return; }
|
|
131
|
+
if (nw !== con) { msgEl.textContent = 'New passwords do not match.'; return; }
|
|
132
|
+
if (nw.length < 6) { msgEl.textContent = 'New password must be at least 6 characters.'; return; }
|
|
133
|
+
|
|
134
|
+
subBtn.disabled = true;
|
|
135
|
+
msgEl.textContent = '';
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
var res = await fetch('./api/users/change-password', {
|
|
139
|
+
method: 'POST',
|
|
140
|
+
headers: { 'Content-Type': 'application/json' },
|
|
141
|
+
body: JSON.stringify({ username: username, currentPassword: cur, newPassword: nw }),
|
|
142
|
+
});
|
|
143
|
+
var data = await res.json();
|
|
144
|
+
if (res.ok && data.ok) {
|
|
145
|
+
sessionStorage.removeItem('mm_force_change');
|
|
146
|
+
msgEl.className = 'msg ok';
|
|
147
|
+
msgEl.textContent = 'Password changed! Redirecting…';
|
|
148
|
+
var rank = sessionStorage.getItem('mm_rank');
|
|
149
|
+
setTimeout(function () {
|
|
150
|
+
if (rank === 'administrator') window.location.href = './admin.html';
|
|
151
|
+
else if (rank === 'admin') window.location.href = './control-panel.html';
|
|
152
|
+
else window.location.href = './browse.html';
|
|
153
|
+
}, 1000);
|
|
154
|
+
} else {
|
|
155
|
+
msgEl.className = 'msg error';
|
|
156
|
+
msgEl.textContent = data.error || 'Failed to change password.';
|
|
157
|
+
subBtn.disabled = false;
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
msgEl.className = 'msg error';
|
|
161
|
+
msgEl.textContent = 'Connection error. Try again.';
|
|
162
|
+
subBtn.disabled = false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
})();
|
|
166
|
+
</script>
|
|
167
|
+
</body>
|
|
168
|
+
</html>
|