@oxygen-cms/ui 1.5.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 (84) hide show
  1. package/.babelrc +1 -0
  2. package/.eslintrc.js +22 -0
  3. package/.github/workflows/node.js.yml +29 -0
  4. package/.idea/modules.xml +8 -0
  5. package/.idea/ui.iml +10 -0
  6. package/.jshintrc +3 -0
  7. package/README.md +7 -0
  8. package/assets/oxygen-icon.png +0 -0
  9. package/jest.init.js +1 -0
  10. package/package.json +72 -0
  11. package/src/AuthApi.js +116 -0
  12. package/src/CrudApi.js +112 -0
  13. package/src/EventsApi.js +16 -0
  14. package/src/GroupsApi.js +9 -0
  15. package/src/Internationalize.js +31 -0
  16. package/src/MediaApi.js +52 -0
  17. package/src/MediaDirectoryApi.js +62 -0
  18. package/src/PreferencesApi.js +47 -0
  19. package/src/UserPermissions.js +66 -0
  20. package/src/UserPreferences.js +69 -0
  21. package/src/UserPreferences.test.js +23 -0
  22. package/src/UsersApi.js +41 -0
  23. package/src/api.js +209 -0
  24. package/src/components/App.vue +61 -0
  25. package/src/components/AuthenticatedLayout.vue +254 -0
  26. package/src/components/AuthenticationLog.vue +196 -0
  27. package/src/components/CodeEditor.vue +90 -0
  28. package/src/components/EditButtonOnRowHover.vue +21 -0
  29. package/src/components/Error404.vue +25 -0
  30. package/src/components/EventsChooser.vue +88 -0
  31. package/src/components/EventsTable.vue +82 -0
  32. package/src/components/GenericEditableField.vue +74 -0
  33. package/src/components/GroupsChooser.vue +58 -0
  34. package/src/components/GroupsList.vue +129 -0
  35. package/src/components/ImportExport.vue +45 -0
  36. package/src/components/LegacyPage.vue +256 -0
  37. package/src/components/UserJoined.vue +35 -0
  38. package/src/components/UserManagement.vue +168 -0
  39. package/src/components/UserProfileForm.vue +214 -0
  40. package/src/components/ViewProfile.vue +32 -0
  41. package/src/components/auth/Auth404.vue +16 -0
  42. package/src/components/auth/Login.vue +135 -0
  43. package/src/components/auth/LoginLogo.vue +30 -0
  44. package/src/components/auth/Logout.vue +26 -0
  45. package/src/components/auth/PasswordRemind.vue +71 -0
  46. package/src/components/auth/PasswordReset.vue +97 -0
  47. package/src/components/auth/TwoFactorSetup.vue +115 -0
  48. package/src/components/auth/VerifyEmail.vue +71 -0
  49. package/src/components/auth/WelcomeFloat.vue +87 -0
  50. package/src/components/auth/login.scss +17 -0
  51. package/src/components/media/MediaChooseDirectory.vue +129 -0
  52. package/src/components/media/MediaDirectory.vue +109 -0
  53. package/src/components/media/MediaInsertModal.vue +88 -0
  54. package/src/components/media/MediaItem.vue +282 -0
  55. package/src/components/media/MediaItemPreview.vue +45 -0
  56. package/src/components/media/MediaList.vue +305 -0
  57. package/src/components/media/MediaPage.vue +44 -0
  58. package/src/components/media/MediaResponsiveImages.vue +51 -0
  59. package/src/components/media/MediaUpload.vue +133 -0
  60. package/src/components/media/media.scss +51 -0
  61. package/src/components/preferences/PreferencesAdminAppearance.vue +22 -0
  62. package/src/components/preferences/PreferencesAuthentication.vue +27 -0
  63. package/src/components/preferences/PreferencesEventTemplates.vue +22 -0
  64. package/src/components/preferences/PreferencesField.vue +215 -0
  65. package/src/components/preferences/PreferencesList.vue +50 -0
  66. package/src/components/preferences/PreferencesPageTemplates.vue +23 -0
  67. package/src/components/preferences/PreferencesSiteAppearance.vue +22 -0
  68. package/src/components/preferences/PreferencesThemeChooser.vue +73 -0
  69. package/src/components/preferences/ShowIfPermitted.vue +37 -0
  70. package/src/components/preferences/UserPreferences.vue +30 -0
  71. package/src/components/users/CreateUserModal.vue +73 -0
  72. package/src/components/util.css +47 -0
  73. package/src/icons.js +90 -0
  74. package/src/main.js +112 -0
  75. package/src/modules/LegacyPages.js +18 -0
  76. package/src/modules/Media.js +45 -0
  77. package/src/modules/UserManagement.js +24 -0
  78. package/src/routes/index.js +92 -0
  79. package/src/store/index.js +70 -0
  80. package/src/styles/_variables.scss +23 -0
  81. package/src/styles/app.scss +76 -0
  82. package/src/unsavedChanges.js +16 -0
  83. package/src/util.js +65 -0
  84. package/src/util.test.js +39 -0
