adminforth 4.5.0 → 4.6.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.
@@ -50,7 +50,6 @@
50
50
  "eslint": "^8.57.0",
51
51
  "eslint-plugin-vue": "^9.23.0",
52
52
  "flag-icons": "^7.2.3",
53
- "flowbite": "^3.1.2",
54
53
  "i18n-iso-countries": "^7.12.0",
55
54
  "npm-run-all2": "^9.0.2",
56
55
  "portfinder": "^1.0.32",
@@ -3111,19 +3110,6 @@
3111
3110
  "dev": true,
3112
3111
  "license": "ISC"
3113
3112
  },
3114
- "node_modules/flowbite": {
3115
- "version": "3.1.2",
3116
- "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-3.1.2.tgz",
3117
- "integrity": "sha512-MkwSgbbybCYgMC+go6Da5idEKUFfMqc/AmSjm/2ZbdmvoKf5frLPq/eIhXc9P+rC8t9boZtUXzHDgt5whZ6A/Q==",
3118
- "dev": true,
3119
- "license": "MIT",
3120
- "dependencies": {
3121
- "@popperjs/core": "^2.9.3",
3122
- "flowbite-datepicker": "^1.3.1",
3123
- "mini-svg-data-uri": "^1.4.3",
3124
- "postcss": "^8.5.1"
3125
- }
3126
- },
3127
3113
  "node_modules/flowbite-datepicker": {
3128
3114
  "version": "1.3.2",
3129
3115
  "resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.3.2.tgz",
@@ -55,7 +55,6 @@
55
55
  "eslint": "^8.57.0",
56
56
  "eslint-plugin-vue": "^9.23.0",
57
57
  "flag-icons": "^7.2.3",
58
- "flowbite": "^3.1.2",
59
58
  "i18n-iso-countries": "^7.12.0",
60
59
  "npm-run-all2": "^9.0.2",
61
60
  "portfinder": "^1.0.32",
@@ -129,9 +129,6 @@ importers:
129
129
  flag-icons:
130
130
  specifier: ^7.2.3
131
131
  version: 7.5.0
132
- flowbite:
133
- specifier: ^3.1.2
134
- version: 3.1.2(rollup@4.60.2)
135
132
  i18n-iso-countries:
136
133
  specifier: ^7.12.0
137
134
  version: 7.14.0
@@ -1308,9 +1305,6 @@ packages:
1308
1305
  flowbite@2.5.2:
1309
1306
  resolution: {integrity: sha512-kwFD3n8/YW4EG8GlY3Od9IoKND97kitO+/ejISHSqpn3vw2i5K/+ZI8Jm2V+KC4fGdnfi0XZ+TzYqQb4Q1LshA==}
1310
1307
 
1311
- flowbite@3.1.2:
1312
- resolution: {integrity: sha512-MkwSgbbybCYgMC+go6Da5idEKUFfMqc/AmSjm/2ZbdmvoKf5frLPq/eIhXc9P+rC8t9boZtUXzHDgt5whZ6A/Q==}
1313
-
1314
1308
  fraction.js@5.3.4:
1315
1309
  resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
1316
1310
 
@@ -3263,15 +3257,6 @@ snapshots:
3263
3257
  transitivePeerDependencies:
3264
3258
  - rollup
3265
3259
 
3266
- flowbite@3.1.2(rollup@4.60.2):
3267
- dependencies:
3268
- '@popperjs/core': 2.11.8
3269
- flowbite-datepicker: 1.3.2(rollup@4.60.2)
3270
- mini-svg-data-uri: 1.4.4
3271
- postcss: 8.5.30
3272
- transitivePeerDependencies:
3273
- - rollup
3274
-
3275
3260
  fraction.js@5.3.4: {}
3276
3261
 
3277
3262
  fs.realpath@1.0.0: {}
@@ -0,0 +1,108 @@
1
+ <template>
2
+ <div v-bind="restAttrs" :class="rootClasses">
3
+ <div :class="mergeClasses(
4
+ 'afcl-dialog-panel-header flex items-center gap-3.5 border-b border-lightDialogBreakLine py-5 pl-6 dark:border-darkDialogBreakLine',
5
+ closable ? 'pr-4' : 'pr-6',
6
+ classes.header,
7
+ )">
8
+ <div
9
+ v-if="$slots.icon"
10
+ :class="mergeClasses(
11
+ 'afcl-dialog-panel-icon flex w-10 h-10 shrink-0 items-center justify-center rounded-lg [&>svg]:w-5 [&>svg]:h-5',
12
+ 'bg-lightPrimary/10 text-lightPrimary dark:bg-darkPrimary/20 dark:text-darkPrimary',
13
+ classes.icon,
14
+ )"
15
+ >
16
+ <slot name="icon" />
17
+ </div>
18
+ <div :class="mergeClasses('afcl-dialog-panel-title-wrap flex-1 min-w-0', classes.titleWrap)">
19
+ <slot name="title">
20
+ <h2 :class="mergeClasses('afcl-dialog-panel-title break-words text-lg leading-6 font-semibold text-lightDialogHeaderText dark:text-darkDialogHeaderText', classes.title)">{{ title }}</h2>
21
+ <p v-if="subtitle" :class="mergeClasses('afcl-dialog-panel-subtitle mt-0.5 text-sm leading-5 text-lightInputIconColor dark:text-darkInputIconColor', classes.subtitle)">{{ subtitle }}</p>
22
+ </slot>
23
+ </div>
24
+ <Button
25
+ v-if="closable"
26
+ type="button"
27
+ variant="ghost"
28
+ size="sm"
29
+ iconOnly
30
+ :aria-label="$t('Close')"
31
+ :class="mergeClasses(
32
+ 'afcl-dialog-panel-close text-lightDialogCloseButton hover:text-lightDialogCloseButtonHover hover:bg-lightDialogCloseButtonHoverBackground',
33
+ 'dark:text-darkDialogCloseButton dark:hover:text-darkDialogCloseButtonHover dark:hover:bg-darkDialogCloseButtonHoverBackground',
34
+ classes.close,
35
+ )"
36
+ @click="emit('close')"
37
+ >
38
+ <IconX aria-hidden="true" />
39
+ </Button>
40
+ </div>
41
+
42
+ <div :class="mergeClasses('afcl-dialog-panel-body flex flex-col gap-4 px-6 pt-5 pb-6', classes.body)">
43
+ <slot />
44
+ <p v-if="error" role="alert" :class="mergeClasses('afcl-dialog-panel-error -mt-1 flex items-center gap-1.5 text-sm text-lightDanger dark:text-darkDanger', classes.error)">
45
+ <IconCircleAlert class="w-4 h-4 shrink-0" aria-hidden="true" />
46
+ {{ error }}
47
+ </p>
48
+ </div>
49
+
50
+ <!-- a main action on the right, a secondary one may sit on the left with ms-0 me-auto -->
51
+ <div
52
+ v-if="$slots.footer"
53
+ :class="mergeClasses(
54
+ 'afcl-dialog-panel-footer flex flex-wrap items-center justify-end gap-x-3 gap-y-2 border-t border-lightDialogBreakLine bg-lightFormHeading py-3 pl-6 pr-5',
55
+ 'dark:border-darkDialogBreakLine dark:bg-darkFormHeading',
56
+ classes.footer,
57
+ )"
58
+ >
59
+ <slot name="footer" />
60
+ </div>
61
+ </div>
62
+ </template>
63
+
64
+ <script setup lang="ts">
65
+ import { computed, useAttrs } from 'vue';
66
+ import { type ClassNameValue } from 'tailwind-merge';
67
+ import { IconCircleAlert, IconX } from '@iconify-prerendered/vue-lucide';
68
+ import { mergeClasses } from './classMerge';
69
+ import Button from './Button.vue';
70
+
71
+ type DialogPanelPart = 'header' | 'icon' | 'titleWrap' | 'title' | 'subtitle' | 'close' | 'body' | 'error' | 'footer';
72
+
73
+ defineOptions({ inheritAttrs: false });
74
+
75
+ withDefaults(defineProps<{
76
+ title?: string,
77
+ subtitle?: string,
78
+ error?: string | null,
79
+ // shows a close button in the header which emits close
80
+ closable?: boolean,
81
+ // extra classes for the parts, e.g. the af-* classes apps restyle a screen by
82
+ classes?: Partial<Record<DialogPanelPart, ClassNameValue>>,
83
+ }>(), {
84
+ title: '',
85
+ subtitle: '',
86
+ error: null,
87
+ closable: false,
88
+ classes: () => ({}),
89
+ });
90
+
91
+ const emit = defineEmits<{
92
+ (e: 'close'): void,
93
+ }>();
94
+
95
+ const attrs = useAttrs();
96
+ const restAttrs = computed(() => {
97
+ const { class: _class, ...rest } = attrs;
98
+ return rest;
99
+ });
100
+
101
+ // a panel on the page is drawn like other surfaces: a hairline border and the light shadow of controls,
102
+ // a panel which floats over the page (in a modal) passes shadow-popover
103
+ const rootClasses = computed(() => mergeClasses(
104
+ 'afcl-dialog-panel relative flex flex-col w-full max-w-md overflow-hidden rounded-xl border shadow-customLight',
105
+ 'border-lightDialogBreakLine bg-lightDialogBackgorund dark:border-darkDialogBreakLine dark:bg-darkDialogBackgorund',
106
+ attrs.class as ClassNameValue,
107
+ ));
108
+ </script>
@@ -11,19 +11,39 @@
11
11
  <input
