nuxt-users 1.6.5 → 1.6.7

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 (39) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/dist/cli.mjs +1 -1
  3. package/dist/module.d.mts +4 -70
  4. package/dist/module.d.ts +4 -70
  5. package/dist/module.json +1 -1
  6. package/dist/module.mjs +28 -27
  7. package/dist/runtime/components/LoginForm.vue +6 -3
  8. package/dist/runtime/components/LoginForm.vue.d.ts +4 -4
  9. package/dist/runtime/components/LogoutLink.vue +21 -7
  10. package/dist/runtime/components/LogoutLink.vue.d.ts +22 -5
  11. package/dist/runtime/components/PasswordStrengthIndicator.vue +38 -27
  12. package/dist/runtime/components/ProfileInfo.vue +33 -0
  13. package/dist/runtime/components/ResetPasswordForm.vue +89 -96
  14. package/dist/runtime/components/ResetPasswordForm.vue.d.ts +15 -1
  15. package/dist/runtime/composables/useAuth.d.ts +2 -2
  16. package/dist/runtime/composables/useAuth.js +2 -2
  17. package/dist/runtime/composables/usePasswordValidation.d.ts +1 -1
  18. package/dist/runtime/middleware/auth.client.js +4 -4
  19. package/dist/runtime/server/api/{login.post.js → auth/login.post.js} +5 -3
  20. package/dist/runtime/server/api/{logout.get.js → auth/logout.get.js} +1 -1
  21. package/dist/runtime/server/api/{update-password.post.js → auth/update-password.post.js} +2 -2
  22. package/dist/runtime/server/api/user/profile.get.d.ts +4 -0
  23. package/dist/runtime/server/api/{profile.get.js → user/profile.get.js} +2 -2
  24. package/dist/runtime/server/middleware/auth.server.js +3 -2
  25. package/dist/runtime/server/tasks/cleanup-tokens.d.ts +2 -0
  26. package/dist/runtime/server/tasks/cleanup-tokens.js +30 -0
  27. package/dist/runtime/server/utils/index.d.ts +1 -1
  28. package/dist/runtime/server/utils/index.js +1 -1
  29. package/dist/runtime/server/utils/user.d.ts +20 -0
  30. package/dist/runtime/server/utils/user.js +45 -1
  31. package/package.json +1 -1
  32. package/dist/runtime/components/ForgotPasswordForm.vue +0 -77
  33. package/dist/runtime/components/ProfileForm.vue +0 -240
  34. package/dist/runtime/components/ProfileForm.vue.d.ts +0 -20
  35. package/dist/runtime/server/api/profile.get.d.ts +0 -2
  36. /package/dist/runtime/components/{ForgotPasswordForm.vue.d.ts → ProfileInfo.vue.d.ts} +0 -0
  37. /package/dist/runtime/server/api/{login.post.d.ts → auth/login.post.d.ts} +0 -0
  38. /package/dist/runtime/server/api/{logout.get.d.ts → auth/logout.get.d.ts} +0 -0
  39. /package/dist/runtime/server/api/{update-password.post.d.ts → auth/update-password.post.d.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,6 +1,69 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## v1.6.7
5
+
6
+ [compare changes](https://github.com/rrd108/nuxt-users/compare/v1.6.6...v1.6.7)
7
+
8
+ ### 🩹 Fixes
9
+
10
+ - Authentication middleware logic ([18a3bb5](https://github.com/rrd108/nuxt-users/commit/18a3bb5))
11
+ - Password validation and UI ([07b44b3](https://github.com/rrd108/nuxt-users/commit/07b44b3))
12
+ - Type errors ([c403645](https://github.com/rrd108/nuxt-users/commit/c403645))
13
+
14
+ ### 💅 Refactors
15
+
16
+ - API routes ([3c7603f](https://github.com/rrd108/nuxt-users/commit/3c7603f))
17
+ - Authentication components and logic ([4da544f](https://github.com/rrd108/nuxt-users/commit/4da544f))
18
+ - Token cleanup task and middleware ([d47a4a5](https://github.com/rrd108/nuxt-users/commit/d47a4a5))
19
+
20
+ ### 🏡 Chore
21
+
22
+ - **release:** V1.6.6 ([7b37694](https://github.com/rrd108/nuxt-users/commit/7b37694))
23
+
24
+ ### ✅ Tests
25
+
26
+ - Fix mock ([791dc94](https://github.com/rrd108/nuxt-users/commit/791dc94))
27
+ - Adds unit tests for auth API endpoints ([d844630](https://github.com/rrd108/nuxt-users/commit/d844630))
28
+ - Remove unused test ([3dc23ad](https://github.com/rrd108/nuxt-users/commit/3dc23ad))
29
+ - Fix response ([db3b03c](https://github.com/rrd108/nuxt-users/commit/db3b03c))
30
+ - Fix mysql field type ([2faf0c6](https://github.com/rrd108/nuxt-users/commit/2faf0c6))
31
+
32
+ ### ❤️ Contributors
33
+
34
+ - Rrd108 ([@rrd108](https://github.com/rrd108))
35
+
36
+ ## v1.6.6
37
+
38
+ [compare changes](https://github.com/rrd108/nuxt-users/compare/v1.6.5...v1.6.6)
39
+
40
+ ### 🩹 Fixes
41
+
42
+ - Authentication middleware ([c7de8a4](https://github.com/rrd108/nuxt-users/commit/c7de8a4))
43
+ - Lint and type errors ([dd6ee40](https://github.com/rrd108/nuxt-users/commit/dd6ee40))
44
+ - Type errors ([65c81fb](https://github.com/rrd108/nuxt-users/commit/65c81fb))
45
+ - **cli:** Updates CLI version dynamically ([1cca13a](https://github.com/rrd108/nuxt-users/commit/1cca13a))
46
+ - Authentication middleware logic ([18a3bb5](https://github.com/rrd108/nuxt-users/commit/18a3bb5))
47
+ - Password validation and UI ([07b44b3](https://github.com/rrd108/nuxt-users/commit/07b44b3))
48
+
49
+ ### 💅 Refactors
50
+
51
+ - API routes ([3c7603f](https://github.com/rrd108/nuxt-users/commit/3c7603f))
52
+ - Authentication components and logic ([4da544f](https://github.com/rrd108/nuxt-users/commit/4da544f))
53
+
54
+ ### 🏡 Chore
55
+
56
+ - **release:** V1.6.5 ([f35e526](https://github.com/rrd108/nuxt-users/commit/f35e526))
57
+
58
+ ### ✅ Tests
59
+
60
+ - Fix mock ([791dc94](https://github.com/rrd108/nuxt-users/commit/791dc94))
61
+ - Adds unit tests for auth API endpoints ([d844630](https://github.com/rrd108/nuxt-users/commit/d844630))
62
+
63
+ ### ❤️ Contributors
64
+
65
+ - Rrd108 ([@rrd108](https://github.com/rrd108))
66
+
4
67
  ## v1.6.5
5
68
 
6
69
  [compare changes](https://github.com/rrd108/nuxt-users/compare/v1.6.4...v1.6.5)
package/dist/cli.mjs CHANGED
@@ -247,7 +247,7 @@ const main = defineCommand({
247
247
  meta: {
248
248
  name: "nuxt-users",
249
249
  description: "CLI for Nuxt Users Module - Manage users, migrations, and database operations",
250
- version: "1.6.3"
250
+ version: "1.6.6"
251
251
  },
252
252
  subCommands: {
253
253
  migrate,
package/dist/module.d.mts CHANGED
@@ -9,76 +9,7 @@ type DatabaseConfig = {
9
9
  password?: string;
10
10
  database?: string;
11
11
  };
12
- interface RuntimeModuleOptions {
13
- connector?: {
14
- name: DatabaseType;
15
- options: DatabaseConfig;
16
- };
17
- tables?: {
18
- migrations?: string;
19
- users?: string;
20
- personalAccessTokens?: string;
21
- passwordResetTokens?: string;
22
- };
23
- /**
24
- * Mailer configuration options for sending emails (e.g., password resets)
25
- * Uses nodemailer
26
- */
27
- mailer?: MailerOptions;
28
- /**
29
- * Base URL for password reset links
30
- * @default 'http://localhost:3000' // Example, will be set in module defaults
31
- */
32
- passwordResetBaseUrl?: string;
33
- /**
34
- * Skip database checks during module setup to prevent hanging
35
- * @default false
36
- */
37
- auth?: {
38
- /**
39
- * Whitelisted routes that do not require authentication
40
- * @default ['/login']
41
- * @example ['/login', '/register']
42
- */
43
- whitelist?: string[];
44
- };
45
- /**
46
- * Password validation configuration
47
- */
48
- passwordValidation?: {
49
- /**
50
- * Minimum password length
51
- * @default 8
52
- */
53
- minLength?: number;
54
- /**
55
- * Require uppercase letters
56
- * @default true
57
- */
58
- requireUppercase?: boolean;
59
- /**
60
- * Require lowercase letters
61
- * @default true
62
- */
63
- requireLowercase?: boolean;
64
- /**
65
- * Require numbers
66
- * @default true
67
- */
68
- requireNumbers?: boolean;
69
- /**
70
- * Require special characters
71
- * @default true
72
- */
73
- requireSpecialChars?: boolean;
74
- /**
75
- * Prevent common passwords
76
- * @default true
77
- */
78
- preventCommonPasswords?: boolean;
79
- };
80
- }
81
- interface ModuleOptions extends Omit<RuntimeModuleOptions, 'tables' | 'auth' | 'passwordValidation'> {
12
+ interface ModuleOptions {
82
13
  connector: {
83
14
  name: DatabaseType;
84
15
  options: DatabaseConfig;
@@ -89,8 +20,11 @@ interface ModuleOptions extends Omit<RuntimeModuleOptions, 'tables' | 'auth' | '
89
20
  personalAccessTokens: string;
90
21
  passwordResetTokens: string;
91
22
  };
23
+ mailer?: MailerOptions;
24
+ passwordResetBaseUrl?: string;
92
25
  auth: {
93
26
  whitelist: string[];
27
+ tokenExpiration: number;
94
28
  };
95
29
  passwordValidation: {
96
30
  minLength: number;
package/dist/module.d.ts CHANGED
@@ -9,76 +9,7 @@ type DatabaseConfig = {
9
9
  password?: string;
10
10
  database?: string;
11
11
  };
12
- interface RuntimeModuleOptions {
13
- connector?: {
14
- name: DatabaseType;
15
- options: DatabaseConfig;
16
- };
17
- tables?: {
18
- migrations?: string;
19
- users?: string;
20
- personalAccessTokens?: string;
21
- passwordResetTokens?: string;
22
- };
23
- /**
24
- * Mailer configuration options for sending emails (e.g., password resets)
25
- * Uses nodemailer
26
- */
27
- mailer?: MailerOptions;
28
- /**
29
- * Base URL for password reset links
30
- * @default 'http://localhost:3000' // Example, will be set in module defaults
31
- */
32
- passwordResetBaseUrl?: string;
33
- /**
34
- * Skip database checks during module setup to prevent hanging
35
- * @default false
36
- */
37
- auth?: {
38
- /**
39
- * Whitelisted routes that do not require authentication
40
- * @default ['/login']
41
- * @example ['/login', '/register']
42
- */
43
- whitelist?: string[];
44
- };
45
- /**
46
- * Password validation configuration
47
- */
48
- passwordValidation?: {
49
- /**
50
- * Minimum password length
51
- * @default 8
52
- */
53
- minLength?: number;
54
- /**
55
- * Require uppercase letters
56
- * @default true
57
- */
58
- requireUppercase?: boolean;
59
- /**
60
- * Require lowercase letters
61
- * @default true
62
- */
63
- requireLowercase?: boolean;
64
- /**
65
- * Require numbers
66
- * @default true
67
- */
68
- requireNumbers?: boolean;
69
- /**
70
- * Require special characters
71
- * @default true
72
- */
73
- requireSpecialChars?: boolean;
74
- /**
75
- * Prevent common passwords
76
- * @default true
77
- */
78
- preventCommonPasswords?: boolean;
79
- };
80
- }
81
- interface ModuleOptions extends Omit<RuntimeModuleOptions, 'tables' | 'auth' | 'passwordValidation'> {
12
+ interface ModuleOptions {
82
13
  connector: {
83
14
  name: DatabaseType;
84
15
  options: DatabaseConfig;
@@ -89,8 +20,11 @@ interface ModuleOptions extends Omit<RuntimeModuleOptions, 'tables' | 'auth' | '
89
20
  personalAccessTokens: string;
90
21
  passwordResetTokens: string;
91
22
  };
23
+ mailer?: MailerOptions;
24
+ passwordResetBaseUrl?: string;
92
25
  auth: {
93
26
  whitelist: string[];
27
+ tokenExpiration: number;
94
28
  };
95
29
  passwordValidation: {
96
30
  minLength: number;
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
3
  "configKey": "nuxtUsers",
4
- "version": "1.6.5",
4
+ "version": "1.6.7",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.1",
7
7
  "unbuild": "3.6.0"
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { defineNuxtModule, createResolver, addPlugin, addImportsDir, addServerHandler, addRouteMiddleware, addComponent } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, addPlugin, addImportsDir, addRouteMiddleware, addServerHandler, addComponent } from '@nuxt/kit';
2
2
  import { defu } from 'defu';
3
3
 
4
4
  const defaultOptions = {
@@ -31,7 +31,9 @@ const defaultOptions = {
31
31
  },
32
32
  passwordResetBaseUrl: "http://localhost:3000",
33
33
  auth: {
34
- whitelist: ["/login"]
34
+ whitelist: ["/login"],
35
+ tokenExpiration: 24 * 60
36
+ // 24 hours
35
37
  },
36
38
  passwordValidation: {
37
39
  minLength: 8,
@@ -61,7 +63,8 @@ const module = defineNuxtModule({
61
63
  passwordResetTokens: options.tables?.passwordResetTokens || defaultOptions.tables.passwordResetTokens
62
64
  },
63
65
  auth: {
64
- whitelist: [...defaultOptions.auth?.whitelist || [], ...options.auth?.whitelist || []]
66
+ whitelist: [...defaultOptions.auth?.whitelist || [], ...options.auth?.whitelist || []],
67
+ tokenExpiration: options.auth?.tokenExpiration || defaultOptions.auth.tokenExpiration
65
68
  }
66
69
  };
67
70
  nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
@@ -83,64 +86,62 @@ const module = defineNuxtModule({
83
86
  mode: "server"
84
87
  });
85
88
  addImportsDir(resolver.resolve("./runtime/composables"));
86
- addServerHandler({
87
- middleware: true,
88
- handler: resolver.resolve("./runtime/server/middleware/auth.server")
89
- });
90
89
  addRouteMiddleware({
91
90
  name: "auth.client",
92
91
  path: resolver.resolve("./runtime/middleware/auth.client"),
93
92
  global: true
94
93
  });
95
94
  addServerHandler({
96
- route: "/api/login",
95
+ route: "/api/auth/login",
97
96
  method: "post",
98
- handler: resolver.resolve("./runtime/server/api/login.post")
97
+ handler: resolver.resolve("./runtime/server/api/auth/login.post")
99
98
  });
100
99
  addServerHandler({
101
- route: "/api/forgot-password",
100
+ route: "/api/auth/forgot-password",
102
101
  method: "post",
103
102
  handler: resolver.resolve("./runtime/server/api/auth/forgot-password.post")
104
103
  });
105
104
  addServerHandler({
106
- route: "/api/reset-password",
105
+ route: "/api/auth/reset-password",
107
106
  method: "post",
108
107
  handler: resolver.resolve("./runtime/server/api/auth/reset-password.post")
109
108
  });
110
109
  addServerHandler({
111
- route: "/api/logout",
110
+ route: "/api/auth/logout",
112
111
  method: "get",
113
- handler: resolver.resolve("./runtime/server/api/logout.get")
112
+ handler: resolver.resolve("./runtime/server/api/auth/logout.get")
114
113
  });
115
114
  addServerHandler({
116
- route: "/api/profile",
115
+ route: "/api/user/profile",
117
116
  method: "get",
118
- handler: resolver.resolve("./runtime/server/api/profile.get")
117
+ handler: resolver.resolve("./runtime/server/api/user/profile.get")
119
118
  });
120
119
  addServerHandler({
121
- route: "/api/update-password",
120
+ route: "/api/auth/update-password",
122
121
  method: "post",
123
- handler: resolver.resolve("./runtime/server/api/update-password.post")
122
+ handler: resolver.resolve("./runtime/server/api/auth/update-password.post")
123
+ });
124
+ nuxt.hook("nitro:config", async (nitroConfig) => {
125
+ nitroConfig.experimental = nitroConfig.experimental || {};
126
+ nitroConfig.experimental.tasks = true;
127
+ nitroConfig.scanDirs = nitroConfig.scanDirs || [];
128
+ nitroConfig.scanDirs.push(resolver.resolve("./runtime/server/tasks"));
124
129
  });
125
130
  addComponent({
126
131
  name: "LoginForm",
127
132
  filePath: resolver.resolve("./runtime/components/LoginForm.vue")
128
133
  });
129
- addComponent({
130
- name: "ForgotPasswordForm",
131
- filePath: resolver.resolve("./runtime/components/ForgotPasswordForm.vue")
132
- });
133
- addComponent({
134
- name: "ResetPasswordForm",
135
- filePath: resolver.resolve("./runtime/components/ResetPasswordForm.vue")
136
- });
137
134
  addComponent({
138
135
  name: "LogoutLink",
139
136
  filePath: resolver.resolve("./runtime/components/LogoutLink.vue")
140
137
  });
141
138
  addComponent({
142
- name: "ProfileForm",
143
- filePath: resolver.resolve("./runtime/components/ProfileForm.vue")
139
+ name: "ProfileInfo",
140
+ filePath: resolver.resolve("./runtime/components/ProfileInfo.vue")
141
+ });
142
+ addComponent({
143
+ name: "ResetPasswordForm",
144
+ filePath: resolver.resolve("./runtime/components/ResetPasswordForm.vue")
144
145
  });
145
146
  nuxt.options.css = nuxt.options.css || [];
146
147
  nuxt.options.css.push(resolver.resolve("./runtime/assets/colors.css"));
@@ -1,8 +1,11 @@
1
1
  <script setup>
2
2
  import { ref } from "vue";
3
3
  import { navigateTo } from "#app";
4
+ import { useRuntimeConfig } from "#imports";
5
+ const { public: { nuxtUsers } } = useRuntimeConfig();
6
+ const { passwordValidation } = nuxtUsers;
4
7
  const props = defineProps({
5
- apiEndpoint: { type: String, required: false, default: "/api/login" },
8
+ apiEndpoint: { type: String, required: false, default: "/api/auth/login" },
6
9
  redirectTo: { type: String, required: false, default: "/" },
7
10
  forgotPasswordEndpoint: { type: String, required: false, default: "/api/auth/forgot-password" }
8
11
  });
@@ -108,7 +111,7 @@ const handleForgotPassword = async () => {
108
111
  name="password"
109
112
  placeholder="Enter your password"
110
113
  required
111
- minlength="6"
114
+ :minlength="passwordValidation.minLength"
112
115
  >
113
116
  </div>
114
117
  </slot>
@@ -190,5 +193,5 @@ const handleForgotPassword = async () => {
190
193
  </template>
191
194
 
192
195
  <style scoped>
193
- 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:8px;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:8px;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:8px;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:8px;color:var(--color-success)}@media (max-width:480px){.login-form-container{margin:1em;padding:1.5em}.login-title{font-size:1.5rem}}
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}}
194
197
  </style>
@@ -22,16 +22,16 @@ 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
- error: (error: string) => any;
26
- submit: (data: LoginFormData) => any;
27
25
  success: (user: UserWithoutPassword) => any;
26
+ error: (error: string) => any;
28
27
  "forgot-password-error": (error: string) => any;
28
+ submit: (data: LoginFormData) => any;
29
29
  "forgot-password-success": () => any;
30
30
  }, string, import("vue").PublicProps, Readonly<LoginFormProps> & Readonly<{
31
- onError?: ((error: string) => any) | undefined;
32
- onSubmit?: ((data: LoginFormData) => any) | undefined;
33
31
  onSuccess?: ((user: UserWithoutPassword) => any) | undefined;
32
+ onError?: ((error: string) => any) | undefined;
34
33
  "onForgot-password-error"?: ((error: string) => any) | undefined;
34
+ onSubmit?: ((data: LoginFormData) => any) | undefined;
35
35
  "onForgot-password-success"?: (() => any) | undefined;
36
36
  }>, {
37
37
  apiEndpoint: string;
@@ -6,7 +6,14 @@ const props = defineProps({
6
6
  redirectTo: { type: String, required: false, default: "/login" },
7
7
  linkText: { type: String, required: false, default: "Logout" },
8
8
  confirmMessage: { type: String, required: false, default: "Are you sure you want to logout?" },
9
- class: { type: String, required: false }
9
+ class: { type: String, required: false },
10
+ style: { type: [String, Object], required: false },
11
+ linkClass: { type: String, required: false },
12
+ linkStyle: { type: [String, Object], required: false },
13
+ errorClass: { type: String, required: false },
14
+ errorStyle: { type: [String, Object], required: false },
15
+ containerClass: { type: String, required: false },
16
+ containerStyle: { type: [String, Object], required: false }
10
17
  });
11
18
  const emit = defineEmits(["success", "click", "error"]);
12
19
  const { logout } = useAuth();
@@ -37,26 +44,33 @@ const handleLogout = async (event) => {
37
44
  </script>
38
45
 
39
46
  <template>
40
- <div>
47
+ <div
48
+ :class="['logout-container', props.containerClass]"
49
+ :style="props.containerStyle"
50
+ >
41
51
  <div
42
52
  v-if="error"
43
- class="error-message"
53
+ :class="['error-message', props.errorClass]"
54
+ :style="props.errorStyle"
44
55
  >
45
56
  {{ error }}
46
57
  </div>
47
58
 
48
59
  <a
49
60
  href="#"
50
- :class="['logout-link', { loading: isLoading }, props.class]"
61
+ :class="['logout-link', { loading: isLoading }, props.linkClass]"
62
+ :style="props.linkStyle"
51
63
  :disabled="isLoading"
52
64
  @click="handleLogout"
53
65
  >
54
- <span v-if="isLoading">Logging out...</span>
55
- <span v-else>{{ linkText }}</span>
66
+ <slot>
67
+ <span v-if="isLoading">Logging out...</span>
68
+ <span v-else>{{ linkText }}</span>
69
+ </slot>
56
70
  </a>
57
71
  </div>
58
72
  </template>
59
73
 
60
74
  <style scoped>
61
- .logout-link{color:#dc3545;cursor:pointer;font-size:.875rem;font-weight:500;text-decoration:none;transition:color .2s}.logout-link:hover:not(.loading){color:#c82333;text-decoration:underline}.logout-link.loading{cursor:not-allowed;opacity:.6;pointer-events:none}.error-message{color:#dc3545;font-size:.875rem;margin-bottom:.5rem}
75
+ .logout-link{background:var(--color-error);border:2px solid transparent;border-color:var(--color-error-dark);border-radius:.5em;color:#fff;display:inline-block;font-weight:500;padding:.5em 1em;text-decoration:none;transition:all .2s}.logout-link:hover{border-color:var(--color-primary)}.error-message{color:var(--color-error);font-size:1rem;margin-bottom:.5em}
62
76
  </style>
@@ -3,18 +3,35 @@ interface LogoutLinkProps {
3
3
  linkText?: string;
4
4
  confirmMessage?: string;
5
5
  class?: string;
6
+ style?: string | Record<string, string | number>;
7
+ linkClass?: string;
8
+ linkStyle?: string | Record<string, string | number>;
9
+ errorClass?: string;
10
+ errorStyle?: string | Record<string, string | number>;
11
+ containerClass?: string;
12
+ containerStyle?: string | Record<string, string | number>;
6
13
  }
7
- declare const _default: import("vue").DefineComponent<LogoutLinkProps, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
8
- click: () => any;
9
- error: (error: string) => any;
14
+ declare var __VLS_1: {};
15
+ type __VLS_Slots = {} & {
16
+ default?: (props: typeof __VLS_1) => any;
17
+ };
18
+ declare const __VLS_component: import("vue").DefineComponent<LogoutLinkProps, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
10
19
  success: () => any;
20
+ error: (error: string) => any;
21
+ click: () => any;
11
22
  }, string, import("vue").PublicProps, Readonly<LogoutLinkProps> & Readonly<{
12
- onClick?: (() => any) | undefined;
13
- onError?: ((error: string) => any) | undefined;
14
23
  onSuccess?: (() => any) | undefined;
24
+ onError?: ((error: string) => any) | undefined;
25
+ onClick?: (() => any) | undefined;
15
26
  }>, {
16
27
  redirectTo: string;
17
28
  linkText: string;
18
29
  confirmMessage: string;
19
30
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
31
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
20
32
  export default _default;
33
+ type __VLS_WithSlots<T, S> = T & {
34
+ new (): {
35
+ $slots: S;
36
+ };
37
+ };
@@ -1,49 +1,60 @@
1
1
  <script setup>
2
2
  import { computed } from "vue";
3
+ import { useRuntimeConfig } from "#imports";
3
4
  const props = defineProps({
4
5
  password: { type: String, required: true },
5
6
  validationResult: { type: [Object, null], required: true },
6
7
  showHints: { type: Boolean, required: false, default: true },
7
8
  showRules: { type: Boolean, required: false, default: true }
8
9
  });
10
+ const { public: { nuxtUsers } } = useRuntimeConfig();
11
+ const moduleOptions = nuxtUsers;
9
12
  const hasErrors = computed(() => (props.validationResult?.errors?.length || 0) > 0);
10
13
  const hasHints = computed(() => (props.validationResult?.hints?.length || 0) > 0);
11
14
  const hasPassword = computed(() => props.password.length > 0);
12
15
  const validationRules = computed(() => {
13
16
  if (!props.password) return [];
14
17
  const rules = [];
15
- const minLength = 8;
16
- const hasMinLength = props.password.length >= minLength;
17
- rules.push({
18
- text: `At least ${minLength} characters`,
19
- passed: hasMinLength
20
- });
21
- const hasUppercase = /[A-Z]/.test(props.password);
22
- rules.push({
23
- text: "Contains uppercase letter (A-Z)",
24
- passed: hasUppercase
25
- });
26
- const hasLowercase = /[a-z]/.test(props.password);
27
- rules.push({
28
- text: "Contains lowercase letter (a-z)",
29
- passed: hasLowercase
30
- });
31
- const hasNumbers = /\d/.test(props.password);
32
- rules.push({
33
- text: "Contains number (0-9)",
34
- passed: hasNumbers
35
- });
36
- const hasSpecialChars = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(props.password);
37
- rules.push({
38
- text: "Contains special character (!@#$%^&*)",
39
- passed: hasSpecialChars
40
- });
18
+ if (moduleOptions.passwordValidation.minLength) {
19
+ const hasMinLength = props.password.length >= moduleOptions.passwordValidation.minLength;
20
+ rules.push({
21
+ text: `At least ${moduleOptions.passwordValidation.minLength} characters`,
22
+ passed: hasMinLength
23
+ });
24
+ }
25
+ if (moduleOptions.passwordValidation.requireUppercase) {
26
+ const hasUppercase = /[A-Z]/.test(props.password);
27
+ rules.push({
28
+ text: "Contains uppercase letter (A-Z)",
29
+ passed: hasUppercase
30
+ });
31
+ }
32
+ if (moduleOptions.passwordValidation.requireLowercase) {
33
+ const hasLowercase = /[a-z]/.test(props.password);
34
+ rules.push({
35
+ text: "Contains lowercase letter (a-z)",
36
+ passed: hasLowercase
37
+ });
38
+ }
39
+ if (moduleOptions.passwordValidation.requireNumbers) {
40
+ const hasNumbers = /\d/.test(props.password);
41
+ rules.push({
42
+ text: "Contains number (0-9)",
43
+ passed: hasNumbers
44
+ });
45
+ }
46
+ if (moduleOptions.passwordValidation.requireSpecialChars) {
47
+ const hasSpecialChars = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(props.password);
48
+ rules.push({
49
+ text: "Contains special character (!@#$%^&*)",
50
+ passed: hasSpecialChars
51
+ });
52
+ }
41
53
  return rules;
42
54
  });
43
55
  </script>
44
56
 
45
57
  <template>
46
- <!-- Password strength indicator -->
47
58
  <div
48
59
  v-if="hasPassword"
49
60
  class="password-strength"
@@ -0,0 +1,33 @@
1
+ <script setup>
2
+ import { useAuth } from "../composables/useAuth";
3
+ const { user } = useAuth();
4
+ </script>
5
+
6
+ <template>
7
+ <div>
8
+ <h2>Profile Information</h2>
9
+ <dl
10
+ v-if="user"
11
+ class="profile-info"
12
+ >
13
+ <dt>Name:</dt>
14
+ <dd>{{ user.name }}</dd>
15
+
16
+ <dt>Email:</dt>
17
+ <dd>{{ user.email }}</dd>
18
+
19
+ <dt>Role:</dt>
20
+ <dd>{{ user.role }}</dd>
21
+
22
+ <dt>Registered:</dt>
23
+ <dd>{{ Intl.DateTimeFormat().format(new Date(user.created_at)) }}</dd>
24
+
25
+ <dt>Last updated:</dt>
26
+ <dd>{{ Intl.DateTimeFormat().format(new Date(user.updated_at)) }}</dd>
27
+ </dl>
28
+ </div>
29
+ </template>
30
+
31
+ <style scoped>
32
+ div{background-color:var(--color-bg-primary);border-top-left-radius:.5em;border-top-right-radius:.5em;margin:0 auto;max-width:42em;padding:1em}h2{border-bottom:.1em solid var(--color-border)}dl{display:grid;font-size:1rem;gap:16px;grid-template-columns:1fr 5fr;list-style:none;margin:0;padding:0}
33
+ </style>