nuxt-users 1.10.0 → 1.12.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/CHANGELOG.md +58 -0
- package/dist/cli.mjs +2 -3
- package/dist/module.d.mts +1 -1
- package/dist/module.d.ts +1 -1
- package/dist/module.json +2 -2
- package/dist/module.mjs +55 -30
- package/dist/runtime/assets/nuxt-users.css +1 -0
- package/dist/runtime/components/NUsersList.vue +136 -0
- package/dist/runtime/components/NUsersList.vue.d.ts +74 -0
- package/dist/runtime/components/{LoginForm.vue → NUsersLoginForm.vue} +20 -22
- package/dist/runtime/components/{LoginForm.vue.d.ts → NUsersLoginForm.vue.d.ts} +5 -9
- package/dist/runtime/components/{LogoutLink.vue → NUsersLogoutLink.vue} +2 -6
- package/dist/runtime/components/{PasswordStrengthIndicator.vue → NUsersPasswordStrengthIndicator.vue} +18 -22
- package/dist/runtime/components/{ProfileInfo.vue → NUsersProfileInfo.vue} +5 -7
- package/dist/runtime/components/{ResetPasswordForm.vue → NUsersResetPasswordForm.vue} +25 -28
- package/dist/runtime/components/{ResetPasswordForm.vue.d.ts → NUsersResetPasswordForm.vue.d.ts} +3 -6
- package/dist/runtime/components/NUsersUserCard.vue +93 -0
- package/dist/runtime/components/NUsersUserCard.vue.d.ts +33 -0
- package/dist/runtime/components/NUsersUserForm.vue +141 -0
- package/dist/runtime/components/NUsersUserForm.vue.d.ts +12 -0
- package/dist/runtime/composables/useAuthentication.d.ts +2 -0
- package/dist/runtime/composables/useAuthentication.js +5 -7
- package/dist/runtime/composables/usePasswordValidation.d.ts +2 -2
- package/dist/runtime/constants.d.ts +1 -0
- package/dist/runtime/constants.js +2 -1
- package/dist/runtime/middleware/authorization.client.js +6 -1
- package/dist/runtime/server/api/nuxt-users/index.get.d.ts +20 -0
- package/dist/runtime/server/api/nuxt-users/index.get.js +58 -0
- package/dist/runtime/server/api/{auth/forgot-password.post.js → nuxt-users/password/forgot.post.js} +1 -1
- package/dist/runtime/server/api/{auth/update-password.post.js → nuxt-users/password/index.patch.js} +2 -2
- package/dist/runtime/server/api/{auth/reset-password.post.js → nuxt-users/password/reset.post.js} +2 -2
- package/dist/runtime/server/api/{auth/logout.get.js → nuxt-users/session/index.delete.js} +1 -1
- package/dist/runtime/server/api/{auth/login.post.d.ts → nuxt-users/session/index.post.d.ts} +1 -0
- package/dist/runtime/server/api/{auth/login.post.js → nuxt-users/session/index.post.js} +1 -1
- package/dist/runtime/server/middleware/authorization.server.js +29 -9
- package/dist/runtime/server/utils/user.js +8 -5
- package/dist/shared/{nuxt-users.CXezgQLO.d.mts → nuxt-users.B5ttEpIO.d.mts} +8 -1
- package/dist/shared/{nuxt-users.CXezgQLO.d.ts → nuxt-users.B5ttEpIO.d.ts} +8 -1
- package/dist/utils.d.mts +1 -2
- package/dist/utils.d.ts +1 -2
- package/dist/utils.mjs +162 -2
- package/package.json +2 -2
- package/dist/runtime/assets/colors.css +0 -1
- package/dist/shared/nuxt-users.BnOQs2LI.mjs +0 -162
- package/dist/runtime/components/{LogoutLink.vue.d.ts → NUsersLogoutLink.vue.d.ts} +4 -4
- /package/dist/runtime/components/{PasswordStrengthIndicator.vue.d.ts → NUsersPasswordStrengthIndicator.vue.d.ts} +0 -0
- /package/dist/runtime/components/{ProfileInfo.vue.d.ts → NUsersProfileInfo.vue.d.ts} +0 -0
- /package/dist/runtime/server/api/nuxt-users/{profile.get.d.ts → me.get.d.ts} +0 -0
- /package/dist/runtime/server/api/nuxt-users/{profile.get.js → me.get.js} +0 -0
- /package/dist/runtime/server/api/{auth/forgot-password.post.d.ts → nuxt-users/password/forgot.post.d.ts} +0 -0
- /package/dist/runtime/server/api/{auth/logout.get.d.ts → nuxt-users/password/index.patch.d.ts} +0 -0
- /package/dist/runtime/server/api/{auth/reset-password.post.d.ts → nuxt-users/password/reset.post.d.ts} +0 -0
- /package/dist/runtime/server/api/{auth/update-password.post.d.ts → nuxt-users/session/index.delete.d.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v1.12.0
|
|
5
|
+
|
|
6
|
+
[compare changes](https://github.com/rrd108/nuxt-users/compare/v1.11.0...v1.12.0)
|
|
7
|
+
|
|
8
|
+
### 🚀 Enhancements
|
|
9
|
+
|
|
10
|
+
- Enhances user management and login flow ([894563a](https://github.com/rrd108/nuxt-users/commit/894563a))
|
|
11
|
+
- Implement new components ([87689c9](https://github.com/rrd108/nuxt-users/commit/87689c9))
|
|
12
|
+
|
|
13
|
+
### 🩹 Fixes
|
|
14
|
+
|
|
15
|
+
- Authorization middleware for API routes closes #76 ([#76](https://github.com/rrd108/nuxt-users/issues/76))
|
|
16
|
+
- (temporary) strange test failing ([ff8c376](https://github.com/rrd108/nuxt-users/commit/ff8c376))
|
|
17
|
+
|
|
18
|
+
### 💅 Refactors
|
|
19
|
+
|
|
20
|
+
- Component names ([159f334](https://github.com/rrd108/nuxt-users/commit/159f334))
|
|
21
|
+
- Css classes renamed, removed or added ([c5c3334](https://github.com/rrd108/nuxt-users/commit/c5c3334))
|
|
22
|
+
- Remove unused css ([7abc8a5](https://github.com/rrd108/nuxt-users/commit/7abc8a5))
|
|
23
|
+
|
|
24
|
+
### 🏡 Chore
|
|
25
|
+
|
|
26
|
+
- Bump version number ([5045086](https://github.com/rrd108/nuxt-users/commit/5045086))
|
|
27
|
+
|
|
28
|
+
### ✅ Tests
|
|
29
|
+
|
|
30
|
+
- Fix ([cbf300c](https://github.com/rrd108/nuxt-users/commit/cbf300c))
|
|
31
|
+
- Fix failing test ([3973eec](https://github.com/rrd108/nuxt-users/commit/3973eec))
|
|
32
|
+
|
|
33
|
+
### ❤️ Contributors
|
|
34
|
+
|
|
35
|
+
- Rrd108 ([@rrd108](https://github.com/rrd108))
|
|
36
|
+
|
|
37
|
+
## v1.11.0
|
|
38
|
+
|
|
39
|
+
[compare changes](https://github.com/rrd108/nuxt-users/compare/v1.9.0...v1.11.0)
|
|
40
|
+
|
|
41
|
+
### 🚀 Enhancements
|
|
42
|
+
|
|
43
|
+
- Add user CRUD API endpoints ([8cf5e0a](https://github.com/rrd108/nuxt-users/commit/8cf5e0a))
|
|
44
|
+
- Add user API endpoints ([05f326e](https://github.com/rrd108/nuxt-users/commit/05f326e))
|
|
45
|
+
|
|
46
|
+
### 💅 Refactors
|
|
47
|
+
|
|
48
|
+
- API endpoints ([784dca4](https://github.com/rrd108/nuxt-users/commit/784dca4))
|
|
49
|
+
|
|
50
|
+
### 🏡 Chore
|
|
51
|
+
|
|
52
|
+
- **release:** V1.10.0 ([d6100db](https://github.com/rrd108/nuxt-users/commit/d6100db))
|
|
53
|
+
|
|
54
|
+
### ✅ Tests
|
|
55
|
+
|
|
56
|
+
- Skip for jiti error ([ce562c5](https://github.com/rrd108/nuxt-users/commit/ce562c5))
|
|
57
|
+
|
|
58
|
+
### ❤️ Contributors
|
|
59
|
+
|
|
60
|
+
- Rrd108 ([@rrd108](https://github.com/rrd108))
|
|
61
|
+
|
|
4
62
|
## v1.10.0
|
|
5
63
|
|
|
6
64
|
[compare changes](https://github.com/rrd108/nuxt-users/compare/v1.9.0...v1.10.0)
|
package/dist/cli.mjs
CHANGED
|
@@ -4,8 +4,7 @@ import { defaultOptions } from './module.mjs';
|
|
|
4
4
|
import { loadNuxt } from '@nuxt/kit';
|
|
5
5
|
import { defu } from 'defu';
|
|
6
6
|
import { runMigrations, createUser as createUser$1, createUsersTable as createUsersTable$1, createPersonalAccessTokensTable as createPersonalAccessTokensTable$1, createPasswordResetTokensTable as createPasswordResetTokensTable$1, createMigrationsTable as createMigrationsTable$1 } from '../dist/runtime/server/utils/index.js';
|
|
7
|
-
import {
|
|
8
|
-
import '../dist/runtime/server/utils/user.js';
|
|
7
|
+
import { getPasswordValidationOptions, validatePassword } from './utils.mjs';
|
|
9
8
|
|
|
10
9
|
const getOptionsFromEnv = () => {
|
|
11
10
|
const connectorName = process.env.DB_CONNECTOR || "sqlite";
|
|
@@ -248,7 +247,7 @@ const main = defineCommand({
|
|
|
248
247
|
meta: {
|
|
249
248
|
name: "nuxt-users",
|
|
250
249
|
description: "CLI for Nuxt Users Module - Manage users, migrations, and database operations",
|
|
251
|
-
version: "1.
|
|
250
|
+
version: "1.11.0"
|
|
252
251
|
},
|
|
253
252
|
subCommands: {
|
|
254
253
|
migrate,
|
package/dist/module.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
-
import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.
|
|
2
|
+
import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.B5ttEpIO.mjs';
|
|
3
3
|
export * from '../dist/runtime/server/utils/index.js';
|
|
4
4
|
|
|
5
5
|
declare const defaultOptions: ModuleOptions;
|
package/dist/module.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
-
import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.
|
|
2
|
+
import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.B5ttEpIO.js';
|
|
3
3
|
export * from '../dist/runtime/server/utils/index.js';
|
|
4
4
|
|
|
5
5
|
declare const defaultOptions: ModuleOptions;
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -9,6 +9,7 @@ const defaultOptions = {
|
|
|
9
9
|
path: "./data/users.sqlite3"
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
|
+
apiBasePath: "/api/nuxt-users",
|
|
12
13
|
tables: {
|
|
13
14
|
migrations: "migrations",
|
|
14
15
|
users: "users",
|
|
@@ -59,6 +60,7 @@ const module = defineNuxtModule({
|
|
|
59
60
|
const runtimeConfigOptions = defu(options, nuxt.options.runtimeConfig.nuxtUsers || {}, defaultOptions);
|
|
60
61
|
nuxt.options.runtimeConfig.nuxtUsers = {
|
|
61
62
|
...runtimeConfigOptions,
|
|
63
|
+
apiBasePath: options.apiBasePath || defaultOptions.apiBasePath,
|
|
62
64
|
tables: {
|
|
63
65
|
migrations: options.tables?.migrations || defaultOptions.tables.migrations,
|
|
64
66
|
users: options.tables?.users || defaultOptions.tables.users,
|
|
@@ -84,7 +86,8 @@ const module = defineNuxtModule({
|
|
|
84
86
|
auth: {
|
|
85
87
|
whitelist: [...defaultOptions.auth?.whitelist || [], ...options.auth?.whitelist || []],
|
|
86
88
|
permissions: options.auth?.permissions || defaultOptions.auth.permissions
|
|
87
|
-
}
|
|
89
|
+
},
|
|
90
|
+
apiBasePath: options.apiBasePath || defaultOptions.apiBasePath
|
|
88
91
|
};
|
|
89
92
|
addPlugin({
|
|
90
93
|
src: resolver.resolve("./runtime/plugin"),
|
|
@@ -96,53 +99,59 @@ const module = defineNuxtModule({
|
|
|
96
99
|
path: resolver.resolve("./runtime/middleware/authorization.client"),
|
|
97
100
|
global: true
|
|
98
101
|
});
|
|
102
|
+
const base = nuxt.options.runtimeConfig.nuxtUsers.apiBasePath || defaultOptions.apiBasePath;
|
|
99
103
|
addServerHandler({
|
|
100
|
-
route:
|
|
104
|
+
route: `${base}/session`,
|
|
101
105
|
method: "post",
|
|
102
|
-
handler: resolver.resolve("./runtime/server/api/
|
|
106
|
+
handler: resolver.resolve("./runtime/server/api/nuxt-users/session/index.post")
|
|
103
107
|
});
|
|
104
108
|
addServerHandler({
|
|
105
|
-
route:
|
|
106
|
-
method: "
|
|
107
|
-
handler: resolver.resolve("./runtime/server/api/
|
|
109
|
+
route: `${base}/session`,
|
|
110
|
+
method: "delete",
|
|
111
|
+
handler: resolver.resolve("./runtime/server/api/nuxt-users/session/index.delete")
|
|
108
112
|
});
|
|
109
113
|
addServerHandler({
|
|
110
|
-
route:
|
|
111
|
-
method: "
|
|
112
|
-
handler: resolver.resolve("./runtime/server/api/
|
|
114
|
+
route: `${base}/me`,
|
|
115
|
+
method: "get",
|
|
116
|
+
handler: resolver.resolve("./runtime/server/api/nuxt-users/me.get")
|
|
113
117
|
});
|
|
114
118
|
addServerHandler({
|
|
115
|
-
route:
|
|
116
|
-
method: "
|
|
117
|
-
handler: resolver.resolve("./runtime/server/api/
|
|
119
|
+
route: `${base}/password`,
|
|
120
|
+
method: "patch",
|
|
121
|
+
handler: resolver.resolve("./runtime/server/api/nuxt-users/password/index.patch")
|
|
118
122
|
});
|
|
119
123
|
addServerHandler({
|
|
120
|
-
route:
|
|
121
|
-
method: "
|
|
122
|
-
handler: resolver.resolve("./runtime/server/api/nuxt-users/
|
|
124
|
+
route: `${base}/password/forgot`,
|
|
125
|
+
method: "post",
|
|
126
|
+
handler: resolver.resolve("./runtime/server/api/nuxt-users/password/forgot.post")
|
|
123
127
|
});
|
|
124
128
|
addServerHandler({
|
|
125
|
-
route:
|
|
129
|
+
route: `${base}/password/reset`,
|
|
126
130
|
method: "post",
|
|
127
|
-
handler: resolver.resolve("./runtime/server/api/
|
|
131
|
+
handler: resolver.resolve("./runtime/server/api/nuxt-users/password/reset.post")
|
|
128
132
|
});
|
|
129
133
|
addServerHandler({
|
|
130
|
-
route:
|
|
134
|
+
route: `${base}`,
|
|
135
|
+
method: "get",
|
|
136
|
+
handler: resolver.resolve("./runtime/server/api/nuxt-users/index.get")
|
|
137
|
+
});
|
|
138
|
+
addServerHandler({
|
|
139
|
+
route: `${base}`,
|
|
131
140
|
method: "post",
|
|
132
141
|
handler: resolver.resolve("./runtime/server/api/nuxt-users/index.post")
|
|
133
142
|
});
|
|
134
143
|
addServerHandler({
|
|
135
|
-
route:
|
|
144
|
+
route: `${base}/:id`,
|
|
136
145
|
method: "get",
|
|
137
146
|
handler: resolver.resolve("./runtime/server/api/nuxt-users/[id].get")
|
|
138
147
|
});
|
|
139
148
|
addServerHandler({
|
|
140
|
-
route:
|
|
149
|
+
route: `${base}/:id`,
|
|
141
150
|
method: "patch",
|
|
142
151
|
handler: resolver.resolve("./runtime/server/api/nuxt-users/[id].patch")
|
|
143
152
|
});
|
|
144
153
|
addServerHandler({
|
|
145
|
-
route:
|
|
154
|
+
route: `${base}/:id`,
|
|
146
155
|
method: "delete",
|
|
147
156
|
handler: resolver.resolve("./runtime/server/api/nuxt-users/[id].delete")
|
|
148
157
|
});
|
|
@@ -176,23 +185,39 @@ const module = defineNuxtModule({
|
|
|
176
185
|
nitroConfig.scanDirs.push(resolver.resolve("./runtime/server/tasks"));
|
|
177
186
|
});
|
|
178
187
|
addComponent({
|
|
179
|
-
name: "
|
|
180
|
-
filePath: resolver.resolve("./runtime/components/
|
|
188
|
+
name: "NUsersLoginForm",
|
|
189
|
+
filePath: resolver.resolve("./runtime/components/NUsersLoginForm.vue")
|
|
190
|
+
});
|
|
191
|
+
addComponent({
|
|
192
|
+
name: "NUsersLogoutLink",
|
|
193
|
+
filePath: resolver.resolve("./runtime/components/NUsersLogoutLink.vue")
|
|
194
|
+
});
|
|
195
|
+
addComponent({
|
|
196
|
+
name: "NUsersProfileInfo",
|
|
197
|
+
filePath: resolver.resolve("./runtime/components/NUsersProfileInfo.vue")
|
|
198
|
+
});
|
|
199
|
+
addComponent({
|
|
200
|
+
name: "NUsersResetPasswordForm",
|
|
201
|
+
filePath: resolver.resolve("./runtime/components/NUsersResetPasswordForm.vue")
|
|
202
|
+
});
|
|
203
|
+
addComponent({
|
|
204
|
+
name: "NUsersPasswordStrengthIndicator",
|
|
205
|
+
filePath: resolver.resolve("./runtime/components/NUsersPasswordStrengthIndicator.vue")
|
|
181
206
|
});
|
|
182
207
|
addComponent({
|
|
183
|
-
name: "
|
|
184
|
-
filePath: resolver.resolve("./runtime/components/
|
|
208
|
+
name: "NUsersList",
|
|
209
|
+
filePath: resolver.resolve("./runtime/components/NUsersList.vue")
|
|
185
210
|
});
|
|
186
211
|
addComponent({
|
|
187
|
-
name: "
|
|
188
|
-
filePath: resolver.resolve("./runtime/components/
|
|
212
|
+
name: "NUsersUserCard",
|
|
213
|
+
filePath: resolver.resolve("./runtime/components/NUsersUserCard.vue")
|
|
189
214
|
});
|
|
190
215
|
addComponent({
|
|
191
|
-
name: "
|
|
192
|
-
filePath: resolver.resolve("./runtime/components/
|
|
216
|
+
name: "NUsersUserForm",
|
|
217
|
+
filePath: resolver.resolve("./runtime/components/NUsersUserForm.vue")
|
|
193
218
|
});
|
|
194
219
|
nuxt.options.css = nuxt.options.css || [];
|
|
195
|
-
nuxt.options.css.push(resolver.resolve("./runtime/assets/
|
|
220
|
+
nuxt.options.css.push(resolver.resolve("./runtime/assets/nuxt-users.css"));
|
|
196
221
|
}
|
|
197
222
|
});
|
|
198
223
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--color-primary:#3b82f6;--color-primary-dark:#2563eb;--color-primary-light:#60a5fa;--color-secondary:#059669;--color-secondary-dark:#047857;--color-secondary-light:#10b981;--color-white:#fff;--color-black:#000;--color-gray-50:#f9fafb;--color-gray-100:#f3f4f6;--color-gray-200:#e5e7eb;--color-gray-300:#d1d5db;--color-gray-400:#9ca3af;--color-gray-500:#6b7280;--color-gray-600:#4b5563;--color-gray-700:#374151;--color-gray-800:#1f2937;--color-gray-900:#111827;--color-success:#059669;--color-success-light:#f0fdf4;--color-success-border:#9ae6b4;--color-warning:#f59e0b;--color-warning-light:#fef3c7;--color-warning-text:#92400e;--color-error:#dc2626;--color-error-light:#fef2f2;--color-error-border:#fecaca;--color-error-alt:#db5050;--color-error-dark:#b91c1c;--color-border:#e5e7eb;--color-border-light:#d1d5db;--color-border-dark:#ccc;--color-bg-primary:#fff;--color-bg-secondary:#f9fafb;--color-bg-tertiary:#f3f4f6;--color-text-primary:#000;--color-text-secondary:#6b7280;--color-strength-weak:#dc3545;--color-strength-medium:#ffc107;--color-strength-strong:#28a745;--color-strength-unknown:#6c757d;--color-strength-bg:#e9ecef;--color-validation-passed:#28a745;--color-validation-failed:#dc3545;--color-validation-hint:#6c757d}@media (prefers-color-scheme:dark){.dark,:root{--color-primary:#60a5fa;--color-primary-dark:#3b82f6;--color-primary-light:#93c5fd;--color-secondary:#10b981;--color-secondary-dark:#059669;--color-secondary-light:#34d399;--color-white:#000;--color-black:#fff;--color-gray-50:#111827;--color-gray-100:#1f2937;--color-gray-200:#374151;--color-gray-300:#4b5563;--color-gray-400:#6b7280;--color-gray-500:#9ca3af;--color-gray-600:#d1d5db;--color-gray-700:#e5e7eb;--color-gray-800:#f3f4f6;--color-gray-900:#f9fafb;--color-success:#10b981;--color-success-light:#064e3b;--color-success-border:#065f46;--color-warning:#fbbf24;--color-warning-light:#451a03;--color-warning-text:#fef3c7;--color-error:#ef4444;--color-error-light:#450a0a;--color-error-border:#7f1d1d;--color-error-alt:#f87171;--color-error-dark:#dc2626;--color-border:#374151;--color-border-light:#4b5563;--color-border-dark:#6b7280;--color-bg-primary:#111827;--color-bg-secondary:#1f2937;--color-bg-tertiary:#374151;--color-text-primary:#fff;--color-text-secondary:#9ca3af;--color-strength-weak:#ef4444;--color-strength-medium:#fbbf24;--color-strength-strong:#22c55e;--color-strength-unknown:#9ca3af;--color-strength-bg:#374151;--color-validation-passed:#22c55e;--color-validation-failed:#ef4444;--color-validation-hint:#9ca3af}}.n-users-form-group{display:flex;flex-direction:column;gap:.5rem;margin-bottom:1rem;width:100%}.n-users-form-group.n-users-form-group-narrow{width:90%}.n-users-form-group label,.n-users-form-label{color:var(--color-text-primary,#000);display:block;font-size:1rem;font-weight:500;margin-bottom:.5rem}.n-users-form-group input,.n-users-form-group select,.n-users-form-input{border:1px solid var(--color-border,#ccc);border-radius:.25rem;font-size:1rem;padding:.5rem;transition:border-color .2s,box-shadow .2s;width:100%}.n-users-form-group input:focus,.n-users-form-group select:focus,.n-users-form-input:focus{border-color:var(--color-primary);box-shadow:0 0 0 .1rem rgba(59,130,246,.1);outline:none}.n-users-form-group input:disabled,.n-users-form-input:disabled{background-color:var(--color-bg-secondary,#f9fafb);cursor:not-allowed}.n-users-form-group input.error,.n-users-form-input.error{border-color:var(--color-error,#dc3545)}.n-users-form-group input[type=email],.n-users-form-group input[type=password],.n-users-form-group input[type=text]{background-color:var(--color-bg-secondary);border:1px solid var(--color-border-light);border-radius:.5em;box-sizing:border-box;color:var(--color-gray-700);font-size:.875rem;padding:.75em 1em;transition:all .2s ease-in-out;width:100%}.n-users-form-group input[type=email]:focus,.n-users-form-group input[type=password]:focus,.n-users-form-group input[type=text]:focus{background-color:var(--color-bg-primary);border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(59,130,246,.1);outline:none}.n-users-form-group input[type=checkbox]{margin:0;width:auto}.n-users-btn,.n-users-cancel-btn,.n-users-delete-btn,.n-users-edit-btn,.n-users-submit-btn{align-self:flex-start;border:none;border-radius:.25rem;cursor:pointer;display:inline-block;font-size:1rem;font-weight:500;padding:.5rem 1rem;text-decoration:none;transition:background-color .2s,transform .1s}.n-users-btn:hover,.n-users-edit-btn:hover,.n-users-submit-btn:hover{background-color:var(--color-primary-dark,#2563eb);transform:translateY(-1px)}.n-users-btn:disabled,.n-users-submit-btn:disabled{cursor:not-allowed;opacity:.6;transform:none}.n-users-btn-primary,.n-users-edit-btn,.n-users-submit-btn{background-color:var(--color-primary,#3b82f6);color:#fff}.n-users-btn-secondary,.n-users-cancel-btn{background-color:var(--color-gray-500,#6b7280);color:#fff}.n-users-cancel-btn:hover{background-color:var(--color-gray-600,#4b5563)}.n-users-delete-btn{background-color:var(--color-error,#ef4444);color:#fff}.n-users-delete-btn:hover{background-color:var(--color-error-alt,#dc2626)}button[type=submit]{align-items:center;background-color:var(--color-primary);border:1px solid var(--color-primary);border-radius:.5em;color:var(--color-white);cursor:pointer;display:flex;font-size:.875rem;font-weight:500;gap:.5rem;justify-content:center;min-height:44px;padding:.75em 1em;transition:all .2s ease-in-out;width:100%}button[type=submit]:hover:not(:disabled){background-color:var(--color-primary-dark);border-color:var(--color-primary-dark)}button[type=submit]:disabled{cursor:not-allowed;opacity:.6}.n-users-error-message,.n-users-error-text{background-color:var(--color-error-light,#fef2f2);border:1px solid var(--color-error-border,#fecaca);border-radius:.5rem;color:var(--color-error,#dc3545);font-size:.875rem;margin-top:.5rem;padding:.75rem 1rem}.n-users-success-message{background-color:var(--color-success-light,#f0fff4);border:1px solid var(--color-success-border,#9ae6b4);border-radius:.5rem;color:var(--color-success,#10b981);font-size:.875rem;margin-top:.5rem;padding:.75rem 1rem}.n-users-grid{display:grid;gap:1rem}.n-users-grid-2{grid-template-columns:1fr 5fr}.n-users-grid-auto{grid-template-columns:repeat(auto-fill,minmax(12em,1fr))}.n-users-form-actions{align-items:center;display:flex;gap:1rem;margin-top:1.5rem}.n-users-loading-spinner{animation:n-users-spin 1s linear infinite;border:2px solid var(--color-white,#fff);border-radius:50%;border-top:2px solid transparent;height:16px;width:16px}@keyframes n-users-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.n-users-card{background-color:var(--color-bg-primary,#fff);border:1px solid var(--color-border,#e5e7eb);border-radius:.5rem;padding:1rem}.n-users-section{margin:0 auto;max-width:42em;padding:1em}.n-users-section-header{border-bottom:.1em solid var(--color-border,#e5e7eb);margin-bottom:1rem;padding-bottom:.5rem}.n-users-form-help{color:var(--color-text-secondary,#6b7280);font-size:.75rem;margin-top:.25rem}.n-users-strength-bar{background-color:var(--color-strength-bg,#e9ecef);border-radius:2px;height:4px;margin-bottom:4px;overflow:hidden;width:100%}.n-users-strength-fill{height:100%;transition:width .3s ease,background-color .3s ease}.n-users-strength-text{font-size:.75rem;font-weight:500}.n-users-validation-container{background-color:#f8f9fa;border:1px solid #e9ecef;border-radius:4px;margin-top:12px;padding:12px}.n-users-container-title{color:#495057;font-size:.875rem;font-weight:600;margin-bottom:8px}.n-users-list-unstyled{list-style:none;margin:0;padding-left:0}.n-users-list-item{align-items:center;display:flex;font-size:.875rem;margin-bottom:6px;transition:color .2s ease}.n-users-list-item:last-child{margin-bottom:0}.n-users-icon{font-weight:700;margin-right:8px;min-width:16px;text-align:center}.n-users-text{flex:1}.n-users-list ul{list-style:none}form{display:flex;flex-direction:column;gap:1.5rem}.n-users-login-section{background:linear-gradient(135deg,var(--color-bg-primary) 0,var(--color-bg-secondary) 100%);border:1px solid var(--color-border);border-radius:.75em;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);max-width:25rem}.n-users-login-header{margin-bottom:1em;text-align:center}.n-users-login-title{color:var(--color-gray-800);font-size:1.875rem;font-weight:700;line-height:1.2;margin:0 0 .5em}.n-users-login-subtitle{color:var(--color-gray-700);font-size:.875rem;line-height:1.5;margin:0}.n-users-remember-me{align-items:center;display:flex;gap:.5rem}.n-users-remember-me label{color:var(--color-gray-700);font-size:.875rem;margin:0}.n-users-login-footer{margin-top:1em;text-align:center}.n-users-forgot-password{margin:0}.n-users-forgot-link{align-items:center;color:var(--color-primary);display:inline-flex;font-size:.875rem;gap:.5rem;text-decoration:none;transition:color .2s ease-in-out}.n-users-forgot-link:hover:not(.loading){color:var(--color-primary-dark);text-decoration:underline}.n-users-forgot-link.loading{cursor:not-allowed;opacity:.6;pointer-events:none}.n-users-password-strength{margin-top:8px}.n-users-validation-rules{margin-top:12px}.n-users-rules-title{margin-bottom:8px}.n-users-rules-list{padding-left:0}.n-users-rule-item{margin-bottom:6px}.n-users-rule-item:last-child{margin-bottom:0}.n-users-rule-passed{color:var(--color-validation-passed,#28a745)}.n-users-rule-failed{color:var(--color-validation-failed,#dc3545)}.n-users-rule-icon{margin-right:8px}.n-users-rule-text{flex:1}.n-users-validation-errors{margin-top:8px}.n-users-error-text:last-child{margin-bottom:0}.n-users-password-hints{margin-top:8px}.n-users-hint-title{margin-bottom:8px}.n-users-hint-list{padding-left:16px}.n-users-hint-item{color:var(--color-validation-hint,#6c757d);margin-bottom:4px}.n-users-hint-item:last-child{margin-bottom:0}.n-users-logout-link{background:var(--color-error);border:2px solid transparent;border-color:var(--color-error-dark);border-radius:.5em;color:#fff;padding:.5em 1em;text-decoration:none}.n-users-logout-link:hover{border-color:var(--color-primary)}.n-users-user-card dd{margin:0}.n-users-user-card-actions{display:flex;gap:.5rem;margin-top:1rem}.n-users-user-form{margin:0 auto;max-width:500px}.n-users-form-group input.error{border-color:#dc3545}.n-users-profile-info{background-color:var(--color-bg-primary);border-top-left-radius:.5em;border-top-right-radius:.5em}.n-users-profile-details{font-size:1rem;list-style:none;margin:0;padding:0}.n-users-reset-password-container{background-color:var(--color-bg-primary);border-bottom-left-radius:.5em;border-bottom-right-radius:.5em}@media (max-width:480px){.n-users-login-title{font-size:1.5rem}}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, onMounted } from "vue";
|
|
3
|
+
import { useRuntimeConfig } from "#imports";
|
|
4
|
+
import { defaultDisplayFields, defaultFieldLabels } from "../../types";
|
|
5
|
+
defineProps({
|
|
6
|
+
displayFields: { type: Array, required: false, default: () => defaultDisplayFields },
|
|
7
|
+
fieldLabels: { type: Object, required: false, default: () => defaultFieldLabels }
|
|
8
|
+
});
|
|
9
|
+
defineEmits(["editClick", "delete"]);
|
|
10
|
+
const { public: { nuxtUsers } } = useRuntimeConfig();
|
|
11
|
+
const users = ref([]);
|
|
12
|
+
const pagination = ref(null);
|
|
13
|
+
const loading = ref(false);
|
|
14
|
+
const error = ref(null);
|
|
15
|
+
const fetchUsers = async (page = 1, limit = 100) => {
|
|
16
|
+
loading.value = true;
|
|
17
|
+
error.value = null;
|
|
18
|
+
try {
|
|
19
|
+
const response = await $fetch(`${nuxtUsers.apiBasePath}?page=${page}&limit=${limit}`);
|
|
20
|
+
users.value = response.users;
|
|
21
|
+
pagination.value = response.pagination;
|
|
22
|
+
} catch (err) {
|
|
23
|
+
error.value = err instanceof Error ? err.message : "Failed to fetch users";
|
|
24
|
+
} finally {
|
|
25
|
+
loading.value = false;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const refresh = () => {
|
|
29
|
+
fetchUsers();
|
|
30
|
+
};
|
|
31
|
+
defineExpose({
|
|
32
|
+
refresh
|
|
33
|
+
});
|
|
34
|
+
onMounted(() => {
|
|
35
|
+
fetchUsers();
|
|
36
|
+
});
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
40
|
+
<div class="n-users-list">
|
|
41
|
+
<slot name="title">
|
|
42
|
+
<h2>
|
|
43
|
+
Users List
|
|
44
|
+
</h2>
|
|
45
|
+
</slot>
|
|
46
|
+
|
|
47
|
+
<slot
|
|
48
|
+
name="loading"
|
|
49
|
+
:loading="loading"
|
|
50
|
+
>
|
|
51
|
+
<div v-if="loading">
|
|
52
|
+
Loading users...
|
|
53
|
+
</div>
|
|
54
|
+
</slot>
|
|
55
|
+
|
|
56
|
+
<slot
|
|
57
|
+
name="error"
|
|
58
|
+
:error="error"
|
|
59
|
+
>
|
|
60
|
+
<div v-if="error">
|
|
61
|
+
Error: {{ error }}
|
|
62
|
+
</div>
|
|
63
|
+
</slot>
|
|
64
|
+
|
|
65
|
+
<slot name="noUsers">
|
|
66
|
+
<div v-if="users.length === 0">
|
|
67
|
+
No users found
|
|
68
|
+
</div>
|
|
69
|
+
</slot>
|
|
70
|
+
|
|
71
|
+
<div v-if="users.length">
|
|
72
|
+
<slot name="usersList">
|
|
73
|
+
<ul class="n-users-grid n-users-grid-auto">
|
|
74
|
+
<li
|
|
75
|
+
v-for="(user, index) in users"
|
|
76
|
+
:key="user.id"
|
|
77
|
+
>
|
|
78
|
+
<slot
|
|
79
|
+
name="user"
|
|
80
|
+
:user="user"
|
|
81
|
+
:index="index"
|
|
82
|
+
>
|
|
83
|
+
<NUsersUserCard
|
|
84
|
+
:user="user"
|
|
85
|
+
:index="index"
|
|
86
|
+
:display-fields="displayFields"
|
|
87
|
+
:field-labels="fieldLabels"
|
|
88
|
+
@edit-click="$emit('editClick', user)"
|
|
89
|
+
@delete="$emit('delete', user)"
|
|
90
|
+
/>
|
|
91
|
+
</slot>
|
|
92
|
+
</li>
|
|
93
|
+
</ul>
|
|
94
|
+
</slot>
|
|
95
|
+
|
|
96
|
+
<slot
|
|
97
|
+
name="paginationInfo"
|
|
98
|
+
:pagination="pagination"
|
|
99
|
+
>
|
|
100
|
+
<div v-if="pagination && pagination.totalPages > 1">
|
|
101
|
+
<div>
|
|
102
|
+
<span>Page {{ pagination.page }} of {{ pagination.totalPages }}</span>
|
|
103
|
+
<span>Total: {{ pagination.total }} users</span>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</slot>
|
|
107
|
+
|
|
108
|
+
<slot
|
|
109
|
+
name="pagination"
|
|
110
|
+
:pagination="pagination"
|
|
111
|
+
:fetch-users="fetchUsers"
|
|
112
|
+
:loading="loading"
|
|
113
|
+
>
|
|
114
|
+
<div v-if="pagination && pagination.totalPages > 1">
|
|
115
|
+
<div>
|
|
116
|
+
<button
|
|
117
|
+
v-if="pagination.hasPrev"
|
|
118
|
+
:disabled="loading"
|
|
119
|
+
@click="fetchUsers(pagination.page - 1)"
|
|
120
|
+
>
|
|
121
|
+
Previous
|
|
122
|
+
</button>
|
|
123
|
+
|
|
124
|
+
<button
|
|
125
|
+
v-if="pagination.hasNext"
|
|
126
|
+
:disabled="loading"
|
|
127
|
+
@click="fetchUsers(pagination.page + 1)"
|
|
128
|
+
>
|
|
129
|
+
Next
|
|
130
|
+
</button>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</slot>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</template>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type DisplayFieldsProps, type User } from '../../types.js';
|
|
2
|
+
declare var __VLS_1: {}, __VLS_3: {
|
|
3
|
+
loading: boolean;
|
|
4
|
+
}, __VLS_5: {
|
|
5
|
+
error: string | null;
|
|
6
|
+
}, __VLS_7: {}, __VLS_9: {}, __VLS_11: {
|
|
7
|
+
user: {
|
|
8
|
+
id: number;
|
|
9
|
+
email: string;
|
|
10
|
+
name: string;
|
|
11
|
+
password: string;
|
|
12
|
+
role: string;
|
|
13
|
+
created_at: string;
|
|
14
|
+
updated_at: string;
|
|
15
|
+
last_login_at?: string | undefined;
|
|
16
|
+
};
|
|
17
|
+
index: number;
|
|
18
|
+
}, __VLS_22: {
|
|
19
|
+
pagination: {
|
|
20
|
+
page: number;
|
|
21
|
+
limit: number;
|
|
22
|
+
total: number;
|
|
23
|
+
totalPages: number;
|
|
24
|
+
hasNext: boolean;
|
|
25
|
+
hasPrev: boolean;
|
|
26
|
+
} | null;
|
|
27
|
+
}, __VLS_24: {
|
|
28
|
+
pagination: {
|
|
29
|
+
page: number;
|
|
30
|
+
limit: number;
|
|
31
|
+
total: number;
|
|
32
|
+
totalPages: number;
|
|
33
|
+
hasNext: boolean;
|
|
34
|
+
hasPrev: boolean;
|
|
35
|
+
} | null;
|
|
36
|
+
fetchUsers: (page?: number, limit?: number) => Promise<void>;
|
|
37
|
+
loading: boolean;
|
|
38
|
+
};
|
|
39
|
+
type __VLS_Slots = {} & {
|
|
40
|
+
title?: (props: typeof __VLS_1) => any;
|
|
41
|
+
} & {
|
|
42
|
+
loading?: (props: typeof __VLS_3) => any;
|
|
43
|
+
} & {
|
|
44
|
+
error?: (props: typeof __VLS_5) => any;
|
|
45
|
+
} & {
|
|
46
|
+
noUsers?: (props: typeof __VLS_7) => any;
|
|
47
|
+
} & {
|
|
48
|
+
usersList?: (props: typeof __VLS_9) => any;
|
|
49
|
+
} & {
|
|
50
|
+
user?: (props: typeof __VLS_11) => any;
|
|
51
|
+
} & {
|
|
52
|
+
paginationInfo?: (props: typeof __VLS_22) => any;
|
|
53
|
+
} & {
|
|
54
|
+
pagination?: (props: typeof __VLS_24) => any;
|
|
55
|
+
};
|
|
56
|
+
declare const __VLS_component: import("vue").DefineComponent<DisplayFieldsProps, {
|
|
57
|
+
refresh: () => void;
|
|
58
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
59
|
+
delete: (user: User) => void;
|
|
60
|
+
editClick: (user: User) => void;
|
|
61
|
+
}, string, import("vue").PublicProps, Readonly<DisplayFieldsProps> & Readonly<{
|
|
62
|
+
onDelete?: ((user: User) => any) | undefined;
|
|
63
|
+
onEditClick?: ((user: User) => any) | undefined;
|
|
64
|
+
}>, {
|
|
65
|
+
displayFields: string[];
|
|
66
|
+
fieldLabels: Record<string, string>;
|
|
67
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
68
|
+
declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
|
|
69
|
+
export default _default;
|
|
70
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
71
|
+
new (): {
|
|
72
|
+
$slots: S;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { ref } from "vue";
|
|
2
|
+
import { ref, computed } from "vue";
|
|
3
3
|
import { navigateTo } from "#app";
|
|
4
4
|
import { useRuntimeConfig } from "#imports";
|
|
5
5
|
const { public: { nuxtUsers } } = useRuntimeConfig();
|
|
6
6
|
const { passwordValidation } = nuxtUsers;
|
|
7
7
|
const props = defineProps({
|
|
8
|
-
apiEndpoint: { type: String, required: false
|
|
9
|
-
redirectTo: { type: String, required: false
|
|
10
|
-
forgotPasswordEndpoint: { type: String, required: false
|
|
8
|
+
apiEndpoint: { type: String, required: false },
|
|
9
|
+
redirectTo: { type: String, required: false },
|
|
10
|
+
forgotPasswordEndpoint: { type: String, required: false }
|
|
11
11
|
});
|
|
12
12
|
const emit = defineEmits(["success", "error", "forgot-password-error", "submit", "forgot-password-success"]);
|
|
13
|
+
const apiEndpoint = computed(() => props.apiEndpoint || `${nuxtUsers?.apiBasePath}/session`);
|
|
14
|
+
const forgotPasswordEndpoint = computed(() => props.forgotPasswordEndpoint || `${nuxtUsers?.apiBasePath}/password/forgot`);
|
|
13
15
|
const isLoading = ref(false);
|
|
14
16
|
const isForgotPasswordLoading = ref(false);
|
|
15
17
|
const error = ref("");
|
|
@@ -24,7 +26,7 @@ const handleSubmit = async () => {
|
|
|
24
26
|
error.value = "";
|
|
25
27
|
try {
|
|
26
28
|
emit("submit", formData.value);
|
|
27
|
-
const response = await $fetch(
|
|
29
|
+
const response = await $fetch(apiEndpoint.value, {
|
|
28
30
|
method: "POST",
|
|
29
31
|
body: {
|
|
30
32
|
email: formData.value.email,
|
|
@@ -52,7 +54,7 @@ const handleForgotPassword = async () => {
|
|
|
52
54
|
forgotPasswordMessage.value = "";
|
|
53
55
|
error.value = "";
|
|
54
56
|
try {
|
|
55
|
-
const response = await $fetch(
|
|
57
|
+
const response = await $fetch(forgotPasswordEndpoint.value, {
|
|
56
58
|
method: "POST",
|
|
57
59
|
body: {
|
|
58
60
|
email: formData.value.email
|
|
@@ -71,15 +73,15 @@ const handleForgotPassword = async () => {
|
|
|
71
73
|
</script>
|
|
72
74
|
|
|
73
75
|
<template>
|
|
74
|
-
<section>
|
|
76
|
+
<section class="n-users-login-section n-users-section">
|
|
75
77
|
<form @submit.prevent="handleSubmit">
|
|
76
78
|
<!-- Header slot -->
|
|
77
79
|
<slot name="header">
|
|
78
|
-
<div class="login-header">
|
|
79
|
-
<h2 class="login-title">
|
|
80
|
+
<div class="n-users-login-header">
|
|
81
|
+
<h2 class="n-users-login-title">
|
|
80
82
|
Welcome Back
|
|
81
83
|
</h2>
|
|
82
|
-
<p class="login-subtitle">
|
|
84
|
+
<p class="n-users-login-subtitle">
|
|
83
85
|
Sign in to your account
|
|
84
86
|
</p>
|
|
85
87
|
</div>
|
|
@@ -87,7 +89,7 @@ const handleForgotPassword = async () => {
|
|
|
87
89
|
|
|
88
90
|
<!-- Email field -->
|
|
89
91
|
<slot name="email-field">
|
|
90
|
-
<div class="form-group">
|
|
92
|
+
<div class="n-users-form-group">
|
|
91
93
|
<label for="email">Email</label>
|
|
92
94
|
<input
|
|
93
95
|
id="email"
|
|
@@ -102,7 +104,7 @@ const handleForgotPassword = async () => {
|
|
|
102
104
|
|
|
103
105
|
<!-- Password field -->
|
|
104
106
|
<slot name="password-field">
|
|
105
|
-
<div class="form-group">
|
|
107
|
+
<div class="n-users-form-group">
|
|
106
108
|
<label for="password">Password</label>
|
|
107
109
|
<input
|
|
108
110
|
id="password"
|
|
@@ -118,7 +120,7 @@ const handleForgotPassword = async () => {
|
|
|
118
120
|
|
|
119
121
|
<!-- Remember me slot -->
|
|
120
122
|
<slot name="remember-me">
|
|
121
|
-
<div class="remember-me">
|
|
123
|
+
<div class="n-users-remember-me">
|
|
122
124
|
<input
|
|
123
125
|
id="rememberMe"
|
|
124
126
|
v-model="formData.rememberMe"
|
|
@@ -137,7 +139,7 @@ const handleForgotPassword = async () => {
|
|
|
137
139
|
>
|
|
138
140
|
<span
|
|
139
141
|
v-if="isLoading"
|
|
140
|
-
class="loading-spinner"
|
|
142
|
+
class="n-users-loading-spinner"
|
|
141
143
|
/>
|
|
142
144
|
{{ isLoading ? "Signing in..." : "Sign In" }}
|
|
143
145
|
</button>
|
|
@@ -145,17 +147,17 @@ const handleForgotPassword = async () => {
|
|
|
145
147
|
|
|
146
148
|
<!-- Footer slot -->
|
|
147
149
|
<slot name="footer">
|
|
148
|
-
<div class="login-footer">
|
|
149
|
-
<p class="forgot-password">
|
|
150
|
+
<div class="n-users-login-footer">
|
|
151
|
+
<p class="n-users-forgot-password">
|
|
150
152
|
<a
|
|
151
153
|
href="#"
|
|
152
|
-
class="forgot-link"
|
|
154
|
+
class="n-users-forgot-link"
|
|
153
155
|
:class="{ loading: isForgotPasswordLoading }"
|
|
154
156
|
@click.prevent="handleForgotPassword"
|
|
155
157
|
>
|
|
156
158
|
<span
|
|
157
159
|
v-if="isForgotPasswordLoading"
|
|
158
|
-
class="loading-spinner"
|
|
160
|
+
class="n-users-loading-spinner"
|
|
159
161
|
/>
|
|
160
162
|
{{ isForgotPasswordLoading ? "Sending..." : "Forgot your password?" }}
|
|
161
163
|
</a>
|
|
@@ -191,7 +193,3 @@ const handleForgotPassword = async () => {
|
|
|
191
193
|
</form>
|
|
192
194
|
</section>
|
|
193
195
|
</template>
|
|
194
|
-
|
|
195
|
-
<style scoped>
|
|
196
|
-
section{background:linear-gradient(135deg,var(--color-bg-primary) 0,var(--color-bg-secondary) 100%);border:1px solid var(--color-border);border-radius:.75em;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);margin:0 auto;max-width:25rem;padding:1em}form{gap:1.5rem}.form-group,form{display:flex;flex-direction:column}label{display:block;font-weight:500;margin-bottom:.5em}input[type=email],input[type=password],label{color:var(--color-gray-700);font-size:.875rem}input[type=email],input[type=password]{background-color:var(--color-bg-secondary);border:1px solid var(--color-border-light);border-radius:.5em;box-sizing:border-box;padding:.75em 1em;transition:all .2s ease-in-out;width:100%}input[type=email]:focus,input[type=password]:focus{background-color:var(--color-bg-primary);border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(59,130,246,.1);outline:none}.login-header{margin-bottom:1em;text-align:center}.login-title{color:var(--color-gray-800);font-size:1.875rem;font-weight:700;line-height:1.2;margin:0 0 .5em}.login-subtitle{color:var(--color-gray-500);font-size:.875rem;line-height:1.5;margin:0}.remember-me{align-items:center;display:flex;gap:.5rem}input[type=checkbox]{margin:0;width:auto}.remember-me label{color:var(--color-gray-700);font-size:.875rem;margin:0}button[type=submit]{align-items:center;background-color:var(--color-primary);border:1px solid var(--color-primary);border-radius:.5em;color:var(--color-white);cursor:pointer;display:flex;font-size:.875rem;font-weight:500;gap:.5rem;justify-content:center;min-height:44px;padding:.75em 1em;transition:all .2s ease-in-out;width:100%}button[type=submit]:hover:not(:disabled){background-color:var(--color-primary-dark);border-color:var(--color-primary-dark)}button[type=submit]:disabled{cursor:not-allowed;opacity:.6}.loading-spinner{animation:spin 1s linear infinite;border:2px solid var(--color-white);border-radius:50%;border-top:2px solid transparent;height:16px;width:16px}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.login-footer{margin-top:1em;text-align:center}.forgot-password{margin:0}.forgot-link{align-items:center;color:var(--color-primary);display:inline-flex;font-size:.875rem;gap:.5rem;text-decoration:none;transition:color .2s ease-in-out}.forgot-link:hover:not(.loading){color:var(--color-primary-dark);text-decoration:underline}.forgot-link.loading{cursor:not-allowed;opacity:.6;pointer-events:none}.error-message{background-color:var(--color-error-light);border:1px solid var(--color-error-border);border-radius:.5em;color:var(--color-error)}.error-message,.success-message{font-size:.875rem;margin-top:1em;padding:.75em 1em;text-align:center}.success-message{background-color:var(--color-success-light);border:1px solid var(--color-success-border);border-radius:.5em;color:var(--color-success)}@media (max-width:480px){.login-form-container{margin:1em;padding:1.5em}.login-title{font-size:1.5rem}}
|
|
197
|
-
</style>
|
|
@@ -22,22 +22,18 @@ type __VLS_Slots = {} & {
|
|
|
22
22
|
'error-message'?: (props: typeof __VLS_15) => any;
|
|
23
23
|
};
|
|
24
24
|
declare const __VLS_component: import("vue").DefineComponent<LoginFormProps, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
25
|
-
success: (user: UserWithoutPassword) => any;
|
|
26
25
|
error: (error: string) => any;
|
|
27
|
-
"forgot-password-error": (error: string) => any;
|
|
28
26
|
submit: (data: LoginFormData) => any;
|
|
27
|
+
success: (user: UserWithoutPassword) => any;
|
|
28
|
+
"forgot-password-error": (error: string) => any;
|
|
29
29
|
"forgot-password-success": () => any;
|
|
30
30
|
}, string, import("vue").PublicProps, Readonly<LoginFormProps> & Readonly<{
|
|
31
|
-
onSuccess?: ((user: UserWithoutPassword) => any) | undefined;
|
|
32
31
|
onError?: ((error: string) => any) | undefined;
|
|
33
|
-
"onForgot-password-error"?: ((error: string) => any) | undefined;
|
|
34
32
|
onSubmit?: ((data: LoginFormData) => any) | undefined;
|
|
33
|
+
onSuccess?: ((user: UserWithoutPassword) => any) | undefined;
|
|
34
|
+
"onForgot-password-error"?: ((error: string) => any) | undefined;
|
|
35
35
|
"onForgot-password-success"?: (() => any) | undefined;
|
|
36
|
-
}>, {
|
|
37
|
-
apiEndpoint: string;
|
|
38
|
-
redirectTo: string;
|
|
39
|
-
forgotPasswordEndpoint: string;
|
|
40
|
-
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
36
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
41
37
|
declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
|
|
42
38
|
export default _default;
|
|
43
39
|
type __VLS_WithSlots<T, S> = T & {
|