@@ -0,0 +1,214 @@
1
+ <template>
2
+ <div>
3
+ <div class="media">
4
+ <div class="media-left has-background-grey-light centered-icon">
5
+ <b-icon icon="user" size="is-large" class="has-text-grey-lighter"></b-icon>
6
+ </div>
7
+ <div class="media-content">
8
+ <GenericEditableField :api="usersApi" :data="user" label="Full Name" field-name="fullName" @update:data="user => $emit('update:user', user)">
9
+ <template #display="{ value, edit }">
10
+ <p class="title is-4">
11
+ {{ value }} <b-button rounded size="is-small" type="is-light" icon-left="pencil-alt" @click="edit"></b-button>
12
+ </p>
13
+ </template>
14
+ </GenericEditableField>
15
+ <p class="subtitle is-6">
16
+ {{ user.email }}
17
+ <b-tooltip label="To change email addresses, please contact your administrator." position="is-right" multilined><b-icon icon="info-circle"></b-icon></b-tooltip>
18
+ </p>
19
+ </div>
20
+ </div>
21
+
22
+ <div class="content">
23
+ <div class="level level-left">
24
+ <strong class="mr-2">Username:</strong>
25
+ <span>{{ user.username }}
26
+ <b-tooltip label="To change username, please contact your administrator." position="is-right" multilined><b-icon icon="info-circle"></b-icon></b-tooltip>
27
+ </span>
28
+ </div>
29
+ <div class="level level-left"><strong class="mr-2">Group: </strong>
30
+ <transition name="fade" mode="out-in">
31
+ <span v-if="user">{{ user.group.name }}
32
+ <b-tooltip :label="user.group.description" position="is-right" multilined><b-icon icon="info-circle"></b-icon></b-tooltip>
33
+ </span>
34
+ <b-skeleton v-else width="20%" :animated="true" />
35
+ </transition>
36
+ </div>
37
+ <div class="level level-left"><strong class="mr-2">Joined: </strong>
38
+ <transition name="fade" mode="out-in">
39
+ <UserJoined v-if="user" :user="user" />
40
+ <b-skeleton v-else width="20%" :animated="true" />
41
+ </transition>
42
+ </div>
43
+ </div>
44
+
45
+ <b-modal :closable="false" :active.sync="isUserPreferencesModalActive" has-modal-card trap-focus aria-role="dialog" aria-modal>
46
+ <div class="modal-card">
47
+ <header class="modal-card-head">
48
+ <p class="modal-card-title"><b-icon icon="cogs" size="is-normal" class="push-right"></b-icon>User Preferences</p>
49
+ </header>
50
+ <section class="modal-card-body">
51
+ <UserPreferences />
52
+ </section>
53
+ </div>
54
+ </b-modal>
55
+
56
+ <b-button v-if="admin" @click="isUserPreferencesModalActive = true">Open User Preferences...</b-button>
57
+
58
+ <ShowIfPermitted v-if="!admin" :keys="['user.general', 'user.editor']">
59
+ <b-notification :closable="false" class="bottom-margin">
60
+ <h2 class="subtitle">User Preferences</h2>
61
+ <p>You can modify and personalize certain aspects of this administration interface to suit your own preferences.</p>
62
+ <br />
63
+ <b-button @click="isUserPreferencesModalActive = true">Open User Preferences...</b-button>
64
+ </b-notification>
65
+ </ShowIfPermitted>
66
+
67
+ <b-notification v-if="!admin" :closable="false">
68
+ <h2 class="subtitle">Change Password</h2>
69
+ <p>
70
+ Choosing a strong password will help keep your account safe.<br>
71
+ Try to use as many different characters, numbers and symbols as you possibly can, and make sure you don't use the password anywhere else.
72
+ </p>
73
+ <br>
74
+ <b-button type="is-info is-link" @click="isChangePasswordModalActive = true">Change your password now.</b-button>
75
+ </b-notification>
76
+
77
+ <b-notification v-if="!admin" :closable="false">
78
+ <h2 class="subtitle">Terminate Account</h2>
79
+ <p>If you are sure you delete <strong>your entire account and everything associated with it</strong>, then click the button below.</p>
80
+ <br>
81
+ <b-button type="is-danger" @click="deleteAccount">Delete your account</b-button>
82
+ </b-notification>
83
+
84
+ <b-modal
85
+ :active.sync="isChangePasswordModalActive"
86
+ has-modal-card
87
+ trap-focus
88
+ :destroy-on-hide="false"
89
+ aria-role="dialog"
90
+ aria-modal>
91
+ <template #default>
92
+ <div class="modal-card" style="width: 30em;">
93
+ <header class="modal-card-head">
94
+ <p class="modal-card-title">Change Password</p>
95
+ </header>
96
+ <section class="modal-card-body">
97
+ <b-field label="Old password" label-position="inside">
98
+ <b-input
99
+ v-model="oldPassword"
100
+ type="password"
101
+ password-reveal
102
+ placeholder="Old password"
103
+ required>
104
+ </b-input>
105
+ </b-field>
106
+ <b-field label="New password" label-position="inside">
107
+ <b-input
108
+ v-model="newPassword"
109
+ type="password"
110
+ password-reveal
111
+ placeholder="New password"
112
+ required>
113
+ </b-input>
114
+ </b-field>
115
+
116
+ <b-field label="New password again" label-position="inside">
117
+ <b-input
118
+ v-model="newPasswordAgain"
119
+ type="password"
120
+ password-reveal
121
+ placeholder="New password again"
122
+ required
123
+ @keyup.enter.native="changePassword">
124
+ </b-input>
125
+ </b-field>
126
+ </section>
127
+ <footer class="modal-card-foot" style="justify-content: flex-end;">
128
+ <b-button @click="isChangePasswordModalActive = false;">Close</b-button>
129
+ <b-button tag="input" native-type="submit" class="button is-primary" value="Change Password" @click="changePassword" />
130
+ </footer>
131
+ </div>
132
+ </template>
133
+ </b-modal>
134
+ </div>
135
+ </template>
136
+
137
+ <script>
138
+ import {morphToNotification} from "../api";
139
+ import UserJoined from "./UserJoined.vue";
140
+ import ShowIfPermitted from "./preferences/ShowIfPermitted.vue";
141
+ import UserPreferences from "./preferences/UserPreferences.vue";
142
+ import UsersApi from "../UsersApi";
143
+ import AuthApi from "../AuthApi";
144
+ import GenericEditableField from "./GenericEditableField.vue";
145
+
146
+ export default {
147
+ name: "UserProfileForm",
148
+ components: {GenericEditableField, ShowIfPermitted, UserJoined, UserPreferences },
149
+ props: {
150
+ user: {
151
+ type: Object,
152
+ required: true
153
+ },
154
+ admin: {
155
+ type: Boolean,
156
+ default: false
157
+ }
158
+ },
159
+ emits: 'update:user',
160
+ data() {
161
+ return {
162
+ oldPassword: '',
163
+ newPassword: '',
164
+ newPasswordAgain: '',
165
+ isChangePasswordModalActive: false,
166
+ isUserPreferencesModalActive: false,
167
+ authApi: new AuthApi(this.$buefy),
168
+ usersApi: new UsersApi(this.$buefy)
169
+ }
170
+ },
171
+ methods: {
172
+ async changePassword() {
173
+ let response = await this.authApi.changePassword(this.oldPassword, this.newPassword, this.newPasswordAgain);
174
+ this.$buefy.toast.open(morphToNotification(response));
175
+ if(response.status === 'success') {
176
+ this.oldPassword = '';
177
+ this.newPassword = '';
178
+ this.newPasswordAgain = '';
179
+ this.isChangePasswordModalActive = false;
180
+ }
181
+ },
182
+ deleteAccount() {
183
+ this.$buefy.dialog.confirm({
184
+ title: 'Deleting account',
185
+ message: 'Are you sure you want to <b>delete</b> your account? This action may not be reversible.',
186
+ type: 'is-danger',
187
+ hasIcon: true,
188
+ onConfirm: async () => {
189
+ await this.usersApi.delete(this.user.id);
190
+ await this.authApi.logout();
191
+ this.$store.commit('setUser', null);
192
+ await this.$router.push('/auth/logout');
193
+ }
194
+ })
195
+ }
196
+ }
197
+ }
198
+ </script>
199
+
200
+ <style scoped>
201
+ @import './util.css';
202
+
203
+ .centered-icon {
204
+ width: 3.5em; height: 3.5em; display: flex; justify-content: center; align-items: center;
205
+ }
206
+
207
+ .bottom-margin {
208
+ margin-bottom: 1.5rem;
209
+ }
210
+
211
+ .push-right {
212
+ margin-right: 1rem;
213
+ }
214
+ </style>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <div class="full-height scroll-container pad">
3
+ <div class="card">
4
+ <div class="card-content">
5
+ <UserProfileForm :user="user" @update:user="updateUser" />
6
+ </div>
7
+ </div>
8
+ </div>
9
+ </template>
10
+
11
+ <script>
12
+ import AuthApi from "../AuthApi";
13
+ import UserProfileForm from "./UserProfileForm.vue";
14
+
15
+ export default {
16
+ name: "ViewProfile",
17
+ components: {UserProfileForm},
18
+ data() {
19
+ return {
20
+ authApi: new AuthApi(this.$buefy)
21
+ }
22
+ },
23
+ computed: {
24
+ user() { return this.$store.state.user; }
25
+ },
26
+ methods: {
27
+ updateUser(user) {
28
+ this.$store.commit('setUser', user);
29
+ }
30
+ }
31
+ }
32
+ </script>
@@ -0,0 +1,16 @@
1
+ <template>
2
+ <div class="box container is-flex is-flex-direction-column is-align-items-center">
3
+ <h1 class="title mt-6 mb-6">Page Not Found</h1>
4
+ <b-button tag="router-link" to="/" type="is-success">Back to Home</b-button>
5
+ </div>
6
+ </template>
7
+
8
+ <script>
9
+ export default {
10
+ name: "Auth404"
11
+ }
12
+ </script>
13
+
14
+ <style scoped>
15
+ @import './login.scss';
16
+ </style>
@@ -0,0 +1,135 @@
1
+ <template>
2
+ <div class="box container">
3
+ <LoginLogo />
4
+
5
+ <div v-if="!wantsTotpCode">
6
+ <b-notification has-icon icon="exclamation-triangle" type="is-danger is-light" :active="!submitting && hasFailedLogin" :closable="false" class="incorrect-login">
7
+ Username or password incorrect
8
+ </b-notification>
9
+
10
+ <b-field label="Username" label-position="inside" :type="!submitting && hasFailedLogin ? 'is-danger' : ''">
11
+ <b-input v-model="username" name="username" ref="username"></b-input>
12
+ </b-field>
13
+
14
+ <b-field label="Password" label-position="inside" :type="!submitting && hasFailedLogin ? 'is-danger' : ''">
15
+ <b-input v-model="password" name="password" type="password" @keyup.enter.native="submitLogin"></b-input>
16
+ </b-field>
17
+
18
+ <br>
19
+
20
+ <!-- {{ URL::route(Blueprint::get('Password')->getRouteName('getRemind')) }} -->
21
+ <div class="login-justify-content">
22
+ <b-button type="is-primary" tag="input" value="Login" :loading="submitting" @click="submitLogin"></b-button>
23
+ <router-link to="/auth/forgot-password">Forgot Password</router-link>
24
+ </div>
25
+ </div>
26
+
27
+ <div v-else>
28
+ <h2 class="subtitle">Two-factor authentication is required</h2>
29
+ <p>To continue, open up your Authenticator app and issue your 2FA code.</p>
30
+
31
+ <br>
32
+
33
+ <b-field key="totpCode" label="2FA Code" label-position="inside" :type="!submitting && hasFailedLogin ? 'is-danger' : ''" :message="!submitting && hasFailedLogin ? 'Incorrect code. Try again.' : ''">
34
+ <b-input v-model="totpCode" name="totpCode" type="number" placeholder="e.g.: 123456" minlength="6" required @keyup.enter.native="submitLogin" ref="totpCode" />
35
+ </b-field>
36
+
37
+ <br>
38
+
39
+ <div class="login-justify-content">
40
+ <b-button type="is-primary" :disabled="confirmCodeDisabled" :loading="submitting" @click="submitLogin">Confirm code</b-button>
41
+ </div>
42
+ </div>
43
+
44
+ </div>
45
+ </template>
46
+
47
+ <script>
48
+ import LoginLogo from "./LoginLogo.vue";
49
+ import AuthApi from "../../AuthApi";
50
+
51
+ export default {
52
+ name: "Login",
53
+ components: { LoginLogo },
54
+ data() {
55
+ return {
56
+ authApi: new AuthApi(this.$buefy),
57
+ username: '',
58
+ password: '',
59
+ wantsTotpCode: false,
60
+ totpCode: '',
61
+ hasFailedLogin: false,
62
+ submitting: false
63
+ }
64
+ },
65
+ computed: {
66
+ confirmCodeDisabled() {
67
+ return this.totpCode.length !== 6;
68
+ }
69
+ },
70
+ created() {
71
+ console.log(this.$store.state.user);
72
+ if(this.$store.state.loginStatus === true) {
73
+ this.$buefy.notification.open({
74
+ message: 'You\'re already logged in!',
75
+ type: 'is-info',
76
+ duration: 4000
77
+ });
78
+ this.$router.push({ path: '/' });
79
+ }
80
+ },
81
+ mounted() {
82
+ this.$refs.username.focus();
83
+ },
84
+ methods: {
85
+ async submitLogin() {
86
+ try {
87
+ this.submitting = true;
88
+ let response = await this.authApi.login(this.username, this.password, this.totpCode !== '' ? this.totpCode : null);
89
+ this.submitting = false;
90
+ this.hasFailedLogin = false;
91
+ this.$store.commit('setUser', response.user);
92
+ if(this.$route.query.location) {
93
+ window.location = this.$route.query.location;
94
+ }
95
+ this.$buefy.notification.open({
96
+ message: "You're now logged in.",
97
+ type: 'is-info',
98
+ queue: false
99
+ });
100
+ await this.$router.push(this.$route.query.redirect ?? { path: '/' });
101
+ } catch(e) {
102
+ this.submitting = false;
103
+ if(e.response && e.response.code === 'account_deactivated') {
104
+ this.hasFailedLogin = true;
105
+ this.$buefy.notification.open({
106
+ message: 'Your account is deactivated. Please contact the site administrator for help on how to regain access to your account.',
107
+ type: 'is-warning',
108
+ queue: false,
109
+ duration: 10000
110
+ });
111
+ }
112
+ if(e.response && ['incorrect_username_password', 'no_username'].includes(e.response.code)) {
113
+ this.password = '';
114
+ this.hasFailedLogin = true;
115
+ } else if(e.response && e.response.code === 'two_factor_auth_failed') {
116
+ // if we are trying to enter a 2FA code again, then count the previous attempt as "failed"
117
+ this.hasFailedLogin = this.wantsTotpCode;
118
+ this.wantsTotpCode = true;
119
+ this.$nextTick(() => {
120
+ this.$refs.totpCode.focus();
121
+ });
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }
127
+ </script>
128
+
129
+ <style scoped>
130
+ @import "./login.scss";
131
+
132
+ .incorrect-login ::v-deep .media {
133
+ align-items: center;
134
+ }
135
+ </style>
@@ -0,0 +1,30 @@
1
+ <template>
2
+ <div class="is-flex is-align-items-center is-justify-content-center login-welcome">
3
+ <img src="../../../assets/oxygen-icon.png" alt="Oxygen CMS" class="app-logo">
4
+ <span class="app-title">
5
+ Oxygen CMS
6
+ </span>
7
+ </div>
8
+ </template>
9
+
10
+ <script>
11
+ export default {
12
+ name: "LoginLogo"
13
+ }
14
+ </script>
15
+
16
+ <style scoped>
17
+ .app-title {
18
+ font-weight: 200;
19
+ font-size: 2.2rem;
20
+ }
21
+
22
+ .app-logo {
23
+ margin-right: 0.75rem;
24
+ width: 4rem;
25
+ }
26
+
27
+ .login-welcome {
28
+ padding: 3rem 0;
29
+ }
30
+ </style>
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <div class="box container">
3
+ <h1 class="subtitle has-text-centered">
4
+ You've been logged out
5
+ </h1>
6
+
7
+ <div class="login-justify-content">
8
+ <b-button tag="a" href="/" type="is-text">
9
+ Return to Home
10
+ </b-button>
11
+ <b-button to="/auth/login" tag="router-link" type="is-primary">
12
+ Login Again
13
+ </b-button>
14
+ </div>
15
+ </div>
16
+ </template>
17
+
18
+ <script>
19
+ export default {
20
+ name: "Logout"
21
+ }
22
+ </script>
23
+
24
+ <style scoped>
25
+
26
+ </style>
@@ -0,0 +1,71 @@
1
+ <template>
2
+ <div class="box container">
3
+ <LoginLogo />
4
+
5
+ <h1 class="subtitle has-text-centered">
6
+ <span v-if="success">
7
+ Password reset link sent!
8
+ </span>
9
+ <span v-else>Forgot Password</span>
10
+ </h1>
11
+
12
+ <transition name="fade" mode="out-in">
13
+ <div v-if="!success">
14
+ <p>Enter the email address associated with your account, and we'll send you a link to reset your password.</p>
15
+ <br />
16
+ <b-field label="Email Address" label-position="inside">
17
+ <b-input v-model="accountEmail" name="email" type="email" required @keyup.enter.native="submitForm"></b-input>
18
+ </b-field>
19
+
20
+ <div class="login-justify-content">
21
+ <b-button type="is-primary" :loading="submitting" @click="submitForm">Send Reminder Email</b-button>
22
+ <router-link to="/auth/login">
23
+ Back to Login
24
+ </router-link>
25
+ </div>
26
+ </div>
27
+ <div v-else>
28
+ <p>Please check your email for further instructions on how to complete the process.</p>
29
+ </div>
30
+ </transition>
31
+
32
+
33
+ </div>
34
+ </template>
35
+
36
+ <script>
37
+
38
+ import LoginLogo from "./LoginLogo.vue";
39
+ import AuthApi from "../../AuthApi";
40
+ import {morphToNotification} from "../../api";
41
+
42
+ export default {
43
+ name: "PasswordRemind",
44
+ components: { LoginLogo },
45
+ data() {
46
+ return {
47
+ authApi: new AuthApi(this.$buefy),
48
+ accountEmail: '',
49
+ success: false,
50
+ submitting: false,
51
+ }
52
+ },
53
+ methods: {
54
+ async submitForm() {
55
+ try {
56
+ this.submitting = true;
57
+ let response = await this.authApi.sendReminderEmail(this.accountEmail);
58
+ this.$buefy.notification.open(morphToNotification(response));
59
+ this.success = true;
60
+ } catch(e) {
61
+ // let the user try again
62
+ }
63
+ this.submitting = false;
64
+ }
65
+ }
66
+ }
67
+ </script>
68
+
69
+ <style scoped>
70
+
71
+ </style>
@@ -0,0 +1,97 @@
1
+ <template>
2
+ <div class="box container">
3
+ <LoginLogo />
4
+
5
+ <h1 class="subtitle has-text-centered">
6
+ <span v-if="successfullyReset">Password Reset Successful!</span>
7
+ <span v-else-if="hasRequiredParams">Reset Your Password</span>
8
+ <span v-else>Invalid password reset link</span>
9
+ </h1>
10
+
11
+ <div v-if="successfullyReset" class="content">
12
+ <p>
13
+ Your password reset request was successful. You should now be able to login with your new password.
14
+ </p>
15
+ <div class="is-flex is-justify-content-center">
16
+ <b-button tag="router-link" to="/auth/login" type="is-success">Login</b-button>
17
+ </div>
18
+ </div>
19
+ <div v-else-if="hasRequiredParams">
20
+ <div class="content">
21
+ <p>Enter a new, secure password to use for this account.<br />It is recommended you use a password manager to keep your passwords unique and random.</p>
22
+ </div>
23
+
24
+ <b-field label="New Password" label-position="inside">
25
+ <b-input v-model="password" type="password"></b-input>
26
+ </b-field>
27
+
28
+ <b-field label="New Password Again" label-position="inside"
29
+ :type="passwordsMatch ? '' : 'is-danger'"
30
+ :message="passwordsMatch ? '' : 'Passwords do not match'">
31
+ <b-input v-model="passwordConfirmation" type="password" @keyup.native.enter="submit"></b-input>
32
+ </b-field>
33
+
34
+ <div class="login-justify-content">
35
+ <b-button type="is-primary" :disabled="!passwordsMatch" :loading="submitting" @click="submit">Reset</b-button>
36
+ <router-link to="/auth/login">
37
+ Back to Login
38
+ </router-link>
39
+ </div>
40
+ </div>
41
+ <div v-else class="content">
42
+ <p>One or more required parameters were missing from the link. Double-check that you have used the correct password reset link.</p>
43
+ </div>
44
+
45
+ </div>
46
+ </template>
47
+
48
+ <script>
49
+ import LoginLogo from "./LoginLogo.vue";
50
+ import AuthApi from "../../AuthApi";
51
+ import {morphToNotification} from "../../api";
52
+ export default {
53
+ name: "PasswordReset",
54
+ components: {LoginLogo},
55
+ data() {
56
+ return {
57
+ authApi: new AuthApi(this.$buefy),
58
+ password: '',
59
+ passwordConfirmation: '',
60
+ submitting: false,
61
+ successfullyReset: false
62
+ };
63
+ },
64
+ computed: {
65
+ hasRequiredParams() {
66
+ return this.$route.query.email && this.$route.query.token;
67
+ },
68
+ passwordsMatch() {
69
+ return this.password === this.passwordConfirmation;
70
+ }
71
+ },
72
+ methods: {
73
+ async submit() {
74
+ let req = {
75
+ password: this.password,
76
+ password_confirmation: this.passwordConfirmation,
77
+ email: this.$route.query.email,
78
+ token: this.$route.query.token
79
+ };
80
+ console.log(req);
81
+ this.submitting = true;
82
+ try {
83
+ let data = await this.authApi.resetPassword(req);
84
+ this.$buefy.notification.open(morphToNotification(data));
85
+ this.successfullyReset = true;
86
+ } catch(e) {
87
+ // let the user try again
88
+ }
89
+ this.submitting = false;
90
+ }
91
+ }
92
+ }
93
+ </script>
94
+
95
+ <style scoped>
96
+
97
+ </style>