@adminforth/open-signup 1.0.4 → 1.0.6

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.
@@ -31,14 +31,14 @@
31
31
  <!-- Modal header -->
32
32
  <div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
33
33
  <h3 class="text-xl font-semibold text-gray-900 dark:text-white">
34
- Sign up into {{ coreStore.config?.brandName }}
34
+ {{$t('Sign up into')}} {{ coreStore.config?.brandName }}
35
35
  </h3>
36
36
  </div>
37
37
  <!-- Modal body -->
38
38
  <div class="p-4 md:p-5">
39
39
  <form v-if="!requestSent" class="space-y-4" role="alert" @submit.prevent>
40
40
  <div v-if="!verifyToken" class="relative">
41
- <label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
41
+ <label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{$t('Your email')}}</label>
42
42
  <input type="email" name="email" id="email"
43
43
  tabindex="1"
44
44
  autocomplete="username"
@@ -50,7 +50,7 @@
50
50
  />
51
51
  </div>
52
52
  <div v-if="isPasswordNeeded" class="relative">
53
- <label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">New password</label>
53
+ <label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{$t('New password')}}</label>
54
54
  <input
55
55
  tabindex="2"
56
56
  autocomplete="new-password"
@@ -60,7 +60,7 @@
60
60
  v-model="password"
61
61
  @keydown.enter="passwordConfirmationInput?.focus()"
62
62
  class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
63
- placeholder="New password" required
63
+ :placeholder="$t('New password')" required
64
64
  />
65
65
 
66
66
  <button
@@ -74,7 +74,7 @@
74
74
  </div>
75
75
 
76
76
  <div v-if="isPasswordNeeded" class="relative">
77
- <label for="password_confirmation" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Confirm new password</label>
77
+ <label for="password_confirmation" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{$t('Confirm new password')}}</label>
78
78
  <input
79
79
  ref="passwordConfirmationInput"
80
80
  autocomplete="new-password"
@@ -83,7 +83,7 @@
83
83
  tabindex="3"
84
84
  v-model="passwordConfirmation"
85
85
  class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
86
- placeholder="Confirm new password" required
86
+ :placeholder="$t('Confirm new password')" required
87
87
  />
88
88
 
89
89
  <button
@@ -100,7 +100,7 @@
100
100
  <svg class="flex-shrink-0 inline w-4 h-4 me-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
101
101
  <path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
102
102
  </svg>
103
- <span class="sr-only">Info</span>
103
+ <span class="sr-only">{{$t('Info')}}</span>
104
104
  <div>
105
105
  {{ validationError || error }}
106
106
  </div>
@@ -113,15 +113,15 @@
113
113
  class="w-full"
114
114
  :loader="inProgress"
115
115
  >
116
- Sign up
116
+ {{$t('Sign up')}}
117
117
  </Button>
118
118
  </form>
119
119
  <!-- END of set new paasord -->
120
120
  <div v-else class="flex items center justify-center p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:bg-green-800 dark:text-green-400" role="alert">
121
- Please check your email at {{ sentToEmail }} to confirm your email address.
121
+ {{$t('Please check your email at')}} {{ sentToEmail }} {{$t('to confirm your email address.')}}
122
122
  </div>
123
123
  <p class="text-gray-500 dark:text-gray-400 font-sm text-right mt-3">
124
- or <Link to="/login">{{ toLoginText }}</Link>
124
+ {{$t('or')}} <Link to="/login">{{ toLoginText }}</Link>
125
125
  </p>
126
126
  </div>
127
127
  </div>
@@ -141,6 +141,9 @@ import { useRoute, useRouter } from 'vue-router';
141
141
  import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
142
142
  import Button from '@/afcl/Button.vue';
143
143
  import Link from '@/afcl/Link.vue';
144
+ import { useI18n } from 'vue-i18n';
145
+
146
+ const { t } = useI18n();
144
147
 
145
148
  const inProgress = ref(false);
146
149
 
@@ -158,7 +161,7 @@ const sentToEmail: Ref<string> = ref('');
158
161
 
159
162
  const requestEmailConfirmation = computed(() => route.meta.requestEmailConfirmation);
160
163
  const verifyToken = computed(() => route.query.token);
161
- const toLoginText = computed(() => `${verifyToken.value ? 'Go' : 'Back'} to login`);
164
+ const toLoginText = computed(() => verifyToken.value ? t('Go to login') : t('Back to login'));
162
165
  const isPasswordNeeded = computed(() => !requestEmailConfirmation.value || (requestEmailConfirmation.value && verifyToken.value));
163
166
 
164
167
  const user = useUserStore();
