@oxygen-cms/ui 1.4.0 → 1.5.2
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.
- package/.eslintrc.js +23 -0
- package/.github/workflows/node.js.yml +4 -4
- package/.idea/modules.xml +8 -0
- package/.idea/ui.iml +10 -0
- package/package.json +13 -5
- package/src/AuthApi.js +77 -42
- package/src/CrudApi.js +3 -3
- package/src/GroupsApi.js +9 -0
- package/src/MediaDirectoryApi.js +1 -1
- package/src/PreferencesApi.js +2 -0
- package/src/UserPermissions.js +2 -9
- package/src/UserPreferences.js +0 -4
- package/src/UserPreferences.test.js +0 -2
- package/src/UsersApi.js +41 -0
- package/src/api.js +96 -38
- package/src/components/App.vue +19 -240
- package/src/components/AuthenticatedLayout.vue +254 -0
- package/src/components/AuthenticationLog.vue +86 -30
- package/src/components/CodeEditor.vue +16 -32
- package/src/components/EditButtonOnRowHover.vue +21 -0
- package/src/components/Error404.vue +15 -5
- package/src/components/EventsChooser.vue +11 -11
- package/src/components/EventsTable.vue +14 -8
- package/src/components/GenericEditableField.vue +74 -0
- package/src/components/GroupsChooser.vue +58 -0
- package/src/components/GroupsList.vue +129 -0
- package/src/components/ImportExport.vue +32 -1
- package/src/components/LegacyPage.vue +22 -23
- package/src/components/UserJoined.vue +35 -0
- package/src/components/UserManagement.vue +168 -0
- package/src/components/UserProfileForm.vue +214 -0
- package/src/components/ViewProfile.vue +7 -219
- package/src/components/auth/Auth404.vue +16 -0
- package/src/components/auth/Login.vue +135 -0
- package/src/components/auth/LoginLogo.vue +30 -0
- package/src/components/auth/Logout.vue +26 -0
- package/src/components/auth/PasswordRemind.vue +71 -0
- package/src/components/auth/PasswordReset.vue +97 -0
- package/src/components/auth/TwoFactorSetup.vue +115 -0
- package/src/components/auth/VerifyEmail.vue +71 -0
- package/src/components/auth/WelcomeFloat.vue +87 -0
- package/src/components/auth/login.scss +17 -0
- package/src/components/{MediaChooseDirectory.vue → media/MediaChooseDirectory.vue} +12 -12
- package/src/components/{MediaDirectory.vue → media/MediaDirectory.vue} +8 -8
- package/src/components/{MediaInsertModal.vue → media/MediaInsertModal.vue} +2 -2
- package/src/components/{MediaItem.vue → media/MediaItem.vue} +24 -23
- package/src/components/{MediaItemPreview.vue → media/MediaItemPreview.vue} +5 -5
- package/src/components/{MediaList.vue → media/MediaList.vue} +42 -38
- package/src/components/{MediaPage.vue → media/MediaPage.vue} +1 -1
- package/src/components/{MediaResponsiveImages.vue → media/MediaResponsiveImages.vue} +5 -5
- package/src/components/{MediaUpload.vue → media/MediaUpload.vue} +10 -10
- package/src/components/{media.scss → media/media.scss} +1 -1
- package/src/components/preferences/PreferencesField.vue +10 -10
- package/src/components/preferences/PreferencesList.vue +13 -20
- package/src/components/preferences/PreferencesThemeChooser.vue +9 -9
- package/src/components/preferences/ShowIfPermitted.vue +9 -14
- package/src/components/users/CreateUserModal.vue +73 -0
- package/src/icons.js +90 -0
- package/src/main.js +111 -0
- package/src/modules/LegacyPages.js +18 -0
- package/src/modules/Media.js +45 -0
- package/src/modules/UserManagement.js +24 -0
- package/src/routes/index.js +92 -0
- package/src/store/index.js +70 -0
- package/src/styles/_variables.scss +1 -0
- package/src/styles/app.scss +15 -2
- package/src/login.js +0 -17
- package/src/routes.js +0 -61
- package/src/styles/login.scss +0 -86
|
@@ -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>
|
|
@@ -2,243 +2,31 @@
|
|
|
2
2
|
<div class="full-height scroll-container pad">
|
|
3
3
|
<div class="card">
|
|
4
4
|
<div class="card-content">
|
|
5
|
-
<
|
|
6
|
-
<div class="media-left has-background-grey-light centered-icon">
|
|
7
|
-
<b-icon icon="user" size="is-large" class="has-text-grey-lighter"></b-icon>
|
|
8
|
-
</div>
|
|
9
|
-
<div class="media-content">
|
|
10
|
-
<transition name="fade" mode="out-in">
|
|
11
|
-
<p class="title is-4" v-if="user && editingFullName === null">{{ user.fullName }} <b-button rounded size="is-small" type="is-light" icon-left="pencil-alt" @click="editFullName"></b-button></p>
|
|
12
|
-
<b-field v-else-if="user" label="Full Name" label-position="inside" class="not-full-width">
|
|
13
|
-
<b-input v-model="editingFullName"></b-input>
|
|
14
|
-
<p class="control">
|
|
15
|
-
<b-button type="is-primary" :loading="updatingFullName" @click="submitFullName">Change</b-button>
|
|
16
|
-
</p>
|
|
17
|
-
</b-field>
|
|
18
|
-
<b-skeleton size="is-medium" width="40%" :animated="true" v-else></b-skeleton>
|
|
19
|
-
</transition>
|
|
20
|
-
<transition name="fade" mode="out-in">
|
|
21
|
-
<p class="subtitle is-6" v-if="user">
|
|
22
|
-
{{ user.email }}
|
|
23
|
-
<b-tooltip label="To change email addresses, please contact your administrator." position="is-right" multilined><b-icon icon="info-circle"></b-icon></b-tooltip>
|
|
24
|
-
</p>
|
|
25
|
-
<b-skeleton width="30%" :animated="true" v-else></b-skeleton>
|
|
26
|
-
</transition>
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
|
|
30
|
-
<div class="content">
|
|
31
|
-
<div class="level level-left">
|
|
32
|
-
<strong class="mr-2">Username:</strong>
|
|
33
|
-
<transition name="fade" mode="out-in">
|
|
34
|
-
<span v-if="user">{{ user.username }}
|
|
35
|
-
<b-tooltip label="To change username, please contact your administrator." position="is-right" multilined><b-icon icon="info-circle"></b-icon></b-tooltip>
|
|
36
|
-
</span>
|
|
37
|
-
<b-skeleton width="20%" :animated="true" v-else />
|
|
38
|
-
</transition>
|
|
39
|
-
</div>
|
|
40
|
-
<div class="level level-left"><strong class="mr-2">Group: </strong>
|
|
41
|
-
<transition name="fade" mode="out-in">
|
|
42
|
-
<span v-if="user">{{ user.group.name }}
|
|
43
|
-
<b-tooltip :label="user.group.description" position="is-right" multilined><b-icon icon="info-circle"></b-icon></b-tooltip>
|
|
44
|
-
</span>
|
|
45
|
-
<b-skeleton width="20%" :animated="true" v-else />
|
|
46
|
-
</transition>
|
|
47
|
-
</div>
|
|
48
|
-
<div class="level level-left"><strong class="mr-2">Joined: </strong>
|
|
49
|
-
<transition name="fade" mode="out-in">
|
|
50
|
-
<span v-if="user">{{ joined }}, on {{ joinedAbs }}</span>
|
|
51
|
-
<b-skeleton width="20%" :animated="true" v-else />
|
|
52
|
-
</transition>
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
<ShowIfPermitted :keys="['user.general', 'user.editor']">
|
|
57
|
-
<b-modal :closable="false" :active.sync="isUserPreferencesModalActive" has-modal-card trap-focus aria-role="dialog" aria-modal>
|
|
58
|
-
<div class="modal-card">
|
|
59
|
-
<header class="modal-card-head">
|
|
60
|
-
<p class="modal-card-title"><b-icon icon="cogs" size="is-normal" class="push-right"></b-icon>User Preferences</p>
|
|
61
|
-
</header>
|
|
62
|
-
<section class="modal-card-body">
|
|
63
|
-
<UserPreferences></UserPreferences>
|
|
64
|
-
</section>
|
|
65
|
-
</div>
|
|
66
|
-
</b-modal>
|
|
67
|
-
<b-notification :closable="false" class="bottom-margin">
|
|
68
|
-
<h2 class="subtitle">User Preferences</h2>
|
|
69
|
-
<p>You can modify and personalize certain aspects of this administration interface to suit your own preferences.</p>
|
|
70
|
-
<br />
|
|
71
|
-
<b-button @click="isUserPreferencesModalActive = true">Open User Preferences...</b-button>
|
|
72
|
-
</b-notification>
|
|
73
|
-
</ShowIfPermitted>
|
|
74
|
-
|
|
75
|
-
<b-notification :closable="false">
|
|
76
|
-
<h2 class="subtitle">Change Password</h2>
|
|
77
|
-
<p>
|
|
78
|
-
Choosing a strong password will help keep your account safe.<br>
|
|
79
|
-
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.
|
|
80
|
-
</p>
|
|
81
|
-
<br>
|
|
82
|
-
<b-button @click="isChangePasswordModalActive = true" type="is-info is-link">Change your password now.</b-button>
|
|
83
|
-
</b-notification>
|
|
84
|
-
|
|
85
|
-
<b-notification :closable="false">
|
|
86
|
-
<h2 class="subtitle">Terminate Account</h2>
|
|
87
|
-
<p>If you are sure you delete <strong>your entire account and everything associated with it</strong>, then click the button below.</p>
|
|
88
|
-
<br>
|
|
89
|
-
<b-button type="is-danger" @click="deleteAccount">Permanently delete your account</b-button>
|
|
90
|
-
</b-notification>
|
|
5
|
+
<UserProfileForm :user="user" @update:user="updateUser" />
|
|
91
6
|
</div>
|
|
92
7
|
</div>
|
|
93
|
-
|
|
94
|
-
<b-modal
|
|
95
|
-
:active.sync="isChangePasswordModalActive"
|
|
96
|
-
has-modal-card
|
|
97
|
-
trap-focus
|
|
98
|
-
:destroy-on-hide="false"
|
|
99
|
-
aria-role="dialog"
|
|
100
|
-
aria-modal>
|
|
101
|
-
<template #default="props">
|
|
102
|
-
<div class="modal-card" style="width: 30em;">
|
|
103
|
-
<header class="modal-card-head">
|
|
104
|
-
<p class="modal-card-title">Change Password</p>
|
|
105
|
-
</header>
|
|
106
|
-
<section class="modal-card-body">
|
|
107
|
-
<b-field label="Old password" label-position="inside">
|
|
108
|
-
<b-input
|
|
109
|
-
type="password"
|
|
110
|
-
v-model="oldPassword"
|
|
111
|
-
password-reveal
|
|
112
|
-
placeholder="Old password"
|
|
113
|
-
required>
|
|
114
|
-
</b-input>
|
|
115
|
-
</b-field>
|
|
116
|
-
<b-field label="New password" label-position="inside">
|
|
117
|
-
<b-input
|
|
118
|
-
type="password"
|
|
119
|
-
v-model="newPassword"
|
|
120
|
-
password-reveal
|
|
121
|
-
placeholder="New password"
|
|
122
|
-
required>
|
|
123
|
-
</b-input>
|
|
124
|
-
</b-field>
|
|
125
|
-
|
|
126
|
-
<b-field label="New password again" label-position="inside">
|
|
127
|
-
<b-input
|
|
128
|
-
type="password"
|
|
129
|
-
v-model="newPasswordAgain"
|
|
130
|
-
password-reveal
|
|
131
|
-
placeholder="New password again"
|
|
132
|
-
required>
|
|
133
|
-
</b-input>
|
|
134
|
-
</b-field>
|
|
135
|
-
</section>
|
|
136
|
-
<footer class="modal-card-foot" style="justify-content: flex-end;">
|
|
137
|
-
<b-button @click="isChangePasswordModalActive = false;">Close</b-button>
|
|
138
|
-
<b-button tag="input" native-type="submit" class="button is-primary" @click="changePassword" value="Change Password" />
|
|
139
|
-
</footer>
|
|
140
|
-
</div>
|
|
141
|
-
</template>
|
|
142
|
-
</b-modal>
|
|
143
8
|
</div>
|
|
144
9
|
</template>
|
|
145
10
|
|
|
146
11
|
<script>
|
|
147
12
|
import AuthApi from "../AuthApi";
|
|
148
|
-
import
|
|
149
|
-
import {morphToNotification} from "../api";
|
|
150
|
-
import UserPreferences from "./preferences/UserPreferences.vue";
|
|
151
|
-
import ShowIfPermitted from "./preferences/ShowIfPermitted.vue";
|
|
13
|
+
import UserProfileForm from "./UserProfileForm.vue";
|
|
152
14
|
|
|
153
15
|
export default {
|
|
154
16
|
name: "ViewProfile",
|
|
155
|
-
components: {
|
|
17
|
+
components: {UserProfileForm},
|
|
156
18
|
data() {
|
|
157
19
|
return {
|
|
158
|
-
user: null,
|
|
159
|
-
editingFullName: null,
|
|
160
|
-
updatingFullName: false,
|
|
161
|
-
oldPassword: '',
|
|
162
|
-
newPassword: '',
|
|
163
|
-
newPasswordAgain: '',
|
|
164
|
-
isChangePasswordModalActive: false,
|
|
165
|
-
isUserPreferencesModalActive: false,
|
|
166
20
|
authApi: new AuthApi(this.$buefy)
|
|
167
21
|
}
|
|
168
22
|
},
|
|
169
|
-
created() {
|
|
170
|
-
this.fetchUserDetails()
|
|
171
|
-
},
|
|
172
23
|
computed: {
|
|
173
|
-
|
|
174
|
-
const rtf1 = new Intl.RelativeTimeFormat('en', { style: 'narrow' });
|
|
175
|
-
return rtf1.format(
|
|
176
|
-
Math.floor((new Date(this.user.createdAt) - new Date()) / (1000 * 24 * 60 * 60)),
|
|
177
|
-
'day'
|
|
178
|
-
);
|
|
179
|
-
},
|
|
180
|
-
joinedAbs() {
|
|
181
|
-
return Internationalize.formatDate(this.user.createdAt);
|
|
182
|
-
}
|
|
24
|
+
user() { return this.$store.state.user; }
|
|
183
25
|
},
|
|
184
26
|
methods: {
|
|
185
|
-
|
|
186
|
-
this.
|
|
187
|
-
},
|
|
188
|
-
editFullName() {
|
|
189
|
-
this.editingFullName = this.user.fullName;
|
|
190
|
-
},
|
|
191
|
-
async submitFullName() {
|
|
192
|
-
this.updatingFullName = true;
|
|
193
|
-
let response = await this.authApi.updateFullName(this.editingFullName);
|
|
194
|
-
this.updatingFullName = false;
|
|
195
|
-
this.$buefy.toast.open(morphToNotification(response));
|
|
196
|
-
this.user = response.item;
|
|
197
|
-
this.editingFullName = null;
|
|
198
|
-
},
|
|
199
|
-
async changePassword() {
|
|
200
|
-
let response = await this.authApi.changePassword(this.oldPassword, this.newPassword, this.newPasswordAgain);
|
|
201
|
-
this.$buefy.toast.open(morphToNotification(response));
|
|
202
|
-
if(response.status === 'success') {
|
|
203
|
-
this.oldPassword = '';
|
|
204
|
-
this.newPassword = '';
|
|
205
|
-
this.newPasswordAgain = '';
|
|
206
|
-
this.isChangePasswordModalActive = false;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
},
|
|
210
|
-
deleteAccount() {
|
|
211
|
-
this.$buefy.dialog.confirm({
|
|
212
|
-
title: 'Deleting account',
|
|
213
|
-
message: 'Are you sure you want to <b>delete</b> your account? This action cannot be undone.',
|
|
214
|
-
type: 'is-danger',
|
|
215
|
-
hasIcon: true,
|
|
216
|
-
onConfirm: async () => {
|
|
217
|
-
let response = await this.authApi.terminateAccount();
|
|
218
|
-
window.location = '/';
|
|
219
|
-
}
|
|
220
|
-
})
|
|
27
|
+
updateUser(user) {
|
|
28
|
+
this.$store.commit('setUser', user);
|
|
221
29
|
}
|
|
222
30
|
}
|
|
223
31
|
}
|
|
224
|
-
</script>
|
|
225
|
-
|
|
226
|
-
<style scoped>
|
|
227
|
-
@import './util.css';
|
|
228
|
-
|
|
229
|
-
.centered-icon {
|
|
230
|
-
width: 3.5em; height: 3.5em; display: flex; justify-content: center; align-items: center;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
.not-full-width {
|
|
234
|
-
width: 20rem;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
.bottom-margin {
|
|
238
|
-
margin-bottom: 1.5rem;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
.push-right {
|
|
242
|
-
margin-right: 1rem;
|
|
243
|
-
}
|
|
244
|
-
</style>
|
|
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 ref="username" v-model="username" name="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 ref="totpCode" v-model="totpCode" name="totpCode" type="number" placeholder="e.g.: 123456" minlength="6" required @keyup.enter.native="submitLogin" />
|
|
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>
|