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.
Files changed (53) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/dist/cli.mjs +2 -3
  3. package/dist/module.d.mts +1 -1
  4. package/dist/module.d.ts +1 -1
  5. package/dist/module.json +2 -2
  6. package/dist/module.mjs +55 -30
  7. package/dist/runtime/assets/nuxt-users.css +1 -0
  8. package/dist/runtime/components/NUsersList.vue +136 -0
  9. package/dist/runtime/components/NUsersList.vue.d.ts +74 -0
  10. package/dist/runtime/components/{LoginForm.vue → NUsersLoginForm.vue} +20 -22
  11. package/dist/runtime/components/{LoginForm.vue.d.ts → NUsersLoginForm.vue.d.ts} +5 -9
  12. package/dist/runtime/components/{LogoutLink.vue → NUsersLogoutLink.vue} +2 -6
  13. package/dist/runtime/components/{PasswordStrengthIndicator.vue → NUsersPasswordStrengthIndicator.vue} +18 -22
  14. package/dist/runtime/components/{ProfileInfo.vue → NUsersProfileInfo.vue} +5 -7
  15. package/dist/runtime/components/{ResetPasswordForm.vue → NUsersResetPasswordForm.vue} +25 -28
  16. package/dist/runtime/components/{ResetPasswordForm.vue.d.ts → NUsersResetPasswordForm.vue.d.ts} +3 -6
  17. package/dist/runtime/components/NUsersUserCard.vue +93 -0
  18. package/dist/runtime/components/NUsersUserCard.vue.d.ts +33 -0
  19. package/dist/runtime/components/NUsersUserForm.vue +141 -0
  20. package/dist/runtime/components/NUsersUserForm.vue.d.ts +12 -0
  21. package/dist/runtime/composables/useAuthentication.d.ts +2 -0
  22. package/dist/runtime/composables/useAuthentication.js +5 -7
  23. package/dist/runtime/composables/usePasswordValidation.d.ts +2 -2
  24. package/dist/runtime/constants.d.ts +1 -0
  25. package/dist/runtime/constants.js +2 -1
  26. package/dist/runtime/middleware/authorization.client.js +6 -1
  27. package/dist/runtime/server/api/nuxt-users/index.get.d.ts +20 -0
  28. package/dist/runtime/server/api/nuxt-users/index.get.js +58 -0
  29. package/dist/runtime/server/api/{auth/forgot-password.post.js → nuxt-users/password/forgot.post.js} +1 -1
  30. package/dist/runtime/server/api/{auth/update-password.post.js → nuxt-users/password/index.patch.js} +2 -2
  31. package/dist/runtime/server/api/{auth/reset-password.post.js → nuxt-users/password/reset.post.js} +2 -2
  32. package/dist/runtime/server/api/{auth/logout.get.js → nuxt-users/session/index.delete.js} +1 -1
  33. package/dist/runtime/server/api/{auth/login.post.d.ts → nuxt-users/session/index.post.d.ts} +1 -0
  34. package/dist/runtime/server/api/{auth/login.post.js → nuxt-users/session/index.post.js} +1 -1
  35. package/dist/runtime/server/middleware/authorization.server.js +29 -9
  36. package/dist/runtime/server/utils/user.js +8 -5
  37. package/dist/shared/{nuxt-users.CXezgQLO.d.mts → nuxt-users.B5ttEpIO.d.mts} +8 -1
  38. package/dist/shared/{nuxt-users.CXezgQLO.d.ts → nuxt-users.B5ttEpIO.d.ts} +8 -1
  39. package/dist/utils.d.mts +1 -2
  40. package/dist/utils.d.ts +1 -2
  41. package/dist/utils.mjs +162 -2
  42. package/package.json +2 -2
  43. package/dist/runtime/assets/colors.css +0 -1
  44. package/dist/shared/nuxt-users.BnOQs2LI.mjs +0 -162
  45. package/dist/runtime/components/{LogoutLink.vue.d.ts → NUsersLogoutLink.vue.d.ts} +4 -4
  46. /package/dist/runtime/components/{PasswordStrengthIndicator.vue.d.ts → NUsersPasswordStrengthIndicator.vue.d.ts} +0 -0
  47. /package/dist/runtime/components/{ProfileInfo.vue.d.ts → NUsersProfileInfo.vue.d.ts} +0 -0
  48. /package/dist/runtime/server/api/nuxt-users/{profile.get.d.ts → me.get.d.ts} +0 -0
  49. /package/dist/runtime/server/api/nuxt-users/{profile.get.js → me.get.js} +0 -0
  50. /package/dist/runtime/server/api/{auth/forgot-password.post.d.ts → nuxt-users/password/forgot.post.d.ts} +0 -0
  51. /package/dist/runtime/server/api/{auth/logout.get.d.ts → nuxt-users/password/index.patch.d.ts} +0 -0
  52. /package/dist/runtime/server/api/{auth/reset-password.post.d.ts → nuxt-users/password/reset.post.d.ts} +0 -0
  53. /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 { g as getPasswordValidationOptions, v as validatePassword } from './shared/nuxt-users.BnOQs2LI.mjs';
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.9.0"
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.CXezgQLO.mjs';
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.CXezgQLO.js';
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
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
3
  "configKey": "nuxtUsers",
