@necrolab/dashboard 0.4.39 → 0.4.41
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/.claude/settings.local.json +5 -1
- package/.prettierrc +14 -1
- package/backend/mock-data.js +8 -8
- package/dev-server.js +131 -44
- package/package.json +3 -2
- package/postinstall.js +25 -13
- package/src/assets/css/_input.scss +12 -12
- package/src/assets/css/main.scss +63 -10
- package/src/components/Auth/LoginForm.vue +12 -5
- package/src/components/Editors/Account/AccountCreator.vue +51 -22
- package/src/components/Editors/Account/AccountView.vue +64 -13
- package/src/components/Editors/Account/CreateAccount.vue +47 -28
- package/src/components/Editors/Profile/ProfileView.vue +46 -8
- package/src/components/Filter/FilterPreview.vue +0 -4
- package/src/components/Tasks/Stats.vue +22 -16
- package/src/components/Tasks/Task.vue +78 -47
- package/src/components/Tasks/TaskView.vue +10 -9
- package/src/stores/sampleData.js +84 -60
- package/src/stores/ui.js +30 -4
- package/src/views/Console.vue +208 -21
- package/src/views/Editor.vue +2 -6
- package/src/views/FilterBuilder.vue +28 -46
- package/src/views/Login.vue +134 -12
- package/tailwind.config.js +1 -1
- package/vite.config.js +26 -0
- package/src/assets/img/android-chrome-192x192.png +0 -0
package/.prettierrc
CHANGED
|
@@ -6,5 +6,18 @@
|
|
|
6
6
|
"trailingComma": "none",
|
|
7
7
|
"bracketSpacing": true,
|
|
8
8
|
"arrowParens": "always",
|
|
9
|
-
"printWidth": 120
|
|
9
|
+
"printWidth": 120,
|
|
10
|
+
"vueIndentScriptAndStyle": false,
|
|
11
|
+
"htmlWhitespaceSensitivity": "ignore",
|
|
12
|
+
"bracketSameLine": true,
|
|
13
|
+
"singleAttributePerLine": false,
|
|
14
|
+
"overrides": [
|
|
15
|
+
{
|
|
16
|
+
"files": "*.vue",
|
|
17
|
+
"options": {
|
|
18
|
+
"vueIndentScriptAndStyle": false,
|
|
19
|
+
"htmlWhitespaceSensitivity": "ignore"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
]
|
|
10
23
|
}
|
package/backend/mock-data.js
CHANGED
|
@@ -2,17 +2,17 @@ const users = [
|
|
|
2
2
|
{
|
|
3
3
|
event: "auth",
|
|
4
4
|
botChannels: {
|
|
5
|
-
splash: "
|
|
6
|
-
carts: "
|
|
7
|
-
checkouts: "
|
|
8
|
-
declines: "
|
|
9
|
-
stockChecker: "
|
|
10
|
-
venueMaps: "
|
|
5
|
+
splash: "-",
|
|
6
|
+
carts: "-",
|
|
7
|
+
checkouts: "-",
|
|
8
|
+
declines: "-",
|
|
9
|
+
stockChecker: "-",
|
|
10
|
+
venueMaps: "-"
|
|
11
11
|
},
|
|
12
12
|
_id: "641a5292b561088b64fe390b",
|
|
13
|
-
name: "
|
|
13
|
+
name: "Admin",
|
|
14
14
|
password: "admin",
|
|
15
|
-
profilePicture: "https://
|
|
15
|
+
profilePicture: "https://cdn.discordapp.com/avatars/435549216304267264/6cfd74ad7c5939a0bcbf218aa08be8cb.png",
|
|
16
16
|
admin: true,
|
|
17
17
|
proxyList: { checkout: "admin-proxies" },
|
|
18
18
|
profileTags: ["Amex", "Citi", "Mercury", "Slash"],
|
package/dev-server.js
CHANGED
|
@@ -1,49 +1,136 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { createServer } from
|
|
4
|
-
import process from
|
|
3
|
+
import { createServer } from "vite";
|
|
4
|
+
import process from "process";
|
|
5
|
+
import { spawn } from "child_process";
|
|
6
|
+
|
|
7
|
+
// Start backend server with mock data
|
|
8
|
+
const startBackend = () => {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const backend = spawn("node", ["index.js"], {
|
|
11
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
12
|
+
env: { ...process.env, NODE_ENV: "development" }
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
let backendReady = false;
|
|
16
|
+
|
|
17
|
+
backend.stdout.on("data", (data) => {
|
|
18
|
+
const output = data.toString();
|
|
19
|
+
|
|
20
|
+
// Look for backend ready signal
|
|
21
|
+
if (output.includes("Web API started on port") || output.includes("8081")) {
|
|
22
|
+
backendReady = true;
|
|
23
|
+
resolve(backend);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
backend.stderr.on("data", (data) => {
|
|
28
|
+
// Suppress error output unless it's critical
|
|
29
|
+
if (data.toString().includes("Error") || data.toString().includes("EADDRINUSE")) {
|
|
30
|
+
console.error("⚠️ Backend error:", data.toString().trim());
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
backend.on("error", (error) => {
|
|
35
|
+
reject(error);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
backend.on("exit", (code) => {
|
|
39
|
+
if (code !== 0 && !backendReady) {
|
|
40
|
+
reject(new Error(`Backend failed to start`));
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Timeout fallback
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
if (!backendReady) {
|
|
47
|
+
resolve(backend);
|
|
48
|
+
}
|
|
49
|
+
}, 5000);
|
|
50
|
+
});
|
|
51
|
+
};
|
|
5
52
|
|
|
6
53
|
const startServer = async () => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
54
|
+
let backend;
|
|
55
|
+
let server;
|
|
56
|
+
let isShuttingDown = false;
|
|
57
|
+
|
|
58
|
+
// Cleanup function
|
|
59
|
+
const cleanup = () => {
|
|
60
|
+
if (isShuttingDown) return;
|
|
61
|
+
isShuttingDown = true;
|
|
62
|
+
|
|
63
|
+
console.log("\n👋 Goodbye!");
|
|
64
|
+
if (backend) {
|
|
65
|
+
backend.kill("SIGTERM");
|
|
66
|
+
setTimeout(() => backend.kill("SIGKILL"), 3000);
|
|
67
|
+
}
|
|
68
|
+
if (server) {
|
|
69
|
+
server.close();
|
|
17
70
|
}
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Handle
|
|
22
|
-
process.on(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
71
|
+
process.exit(0);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Handle cleanup on various exit signals
|
|
75
|
+
process.on("SIGINT", cleanup);
|
|
76
|
+
process.on("SIGTERM", cleanup);
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
console.log("🚀 Starting Necro Dashboard...");
|
|
80
|
+
|
|
81
|
+
// Ensure proper permissions for Vite cache
|
|
82
|
+
try {
|
|
83
|
+
const fs = await import('node:fs');
|
|
84
|
+
const path = await import('node:path');
|
|
85
|
+
const cacheDir = path.resolve('./node_modules/.vite');
|
|
86
|
+
if (fs.existsSync(cacheDir)) {
|
|
87
|
+
fs.chmodSync(cacheDir, 0o755);
|
|
88
|
+
}
|
|
89
|
+
} catch (e) {
|
|
90
|
+
// Ignore permission errors
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
backend = await startBackend();
|
|
94
|
+
|
|
95
|
+
server = await createServer({
|
|
96
|
+
configFile: "./vite.config.js",
|
|
97
|
+
server: {
|
|
98
|
+
port: 5173,
|
|
99
|
+
strictPort: true,
|
|
100
|
+
host: true,
|
|
101
|
+
cors: true,
|
|
102
|
+
hmr: {
|
|
103
|
+
overlay: false
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Handle unhandled errors gracefully
|
|
109
|
+
process.on("uncaughtException", (error) => {
|
|
110
|
+
if (error.code === "ECONNRESET" || error.errno === -54) {
|
|
111
|
+
return; // Silently handle mobile reconnects
|
|
112
|
+
}
|
|
113
|
+
console.error("❌ Server error:", error.message);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
process.on("unhandledRejection", (reason) => {
|
|
117
|
+
if (reason?.code === "ECONNRESET" || reason?.errno === -54) {
|
|
118
|
+
return; // Silently handle mobile reconnects
|
|
119
|
+
}
|
|
120
|
+
console.error("❌ Unhandled error:", reason?.message || reason);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
await server.listen();
|
|
124
|
+
|
|
125
|
+
console.log("\n✨ Necro Dashboard is ready!");
|
|
126
|
+
console.log("🌐 Development: http://localhost:5173");
|
|
127
|
+
console.log("🌐 Staging: http://localhost:8081\n");
|
|
128
|
+
console.log("💀 Happy debugging");
|
|
129
|
+
console.log("\n\n👆 Press Ctrl+C to stop");
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error("Failed to start server:", error);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
startServer();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@necrolab/dashboard",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.41",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "rm -rf dist && vite build && npx workbox-cli generateSW workbox-config.cjs",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@faker-js/faker": "^7.6.0",
|
|
17
17
|
"@msgpack/msgpack": "^3.0.0-beta2",
|
|
18
|
-
"@necrolab/tm-renderer": "^0.1.
|
|
18
|
+
"@necrolab/tm-renderer": "^0.1.7",
|
|
19
19
|
"@vitejs/plugin-vue": "^5.2.1",
|
|
20
20
|
"@vueuse/core": "^11.3.0",
|
|
21
21
|
"autoprefixer": "^10.4.21",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"dragselect": "^3.1.1",
|
|
26
26
|
"express": "^4.21.2",
|
|
27
27
|
"express-ws": "^5.0.2",
|
|
28
|
+
"ipaddr.js": "^2.2.0",
|
|
28
29
|
"pinia": "^2.3.0",
|
|
29
30
|
"postcss": "^8.4.49",
|
|
30
31
|
"register-service-worker": "^1.7.2",
|
package/postinstall.js
CHANGED
|
@@ -5,19 +5,31 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
|
|
6
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
7
|
|
|
8
|
-
console.log(
|
|
8
|
+
console.log("🔧 Running postinstall build...");
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
execSync(`
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
});
|
|
10
|
+
// Use npx to run vite instead of hardcoded path
|
|
11
|
+
try {
|
|
12
|
+
execSync(`npx vite build`, {
|
|
13
|
+
cwd: __dirname,
|
|
14
|
+
stdio: "inherit"
|
|
15
|
+
});
|
|
16
|
+
console.log("✅ Build completed successfully");
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.log("⚠️ Build failed, but continuing...");
|
|
19
|
+
}
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
recursive: true
|
|
21
|
-
});
|
|
21
|
+
// Only do file operations if we're in a nested structure
|
|
22
|
+
const distPath = path.resolve(path.join(__dirname, "../../../dashboard/dist/"));
|
|
23
|
+
const localDistPath = path.resolve("./dist/");
|
|
22
24
|
|
|
23
|
-
fs.
|
|
25
|
+
if (fs.existsSync(localDistPath) && __dirname.includes("dashboard")) {
|
|
26
|
+
try {
|
|
27
|
+
if (fs.existsSync(distPath)) {
|
|
28
|
+
fs.rmSync(distPath, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
fs.renameSync(localDistPath, distPath);
|
|
31
|
+
console.log("📁 Moved dist folder");
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.log("⚠️ Could not move dist folder, but that's okay");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
.button-default {
|
|
46
|
-
@apply text-white font-medium h-12 rounded-lg duration-150 border border-dark-650 hover:border-dark-700
|
|
46
|
+
@apply text-white font-medium h-12 rounded-lg duration-150 border border-dark-650 hover:border-dark-700 !important;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// Remove number input spinners
|
|
@@ -59,9 +59,10 @@ input[type="number"] {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
.input-incrementer {
|
|
62
|
-
@apply flex flex-col absolute right-2 top-1/2 transform -translate-y-1/2
|
|
62
|
+
@apply flex flex-col absolute right-2 top-1/2 transform -translate-y-1/2 !important;
|
|
63
63
|
gap: 2px;
|
|
64
64
|
|
|
65
|
+
|
|
65
66
|
button {
|
|
66
67
|
@apply w-6 h-4 flex items-center justify-center transition-all duration-150 rounded-sm;
|
|
67
68
|
background: rgba(46, 47, 52, 0.8);
|
|
@@ -125,16 +126,16 @@ input[type="number"] {
|
|
|
125
126
|
.ant-select-dropdown {
|
|
126
127
|
z-index: 10000 !important;
|
|
127
128
|
position: relative;
|
|
128
|
-
@apply rounded-lg
|
|
129
|
+
@apply rounded-lg !important;
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
.ant-select {
|
|
132
|
-
@apply bg-dark-500 flex items-center bg-clip-padding rounded-lg relative box-border px-1 py-1 border-dark-550 border-2
|
|
133
|
+
@apply bg-dark-500 flex items-center bg-clip-padding rounded-lg relative box-border px-1 py-1 border-dark-550 border-2 !important;
|
|
133
134
|
min-height: 3.45em;
|
|
134
135
|
height: fit-content;
|
|
135
136
|
|
|
136
137
|
.ant-select-selector {
|
|
137
|
-
@apply h-full w-full focus:outline-none text-xs text-white
|
|
138
|
+
@apply h-full w-full focus:outline-none text-xs text-white !important;
|
|
138
139
|
background: transparent;
|
|
139
140
|
background-color: transparent !important;
|
|
140
141
|
border: 0 !important;
|
|
@@ -144,7 +145,7 @@ input[type="number"] {
|
|
|
144
145
|
|
|
145
146
|
// Ant Design Select item styling
|
|
146
147
|
.ant-select-multiple .ant-select-selection-item {
|
|
147
|
-
@apply bg-dark-400 items-center gap-x-4
|
|
148
|
+
@apply bg-dark-400 items-center gap-x-4 !important;
|
|
148
149
|
padding-inline-end: 15px;
|
|
149
150
|
padding-inline-start: 15px;
|
|
150
151
|
border-radius: 10px;
|
|
@@ -155,7 +156,6 @@ input[type="number"] {
|
|
|
155
156
|
vertical-align: 0.05rem;
|
|
156
157
|
}
|
|
157
158
|
}
|
|
158
|
-
|
|
159
159
|
.ant-select-selection-item-remove > .anticon svg {
|
|
160
160
|
color: #fff;
|
|
161
161
|
}
|
|
@@ -166,10 +166,10 @@ input[type="number"] {
|
|
|
166
166
|
|
|
167
167
|
.ant-select-item-option {
|
|
168
168
|
&-selected:not(.ant-select-item-option-disabled) {
|
|
169
|
-
@apply bg-dark-500 text-white
|
|
169
|
+
@apply bg-dark-500 text-white !important;
|
|
170
170
|
|
|
171
171
|
.ant-select-item-option-state {
|
|
172
|
-
@apply text-white
|
|
172
|
+
@apply text-white !important;
|
|
173
173
|
|
|
174
174
|
svg {
|
|
175
175
|
margin-top: -1.5px;
|
|
@@ -182,16 +182,16 @@ input[type="number"] {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
&-content {
|
|
185
|
-
@apply text-white
|
|
185
|
+
@apply text-white !important;
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
.ant-select-dropdown {
|
|
190
|
-
@apply bg-dark-400
|
|
190
|
+
@apply bg-dark-400 !important;
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
.ant-select-selector:before {
|
|
194
194
|
content: "";
|
|
195
195
|
position: absolute;
|
|
196
|
-
@apply bg-light-400 h-1 w-1 right-1 rounded-full
|
|
196
|
+
@apply bg-light-400 h-1 w-1 right-1 rounded-full !important;
|
|
197
197
|
}
|
package/src/assets/css/main.scss
CHANGED
|
@@ -142,11 +142,11 @@ img {
|
|
|
142
142
|
|
|
143
143
|
// Button utilities
|
|
144
144
|
.btn-primary {
|
|
145
|
-
@apply bg-
|
|
146
|
-
border: 1px solid #
|
|
145
|
+
@apply bg-dark-550 hover:bg-dark-650 text-white font-medium px-4 py-2 rounded transition-all duration-150;
|
|
146
|
+
border: 1px solid #44454b;
|
|
147
147
|
|
|
148
148
|
&:hover {
|
|
149
|
-
border-color: #
|
|
149
|
+
border-color: #4b4c53;
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
@@ -290,26 +290,35 @@ img {
|
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
-
// Dynamic heights for different devices
|
|
293
|
+
// Dynamic heights for different devices - conservative approach to prevent partial items
|
|
294
294
|
.max-h-big {
|
|
295
|
-
max-height:
|
|
295
|
+
max-height: calc(100vh - 12rem); // Account for navbar, header, stats, controls, utilities
|
|
296
|
+
min-height: 8rem; // 2 items minimum (2 * 64px = 128px = 8rem)
|
|
297
|
+
overflow: hidden; // Ensure no partial items show
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
@media only screen and (min-width: 1440px) {
|
|
301
|
+
.max-h-big {
|
|
302
|
+
max-height: calc(100vh - 11rem); // Large screens get slightly more space
|
|
303
|
+
}
|
|
296
304
|
}
|
|
297
305
|
|
|
298
306
|
@media only screen and (min-device-width: 768px) and (max-device-width: 1366px) {
|
|
299
307
|
.max-h-big {
|
|
300
|
-
max-height:
|
|
308
|
+
max-height: calc(100vh - 12rem);
|
|
301
309
|
}
|
|
302
310
|
}
|
|
303
311
|
|
|
304
312
|
@media only screen and (min-device-width: 768px) and (max-device-width: 1366px) and (orientation: portrait) {
|
|
305
313
|
.max-h-big {
|
|
306
|
-
max-height:
|
|
314
|
+
max-height: calc(100vh - 12rem);
|
|
307
315
|
}
|
|
308
316
|
}
|
|
309
317
|
|
|
310
318
|
@media screen and (max-device-height: 500px) {
|
|
311
319
|
.max-h-big {
|
|
312
|
-
max-height:
|
|
320
|
+
max-height: calc(100vh - 12rem);
|
|
321
|
+
min-height: 8rem;
|
|
313
322
|
}
|
|
314
323
|
}
|
|
315
324
|
|
|
@@ -370,10 +379,30 @@ img {
|
|
|
370
379
|
fill: currentColor !important;
|
|
371
380
|
}
|
|
372
381
|
|
|
382
|
+
// Toast container
|
|
383
|
+
.Toastify__toast-container {
|
|
384
|
+
pointer-events: none;
|
|
385
|
+
--toastify-toast-bd-radius: 6px;
|
|
386
|
+
}
|
|
387
|
+
|
|
373
388
|
.Toastify__toast {
|
|
374
389
|
min-height: 50px !important;
|
|
375
|
-
height: 50px !important;
|
|
376
|
-
pointer-events:
|
|
390
|
+
height: 50px !important; // Keep fixed height for consistency
|
|
391
|
+
pointer-events: auto !important;
|
|
392
|
+
margin-bottom: 6px !important;
|
|
393
|
+
|
|
394
|
+
// Ultra-smooth animations with optimized timing
|
|
395
|
+
transition: transform 0.18s cubic-bezier(0.25, 0.1, 0.25, 1), opacity 0.12s cubic-bezier(0.25, 0.1, 0.25, 1) !important;
|
|
396
|
+
transform: translate3d(0, 0, 0);
|
|
397
|
+
|
|
398
|
+
// Override default animations with smoother curves
|
|
399
|
+
&.Toastify__slide-enter-active {
|
|
400
|
+
animation: slideInRight 0.22s cubic-bezier(0.25, 0.1, 0.25, 1);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
&.Toastify__slide-exit-active {
|
|
404
|
+
animation: slideOutRight 0.12s cubic-bezier(0.4, 0, 1, 1);
|
|
405
|
+
}
|
|
377
406
|
|
|
378
407
|
&-body > div:last-child {
|
|
379
408
|
font-family: "Inter", sans-serif;
|
|
@@ -387,6 +416,29 @@ img {
|
|
|
387
416
|
}
|
|
388
417
|
}
|
|
389
418
|
|
|
419
|
+
// Tighter, more precise animations
|
|
420
|
+
@keyframes slideInRight {
|
|
421
|
+
0% {
|
|
422
|
+
transform: translateX(110%);
|
|
423
|
+
opacity: 0;
|
|
424
|
+
}
|
|
425
|
+
100% {
|
|
426
|
+
transform: translateX(0);
|
|
427
|
+
opacity: 1;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
@keyframes slideOutRight {
|
|
432
|
+
0% {
|
|
433
|
+
transform: translateX(0);
|
|
434
|
+
opacity: 1;
|
|
435
|
+
}
|
|
436
|
+
100% {
|
|
437
|
+
transform: translateX(110%);
|
|
438
|
+
opacity: 0;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
390
442
|
.Toastify__toast--error svg {
|
|
391
443
|
color: #ee8282;
|
|
392
444
|
}
|
|
@@ -401,6 +453,7 @@ img {
|
|
|
401
453
|
|
|
402
454
|
.Toastify__progress-bar {
|
|
403
455
|
height: 2px;
|
|
456
|
+
transition: width 0.08s cubic-bezier(0.25, 0.1, 0.25, 1); // Ultra-smooth progress bar
|
|
404
457
|
}
|
|
405
458
|
|
|
406
459
|
// Mobile optimizations
|
|
@@ -1,26 +1,32 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="form-section">
|
|
3
3
|
<!-- Username -->
|
|
4
|
-
<div class="input-wrapper">
|
|
5
|
-
<label
|
|
4
|
+
<div class="input-wrapper mb-4">
|
|
5
|
+
<label style="display: flex; align-items: center; font-size: 12px; margin-bottom: 8px; color: #a0a0a6">
|
|
6
|
+
Username
|
|
7
|
+
<ProfileIcon style="margin-left: 8px; width: 16px; height: 16px" />
|
|
8
|
+
</label>
|
|
6
9
|
<div class="input-default">
|
|
7
10
|
<input v-model="user" placeholder="Username" />
|
|
8
11
|
</div>
|
|
9
12
|
</div>
|
|
10
13
|
<!-- Password -->
|
|
11
14
|
<div class="input-wrapper">
|
|
12
|
-
<label
|
|
15
|
+
<label style="display: flex; align-items: center; font-size: 12px; margin-bottom: 8px; color: #a0a0a6">
|
|
16
|
+
Password
|
|
17
|
+
<KeyIcon style="margin-left: 8px; width: 16px; height: 16px" />
|
|
18
|
+
</label>
|
|
13
19
|
<div class="input-default">
|
|
14
20
|
<input v-model="password" type="password" placeholder="Password" />
|
|
15
21
|
</div>
|
|
16
22
|
</div>
|
|
17
23
|
<button
|
|
18
|
-
class="
|
|
24
|
+
class="bg-green-400 hover:bg-green-500 smooth-hover text-white font-medium px-4 py-2 rounded transition-all duration-150 w-full mt-4 h-10 flex items-center justify-center"
|
|
19
25
|
@click="login()"
|
|
20
26
|
:disabled="buttonDisabled"
|
|
21
27
|
>
|
|
22
28
|
<span v-if="!buttonDisabled">Login</span>
|
|
23
|
-
<div v-else class="loading-spinner
|
|
29
|
+
<div v-else class="loading-spinner"></div>
|
|
24
30
|
</button>
|
|
25
31
|
</div>
|
|
26
32
|
</template>
|
|
@@ -29,6 +35,7 @@ import { useUIStore } from "@/stores/ui";
|
|
|
29
35
|
import { ref } from "vue";
|
|
30
36
|
import router from "@/router/index";
|
|
31
37
|
import { sendLogin } from "@/stores/requests";
|
|
38
|
+
import { ProfileIcon, KeyIcon } from "@/components/icons";
|
|
32
39
|
|
|
33
40
|
const password = ref("");
|
|
34
41
|
const user = ref("");
|