12
12
  ref="input"
13
13
  v-bind="$attrs"
14
- :type="type"
14
+ :type="passwordToggle && revealed ? 'text' : type"
15
15
  @input="$emit('update:modelValue', type === 'number' ? numberInputValue(($event.target as HTMLInputElement)?.value) : ($event.target as HTMLInputElement)?.value)"
16
16
  :value="modelValue"
17
17
  aria-describedby="helper-text-explanation"
18
- class="afcl-input inline-flex bg-lightInputBackground text-lightInputText dark:text-darkInputText border border-lightInputBorder rounded-0 shadow-customLight hover:border-lightInputBorderHover focus:outline-none focus:ring-[3px] focus:ring-lightInputFocusRing focus:border-lightInputFocusBorder dark:hover:border-darkInputBorderHover dark:focus:ring-darkInputFocusRing dark:focus:border-darkInputFocusBorder
19
- block w-20 px-2.5 py-1.5 leading-5 dark:bg-darkInputBackground dark:border-darkInputBorder placeholder-lightInputPlaceholderText dark:placeholder-darkInputPlaceholderText dark:text-darkInputText translate-y-0 transition-colors disabled:cursor-not-allowed"
20
- :class="{'rounded-l-default': !$slots.prefix && !prefix, 'rounded-r-default': !$slots.suffix && !suffix, 'w-full': fullWidth, 'text-base': isIos, 'text-sm': !isIos, 'pr-10': $slots.rightIcon }"
18
+ :aria-invalid="invalid || undefined"
19
+ class="afcl-input inline-flex bg-lightInputBackground text-lightInputText dark:text-darkInputText border rounded-0 shadow-customLight focus:outline-none focus:ring-[3px]
20
+ block w-20 px-2.5 py-1.5 leading-5 dark:bg-darkInputBackground placeholder-lightInputPlaceholderText dark:placeholder-darkInputPlaceholderText dark:text-darkInputText translate-y-0 transition-colors disabled:cursor-not-allowed"
21
+ :class="[
22
+ invalid
23
+ ? 'border-lightDanger focus:border-lightDanger focus:ring-lightDanger/20 dark:border-darkDanger dark:focus:border-darkDanger dark:focus:ring-darkDanger/30'
24
+ : 'border-lightInputBorder hover:border-lightInputBorderHover focus:ring-lightInputFocusRing focus:border-lightInputFocusBorder dark:border-darkInputBorder dark:hover:border-darkInputBorderHover dark:focus:ring-darkInputFocusRing dark:focus:border-darkInputFocusBorder',
25
+ {'rounded-l-default': !$slots.prefix && !prefix, 'rounded-r-default': !$slots.suffix && !suffix, 'w-full': fullWidth, 'text-base': isIos, 'text-sm': !isIos, 'pr-10': $slots.rightIcon || passwordToggle },
26
+ ]"
21
27
  :disabled="readonly"