4
- "version": "1.10.0",
4
+ "version": "1.12.0",
5
5
  "builder": {
6
- "@nuxt/module-builder": "1.0.1",
6
+ "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.0"
8
8
  }
9
9
  }
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: "/api/auth/login",
104
+ route: `${base}/session`,
101
105
  method: "post",
102
- handler: resolver.resolve("./runtime/server/api/auth/login.post")
106
+ handler: resolver.resolve("./runtime/server/api/nuxt-users/session/index.post")
103
107
  });
104
108
  addServerHandler({
105
- route: "/api/auth/forgot-password",
106
- method: "post",
107
- handler: resolver.resolve("./runtime/server/api/auth/forgot-password.post")
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: "/api/auth/reset-password",
111
- method: "post",
112
- handler: resolver.resolve("./runtime/server/api/auth/reset-password.post")
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: "/api/auth/logout",
116
- method: "get",
117
- handler: resolver.resolve("./runtime/server/api/auth/logout.get")
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: "/api/nuxt-users/profile",
121
- method: "get",
122
- handler: resolver.resolve("./runtime/server/api/nuxt-users/profile.get")
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: "/api/auth/update-password",
129
+ route: `${base}/password/reset`,
126
130
  method: "post",
127
- handler: resolver.resolve("./runtime/server/api/auth/update-password.post")
131
+ handler: resolver.resolve("./runtime/server/api/nuxt-users/password/reset.post")
128
132
  });
129
133
  addServerHandler({
130
- route: "/api/nuxt-users",
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: "/api/nuxt-users/:id",
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: "/api/nuxt-users/:id",
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: "/api/nuxt-users/:id",
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: "LoginForm",
180
- filePath: resolver.resolve("./runtime/components/LoginForm.vue")
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: "LogoutLink",
184
- filePath: resolver.resolve("./runtime/components/LogoutLink.vue")
208
+ name: "NUsersList",
209
+ filePath: resolver.resolve("./runtime/components/NUsersList.vue")
185
210
  });
186
211
  addComponent({
187
- name: "ProfileInfo",
188
- filePath: resolver.resolve("./runtime/components/ProfileInfo.vue")
212
+ name: "NUsersUserCard",
213
+ filePath: resolver.resolve("./runtime/components/NUsersUserCard.vue")
189
214
  });
190
215
  addComponent({
191
- name: "ResetPasswordForm",
192
- filePath: resolver.resolve("./runtime/components/ResetPasswordForm.vue")
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/colors.css"));
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, default: "/api/auth/login" },
9
- redirectTo: { type: String, required: false, default: "/" },
10
- forgotPasswordEndpoint: { type: String, required: false, default: "/api/auth/forgot-password" }
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(props.apiEndpoint, {
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(props.forgotPasswordEndpoint, {
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 & {