@drax/identity-vue 0.0.10
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/README.md +61 -0
- package/package.json +65 -0
- package/src/assets/base.css +86 -0
- package/src/assets/logo.svg +1 -0
- package/src/assets/main.css +35 -0
- package/src/combobox/PermissionCombobox.vue +38 -0
- package/src/combobox/RoleCombobox.vue +37 -0
- package/src/components/IdentityChangeOwnPassword/IdentityChangeOwnPassword.vue +139 -0
- package/src/components/IdentityLogin/IdentityLogin.vue +124 -0
- package/src/components/IdentityProfileAvatar/IdentityProfileAvatar.vue +38 -0
- package/src/components/IdentityProfileDrawer/IdentityProfileDrawer.vue +82 -0
- package/src/components/IdentityProfileView/IdentityProfileView.vue +31 -0
- package/src/components/PermissionSelector/PermissionSelector.vue +89 -0
- package/src/composables/useAuth.ts +53 -0
- package/src/composables/useI18nValidation.ts +11 -0
- package/src/composables/useRole.ts +93 -0
- package/src/composables/useUser.ts +96 -0
- package/src/cruds/role-crud/RoleCrud.vue +165 -0
- package/src/cruds/role-crud/RoleList.vue +110 -0
- package/src/cruds/user-crud/UserCrud.vue +222 -0
- package/src/cruds/user-crud/UserList.vue +100 -0
- package/src/forms/RoleForm.vue +44 -0
- package/src/forms/UserCreateForm.vue +101 -0
- package/src/forms/UserEditForm.vue +87 -0
- package/src/forms/UserPasswordForm.vue +81 -0
- package/src/i18n/I18n.ts +10 -0
- package/src/i18n/I18nMessages.ts +21 -0
- package/src/icons/IconCommunity.vue +7 -0
- package/src/index.ts +51 -0
- package/src/pages/RoleCrudPage.vue +12 -0
- package/src/pages/UserCrudPage.vue +12 -0
- package/src/stores/auth/AuthStore.ts +38 -0
- package/src/views/RoleView.vue +32 -0
- package/src/views/UserView.vue +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# identity-front
|
|
2
|
+
|
|
3
|
+
This template should help get you started developing with Vue 3 in Vite.
|
|
4
|
+
|
|
5
|
+
## Recommended IDE Setup
|
|
6
|
+
|
|
7
|
+
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
|
8
|
+
|
|
9
|
+
## Type Support for `.vue` Imports in TS
|
|
10
|
+
|
|
11
|
+
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
|
12
|
+
|
|
13
|
+
## Customize configuration
|
|
14
|
+
|
|
15
|
+
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
|
16
|
+
|
|
17
|
+
## Project Setup
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Compile and Hot-Reload for Development
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
npm run dev
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Type-Check, Compile and Minify for Production
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
npm run build
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Run Unit Tests with [Vitest](https://vitest.dev/)
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
npm run test:unit
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Run End-to-End Tests with [Cypress](https://www.cypress.io/)
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
npm run test:e2e:dev
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This runs the end-to-end tests against the Vite development server.
|
|
48
|
+
It is much faster than the production build.
|
|
49
|
+
|
|
50
|
+
But it's still recommended to test the production build with `test:e2e` before deploying (e.g. in CI environments):
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
npm run build
|
|
54
|
+
npm run test:e2e
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Lint with [ESLint](https://eslint.org/)
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
npm run lint
|
|
61
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@drax/identity-vue",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "0.0.10",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./src/index.ts",
|
|
9
|
+
"module": "./src/index.ts",
|
|
10
|
+
"types": "./src/index.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "vite",
|
|
16
|
+
"build": "run-p type-check \"build-only {@}\" --",
|
|
17
|
+
"preview": "vite preview",
|
|
18
|
+
"test:unit": "vitest",
|
|
19
|
+
"test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'",
|
|
20
|
+
"test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'",
|
|
21
|
+
"build-only": "vite build",
|
|
22
|
+
"type-check": "vue-tsc --build --force",
|
|
23
|
+
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
|
24
|
+
"format": "prettier --write src/"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@drax/common-front": "^0.0.10",
|
|
28
|
+
"vue-i18n": "^9.13.1"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"pinia": "^2.1.7",
|
|
32
|
+
"vue": "^3.4.21",
|
|
33
|
+
"vuetify": "^3.6.4"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@rushstack/eslint-patch": "^1.8.0",
|
|
37
|
+
"@tsconfig/node20": "^20.1.4",
|
|
38
|
+
"@types/jsdom": "^21.1.7",
|
|
39
|
+
"@types/node": "^20.12.5",
|
|
40
|
+
"@vitejs/plugin-vue": "^5.0.4",
|
|
41
|
+
"@vue/eslint-config-prettier": "^9.0.0",
|
|
42
|
+
"@vue/eslint-config-typescript": "^13.0.0",
|
|
43
|
+
"@vue/test-utils": "^2.4.5",
|
|
44
|
+
"@vue/tsconfig": "^0.5.1",
|
|
45
|
+
"cypress": "^13.7.2",
|
|
46
|
+
"eslint": "^8.57.0",
|
|
47
|
+
"eslint-plugin-cypress": "^2.15.1",
|
|
48
|
+
"eslint-plugin-vue": "^9.23.0",
|
|
49
|
+
"jsdom": "^24.0.0",
|
|
50
|
+
"npm-run-all2": "^6.1.2",
|
|
51
|
+
"pinia": "^2.1.7",
|
|
52
|
+
"pinia-plugin-persistedstate": "^3.2.1",
|
|
53
|
+
"prettier": "^3.2.5",
|
|
54
|
+
"start-server-and-test": "^2.0.3",
|
|
55
|
+
"typescript": "~5.4.0",
|
|
56
|
+
"vite": "^5.2.8",
|
|
57
|
+
"vite-plugin-css-injected-by-js": "^3.5.1",
|
|
58
|
+
"vite-plugin-dts": "^3.9.1",
|
|
59
|
+
"vitest": "^1.4.0",
|
|
60
|
+
"vue": "^3.4.21",
|
|
61
|
+
"vue-tsc": "^2.0.11",
|
|
62
|
+
"vuetify": "^3.6.4"
|
|
63
|
+
},
|
|
64
|
+
"gitHead": "c30e1965d551cc63001a301b8dc98e26eba3afe1"
|
|
65
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* color palette from <https://github.com/vuejs/theme> */
|
|
2
|
+
:root {
|
|
3
|
+
--vt-c-white: #ffffff;
|
|
4
|
+
--vt-c-white-soft: #f8f8f8;
|
|
5
|
+
--vt-c-white-mute: #f2f2f2;
|
|
6
|
+
|
|
7
|
+
--vt-c-black: #181818;
|
|
8
|
+
--vt-c-black-soft: #222222;
|
|
9
|
+
--vt-c-black-mute: #282828;
|
|
10
|
+
|
|
11
|
+
--vt-c-indigo: #2c3e50;
|
|
12
|
+
|
|
13
|
+
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
|
14
|
+
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
|
15
|
+
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
|
16
|
+
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
|
17
|
+
|
|
18
|
+
--vt-c-text-light-1: var(--vt-c-indigo);
|
|
19
|
+
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
|
20
|
+
--vt-c-text-dark-1: var(--vt-c-white);
|
|
21
|
+
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* semantic color variables for this project */
|
|
25
|
+
:root {
|
|
26
|
+
--color-background: var(--vt-c-white);
|
|
27
|
+
--color-background-soft: var(--vt-c-white-soft);
|
|
28
|
+
--color-background-mute: var(--vt-c-white-mute);
|
|
29
|
+
|
|
30
|
+
--color-border: var(--vt-c-divider-light-2);
|
|
31
|
+
--color-border-hover: var(--vt-c-divider-light-1);
|
|
32
|
+
|
|
33
|
+
--color-heading: var(--vt-c-text-light-1);
|
|
34
|
+
--color-text: var(--vt-c-text-light-1);
|
|
35
|
+
|
|
36
|
+
--section-gap: 160px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@media (prefers-color-scheme: dark) {
|
|
40
|
+
:root {
|
|
41
|
+
--color-background: var(--vt-c-black);
|
|
42
|
+
--color-background-soft: var(--vt-c-black-soft);
|
|
43
|
+
--color-background-mute: var(--vt-c-black-mute);
|
|
44
|
+
|
|
45
|
+
--color-border: var(--vt-c-divider-dark-2);
|
|
46
|
+
--color-border-hover: var(--vt-c-divider-dark-1);
|
|
47
|
+
|
|
48
|
+
--color-heading: var(--vt-c-text-dark-1);
|
|
49
|
+
--color-text: var(--vt-c-text-dark-2);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
*,
|
|
54
|
+
*::before,
|
|
55
|
+
*::after {
|
|
56
|
+
box-sizing: border-box;
|
|
57
|
+
margin: 0;
|
|
58
|
+
font-weight: normal;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
body {
|
|
62
|
+
min-height: 100vh;
|
|
63
|
+
color: var(--color-text);
|
|
64
|
+
background: var(--color-background);
|
|
65
|
+
transition:
|
|
66
|
+
color 0.5s,
|
|
67
|
+
background-color 0.5s;
|
|
68
|
+
line-height: 1.6;
|
|
69
|
+
font-family:
|
|
70
|
+
Inter,
|
|
71
|
+
-apple-system,
|
|
72
|
+
BlinkMacSystemFont,
|
|
73
|
+
'Segoe UI',
|
|
74
|
+
Roboto,
|
|
75
|
+
Oxygen,
|
|
76
|
+
Ubuntu,
|
|
77
|
+
Cantarell,
|
|
78
|
+
'Fira Sans',
|
|
79
|
+
'Droid Sans',
|
|
80
|
+
'Helvetica Neue',
|
|
81
|
+
sans-serif;
|
|
82
|
+
font-size: 15px;
|
|
83
|
+
text-rendering: optimizeLegibility;
|
|
84
|
+
-webkit-font-smoothing: antialiased;
|
|
85
|
+
-moz-osx-font-smoothing: grayscale;
|
|
86
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
@import './base.css';
|
|
2
|
+
|
|
3
|
+
#app {
|
|
4
|
+
max-width: 1280px;
|
|
5
|
+
margin: 0 auto;
|
|
6
|
+
padding: 2rem;
|
|
7
|
+
font-weight: normal;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
a,
|
|
11
|
+
.green {
|
|
12
|
+
text-decoration: none;
|
|
13
|
+
color: hsla(160, 100%, 37%, 1);
|
|
14
|
+
transition: 0.4s;
|
|
15
|
+
padding: 3px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@media (hover: hover) {
|
|
19
|
+
a:hover {
|
|
20
|
+
background-color: hsla(160, 100%, 37%, 0.2);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@media (min-width: 1024px) {
|
|
25
|
+
body {
|
|
26
|
+
display: flex;
|
|
27
|
+
place-items: center;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#app {
|
|
31
|
+
display: grid;
|
|
32
|
+
grid-template-columns: 1fr 1fr;
|
|
33
|
+
padding: 0 2rem;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
import {ref, onMounted, defineModel} from 'vue'
|
|
4
|
+
import type { PropType } from 'vue'
|
|
5
|
+
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
errorMessages: {
|
|
8
|
+
type: String as PropType<string | string[] | undefined>,
|
|
9
|
+
}
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const model = defineModel()
|
|
13
|
+
import {useRole} from "../composables/useRole";
|
|
14
|
+
const {fetchPermissions} = useRole()
|
|
15
|
+
let items = ref([])
|
|
16
|
+
|
|
17
|
+
onMounted(async () => {
|
|
18
|
+
items.value = await fetchPermissions()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<v-select
|
|
26
|
+
v-model="model"
|
|
27
|
+
label="Permissions"
|
|
28
|
+
:items="items"
|
|
29
|
+
variant="outlined"
|
|
30
|
+
:error-messages="errorMessages"
|
|
31
|
+
multiple
|
|
32
|
+
>
|
|
33
|
+
</v-select>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<style scoped>
|
|
37
|
+
|
|
38
|
+
</style>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
import {ref, onMounted, defineModel} from 'vue'
|
|
4
|
+
import type { PropType } from 'vue'
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
errorMessages: {
|
|
7
|
+
type: String as PropType<string | string[] | undefined>,
|
|
8
|
+
}
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const model = defineModel()
|
|
12
|
+
import {useRole} from "../composables/useRole";
|
|
13
|
+
const {fetchRole} = useRole()
|
|
14
|
+
let items = ref([])
|
|
15
|
+
|
|
16
|
+
onMounted(async () => {
|
|
17
|
+
items.value = await fetchRole()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<v-select
|
|
25
|
+
v-model="model"
|
|
26
|
+
label="Rol"
|
|
27
|
+
:items="items"
|
|
28
|
+
item-title="name"
|
|
29
|
+
item-value="id"
|
|
30
|
+
variant="outlined"
|
|
31
|
+
:error-messages="errorMessages"
|
|
32
|
+
></v-select>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<style scoped>
|
|
36
|
+
|
|
37
|
+
</style>
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {computed, ref} from 'vue'
|
|
3
|
+
import {useAuth} from "../../composables/useAuth.js";
|
|
4
|
+
import {ClientError} from "@drax/common-front";
|
|
5
|
+
import type {IClientInputError} from "@drax/common-front";
|
|
6
|
+
import {useI18nValidation} from "../../composables/useI18nValidation.js";
|
|
7
|
+
import {useI18n} from "vue-i18n";
|
|
8
|
+
|
|
9
|
+
const {t} = useI18n()
|
|
10
|
+
const {$ta} = useI18nValidation()
|
|
11
|
+
|
|
12
|
+
const {changeOwnPassword} = useAuth()
|
|
13
|
+
|
|
14
|
+
const currentPassword = ref('')
|
|
15
|
+
const newPassword = ref('')
|
|
16
|
+
const confirmPassword = ref('')
|
|
17
|
+
const inputErrors = ref<IClientInputError|undefined>({currentPassword: [], newPassword: [], confirmPassword: []})
|
|
18
|
+
const errorMsg = ref('')
|
|
19
|
+
const loading = ref(false)
|
|
20
|
+
const changed = ref(false)
|
|
21
|
+
|
|
22
|
+
let currentPasswordVisibility = ref(false)
|
|
23
|
+
let newPasswordVisibility = ref(false)
|
|
24
|
+
|
|
25
|
+
const isFormValid = computed(() =>
|
|
26
|
+
currentPassword.value.trim() !== '' && newPassword.value.trim() !== ''
|
|
27
|
+
&& newPassword.value.trim() === confirmPassword.value.trim()
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
function confirmPasswordRule(value: string) {
|
|
31
|
+
return newPassword.value.trim() === confirmPassword.value.trim() || t('validation.password.confirmed')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function submitChangePassowrd() {
|
|
35
|
+
try {
|
|
36
|
+
loading.value = true
|
|
37
|
+
await changeOwnPassword(currentPassword.value.trim(), newPassword.value.trim())
|
|
38
|
+
changed.value = true
|
|
39
|
+
} catch (err) {
|
|
40
|
+
if (err instanceof ClientError) {
|
|
41
|
+
inputErrors.value = err.inputErrors
|
|
42
|
+
}if(err instanceof Error) {
|
|
43
|
+
errorMsg.value = err.message
|
|
44
|
+
}
|
|
45
|
+
const error = err as Error
|
|
46
|
+
errorMsg.value = error.message
|
|
47
|
+
} finally {
|
|
48
|
+
loading.value = false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<v-sheet>
|
|
57
|
+
|
|
58
|
+
<template v-if="changed">
|
|
59
|
+
<v-alert type="success">
|
|
60
|
+
{{ $t('user.passwordChanged') }}
|
|
61
|
+
</v-alert>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<template v-else>
|
|
65
|
+
|
|
66
|
+
<v-form @submit.prevent="submitChangePassowrd">
|
|
67
|
+
<v-card variant="outlined">
|
|
68
|
+
<v-card-title class="pa-4 text-center">{{ $t('user.changeOwnPassword') }}</v-card-title>
|
|
69
|
+
<v-card-text v-if="errorMsg">
|
|
70
|
+
<v-alert type="error">
|
|
71
|
+
{{ $t ? $t(errorMsg) : errorMsg }}
|
|
72
|
+
</v-alert>
|
|
73
|
+
</v-card-text>
|
|
74
|
+
<v-card-text>
|
|
75
|
+
<div class="text-subtitle-1 text-medium-emphasis">{{ $t('user.currentPassword') }}</div>
|
|
76
|
+
<v-text-field
|
|
77
|
+
variant="outlined"
|
|
78
|
+
id="current-password-input"
|
|
79
|
+
v-model="currentPassword"
|
|
80
|
+
prepend-inner-icon="mdi-lock-outline"
|
|
81
|
+
required
|
|
82
|
+
:type="currentPasswordVisibility ? 'text': 'password'"
|
|
83
|
+
:append-inner-icon="currentPasswordVisibility ? 'mdi-eye-off': 'mdi-eye'"
|
|
84
|
+
@click:append-inner="currentPasswordVisibility = !currentPasswordVisibility"
|
|
85
|
+
autocomplete="new-password"
|
|
86
|
+
:error-messages="$ta(inputErrors.currentPassword)"
|
|
87
|
+
></v-text-field>
|
|
88
|
+
<div class="text-subtitle-1 text-medium-emphasis">{{ $t('user.newPassword') }}</div>
|
|
89
|
+
<v-text-field
|
|
90
|
+
variant="outlined"
|
|
91
|
+
id="new-password-input"
|
|
92
|
+
v-model="newPassword"
|
|
93
|
+
:type="newPasswordVisibility ? 'text': 'password'"
|
|
94
|
+
required
|
|
95
|
+
prepend-inner-icon="mdi-lock-outline"
|
|
96
|
+
:append-inner-icon="newPasswordVisibility ? 'mdi-eye-off': 'mdi-eye'"
|
|
97
|
+
@click:append-inner="newPasswordVisibility = !newPasswordVisibility"
|
|
98
|
+
autocomplete="new-password"
|
|
99
|
+
:error-messages="$ta(inputErrors.newPassword)"
|
|
100
|
+
></v-text-field>
|
|
101
|
+
<div class="text-subtitle-1 text-medium-emphasis">{{ $t('user.confirmPassword') }}</div>
|
|
102
|
+
<v-text-field
|
|
103
|
+
variant="outlined"
|
|
104
|
+
id="confirm-password-input"
|
|
105
|
+
v-model="confirmPassword"
|
|
106
|
+
:type="newPasswordVisibility ? 'text': 'password'"
|
|
107
|
+
required
|
|
108
|
+
prepend-inner-icon="mdi-lock-outline"
|
|
109
|
+
:append-inner-icon="newPasswordVisibility ? 'mdi-eye-off': 'mdi-eye'"
|
|
110
|
+
@click:append-inner="newPasswordVisibility = !newPasswordVisibility"
|
|
111
|
+
autocomplete="new-password"
|
|
112
|
+
:error-messages="$ta(inputErrors.confirmPassword)"
|
|
113
|
+
:rules="[confirmPasswordRule]"
|
|
114
|
+
></v-text-field>
|
|
115
|
+
</v-card-text>
|
|
116
|
+
<v-card-actions>
|
|
117
|
+
<v-spacer></v-spacer>
|
|
118
|
+
<v-btn
|
|
119
|
+
class="mb-8"
|
|
120
|
+
color="blue"
|
|
121
|
+
size="large"
|
|
122
|
+
variant="tonal"
|
|
123
|
+
id="submit-button"
|
|
124
|
+
type="submit"
|
|
125
|
+
block
|
|
126
|
+
:disabled="!isFormValid"
|
|
127
|
+
>
|
|
128
|
+
{{ $t('action.sent') }}
|
|
129
|
+
</v-btn>
|
|
130
|
+
</v-card-actions>
|
|
131
|
+
</v-card>
|
|
132
|
+
</v-form>
|
|
133
|
+
</template>
|
|
134
|
+
</v-sheet>
|
|
135
|
+
</template>
|
|
136
|
+
|
|
137
|
+
<style scoped lang="sass">
|
|
138
|
+
// Your styles here
|
|
139
|
+
</style>
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {computed, defineProps, ref} from 'vue'
|
|
3
|
+
import {useAuth} from '../../composables/useAuth.js'
|
|
4
|
+
import IdentityProfileView from "../IdentityProfileView/IdentityProfileView.vue";
|
|
5
|
+
|
|
6
|
+
const {login, isAuthenticated} = useAuth()
|
|
7
|
+
|
|
8
|
+
const username = ref('')
|
|
9
|
+
const password = ref('')
|
|
10
|
+
const authError = ref('')
|
|
11
|
+
const loading = ref(false)
|
|
12
|
+
|
|
13
|
+
const isFormValid = computed(() => username.value.trim() !== '' && password.value.trim() !== '')
|
|
14
|
+
|
|
15
|
+
async function submitLogin() {
|
|
16
|
+
try {
|
|
17
|
+
loading.value = true
|
|
18
|
+
await login(username.value, password.value)
|
|
19
|
+
} catch (e) {
|
|
20
|
+
const error = e as Error
|
|
21
|
+
authError.value = error.message
|
|
22
|
+
} finally {
|
|
23
|
+
loading.value = false
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Define props for customizing labels, title, and button text
|
|
28
|
+
const props = defineProps({
|
|
29
|
+
title: {
|
|
30
|
+
type: String,
|
|
31
|
+
default: 'Login'
|
|
32
|
+
},
|
|
33
|
+
usernameLabel: {
|
|
34
|
+
type: String,
|
|
35
|
+
default: 'Username'
|
|
36
|
+
},
|
|
37
|
+
passwordLabel: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: 'Password'
|
|
40
|
+
},
|
|
41
|
+
buttonText: {
|
|
42
|
+
type: String,
|
|
43
|
+
default: 'Login'
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
let passwordVisibility = ref(false)
|
|
48
|
+
|
|
49
|
+
function togglePasswordVisibility() {
|
|
50
|
+
passwordVisibility.value = !passwordVisibility.value
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<v-container>
|
|
57
|
+
|
|
58
|
+
<template v-if="isAuthenticated()">
|
|
59
|
+
<v-card>
|
|
60
|
+
<identity-profile-view></identity-profile-view>
|
|
61
|
+
</v-card>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<template v-else>
|
|
65
|
+
<v-row justify="center">
|
|
66
|
+
<v-col cols="12" sm="8" md="6" lg="5" xl="4">
|
|
67
|
+
<v-form @submit.prevent="submitLogin">
|
|
68
|
+
<v-card variant="elevated" class="pa-6">
|
|
69
|
+
<v-card-title class="pa-4 text-center">{{ props.title }}</v-card-title>
|
|
70
|
+
<v-card-text v-if="authError">
|
|
71
|
+
<v-alert type="error">
|
|
72
|
+
{{ $t ? $t(authError) : authError }}
|
|
73
|
+
</v-alert>
|
|
74
|
+
</v-card-text>
|
|
75
|
+
<v-card-text>
|
|
76
|
+
<div class="text-subtitle-1 text-medium-emphasis">{{ props.usernameLabel }}</div>
|
|
77
|
+
<v-text-field
|
|
78
|
+
variant="outlined"
|
|
79
|
+
id="username-input"
|
|
80
|
+
v-model="username"
|
|
81
|
+
prepend-inner-icon="mdi-lock-outline"
|
|
82
|
+
required
|
|
83
|
+
autocomplete="new-username"
|
|
84
|
+
></v-text-field>
|
|
85
|
+
<div class="text-subtitle-1 text-medium-emphasis">{{ props.passwordLabel }}</div>
|
|
86
|
+
<v-text-field
|
|
87
|
+
variant="outlined"
|
|
88
|
+
id="password-input"
|
|
89
|
+
v-model="password"
|
|
90
|
+
:type="passwordVisibility ? 'text': 'password'"
|
|
91
|
+
required
|
|
92
|
+
prepend-inner-icon="mdi-lock-outline"
|
|
93
|
+
:append-inner-icon="passwordVisibility ? 'mdi-eye-off': 'mdi-eye'"
|
|
94
|
+
@click:append-inner="togglePasswordVisibility"
|
|
95
|
+
autocomplete="new-password"
|
|
96
|
+
></v-text-field>
|
|
97
|
+
</v-card-text>
|
|
98
|
+
<v-card-actions>
|
|
99
|
+
<v-spacer></v-spacer>
|
|
100
|
+
<v-btn
|
|
101
|
+
class="mb-8"
|
|
102
|
+
color="blue"
|
|
103
|
+
size="large"
|
|
104
|
+
variant="tonal"
|
|
105
|
+
id="submit-button"
|
|
106
|
+
type="submit"
|
|
107
|
+
block
|
|
108
|
+
:disabled="!isFormValid"
|
|
109
|
+
:loading="loading"
|
|
110
|
+
>
|
|
111
|
+
{{ props.buttonText }}
|
|
112
|
+
</v-btn>
|
|
113
|
+
</v-card-actions>
|
|
114
|
+
</v-card>
|
|
115
|
+
</v-form>
|
|
116
|
+
</v-col>
|
|
117
|
+
</v-row>
|
|
118
|
+
</template>
|
|
119
|
+
</v-container>
|
|
120
|
+
</template>
|
|
121
|
+
|
|
122
|
+
<style scoped lang="sass">
|
|
123
|
+
// Your styles here
|
|
124
|
+
</style>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {useAuthStore} from "../../stores/auth/AuthStore.js";
|
|
3
|
+
|
|
4
|
+
const emit = defineEmits(['click'])
|
|
5
|
+
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
avatarSize: {
|
|
8
|
+
type: Number,
|
|
9
|
+
default: 54
|
|
10
|
+
},
|
|
11
|
+
customClass:{
|
|
12
|
+
type: String,
|
|
13
|
+
default: ''
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const authStore = useAuthStore()
|
|
18
|
+
const onClick = () => {
|
|
19
|
+
emit('click')
|
|
20
|
+
}
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<v-avatar
|
|
25
|
+
:size="avatarSize"
|
|
26
|
+
@click="onClick"
|
|
27
|
+
color="surface"
|
|
28
|
+
class="border-md border-opacity-100 border-surface-light"
|
|
29
|
+
:class="customClass"
|
|
30
|
+
>
|
|
31
|
+
<v-img v-if="authStore.authUser && authStore.authUser.avatar" :src="authStore.authUser.avatar"/>
|
|
32
|
+
<v-icon v-else size="50">mdi-account-circle</v-icon>
|
|
33
|
+
</v-avatar>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<style scoped>
|
|
37
|
+
|
|
38
|
+
</style>
|