@@ -169,22 +172,25 @@ const router = useRouter();
169
172
  function checkPassword() {
170
173
 
171
174
  if (!password.value || !passwordConfirmation.value) {
172
- return 'Please enter both password and password confirmation';
175
+ return t('Please enter both password and password confirmation');
173
176
  }
174
177
  if (password.value !== passwordConfirmation.value) {
175
- return 'Passwords do not match';
178
+ return t('Passwords do not match');
176
179
  }
177
180
 
178
- if (password.value.length < passwordField.value.minLength) {
179
- return `Password must be at least ${passwordField.value.minLength} characters long`;
181
+ if (!passwordConstraints.value) {
182
+ return null;
183
+ }
184
+ if (password.value.length < passwordConstraints.value.minLength) {
185
+ return t(`Password must be at least {minLength} characters long`, { minLength: passwordConstraints.value.minLength });
180
186
  }
181
187
 
182
- if (password.value.length > passwordField.value.maxLength) {
183
- return `Password must be at most ${passwordField.value.maxLength} characters long`;
188
+ if (password.value.length > passwordConstraints.value.maxLength) {
189
+ return t(`Password must be at most {maxLength} characters long`, { maxLength: passwordConstraints.value.maxLength });
184
190
  }
185
191
 
186
- if (passwordField.value.validation) {
187
- const valError = applyRegexValidation(password.value, passwordField.value.validation);
192
+ if (passwordConstraints.value.validation) {
193
+ const valError = applyRegexValidation(password.value, passwordConstraints.value.validation);
188
194
  if (valError) {
189
195
  return valError;
190
196
  }
@@ -213,19 +219,31 @@ const backgroundPosition = computed(() => {
213
219
  return coreStore.config?.loginBackgroundPosition || '1/2';
214
220
  });
215
221
 
216
- const passwordField = computed(
217
- () => route.meta.passwordField
218
- )
222
+
223
+ const passwordConstraints: Ref<{
224
+ minLength: number;
225
+ maxLength: number;
226
+ validation: string;
227
+ }> = ref({
228
+ minLength: 8,
229
+ maxLength: 100,
230
+ validation: '',
231
+ });
219
232
 
220
233
  onMounted(async () => {
221
234
  await coreStore.getPublicConfig();
235
+ // getPasswordConstraints
236
+ passwordConstraints.value = await callAdminForthApi({
237
+ path: `/plugin/${route.meta.pluginInstanceId}/password-constraints`,
238
+ method: 'GET',
239
+ });
222
240
  });
223
241
 
224
242
  async function doSignup() {
225
243
  error.value = null;
226
244
  const email = emailInput.value!.value;
227
245
  if (!email) {
228
- error.value = 'Please enter your email';
246
+ error.value = t('Please enter your email');
229
247
  return;
230
248
  }
231
249
  if (!requestEmailConfirmation.value && checkPassword()) {
@@ -274,7 +292,7 @@ const signupAfterEmailConfirmation = async () => {
274
292
  });
275
293
  if (resp.error) {
276
294
  window.adminforth.alert({
277
- message: `Error fetching data: ${resp.error}`,
295
+ message: t(`Error fetching data: {error}`, { error: resp.error }),
278
296
  variant: 'danger',
279
297
  });
280
298
  } else if (resp.redirectTo) {
@@ -1,8 +1,8 @@
1
1
  <template>
2
2
  <p class="text-gray-500 dark:text-gray-400 font-sm text-left mt-3">
3
- Have no account?
3
+ {{$t('Have no account?')}}
4
4
  <Link to="/signup">
5
- sign up now
5
+ {{$t('sign up now')}}
6
6
  </Link>
7
7
  </p>
8
8
  </template>
@@ -0,0 +1,295 @@
1
+ <template>
2
+ <div class="relative flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-800 relative w-screen h-screen"
3
+ :style="coreStore.config?.loginBackgroundImage && backgroundPosition === 'over' ? {
4
+ 'background-image': 'url(' + loadFile(coreStore.config?.loginBackgroundImage) + ')',
5
+ 'background-size': 'cover',
6
+ 'background-position': 'center',
7
+ 'background-blend-mode': 'darken'
8
+ }: {}"
9
+ >
10
+
11
+ <img v-if="coreStore.config?.loginBackgroundImage && backgroundPosition !== 'over'"
12
+ :src="loadFile(coreStore.config?.loginBackgroundImage)"
13
+ class="position-absolute top-0 left-0 h-screen object-cover w-0"
14
+ :class="{
15
+ '1/2': 'md:w-1/2',
16
+ '1/3': 'md:w-1/3',
17
+ '2/3': 'md:w-2/3',
18
+ '3/4': 'md:w-3/4',
19
+ '2/5': 'md:w-2/5',
20
+ '3/5': 'md:w-3/5',
21
+ }[backgroundPosition]"
22
+ />
23
+
24
+ <!-- Main modal -->
25
+ <div id="authentication-modal" tabindex="-1"
26
+ class="overflow-y-auto flex flex-grow
27
+ overflow-x-hidden z-50 min-w-[350px] justify-center items-center md:inset-0 h-[calc(100%-1rem)] max-h-full">
28
+ <div class="relative p-4 w-full max-h-full max-w-[400px]">
29
+ <!-- Modal content -->
30
+ <div class="relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black" >
31
+ <!-- Modal header -->
32
+ <div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
33
+ <h3 class="text-xl font-semibold text-gray-900 dark:text-white">
34
+ Reset your password on {{ coreStore.config?.brandName }}
35
+ </h3>
36
+ </div>
37
+ <!-- Modal body -->
38
+ <div class="p-4 md:p-5">
39
+ <form v-if="enteringNew" class="space-y-4" role="alert" @submit.prevent>
40
+ <div class="relative">
41
+ <label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">New password</label>
42
+ <input
43
+ :type="unmasked ? 'text' : 'password'"
44
+ name="password" id="password"
45
+ v-model="password"
46
+ @keydown.enter="passwordConfirmationInput.focus()"
47
+ class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
48
+ placeholder="New password" required
49
+ />
50
+
51
+ <button
52
+ type="button"
53
+ @click="unmasked = !unmasked"
54
+ class="h-6 absolute inset-y-2 top-9 right-1 flex items-center pr-2 z-index-100 focus:outline-none"
55
+ >
56
+ <IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked" />
57
+ <IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
58
+ </button>
59
+ </div>
60
+
61
+ <div class="relative">
62
+ <label for="password_confirmation" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Confirm new password</label>
63
+ <input
64
+ :type="unmasked ? 'text' : 'password'"
65
+ name="password_confirmation" id="password_confirmation"
66
+ v-model="passwordConfirmation"
67
+ class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
68
+ placeholder="Confirm new password" required
69
+ />
70
+
71
+ <button
72
+ type="button"
73
+ @click="unmasked = !unmasked"
74
+ class="h-6 absolute inset-y-2 top-9 right-1 flex items-center pr-2 z-index-100 focus:outline-none"
75
+ >
76
+ <IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked" />
77
+ <IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
78
+ </button>
79
+ </div>
80
+
81
+ <div v-if="validationError || error" class="flex items-center p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
82
+ <svg class="flex-shrink-0 inline w-4 h-4 me-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
83
+ <path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
84
+ </svg>
85
+ <span class="sr-only">Info</span>
86
+ <div>
87
+ {{ validationError || error }}
88
+ </div>
89
+ </div>
90
+
91
+ <button
92
+ :disabled="inProgress || (validationRunning && validationError)"
93
+ @click="setNewPassword"
94
+ type="submit" class="flex items-center justify-center gap-1 w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 disabled:opacity-50">
95
+ <svg v-if="inProgress"
96
+ aria-hidden="true" class="w-4 h-4 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/><path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874
97
+ // 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/></svg>
98
+ Set new password
99
+ </button>
100
+ </form>
101
+ <!-- END of set new paasord -->
102
+ <div v-if="!enteringNew && requestSent" class="flex items center justify-center p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:bg-green-800 dark:text-green-400" role="alert">
103
+ If user with specified email exists in our db, then request was sent. Please check your email at {{ sentToEmail }} to reset your password.
104
+ </div>
105
+
106
+ <form v-if="!enteringNew && !requestSent" class="space-y-4" @submit.prevent>
107
+ <div>
108
+ <label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
109
+ <input type="email" name="email" id="email"
110
+ @keydown.enter="reset()"
111
+ ref="emailInput"
112
+ class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
113
+ placeholder="name@company.com"
114
+ required
115
+ />
116
+ </div>
117
+
118
+ <div v-if="error" class="flex items-center p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
119
+ <svg class="flex-shrink-0 inline w-4 h-4 me-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
120
+ <path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
121
+ </svg>
122
+ <span class="sr-only">Info</span>
123
+ <div>
124
+ {{ error }}
125
+ </div>
126
+ </div>
127
+
128
+
129
+ <button
130
+ @click="reset"
131
+ type="submit" class="flex items-center justify-center gap-1 w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
132
+ <svg v-if="inProgress"
133
+ aria-hidden="true" class="w-4 h-4 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/><path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/></svg>
134
+
135
+ Reset Password
136
+
137
+ </button>
138
+
139
+ <p class="text-gray-500 dark:text-gray-400 font-sm text-right mt-3">
140
+ or <router-link class=" text-blue-600 underline dark:text-blue-500 hover:no-underline
141
+ cursor-pointer" to="/login">
142
+ Back to login
143
+ </router-link>
144
+ </p>
145
+
146
+ </form>
147
+
148
+ </div>
149
+ </div>
150
+ </div>
151
+ </div>
152
+
153
+ </div>
154
+ </template>
155
+
156
+
157
+ <script setup lang="ts">
158
+
159
+ import { onMounted, ref, computed, Ref } from 'vue';
160
+ import { useCoreStore } from '@/stores/core';
161
+ import { callAdminForthApi, loadFile, applyRegexValidation } from '@/utils';
162
+ import { useRoute, useRouter } from 'vue-router';
163
+ import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
164
+
165
+
166
+ const inProgress = ref(false);
167
+
168
+ const coreStore = useCoreStore();
169
+ const requestSent = ref(false);
170
+
171
+ const emailInput = ref(null);
172
+ const password = ref("");
173
+ const passwordConfirmation = ref("");
174
+ const unmasked = ref(false);
175
+ const sentToEmail: Ref<string> = ref('');
176
+
177
+
178
+ const route = useRoute();
179
+ const router = useRouter();
180
+
181
+ function checkPassowrd() {
182
+
183
+ if (!password.value || !passwordConfirmation.value) {
184
+ return 'Please enter both password and password confirmation';
185
+ }
186
+ if (password.value !== passwordConfirmation.value) {
187
+ return 'Passwords do not match';
188
+ }
189
+
190
+ if (password.value.length < passwordField.value.minLength) {
191
+ return `Password must be at least ${passwordField.value.minLength} characters long`;
192
+ }
193
+
194
+ if (password.value.length > passwordField.value.maxLength) {
195
+ return `Password must be at most ${passwordField.value.maxLength} characters long`;
196
+ }
197
+
198
+ if (passwordField.value.validation) {
199
+ const valError = applyRegexValidation(password.value, passwordField.value.validation);
200
+ if (valError) {
201
+ return valError;
202
+ }
203
+ }
204
+
205
+ return null;
206
+ }
207
+
208
+ const validationRunning = ref(false);
209
+
210
+ const validationError = computed(() => {
211
+ console.log('validationRunning.value', validationRunning.value, 'aa', checkPassowrd());
212
+ if (validationRunning.value) {
213
+ return checkPassowrd();
214
+ }
215
+ return null;
216
+ });
217
+
218
+ const error = ref(null);
219
+
220
+ // if token query param is present, we are entering new password
221
+ const enteringNew = computed(() => route.query.token);
222
+
223
+ const backgroundPosition = computed(() => {
224
+ return coreStore.config?.loginBackgroundPosition || '1/2';
225
+ });
226
+
227
+ const passwordField = computed(
228
+ () => route.meta.passwordField
229
+ )
230
+
231
+ onMounted(async () => {
232
+ await coreStore.getPublicConfig();
233
+ });
234
+
235
+ async function reset() {
236
+ error.value = null;
237
+ const email = emailInput.value.value;
238
+ if (!email) {
239
+ error.value = 'Please enter your email';
240
+ return;
241
+ }
242
+
243
+ inProgress.value = true;
244
+ const resp = await callAdminForthApi({
245
+ path: `/plugin/${route.meta.pluginInstanceId}/reset-password`,
246
+ method: 'POST',
247
+ body: {
248
+ email,
249
+ url: window.location.origin + window.location.pathname,
250
+ }
251
+ });
252
+ inProgress.value = false;
253
+ if (resp.error) {
254
+ error.value = resp.error;
255
+ } else {
256
+ error.value = null;
257
+ requestSent.value = true;
258
+ sentToEmail.value = email;
259
+ }
260
+ }
261
+
262
+ async function setNewPassword() {
263
+ error.value = null;
264
+
265
+ if (checkPassowrd()) {
266
+ validationRunning.value = true;
267
+ return;
268
+ }
269
+
270
+ inProgress.value = true;
271
+ const resp = await callAdminForthApi({
272
+ path: `/plugin/${route.meta.pluginInstanceId}/reset-password-confirm`,
273
+ method: 'POST',
274
+ body: {
275
+ token: route.query.token,
276
+ password: password.value,
277
+ }
278
+ });
279
+ inProgress.value = false;
280
+ if (resp.error) {
281
+ error.value = resp.error;
282
+ } else {
283
+ error.value = null;
284
+ router.push('/login');
285
+ window.adminforth.alert({
286
+ message: 'Password reset successfully. Please login with your new password',
287
+ variant: 'success',
288
+ timeout: 15,
289
+ });
290
+
291
+ }
292
+ }
293
+
294
+
295
+ </script>
@@ -0,0 +1,13 @@
1
+ <template>
2
+ <p class="text-gray-500 dark:text-gray-400 font-sm text-left mt-3">
3
+ Forgot password? <router-link class=" text-blue-600 underline dark:text-blue-500 hover:no-underline
4
+ cursor-pointer" to="/reset-password">
5
+ reset it
6
+ </router-link>
7
+ </p>
8
+ </template>
9
+
10
+ <script setup>
11
+
12
+
13
+ </script>
@@ -31,14 +31,14 @@
31
31
  <!-- Modal header -->
32
32
  <div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
33
33
  <h3 class="text-xl font-semibold text-gray-900 dark:text-white">
34
- Sign up into {{ coreStore.config?.brandName }}
34
+ {{$t('Sign up into')}} {{ coreStore.config?.brandName }}
35
35
  </h3>
36
36
  </div>
37
37
  <!-- Modal body -->
38
38
  <div class="p-4 md:p-5">
39
39
  <form v-if="!requestSent" class="space-y-4" role="alert" @submit.prevent>
40
40
  <div v-if="!verifyToken" class="relative">
41
- <label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
41
+ <label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{$t('Your email')}}</label>
42
42
  <input type="email" name="email" id="email"
43
43
  tabindex="1"
44
44
  autocomplete="username"
@@ -50,7 +50,7 @@
50
50
  />
51
51
  </div>
52
52
  <div v-if="isPasswordNeeded" class="relative">
53
- <label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">New password</label>
53
+ <label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{$t('New password')}}</label>
54
54
  <input
55
55
  tabindex="2"
56
56
  autocomplete="new-password"
@@ -60,7 +60,7 @@
60
60
  v-model="password"
61
61
  @keydown.enter="passwordConfirmationInput?.focus()"
62
62
  class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
63
- placeholder="New password" required
63
+ :placeholder="$t('New password')" required
64
64
  />
65
65
 
66
66
  <button
@@ -74,7 +74,7 @@
74
74
  </div>
75
75
 
76
76
  <div v-if="isPasswordNeeded" class="relative">
77
- <label for="password_confirmation" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Confirm new password</label>
77
+ <label for="password_confirmation" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{$t('Confirm new password')}}</label>
78
78
  <input
79
79
  ref="passwordConfirmationInput"
80
80
  autocomplete="new-password"
@@ -83,7 +83,7 @@
83
83
  tabindex="3"
84
84
  v-model="passwordConfirmation"
85
85
  class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
86
- placeholder="Confirm new password" required
86
+ :placeholder="$t('Confirm new password')" required
87
87
  />
88
88
 
89
89
  <button
@@ -100,7 +100,7 @@
100
100
  <svg class="flex-shrink-0 inline w-4 h-4 me-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
101
101
  <path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
102
102
  </svg>
103
- <span class="sr-only">Info</span>
103
+ <span class="sr-only">{{$t('Info')}}</span>
104
104
  <div>
105
105
  {{ validationError || error }}
106
106
  </div>
@@ -113,15 +113,15 @@
113
113
  class="w-full"
114
114
  :loader="inProgress"
115
115
  >
116
- Sign up
116
+ {{$t('Sign up')}}
117
117
  </Button>
118
118
  </form>
119
119
  <!-- END of set new paasord -->
120
120
  <div v-else class="flex items center justify-center p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:bg-green-800 dark:text-green-400" role="alert">
121
- Please check your email at {{ sentToEmail }} to confirm your email address.
121
+ {{$t('Please check your email at')}} {{ sentToEmail }} {{$t('to confirm your email address.')}}
122
122
  </div>
123
123
  <p class="text-gray-500 dark:text-gray-400 font-sm text-right mt-3">
124
- or <Link to="/login">{{ toLoginText }}</Link>
124
+ {{$t('or')}} <Link to="/login">{{ toLoginText }}</Link>
125
125
  </p>
126
126
  </div>
127
127
  </div>
@@ -141,6 +141,9 @@ import { useRoute, useRouter } from 'vue-router';
141
141
  import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
142
142
  import Button from '@/afcl/Button.vue';
143
143
  import Link from '@/afcl/Link.vue';
144
+ import { useI18n } from 'vue-i18n';
145
+
146
+ const { t } = useI18n();
144
147
 
145
148
  const inProgress = ref(false);
146
149
 
@@ -158,7 +161,7 @@ const sentToEmail: Ref<string> = ref('');
158
161
 
159
162
  const requestEmailConfirmation = computed(() => route.meta.requestEmailConfirmation);
160
163
  const verifyToken = computed(() => route.query.token);
161
- const toLoginText = computed(() => `${verifyToken.value ? 'Go' : 'Back'} to login`);
164
+ const toLoginText = computed(() => verifyToken.value ? t('Go to login') : t('Back to login'));
162
165
  const isPasswordNeeded = computed(() => !requestEmailConfirmation.value || (requestEmailConfirmation.value && verifyToken.value));
163
166
 
164
167
  const user = useUserStore();
@@ -169,22 +172,25 @@ const router = useRouter();
169
172
  function checkPassword() {
170
173
 
171
174
  if (!password.value || !passwordConfirmation.value) {
172
- return 'Please enter both password and password confirmation';
175
+ return t('Please enter both password and password confirmation');
173
176
  }
174
177
  if (password.value !== passwordConfirmation.value) {
175
- return 'Passwords do not match';
178
+ return t('Passwords do not match');
176
179
  }
177
180
 
178
- if (password.value.length < passwordField.value.minLength) {
179
- return `Password must be at least ${passwordField.value.minLength} characters long`;
181
+ if (!passwordConstraints.value) {
182
+ return null;
183
+ }
184
+ if (password.value.length < passwordConstraints.value.minLength) {
185
+ return t(`Password must be at least {minLength} characters long`, { minLength: passwordConstraints.value.minLength });
180
186
  }
181
187
 
182
- if (password.value.length > passwordField.value.maxLength) {
183
- return `Password must be at most ${passwordField.value.maxLength} characters long`;
188
+ if (password.value.length > passwordConstraints.value.maxLength) {
189
+ return t(`Password must be at most {maxLength} characters long`, { maxLength: passwordConstraints.value.maxLength });
184
190
  }
185
191
 
186
- if (passwordField.value.validation) {
187
- const valError = applyRegexValidation(password.value, passwordField.value.validation);
192
+ if (passwordConstraints.value.validation) {
193
+ const valError = applyRegexValidation(password.value, passwordConstraints.value.validation);
188
194
  if (valError) {
189
195
  return valError;
190
196
  }
@@ -213,19 +219,31 @@ const backgroundPosition = computed(() => {
213
219
  return coreStore.config?.loginBackgroundPosition || '1/2';
214
220
  });
215
221
 
216
- const passwordField = computed(
217
- () => route.meta.passwordField
218
- )
222
+
223
+ const passwordConstraints: Ref<{
224
+ minLength: number;
225
+ maxLength: number;
226
+ validation: string;
227
+ }> = ref({
228
+ minLength: 8,
229
+ maxLength: 100,
230
+ validation: '',
231
+ });
219
232
 
220
233
  onMounted(async () => {
221
234
  await coreStore.getPublicConfig();
235
+ // getPasswordConstraints
236
+ passwordConstraints.value = await callAdminForthApi({
237
+ path: `/plugin/${route.meta.pluginInstanceId}/password-constraints`,
238
+ method: 'GET',
239
+ });
222
240
  });
223
241
 
224
242
  async function doSignup() {
225
243
  error.value = null;
226
244
  const email = emailInput.value!.value;
227
245
  if (!email) {
228
- error.value = 'Please enter your email';
246
+ error.value = t('Please enter your email');
229
247
  return;
230
248
  }
231
249
  if (!requestEmailConfirmation.value && checkPassword()) {
@@ -274,7 +292,7 @@ const signupAfterEmailConfirmation = async () => {
274
292
  });
275
293
  if (resp.error) {
276
294
  window.adminforth.alert({
277
- message: `Error fetching data: ${resp.error}`,
295
+ message: t(`Error fetching data: {error}`, { error: resp.error }),
278
296
  variant: 'danger',
279
297
  });
280
298
  } else if (resp.redirectTo) {
@@ -1,8 +1,8 @@
1
1
  <template>
2
2
  <p class="text-gray-500 dark:text-gray-400 font-sm text-left mt-3">
3
- Have no account?
3
+ {{$t('Have no account?')}}
4
4
  <Link to="/signup">
5
- sign up now
5
+ {{$t('sign up now')}}
6
6
  </Link>
7
7
  </p>
8
8
  </template>
package/dist/index.js CHANGED
@@ -73,11 +73,6 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
73
73
  meta: {
74
74
  customLayout: true,
75
75
  pluginInstanceId: this.pluginInstanceId,
76
- passwordField: {
77
- minLength: passwordField.minLength,
78
- maxLength: passwordField.maxLength,
79
- validation: passwordField.validation
80
- },
81
76
  requestEmailConfirmation: !!this.options.confirmEmails
82
77
  }
83
78
  }
@@ -91,6 +86,9 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
91
86
  });
92
87
  }
93
88
  validateConfigAfterDiscover(adminforth, resourceConfig) {
89
+ if (this.options.confirmEmails) {
90
+ this.options.confirmEmails.adapter.validate();
91
+ }
94
92
  if (this.options.confirmEmails) {
95
93
  const emailConfirmedColumn = this.resourceConfig.columns.find(f => f.name === this.options.confirmEmails.emailConfirmedField);
96
94
  if (emailConfirmedColumn.type !== AdminForthDataTypes.BOOLEAN) {
@@ -125,25 +123,37 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
125
123
  });
126
124
  }
127
125
  setupEndpoints(server) {
126
+ server.endpoint({
127
+ method: 'GET',
128
+ path: `/plugin/${this.pluginInstanceId}/password-constraints`,
129
+ noAuth: true,
130
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ tr }) {
131
+ return {
132
+ minLength: this.passwordField.minLength,
133
+ maxLength: this.passwordField.maxLength,
134
+ validation: yield Promise.all(this.passwordField.validation.map((_b) => __awaiter(this, [_b], void 0, function* ({ regExp, message }) { return ({ regExp, message: yield tr(message, 'opensignup') }); }))),
135
+ };
136
+ })
137
+ });
128
138
  server.endpoint({
129
139
  method: 'POST',
130
140
  path: `/plugin/${this.pluginInstanceId}/complete-verified-signup`,
131
141
  noAuth: true,
132
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response, headers, query, cookies }) {
142
+ handler: (_c) => __awaiter(this, [_c], void 0, function* ({ body, response, headers, query, cookies, tr }) {
133
143
  const { token, password } = body;
134
144
  const { email } = yield this.adminforth.auth.verify(token, 'tempVerifyEmailToken', false);
135
145
  if (!email) {
136
146
  return { error: 'Invalid token', ok: false };
137
147
  }
138
148
  if (!password) {
139
- return { error: 'Password is required', ok: false };
149
+ return { error: tr('Password is required', 'opensignup'), ok: false };
140
150
  }
141
151
  const userRecord = yield this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
142
152
  if (!userRecord) {
143
- return { error: 'User not found', ok: false };
153
+ return { error: tr('User not found'), ok: false };
144
154
  }
145
155
  if (userRecord[this.options.confirmEmails.emailConfirmedField]) {
146
- return { error: 'Email already confirmed', ok: false };
156
+ return { error: tr('Email already confirmed'), ok: false };
147
157
  }
148
158
  yield this.adminforth.resource(this.authResource.resourceId).update(userRecord[this.authResource.columns.find((col) => col.primaryKey).name], {
149
159
  [this.options.confirmEmails.emailConfirmedField]: true,
@@ -156,7 +166,7 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
156
166
  method: 'POST',
157
167
  path: `/plugin/${this.pluginInstanceId}/signup`,
158
168
  noAuth: true,
159
- handler: (_b) => __awaiter(this, [_b], void 0, function* ({ body, response, headers, query, cookies }) {
169
+ handler: (_d) => __awaiter(this, [_d], void 0, function* ({ body, response, headers, query, cookies, tr }) {
160
170
  const { email, url, password } = body;
161
171
  // validate email
162
172
  if (this.emailField.validation) {
@@ -169,15 +179,21 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
169
179
  // validate password
170
180
  if (!this.options.confirmEmails) {
171
181
  if (password.length < this.passwordField.minLength) {
172
- return { error: `Password must be at least ${this.passwordField.minLength} characters long`, ok: false };
182
+ return {
183
+ error: tr(`Password must be at least ${this.passwordField.minLength} characters long`, 'opensignup'),
184
+ ok: false
185
+ };
173
186
  }
174
187
  if (password.length > this.passwordField.maxLength) {
175
- return { error: `Password must be at most ${this.passwordField.maxLength} characters long`, ok: false };
188
+ return {
189
+ error: tr(`Password must be at most ${this.passwordField.maxLength} characters long`, 'opensignup'),
190
+ ok: false
191
+ };
176
192
  }
177
193
  if (this.passwordField.validation) {
178
194
  for (const { regExp, message } of this.passwordField.validation) {
179
195
  if (!new RegExp(regExp).test(password)) {
180
- return { error: message, ok: false };
196
+ return { error: tr(message, 'opensignup'), ok: false };
181
197
  }
182
198
  }
183
199
  }
@@ -185,7 +201,7 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
185
201
  // first check again if email already exists
186
202
  const existingUser = yield this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
187
203
  if ((!this.options.confirmEmails && existingUser) || (this.options.confirmEmails && (existingUser === null || existingUser === void 0 ? void 0 : existingUser[this.emailConfirmedField.name]))) {
188
- return { error: 'Email already exists', ok: false };
204
+ return { error: tr('Email already exists', 'opensignup'), ok: false };
189
205
  }
190
206
  // create user
191
207
  if (!existingUser) {
@@ -199,39 +215,37 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
199
215
  const brandName = this.adminforth.config.customization.brandName;
200
216
  const verifyToken = this.adminforth.auth.issueJWT({ email, issuer: brandName }, 'tempVerifyEmailToken', '2h');
201
217
  process.env.HEAVY_DEBUG && console.log('🐛Sending reset tok to', verifyToken);
202
- const emailText = `
218
+ const emailText = tr(`
203
219
  Dear user,
204
- Welcome to ${brandName}!
220
+ Welcome to {brandName}!
205
221
 
206
222
  To confirm your email, click the link below:\n\n
207
223
 
208
- ${url}?verifyToken=${verifyToken}\n\n
224
+ {url}?verifyToken={verifyToken}\n\n
209
225
 
210
226
  If you didn't request this, please ignore this email.\n\n
211
227
  Link is valid for 2 hours.\n\n
212
228
 
213
229
  Thanks,
214
- The ${brandName} Team
230
+ The {brandName} Team
215
231
 
216
- `;
217
- const emailHtml = `
218
- <html>
219
- <head></head>
220
- <body>
221
- <p>Dear user,</p>
222
- <p>Welcome to ${brandName}!</p>
223
- <p>To confirm your email, click the link below:</p>
224
- <a href="${url}?token=${verifyToken}">Confirm email</a>
225
- <p>If you didn't request this, please ignore this email.</p>
226
- <p>Link is valid for 2 hours.</p>
227
- <p>Thanks,</p>
228
- <p>The ${brandName} Team</p>
229
- </body>
230
- </html>
231
-
232
-
233
- `;
234
- const emailSubject = `Signup request at ${brandName}`;
232
+ `, 'opensignup', { brandName, url, verifyToken });
233
+ const emailHtml = tr(`
234
+ <html>
235
+ <head></head>
236
+ <body>
237
+ <p>Dear user,</p>
238
+ <p>Welcome to {brandName}!</p>
239
+ <p>To confirm your email, click the link below:</p>
240
+ <a href="{url}?token={verifyToken}">Confirm email</a>
241
+ <p>If you didn't request this, please ignore this email.</p>
242
+ <p>Link is valid for 2 hours.</p>
243
+ <p>Thanks,</p>
244
+ <p>The {brandName} Team</p>
245
+ </body>
246
+ </html>
247
+ `, 'opensignup', { brandName, url, verifyToken });
248
+ const emailSubject = tr(`Signup request at {brandName}`, 'opensignup', { brandName });
235
249
  // send email with AWS SES this.options.providerOptions.AWS_SES
236
250
  this.options.confirmEmails.adapter.sendEmail(this.options.confirmEmails.sendFrom, email, emailText, emailHtml, emailSubject);
237
251
  return { ok: true };
package/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import AdminForth, { AdminForthPlugin, Filters, suggestIfTypo, AdminForthDataTypes } from "adminforth";
2
2
  import type { IAdminForth, IHttpServer, AdminForthComponentDeclaration, AdminForthResourceColumn, AdminForthResource, BeforeLoginConfirmationFunction, HttpExtra } from "adminforth";
3
3
  import type { PluginOptions } from './types.js';
4
+ import { error } from "console";
4
5
 
5
6
 
6
7
  export default class OpenSignupPlugin extends AdminForthPlugin {
@@ -81,11 +82,6 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
81
82
  meta: {
82
83
  customLayout: true,
83
84
  pluginInstanceId: this.pluginInstanceId,
84
- passwordField: {
85
- minLength: passwordField.minLength,
86
- maxLength: passwordField.maxLength,
87
- validation: passwordField.validation
88
- },
89
85
  requestEmailConfirmation: !!this.options.confirmEmails
90
86
  }
91
87
  }
@@ -100,6 +96,11 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
100
96
  }
101
97
 
102
98
  validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
99
+ if (this.options.confirmEmails) {
100
+ this.options.confirmEmails.adapter.validate();
101
+ }
102
+
103
+
103
104
  if(this.options.confirmEmails){
104
105
  const emailConfirmedColumn = this.resourceConfig.columns.find(f => f.name === this.options.confirmEmails.emailConfirmedField);
105
106
  if (emailConfirmedColumn.type !== AdminForthDataTypes.BOOLEAN) {
@@ -141,11 +142,27 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
141
142
 
142
143
 
143
144
  setupEndpoints(server: IHttpServer) {
145
+
146
+ server.endpoint({
147
+ method: 'GET',
148
+ path: `/plugin/${this.pluginInstanceId}/password-constraints`,
149
+ noAuth: true,
150
+ handler: async ({tr}) => {
151
+ return {
152
+ minLength: this.passwordField.minLength,
153
+ maxLength: this.passwordField.maxLength,
154
+ validation: await Promise.all(
155
+ this.passwordField.validation.map(async ({ regExp, message }) => ({ regExp, message: await tr(message, 'opensignup') }))
156
+ ),
157
+ };
158
+ }
159
+ });
160
+
144
161
  server.endpoint({
145
162
  method: 'POST',
146
163
  path: `/plugin/${this.pluginInstanceId}/complete-verified-signup`,
147
164
  noAuth: true,
148
- handler: async ({ body, response, headers, query, cookies }) => {
165
+ handler: async ({ body, response, headers, query, cookies, tr }) => {
149
166
  const { token, password } = body;
150
167
  const { email } = await this.adminforth.auth.verify(token, 'tempVerifyEmailToken', false);
151
168
  if (!email) {
@@ -153,15 +170,15 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
153
170
  }
154
171
 
155
172
  if(!password) {
156
- return { error: 'Password is required', ok: false };
173
+ return { error: tr('Password is required', 'opensignup'), ok: false };
157
174
  }
158
175
  const userRecord = await this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
159
176
  if (!userRecord) {
160
- return { error: 'User not found', ok: false };
177
+ return { error: tr('User not found'), ok: false };
161
178
  }
162
179
 
163
180
  if (userRecord[this.options.confirmEmails.emailConfirmedField]) {
164
- return { error: 'Email already confirmed', ok: false };
181
+ return { error: tr('Email already confirmed'), ok: false };
165
182
  }
166
183
 
167
184
  await this.adminforth.resource(this.authResource.resourceId).update(userRecord[this.authResource.columns.find((col) => col.primaryKey).name], {
@@ -176,7 +193,7 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
176
193
  method: 'POST',
177
194
  path: `/plugin/${this.pluginInstanceId}/signup`,
178
195
  noAuth: true,
179
- handler: async ({ body, response, headers, query, cookies }) => {
196
+ handler: async ({ body, response, headers, query, cookies, tr }) => {
180
197
  const { email, url, password } = body;
181
198
 
182
199
  // validate email
@@ -191,15 +208,21 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
191
208
  // validate password
192
209
  if (!this.options.confirmEmails) {
193
210
  if (password.length < this.passwordField.minLength) {
194
- return { error: `Password must be at least ${this.passwordField.minLength} characters long`, ok: false };
211
+ return {
212
+ error: tr(`Password must be at least ${this.passwordField.minLength} characters long`, 'opensignup'),
213
+ ok: false
214
+ };
195
215
  }
196
216
  if (password.length > this.passwordField.maxLength) {
197
- return { error: `Password must be at most ${this.passwordField.maxLength} characters long`, ok: false };
217
+ return {
218
+ error: tr(`Password must be at most ${this.passwordField.maxLength} characters long`, 'opensignup'),
219
+ ok: false
220
+ };
198
221
  }
199
222
  if (this.passwordField.validation) {
200
223
  for (const { regExp, message } of this.passwordField.validation) {
201
224
  if (!new RegExp(regExp).test(password)) {
202
- return { error: message, ok: false };
225
+ return { error: tr(message, 'opensignup'), ok: false };
203
226
  }
204
227
  }
205
228
  }
@@ -208,7 +231,7 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
208
231
  // first check again if email already exists
209
232
  const existingUser = await this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
210
233
  if ((!this.options.confirmEmails && existingUser) || (this.options.confirmEmails && existingUser?.[this.emailConfirmedField.name])) {
211
- return { error: 'Email already exists', ok: false };
234
+ return { error: tr('Email already exists', 'opensignup'), ok: false };
212
235
  }
213
236
 
214
237
  // create user
@@ -232,40 +255,40 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
232
255
 
233
256
  const verifyToken = this.adminforth.auth.issueJWT({email, issuer: brandName }, 'tempVerifyEmailToken', '2h');
234
257
  process.env.HEAVY_DEBUG && console.log('🐛Sending reset tok to', verifyToken);
235
- const emailText = `
258
+ const emailText = tr(`
236
259
  Dear user,
237
- Welcome to ${brandName}!
260
+ Welcome to {brandName}!
238
261
 
239
262
  To confirm your email, click the link below:\n\n
240
263
 
241
- ${url}?verifyToken=${verifyToken}\n\n
264
+ {url}?verifyToken={verifyToken}\n\n
242
265
 
243
266
  If you didn't request this, please ignore this email.\n\n
244
267
  Link is valid for 2 hours.\n\n
245
268
 
246
269
  Thanks,
247
- The ${brandName} Team
270
+ The {brandName} Team
248
271
 
249
- `;
272
+ `, 'opensignup', { brandName, url, verifyToken }
273
+ );
250
274
 
251
- const emailHtml = `
252
- <html>
253
- <head></head>
254
- <body>
255
- <p>Dear user,</p>
256
- <p>Welcome to ${brandName}!</p>
257
- <p>To confirm your email, click the link below:</p>
258
- <a href="${url}?token=${verifyToken}">Confirm email</a>
259
- <p>If you didn't request this, please ignore this email.</p>
260
- <p>Link is valid for 2 hours.</p>
261
- <p>Thanks,</p>
262
- <p>The ${brandName} Team</p>
263
- </body>
264
- </html>
265
-
266
-
267
- `;
268
- const emailSubject = `Signup request at ${brandName}`;
275
+ const emailHtml = tr(`
276
+ <html>
277
+ <head></head>
278
+ <body>
279
+ <p>Dear user,</p>
280
+ <p>Welcome to {brandName}!</p>
281
+ <p>To confirm your email, click the link below:</p>
282
+ <a href="{url}?token={verifyToken}">Confirm email</a>
283
+ <p>If you didn't request this, please ignore this email.</p>
284
+ <p>Link is valid for 2 hours.</p>
285
+ <p>Thanks,</p>
286
+ <p>The {brandName} Team</p>
287
+ </body>
288
+ </html>
289
+ `, 'opensignup', { brandName, url, verifyToken });
290
+
291
+ const emailSubject = tr(`Signup request at {brandName}`, 'opensignup', { brandName });
269
292
 
270
293
  // send email with AWS SES this.options.providerOptions.AWS_SES
271
294
  this.options.confirmEmails.adapter.sendEmail(this.options.confirmEmails.sendFrom, email, emailText, emailHtml, emailSubject);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/open-signup",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",