nuxt-users 1.11.0 → 1.13.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 (44) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +27 -59
  3. package/dist/cli.mjs +2 -3
  4. package/dist/module.d.mts +1 -1
  5. package/dist/module.d.ts +1 -1
  6. package/dist/module.json +1 -1
  7. package/dist/module.mjs +35 -9
  8. package/dist/runtime/assets/nuxt-users.css +1 -0
  9. package/dist/runtime/components/NUsersList.vue +136 -0
  10. package/dist/runtime/components/NUsersList.vue.d.ts +74 -0
  11. package/dist/runtime/components/{LoginForm.vue → NUsersLoginForm.vue} +20 -23
  12. package/dist/runtime/components/{LoginForm.vue.d.ts → NUsersLoginForm.vue.d.ts} +5 -9
  13. package/dist/runtime/components/{LogoutLink.vue → NUsersLogoutLink.vue} +2 -6
  14. package/dist/runtime/components/{PasswordStrengthIndicator.vue → NUsersPasswordStrengthIndicator.vue} +18 -22
  15. package/dist/runtime/components/{ProfileInfo.vue → NUsersProfileInfo.vue} +5 -7
  16. package/dist/runtime/components/{ResetPasswordForm.vue → NUsersResetPasswordForm.vue} +22 -26
  17. package/dist/runtime/components/{ResetPasswordForm.vue.d.ts → NUsersResetPasswordForm.vue.d.ts} +3 -6
  18. package/dist/runtime/components/NUsersUserCard.vue +93 -0
  19. package/dist/runtime/components/NUsersUserCard.vue.d.ts +33 -0
  20. package/dist/runtime/components/NUsersUserForm.vue +141 -0
  21. package/dist/runtime/components/NUsersUserForm.vue.d.ts +12 -0
  22. package/dist/runtime/composables/useAuthentication.d.ts +2 -0
  23. package/dist/runtime/composables/usePasswordValidation.d.ts +2 -2
  24. package/dist/runtime/middleware/authorization.client.js +1 -1
  25. package/dist/runtime/server/api/nuxt-users/index.get.d.ts +20 -0
  26. package/dist/runtime/server/api/nuxt-users/index.get.js +58 -0
  27. package/dist/runtime/server/api/nuxt-users/me.patch.d.ts +4 -0
  28. package/dist/runtime/server/api/nuxt-users/me.patch.js +33 -0
  29. package/dist/runtime/server/api/nuxt-users/session/index.post.d.ts +1 -0
  30. package/dist/runtime/server/middleware/authorization.server.js +24 -9
  31. package/dist/runtime/server/utils/user.js +20 -18
  32. package/dist/runtime/utils/permissions.d.ts +5 -3
  33. package/dist/runtime/utils/permissions.js +20 -4
  34. package/dist/shared/{nuxt-users.C7T1DKBV.d.ts → nuxt-users.u7N4meyI.d.mts} +8 -2
  35. package/dist/shared/{nuxt-users.C7T1DKBV.d.mts → nuxt-users.u7N4meyI.d.ts} +8 -2
  36. package/dist/utils.d.mts +1 -2
  37. package/dist/utils.d.ts +1 -2
  38. package/dist/utils.mjs +162 -2
  39. package/package.json +1 -1
  40. package/dist/runtime/assets/colors.css +0 -1
  41. package/dist/shared/nuxt-users.BnOQs2LI.mjs +0 -162
  42. package/dist/runtime/components/{LogoutLink.vue.d.ts → NUsersLogoutLink.vue.d.ts} +4 -4
  43. /package/dist/runtime/components/{PasswordStrengthIndicator.vue.d.ts → NUsersPasswordStrengthIndicator.vue.d.ts} +0 -0
  44. /package/dist/runtime/components/{ProfileInfo.vue.d.ts → NUsersProfileInfo.vue.d.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,6 +1,74 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## v1.13.0
5
+
6
+ [compare changes](https://github.com/rrd108/nuxt-users/compare/v1.12.0...v1.13.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
+ - Define allowed HTTP methods for api endpoints permissions ([7feca0a](https://github.com/rrd108/nuxt-users/commit/7feca0a))
13
+ - New api endpoint for user edit own data is added ([3bd13f9](https://github.com/rrd108/nuxt-users/commit/3bd13f9))
14
+
15
+ ### 🩹 Fixes
16
+
17
+ - Authorization middleware for API routes closes #76 ([#76](https://github.com/rrd108/nuxt-users/issues/76))
18
+ - (temporary) strange test failing ([ff8c376](https://github.com/rrd108/nuxt-users/commit/ff8c376))
19
+
20
+ ### 💅 Refactors
21
+
22
+ - Component names ([159f334](https://github.com/rrd108/nuxt-users/commit/159f334))
23
+ - Css classes renamed, removed or added ([c5c3334](https://github.com/rrd108/nuxt-users/commit/c5c3334))
24
+ - Remove unused css ([7abc8a5](https://github.com/rrd108/nuxt-users/commit/7abc8a5))
25
+
26
+ ### 🏡 Chore
27
+
28
+ - **release:** V1.12.0 ([0727c0f](https://github.com/rrd108/nuxt-users/commit/0727c0f))
29
+
30
+ ### ✅ Tests
31
+
32
+ - Fix ([cbf300c](https://github.com/rrd108/nuxt-users/commit/cbf300c))
33
+ - Fix failing test ([3973eec](https://github.com/rrd108/nuxt-users/commit/3973eec))
34
+
35
+ ### ❤️ Contributors
36
+
37
+ - Rrd108 ([@rrd108](https://github.com/rrd108))
38
+
39
+ ## v1.12.0
40
+
41
+ [compare changes](https://github.com/rrd108/nuxt-users/compare/v1.11.0...v1.12.0)
42
+
43
+ ### 🚀 Enhancements
44
+
45
+ - Enhances user management and login flow ([894563a](https://github.com/rrd108/nuxt-users/commit/894563a))
46
+ - Implement new components ([87689c9](https://github.com/rrd108/nuxt-users/commit/87689c9))
47
+
48
+ ### 🩹 Fixes
49
+
50
+ - Authorization middleware for API routes closes #76 ([#76](https://github.com/rrd108/nuxt-users/issues/76))
51
+ - (temporary) strange test failing ([ff8c376](https://github.com/rrd108/nuxt-users/commit/ff8c376))
52
+
53
+ ### 💅 Refactors
54
+
55
+ - Component names ([159f334](https://github.com/rrd108/nuxt-users/commit/159f334))
56
+ - Css classes renamed, removed or added ([c5c3334](https://github.com/rrd108/nuxt-users/commit/c5c3334))
57
+ - Remove unused css ([7abc8a5](https://github.com/rrd108/nuxt-users/commit/7abc8a5))
58
+
59
+ ### 🏡 Chore
60
+
61
+ - Bump version number ([5045086](https://github.com/rrd108/nuxt-users/commit/5045086))
62
+
63
+ ### ✅ Tests
64
+
65
+ - Fix ([cbf300c](https://github.com/rrd108/nuxt-users/commit/cbf300c))
66
+ - Fix failing test ([3973eec](https://github.com/rrd108/nuxt-users/commit/3973eec))
67
+
68
+ ### ❤️ Contributors
69
+
70
+ - Rrd108 ([@rrd108](https://github.com/rrd108))
71
+
4
72
  ## v1.11.0
5
73
 
6
74
  [compare changes](https://github.com/rrd108/nuxt-users/compare/v1.9.0...v1.11.0)
package/README.md CHANGED
@@ -4,68 +4,36 @@ A user authentication module for Nuxt 3 and Nuxt 4 with database support for SQL
4
4
 
5
5
  ## Features
6
6
 
7
- - 🔐 User authentication with bcrypt password hashing
8
- - 🗄️ Database support (SQLite, MySQL, PostgreSQL)
9
- - 🛠️ CLI commands for database management
10
- - 🔑 Password Reset Functionality
11
- - 📦 Zero-config setup with sensible defaults
12
- - 🔧 TypeScript support
13
- - 🚀 Nuxt 3 & 4 compatibility
14
-
15
- ## Quick Start
16
-
17
- ```bash
18
- # Install the module
19
- npm install nuxt-users
20
-
21
- # Add to your nuxt.config.ts
22
- export default defineNuxtConfig({
23
- modules: ['nuxt-users'],
24
- nuxtUsers: {
25
- connector: {
26
- name: 'sqlite',
27
- options: {
28
- path: './data/default.sqlite3'
29
- }
30
- }
31
- }
32
- })
33
-
34
- # Run migrations
35
- npx nuxt-users migrate
36
-
37
- # Create your first user
38
- npx nuxt-users create-user user@example.com "John Doe" password123
39
- # Create an admin user
40
- npx nuxt-users create-user admin@example.com "Admin User" adminpass123 admin
41
- ```
7
+ - 🔐 **Authentication & Security**
8
+ - User registration, login, and logout
9
+ - Secure password hashing with bcrypt
10
+ - Password reset functionality
11
+ - Session management
12
+
13
+ - 🗄️ **Database Support**
14
+ - SQLite, MySQL, and PostgreSQL
15
+ - Automatic migrations and schema management
16
+ - CLI tools for database operations
17
+
18
+ - 🛡️ **Access Control**
19
+ - Role-Based Access Control (RBAC)
20
+ - Middleware for route protection
21
+ - Permission-based authorization
22
+
23
+ - 🎨 **UI Components**
24
+ - Pre-built authentication forms
25
+ - User management interfaces
26
+ - Responsive and customizable components
27
+
28
+ - ⚡ **Developer Experience**
29
+ - Zero-config setup with sensible defaults
30
+ - TypeScript support throughout
31
+ - Nuxt 3 & 4 compatibility
32
+ - Easy customization and extension
42
33
 
43
34
  ## Documentation
44
35
 
45
- 📚 **Full documentation is available at: [docs/](/docs/)**
46
-
47
- - [Installation Guide](/docs/guide/installation)
48
- - [Configuration Options](/docs/guide/configuration)
49
- - [API Reference](/docs/api/)
50
- - [Component Documentation](/docs/components/)
51
- - [Database Schema](/docs/database/schema)
52
- - [Contributing Guidelines](/docs/contributing/)
53
-
54
- ## Development
55
-
56
- ```bash
57
- # Install dependencies
58
- yarn install
59
-
60
- # Start development server
61
- yarn dev
62
-
63
- # Run tests
64
- yarn test
65
-
66
- # Build documentation
67
- cd docs && yarn dev
68
- ```
36
+ 📚 **Full documentation is available at: [https://nuxt-users.webmania.cc/](https://nuxt-users.webmania.cc/)**
69
37
 
70
38
  ## License
71
39
 
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.10.0"
250
+ version: "1.12.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.C7T1DKBV.mjs';
2
+ import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.u7N4meyI.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.C7T1DKBV.js';
2
+ import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.u7N4meyI.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,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
3
  "configKey": "nuxtUsers",
4
- "version": "1.11.0",
4
+ "version": "1.13.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.0"
package/dist/module.mjs CHANGED
@@ -115,6 +115,11 @@ const module = defineNuxtModule({
115
115
  method: "get",
116
116
  handler: resolver.resolve("./runtime/server/api/nuxt-users/me.get")
117
117
  });
118
+ addServerHandler({
119
+ route: `${base}/me`,
120
+ method: "patch",
121
+ handler: resolver.resolve("./runtime/server/api/nuxt-users/me.patch")
122
+ });
118
123
  addServerHandler({
119
124
  route: `${base}/password`,
120
125
  method: "patch",
@@ -130,6 +135,11 @@ const module = defineNuxtModule({
130
135
  method: "post",
131
136
  handler: resolver.resolve("./runtime/server/api/nuxt-users/password/reset.post")
132
137
  });
138
+ addServerHandler({
139
+ route: `${base}`,
140
+ method: "get",
141
+ handler: resolver.resolve("./runtime/server/api/nuxt-users/index.get")
142
+ });
133
143
  addServerHandler({
134
144
  route: `${base}`,
135
145
  method: "post",
@@ -180,23 +190,39 @@ const module = defineNuxtModule({
180
190
  nitroConfig.scanDirs.push(resolver.resolve("./runtime/server/tasks"));
181
191
  });
182
192
  addComponent({
183
- name: "LoginForm",
184
- filePath: resolver.resolve("./runtime/components/LoginForm.vue")
193
+ name: "NUsersLoginForm",
194
+ filePath: resolver.resolve("./runtime/components/NUsersLoginForm.vue")
195
+ });
196
+ addComponent({
197
+ name: "NUsersLogoutLink",
198
+ filePath: resolver.resolve("./runtime/components/NUsersLogoutLink.vue")
199
+ });
200
+ addComponent({
201
+ name: "NUsersProfileInfo",
202
+ filePath: resolver.resolve("./runtime/components/NUsersProfileInfo.vue")
203
+ });
204
+ addComponent({
205
+ name: "NUsersResetPasswordForm",
206
+ filePath: resolver.resolve("./runtime/components/NUsersResetPasswordForm.vue")
207
+ });
208
+ addComponent({
209
+ name: "NUsersPasswordStrengthIndicator",
210
+ filePath: resolver.resolve("./runtime/components/NUsersPasswordStrengthIndicator.vue")
185
211
  });
186
212
  addComponent({
187
- name: "LogoutLink",
188
- filePath: resolver.resolve("./runtime/components/LogoutLink.vue")
213
+ name: "NUsersList",
214
+ filePath: resolver.resolve("./runtime/components/NUsersList.vue")
189
215
  });
190
216
  addComponent({
191
- name: "ProfileInfo",
192
- filePath: resolver.resolve("./runtime/components/ProfileInfo.vue")
217
+ name: "NUsersUserCard",
218
+ filePath: resolver.resolve("./runtime/components/NUsersUserCard.vue")
193
219
  });
194
220
  addComponent({
195
- name: "ResetPasswordForm",
196
- filePath: resolver.resolve("./runtime/components/ResetPasswordForm.vue")
221
+ name: "NUsersUserForm",
222
+ filePath: resolver.resolve("./runtime/components/NUsersUserForm.vue")
197
223
  });
198
224
  nuxt.options.css = nuxt.options.css || [];
199
- nuxt.options.css.push(resolver.resolve("./runtime/assets/colors.css"));
225
+ nuxt.options.css.push(resolver.resolve("./runtime/assets/nuxt-users.css"));
200
226
  }
201
227
  });
202
228
 
@@ -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,16 +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
- const apiBasePath = nuxtUsers?.apiBasePath;
8
7
  const props = defineProps({
9
- apiEndpoint: { type: String, required: false, default: `${apiBasePath}/session` },
10
- redirectTo: { type: String, required: false, default: "/" },
11
- forgotPasswordEndpoint: { type: String, required: false, default: `${apiBasePath}/password/forgot` }
8
+ apiEndpoint: { type: String, required: false },
9
+ redirectTo: { type: String, required: false },
10
+ forgotPasswordEndpoint: { type: String, required: false }
12
11
  });
13
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`);
14
15
  const isLoading = ref(false);
15
16
  const isForgotPasswordLoading = ref(false);
16
17
  const error = ref("");
@@ -25,7 +26,7 @@ const handleSubmit = async () => {
25
26
  error.value = "";
26
27
  try {
27
28
  emit("submit", formData.value);
28
- const response = await $fetch(props.apiEndpoint, {
29
+ const response = await $fetch(apiEndpoint.value, {
29
30
  method: "POST",
30
31
  body: {
31
32
  email: formData.value.email,
@@ -53,7 +54,7 @@ const handleForgotPassword = async () => {
53
54
  forgotPasswordMessage.value = "";
54
55
  error.value = "";
55
56
  try {
56
- const response = await $fetch(props.forgotPasswordEndpoint, {
57
+ const response = await $fetch(forgotPasswordEndpoint.value, {
57
58
  method: "POST",
58
59
  body: {
59
60
  email: formData.value.email
@@ -72,15 +73,15 @@ const handleForgotPassword = async () => {
72
73
  </script>
73
74
 
74
75
  <template>
75
- <section>
76
+ <section class="n-users-login-section n-users-section">
76
77
  <form @submit.prevent="handleSubmit">
77
78
  <!-- Header slot -->
78
79
  <slot name="header">
79
- <div class="login-header">
80
- <h2 class="login-title">
80
+ <div class="n-users-login-header">
81
+ <h2 class="n-users-login-title">
81
82
  Welcome Back
82
83
  </h2>
83
- <p class="login-subtitle">
84
+ <p class="n-users-login-subtitle">
84
85
  Sign in to your account
85
86
  </p>
86
87
  </div>
@@ -88,7 +89,7 @@ const handleForgotPassword = async () => {
88
89
 
89
90
  <!-- Email field -->
90
91
  <slot name="email-field">
91
- <div class="form-group">
92
+ <div class="n-users-form-group">
92
93
  <label for="email">Email</label>
93
94
  <input
94
95
  id="email"
@@ -103,7 +104,7 @@ const handleForgotPassword = async () => {
103
104
 
104
105
  <!-- Password field -->
105
106
  <slot name="password-field">
106
- <div class="form-group">
107
+ <div class="n-users-form-group">
107
108
  <label for="password">Password</label>
108
109
  <input
109
110
  id="password"
@@ -119,7 +120,7 @@ const handleForgotPassword = async () => {
119
120
 
120
121
  <!-- Remember me slot -->
121
122
  <slot name="remember-me">
122
- <div class="remember-me">
123
+ <div class="n-users-remember-me">
123
124
  <input
124
125
  id="rememberMe"
125
126
  v-model="formData.rememberMe"
@@ -138,7 +139,7 @@ const handleForgotPassword = async () => {
138
139
  >
139
140
  <span
140
141
  v-if="isLoading"
141
- class="loading-spinner"
142
+ class="n-users-loading-spinner"
142
143
  />
143
144
  {{ isLoading ? "Signing in..." : "Sign In" }}
144
145
  </button>
@@ -146,17 +147,17 @@ const handleForgotPassword = async () => {
146
147
 
147
148
  <!-- Footer slot -->
148
149
  <slot name="footer">
149
- <div class="login-footer">
150
- <p class="forgot-password">
150
+ <div class="n-users-login-footer">
151
+ <p class="n-users-forgot-password">
151
152
  <a
152
153
  href="#"
153
- class="forgot-link"
154
+ class="n-users-forgot-link"
154
155
  :class="{ loading: isForgotPasswordLoading }"
155
156
  @click.prevent="handleForgotPassword"
156
157
  >
157
158
  <span
158
159
  v-if="isForgotPasswordLoading"
159
- class="loading-spinner"
160
+ class="n-users-loading-spinner"
160
161
  />
161
162
  {{ isForgotPasswordLoading ? "Sending..." : "Forgot your password?" }}
162
163
  </a>
@@ -192,7 +193,3 @@ const handleForgotPassword = async () => {
192
193
  </form>
193
194
  </section>
194
195
  </template>
195
-
196
- <style scoped>
197
- 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}}
198
- </style>