22
28
  >
23
29
 
24
30
  <div v-if="$slots.rightIcon" class="absolute inset-y-0 right-0 pr-3 flex items-center">
25
31
  <slot name="rightIcon" />
26
32
  </div>
33
+ <div v-else-if="passwordToggle" class="absolute inset-y-0 right-0 pr-1 flex items-center">
34
+ <Button
35
+ type="button"
36
+ variant="ghost"
37
+ size="sm"
38
+ iconOnly
39
+ class="afcl-input-password-toggle"
40
+ :aria-label="revealed ? $t('Hide password') : $t('Show password')"
41
+ @click="revealed = !revealed"
42
+ >
43
+ <IconEyeOff v-if="revealed" aria-hidden="true" />
44
+ <IconEye v-else aria-hidden="true" />
45
+ </Button>
46
+ </div>
27
47
  <span
28
48
  v-if="$slots.suffix || suffix"
29
49
  class="inline-flex items-center px-2.5 text-sm text-lightInputIconColor bg-lightSecondaryDarken border border-s-0 border-lightInputBorder rounded-e-default dark:bg-darkSecondaryLighten dark:text-darkInputIconColor dark:border-darkInputBorder ">
@@ -36,8 +56,10 @@
36
56
  <script setup lang="ts">
37
57
 
38
58
  import { ref } from 'vue';
59
+ import { IconEye, IconEyeOff } from '@iconify-prerendered/vue-lucide';
39
60
  import { useCoreStore } from '@/stores/core';
40
61
  import { numberInputValue } from './inputValue';
62
+ import Button from './Button.vue';
41
63
 
42
64
  const coreStore = useCoreStore();
43
65
  const isIos = coreStore.isIos;
@@ -49,9 +71,14 @@ const props = defineProps<{
49
71
  suffix?: string,
50
72
  prefix?: string,
51
73
  readonly?: boolean,
74
+ // paints the border red, e.g. for a value which did not pass validation
75
+ invalid?: boolean,
76
+ // for type="password": a button on the right shows and hides the value
77
+ passwordToggle?: boolean,
52
78
  }>()
53
79
 
54
80
  const input = ref<HTMLInputElement | null>(null)
81
+ const revealed = ref(false)
55
82
 
56
83
  defineExpose({
57
84
  focus: () => input.value?.focus(),
@@ -1,87 +1,111 @@
1
1
  <template>
2
- <div ref="triggerEl" class="afcl-tooltip inline-flex items-center" @mouseenter="mouseOn" @mouseleave="mouseOff">
2
+ <div ref="triggerEl" class="afcl-tooltip inline-flex items-center" :style="{ anchorName }" @mouseenter="show" @mouseleave="hide">
3
3
  <slot></slot>
4
4
  </div>
5
- <teleport to="body" v-if="showTooltip">
5
+ <teleport to="body" v-if="isOpen">
6
6
  <div
7
+ ref="tooltipEl"
8
+ popover="manual"
7
9
  role="tooltip"
8
- class="absolute z-[100] invisible inline-block px-2.5 py-1.5 text-xs font-medium text-lightTooltipText dark:text-darkTooltipText transition-opacity duration-150 bg-lightTooltipBackground rounded-md shadow-popover opacity-0 tooltip dark:bg-darkTooltipBackground dark:shadow-darkPopover"
9
- ref="tooltip"
10
+ :data-placement="placement"
11
+ :style="{ positionAnchor: anchorName }"
12
+ class="afcl-tooltip-popover inline-block px-2.5 py-1.5 text-xs font-medium text-lightTooltipText dark:text-darkTooltipText transition-opacity duration-150 bg-lightTooltipBackground rounded-md shadow-popover dark:bg-darkTooltipBackground dark:shadow-darkPopover"
10
13
  >
11
14
  <slot name="tooltip"></slot>
12
- <div class="tooltip-arrow" data-popper-arrow></div>
15
+ <div class="afcl-tooltip-arrow" :style="{ left: `${arrowLeft}px` }"></div>
13
16
  </div>
14
17
  </teleport>
15
18
  </template>
16
19
 
17
20
  <script setup lang="ts">
18
- import { ref, nextTick, type Ref } from 'vue';
19
- import { Tooltip } from 'flowbite';
21
+ import { ref, nextTick, onBeforeUnmount, useId } from 'vue';
20
22
 
21
- const triggerEl = ref(null);
22
- const tooltip = ref(null);
23
- const showTooltip = ref(false);
24
- const tp: Ref<Tooltip|null> = ref(null);
23
+ const OFFSET = 8;
24
+ const SUPPORTS_ANCHOR = CSS.supports('anchor-name: --a');
25
25
 
26
- async function mouseOn() {
27
- showTooltip.value = true;
26
+ const anchorName = `--afcl-tooltip-${useId()}`;
27
+ const triggerEl = ref<HTMLElement>();
28
+ const tooltipEl = ref<HTMLElement>();
29
+ const isOpen = ref(false);
30
+ const placement = ref<'top' | 'bottom'>('bottom');
31
+ const arrowLeft = ref(0);
32
+
33
+ async function show() {
34
+ isOpen.value = true;
28
35
  await nextTick();
36
+ tooltipEl.value!.showPopover();
29
37
 
30
- tp.value?.destroy();
38
+ const trigger = triggerEl.value!.getBoundingClientRect();
39
+ if (!SUPPORTS_ANCHOR) {
40
+ placeByRect(trigger);
41
+ }
42
+ const tooltip = tooltipEl.value!.getBoundingClientRect();
43
+ placement.value = tooltip.top < trigger.top ? 'top' : 'bottom';
44
+ arrowLeft.value = trigger.left + trigger.width / 2 - tooltip.left;
31
45
 
32
- tp.value = new Tooltip(
33
- tooltip.value,
34
- triggerEl.value,
35
- {
36
- placement: 'bottom',
37
- triggerType: 'hover',
38
- },
39
- );
46
+ document.addEventListener('keydown', onKeydown);
47
+ }
40
48
 
41
- tp.value.show();
49
+ function hide() {
50
+ isOpen.value = false;
51
+ document.removeEventListener('keydown', onKeydown);
42
52
  }
43
53
 
44
- function mouseOff() {
45
- tp.value?.hide();
46
- tp.value?.destroy();
47
- tp.value = null;
48
- showTooltip.value = false;
54
+ function onKeydown(e: KeyboardEvent) {
55
+ if (e.key === 'Escape') {
56
+ hide();
57
+ }
49
58
  }
50
59
 
60
+ // Safari before 26 and Firefox before 147 have no CSS anchor positioning
61
+ function placeByRect(trigger: DOMRect) {
62
+ const el = tooltipEl.value!;
63
+ const { width, height } = el.getBoundingClientRect();
64
+ const fitsBelow = trigger.bottom + OFFSET + height <= window.innerHeight;
65
+ const top = fitsBelow ? trigger.bottom + OFFSET : trigger.top - OFFSET - height;
66
+ const left = Math.min(Math.max(trigger.left + (trigger.width - width) / 2, 0), window.innerWidth - width);
67
+ el.style.top = `${top}px`;
68
+ el.style.left = `${left}px`;
69
+ }
70
+
71
+ onBeforeUnmount(hide);
51
72
  </script>
52
73
 
53
74
  <style>
54
- .tooltip .tooltip-arrow,
55
- .tooltip .tooltip-arrow::before {
56
- position: absolute;
57
- width: 8px;
58
- height: 8px;
59
- background: inherit;
60
- }
61
-
62
- .tooltip .tooltip-arrow {
63
- visibility: hidden;
75
+ /* popover puts the tooltip into the top layer, so no z-index and no clipping by overflow of the parents */
76
+ .afcl-tooltip-popover {
77
+ inset: auto;
78
+ margin: 0;
79
+ overflow: visible;
64
80
  }
65
81
 
66
- .tooltip .tooltip-arrow::before {
67
- visibility: visible;
68
- content: '';
69
- transform: rotate(45deg);
82
+ @supports (anchor-name: --a) {
83
+ .afcl-tooltip-popover {
84
+ position-area: bottom;
85
+ position-try-fallbacks: flip-block;
86
+ margin-top: 8px;
87
+ }
70
88
  }
71
89
 
72
- .tooltip[data-popper-placement^='top'] > .tooltip-arrow {
73
- bottom: -4px;
90
+ @starting-style {
91
+ .afcl-tooltip-popover {
92
+ opacity: 0;
93
+ }
74
94
  }
75
95
 
76
- .tooltip[data-popper-placement^='bottom'] > .tooltip-arrow {
77
- top: -4px;
96
+ .afcl-tooltip-arrow {
97
+ position: absolute;
98
+ width: 8px;
99
+ height: 8px;
100
+ background: inherit;
101
+ transform: translateX(-50%) rotate(45deg);
78
102
  }
79
103
 
80
- .tooltip[data-popper-placement^='left'] > .tooltip-arrow {
81
- right: -4px;
104
+ .afcl-tooltip-popover[data-placement='top'] > .afcl-tooltip-arrow {
105
+ bottom: -4px;
82
106
  }
83
107
 
84
- .tooltip[data-popper-placement^='right'] > .tooltip-arrow {
85
- left: -4px;
108
+ .afcl-tooltip-popover[data-placement='bottom'] > .afcl-tooltip-arrow {
109
+ top: -4px;
86
110
  }
87
- </style>
111
+ </style>
@@ -19,6 +19,7 @@ export { default as ProgressBar } from './ProgressBar.vue';
19
19
  export { default as Spinner } from './Spinner.vue';
20
20
  export { default as Skeleton } from './Skeleton.vue';
21
21
  export { default as Dialog } from './Dialog.vue';
22
+ export { default as DialogPanel } from './DialogPanel.vue';
22
23
  export { default as MixedChart } from './MixedChart.vue';
23
24
  export { default as CountryFlag } from './CountryFlag.vue';
24
25
  export { default as JsonViewer } from './JsonViewer.vue';
@@ -35,7 +35,6 @@
35
35
  <template v-if="isResourceReady && checkboxes.length" #selection>
36
36
  <Button
37
37
  @click="()=>{checkboxes = []}"
38
- data-tooltip-target="tooltip-remove-all"
39
38
  variant="secondary"
40
39
  iconOnly
41
40
  >
@@ -32,11 +32,15 @@
32
32
  class="af-login-modal overflow-y-auto flex flex-grow
33
33
  overflow-x-hidden z-50 min-w-[350px] justify-center items-center md:inset-0 h-[calc(100%-1rem)] max-h-full">
34
34
  <div class="relative p-4 w-full max-h-full max-w-[400px]">
35
- <!-- Modal content -->
36
- <div class="af-login-popup af-login-modal-content relative bg-lightLoginViewBackground rounded-default shadow dark:bg-darkLoginViewBackground dark:shadow-black" :class=" { 'rounded-b-none overflow-hidden': error } ">
37
- <!-- Modal header -->
38
- <div class="af-login-modal-header flex items-center justify-between flex-col p-4 md:p-5 border-b rounded-t dark:border-gray-600">
39
-
35
+ <DialogPanel
36
+ class="af-login-popup af-login-modal-content max-w-none bg-lightLoginViewBackground dark:bg-darkLoginViewBackground"
37
+ :error="error"
38
+ :classes="{
39
+ header: 'af-login-modal-header',
40
+ body: 'af-login-modal-body',
41
+ }"
42
+ >
43
+ <template #title>
40
44
  <template v-if="coreStore?.config?.loginPageInjections?.panelHeader.length && coreStore?.config?.loginPageInjections?.panelHeader.length > 0">
41
45
  <component
42
46
  v-for="(c, index) in coreStore?.config?.loginPageInjections?.panelHeader || []"
@@ -46,15 +50,13 @@
46
50
  @update:setLoginParam="setLoginParam"
47
51
  />
48
52
  </template>
49
- <h3 v-else class="text-xl font-semibold text-lightLoginViewText dark:text-darkLoginViewTextColor">
53
+ <p v-else class="text-lg leading-6 font-semibold text-lightLoginViewTextColor dark:text-darkLoginViewTextColor">
50
54
  {{ $t('Sign in to') }} {{ coreStore.config?.brandName }}
51
- </h3>
52
- </div>
53
- <!-- Modal body -->
54
- <div class="af-login-modal-body p-4 md:p-5">
55
- <form class="space-y-4" @submit.prevent>
55
+ </p>
56
+ </template>
57
+ <form class="space-y-4" @submit.prevent>
56
58
  <div>
57
- <label for="username" class="block mb-2 text-sm font-medium text-lightLoginViewText dark:text-darkLoginViewTextColor">{{ $t('Your') }} {{ coreStore.config?.usernameFieldName?.toLowerCase() }}</label>
59
+ <label for="username" class="block mb-2 text-sm font-medium text-lightLoginViewTextColor dark:text-darkLoginViewTextColor">{{ $t('Your') }} {{ coreStore.config?.usernameFieldName?.toLowerCase() }}</label>
58
60
  <Input
59
61
  v-model="username"
60
62
  autocomplete="username"
@@ -67,20 +69,13 @@
67
69
  placeholder="name@company.com" required />
68
70
  </div>
69
71
  <div class="">
70
- <label for="password" class="block mb-2 text-sm font-medium text-lightLoginViewText dark:text-darkLoginViewTextColor">{{ $t('Your password') }}</label>
72
+ <label for="password" class="block mb-2 text-sm font-medium text-lightLoginViewTextColor dark:text-darkLoginViewTextColor">{{ $t('Your password') }}</label>
71
73
  <Input
72
74
  v-model="password"
73
75
  ref="passwordInput"
74
76
  autocomplete="current-password"
75
77
  @keydown.enter="login"
76
- :type="!showPw ? 'password': 'text'" name="password" id="password" placeholder="••••••••" class="w-full" required>
77
- <template #rightIcon>
78
- <button type="button" @click="showPw = !showPw" class="text-lightLoginViewSubTextColor dark:text-darkLoginViewSubTextColor">
79
- <IconEye class="w-4 h-4" v-if="!showPw" />
80
- <IconEyeOff class="w-4 h-4" v-else />
81
- </button>
82
- </template>
83
- </Input>
78
+ type="password" passwordToggle name="password" id="password" placeholder="••••••••" class="w-full" required />
84
79
  </div>
85
80
 
86
81
  <div v-if="coreStore?.config?.rememberMeDuration"
@@ -123,10 +118,8 @@
123
118
  @update:setLoginParam="setLoginParam"
124
119
  @update:oauthRedirecting="oauthRedirecting = $event"
125
120
  />
126
- </form>
127
- </div>
128
- </div>
129
- <ErrorMessage v-if="error" :error="error" class="absolute left-4 right-4 rounded-t-none mb-0 shadow px-9" />
121
+ </form>
122
+ </DialogPanel>
130
123
  </div>
131
124
  </div>
132
125
 
@@ -140,11 +133,10 @@ import { getCustomComponent, formatComponent } from '@/utils';
140
133
  import { onMounted, ref, computed } from 'vue';
141
134
  import { useCoreStore } from '@/stores/core';
142
135
  import { useUserStore } from '@/stores/user';
143
- import { IconEye, IconEyeOff, IconInfo } from '@iconify-prerendered/vue-lucide';
136
+ import { IconInfo } from '@iconify-prerendered/vue-lucide';
144
137
  import { callAdminForthApi, loadFile } from '@/utils';
145
138
  import { useRouter, useRoute } from 'vue-router';
146
- import { Button, Checkbox, Input, Spinner } from '@/afcl';
147
- import ErrorMessage from '@/components/ErrorMessage.vue';
139
+ import { Button, Checkbox, DialogPanel, Input, Spinner } from '@/afcl';
148
140
 
149
141
  const passwordInput = ref<InstanceType<typeof Input> | null>(null);
150
142
  const usernameInput = ref<InstanceType<typeof Input> | null>(null);
@@ -161,7 +153,6 @@ const isSuccess = ref<boolean>(false);
161
153
  const coreStore = useCoreStore();
162
154
  const user = useUserStore();
163
155
 
164
- const showPw = ref(false);
165
156
 
166
157
  const error = ref(null);
167
158
  const disableLoginButton = ref(false);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "4.5.0",
3
+ "version": "4.6.0",
4
4
  "description": "OpenSource Agent-Native forth-generation admin panel",
5
5
  "keywords": [
6
6
  "adminforth",