aboutsummaryrefslogtreecommitdiff
path: root/front-end/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'front-end/src/components')
-rw-r--r--front-end/src/components/Bookmarks.vue77
-rw-r--r--front-end/src/components/Boomarks/AddOrUpdateForm.vue53
-rw-r--r--front-end/src/components/Boomarks/BookmarkList.vue157
-rw-r--r--front-end/src/components/Login/AdminReg.vue16
-rw-r--r--front-end/src/components/Login/PkiLogin.vue39
-rw-r--r--front-end/src/components/Login/UserPass.vue13
-rw-r--r--front-end/src/components/global/Dialog.vue2
7 files changed, 251 insertions, 106 deletions
diff --git a/front-end/src/components/Bookmarks.vue b/front-end/src/components/Bookmarks.vue
index 274b0b4..b83743c 100644
--- a/front-end/src/components/Bookmarks.vue
+++ b/front-end/src/components/Bookmarks.vue
@@ -1,31 +1,28 @@
<script setup lang="ts">
import { MaybeRef, Ref, computed, defineAsyncComponent, ref, shallowRef, watch } from 'vue';
import { useQuery, useStore } from '../store';
-import { get, set, formatTimeAgo, useToggle, useTimestamp, useFileDialog, asyncComputed, toReactive, useClipboard } from '@vueuse/core';
+import { get, set, useToggle, useFileDialog, asyncComputed, toReactive } from '@vueuse/core';
import { useVuelidate } from '@vuelidate/core';
import { required, maxLength, minLength, helpers } from '@vuelidate/validators';
-import { apiCall, useConfirm, useGeneralToaster, useVuelidateWrapper, useWait } from '@vnuge/vnlib.browser';
-import { clone, cloneDeep, join, defaultTo, every, filter, includes, isEmpty, isEqual, first, isString, chunk, map, forEach, isNil } from 'lodash-es';
+import { apiCall, useGeneralToaster, useVuelidateWrapper, useWait } from '@vnuge/vnlib.browser';
+import { clone, cloneDeep, defaultTo, every, filter, includes, isEmpty, isEqual, first, isString, chunk, map, forEach, isNil } from 'lodash-es';
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
import { parseNetscapeBookmarkString } from './Boomarks/util.ts';
import type { BatchUploadResult, Bookmark, BookmarkError } from '../store/bookmarks';
import AddOrUpdateForm from './Boomarks/AddOrUpdateForm.vue';
+import BookmarkList from './Boomarks/BookmarkList.vue';
const Dialog = defineAsyncComponent(() => import('./global/Dialog.vue'));
const store = useStore();
const { waiting } = useWait();
-const { reveal } = useConfirm();
const toaster = useGeneralToaster();
const bookmarks = computed(() => store.bookmarks.list);
const tags = computed(() => store.bookmarks.allTags);
-const now = useTimestamp({interval: 1000});
+
const selectedTags = computed(() => store.bookmarks.tags);
const localSearch = shallowRef<string>(store.bookmarks.query);
const nextPageAvailable = computed(() => isEqual(bookmarks.value?.length, get(store.bookmarks.pages.currentPageSize)));
-const { copy } = useClipboard()
-//Refresh on page load
-store.bookmarks.refresh();
const safeNameRegex = /^[a-zA-Z0-9_\-\|\., ]*$/;
const safeUrlRegex = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/;
@@ -76,27 +73,6 @@ const clear = () => {
set(localSearch, '');
}
-const bmDelete = async (bookmark: Bookmark) => {
- const { isCanceled } = await reveal({
- title: 'Delete bookmark',
- text: `Are you sure you want to delete ${bookmark.Name} ?`,
- })
-
- if(isCanceled) return;
-
- apiCall(async ({ toaster }) => {
-
- await store.bookmarks.api.delete(bookmark);
-
- toaster.general.success({
- title: 'Bookmark deleted',
- text: 'Bookmark has been deleted successfully'
- });
-
- store.bookmarks.refresh();
- })
-}
-
const isTagSelected = (tag: string, currentTags: MaybeRef<string[]>) => includes(get(currentTags), tag);
const execSearch = () => store.bookmarks.query = get(localSearch);
const clearTags = () => store.bookmarks.tags = [];
@@ -441,43 +417,9 @@ const upload = (() => {
</div>
<div class="mx-auto sm:mt-2">
- <div class="grid h-full grid-cols-1 gap-1 leading-tight md:leading-normal">
-
- <div v-for="bm in bookmarks" :key="bm.Id" :id="join(['bm', bm.Id], '-')" class="w-full p-1">
- <div class="">
- <a class="bl-link" :href="bm.Url" target="_blank">
- {{ bm.Name }}
- </a>
- </div>
- <div class="flex flex-row items-center">
- <span v-for="tag in bm.Tags">
- <span class="mr-1 text-sm text-teal-500 cursor-pointer dark:text-teal-300" @click="toggleTag(tag)">
- #{{ tag }}
- </span>
- </span>
- <p class="ml-2 text-sm text-gray-500 truncate dark:text-gray-400 text-ellipsis">
- {{ bm.Description }}
- </p>
- </div>
- <div class="">
- <span class="text-xs text-gray-500 dark:text-gray-400">
- {{ formatTimeAgo(new Date(bm.Created), {}, now) }}
- </span>
- |
- <span class="inline-flex gap-1.5">
- <button class="text-xs text-gray-700 dark:text-gray-400" @click="copy(bm.Url)">
- Copy
- </button>
- <button class="text-xs text-gray-700 dark:text-gray-400" @click="edit.editBookmark(bm)">
- Edit
- </button>
- <button class="text-xs text-gray-700 dark:text-gray-400" @click="bmDelete(bm)">
- Delete
- </button>
- </span>
- </div>
- </div>
- </div>
+
+ <BookmarkList @toggle-tag="toggleTag" @edit="edit.editBookmark" />
+
<div class="pr-4 mt-5 mb-10 ml-auto w-fit">
<div class="flex flex-col items-center">
<div class="text-sm">
@@ -600,9 +542,6 @@ const upload = (() => {
Upload
</button>
</div>
- <div class="">
-
- </div>
</form>
</div>
</div>
diff --git a/front-end/src/components/Boomarks/AddOrUpdateForm.vue b/front-end/src/components/Boomarks/AddOrUpdateForm.vue
index 0370e0c..d6ea4bc 100644
--- a/front-end/src/components/Boomarks/AddOrUpdateForm.vue
+++ b/front-end/src/components/Boomarks/AddOrUpdateForm.vue
@@ -1,8 +1,10 @@
<script setup lang="ts">
-import { computed, toRefs } from 'vue';
-import { isEmpty, join, split } from 'lodash-es';
+import { computed, shallowRef, toRefs } from 'vue';
+import { set, watchDebounced } from '@vueuse/core'
+import { isEmpty, join, noop, split } from 'lodash-es';
import { useStore } from '../../store';
-import { useWait } from '@vnuge/vnlib.browser';
+import { WebMessage, useWait } from '@vnuge/vnlib.browser';
+import { AxiosError } from 'axios';
const emit = defineEmits(['submit'])
const props = defineProps<{
@@ -20,6 +22,8 @@ const tags = computed({
const { websiteLookup:lookup } = useStore()
const { setWaiting, waiting } = useWait()
+const errMessage = shallowRef();
+
const execLookup = async () => {
//url must be valid before searching
if(v$.value.Url.$invalid) return
@@ -45,9 +49,10 @@ const execLookup = async () => {
v$.value.Tags.$dirty = true;
}
}
- catch(e){
- //Mostly ignore errors
+ catch(e){
console.error(e)
+ const res = (e as AxiosError).response?.data;
+ set(errMessage, (res as WebMessage)?.result);
}
finally{
setWaiting(false)
@@ -56,6 +61,9 @@ const execLookup = async () => {
const showSearchButton = computed(() => lookup.isSupported && !isEmpty(v$.value.Url.$model))
+//Clear error message after 5 seconds
+watchDebounced(errMessage, v => v ? setTimeout(() => set(errMessage, ''), 5000) : noop())
+
</script>
<template>
<form id="bm-add-or-update-form" class="grid grid-cols-1 gap-4 p-4" @submit.prevent="emit('submit')">
@@ -67,22 +75,31 @@ const showSearchButton = computed(() => lookup.isSupported && !isEmpty(v$.value.
<input type="text" id="url" class="input" placeholder="https://www.example.com" v-model="v$.Url.$model"
:class="{'dirty': v$.Url.$dirty, 'error': v$.Url.$invalid}" required>
- <div class="">
- <button
- type="button"
- :disabled="!showSearchButton || waiting"
- @click.self.prevent="execLookup"
- id="search-btn"
- class="btn blue search-btn"
- >
- <svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
- viewBox="0 0 20 20">
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
- d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" />
- </svg>
+ <div class="my-auto">
+ <button type="button" :disabled="!showSearchButton || waiting" @click.prevent="execLookup"
+ id="search-btn" class="btn blue search-btn">
+ <span v-if="waiting" class="mx-auto">
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 animate-spin" viewBox="0 0 15 15">
+ <path fill="currentColor" fill-rule="evenodd"
+ d="M8 .5V5H7V.5h1ZM5.146 5.854l-3-3l.708-.708l3 3l-.708.708Zm4-.708l3-3l.708.708l-3 3l-.708-.708Zm.855 1.849L14.5 7l-.002 1l-4.5-.006l.002-1Zm-9.501 0H5v1H.5v-1Zm5.354 2.859l-3 3l-.708-.708l3-3l.708.708Zm6.292 3l-3-3l.708-.708l3 3l-.708.708ZM8 10v4.5H7V10h1Z"
+ clip-rule="evenodd" />
+ </svg>
+ </span>
+ <span v-else>
+ <svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
+ viewBox="0 0 20 20">
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
+ stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" />
+ </svg>
+ </span>
</button>
</div>
</div>
+ <div v-if="errMessage" class="pl-2">
+ <p class="text-xs italic text-red-800 dark:text-red-500">
+ {{ errMessage }}
+ </p>
+ </div>
</fieldset>
<fieldset>
<label for="name" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Title</label>
diff --git a/front-end/src/components/Boomarks/BookmarkList.vue b/front-end/src/components/Boomarks/BookmarkList.vue
new file mode 100644
index 0000000..ac0d49b
--- /dev/null
+++ b/front-end/src/components/Boomarks/BookmarkList.vue
@@ -0,0 +1,157 @@
+<script setup lang="ts">
+import { type Bookmark } from '../../store/bookmarks';
+
+import { computed, ref } from 'vue';
+import { useStore } from '../../store';
+import { formatTimeAgo, useTimestamp, useClipboard } from '@vueuse/core';
+import { apiCall, useConfirm } from '@vnuge/vnlib.browser';
+import { join, truncate } from 'lodash-es';
+
+const emit = defineEmits(['toggleTag', 'edit']);
+
+const store = useStore();
+const bookmarks = computed(() => store.bookmarks.list);
+const readable = ref(true); //Future allow users to switch between clean and readable layout
+
+//Refresh on page load
+store.bookmarks.refresh();
+
+const { copy } = useClipboard()
+const { reveal } = useConfirm();
+const now = useTimestamp({ interval: 1000 });
+
+const bmDelete = async (bookmark: Bookmark) => {
+ const { isCanceled } = await reveal({
+ title: 'Delete bookmark',
+ text: `Are you sure you want to delete ${bookmark.Name} ?`,
+ })
+
+ if (isCanceled) return;
+
+ apiCall(async ({ toaster }) => {
+
+ await store.bookmarks.api.delete(bookmark);
+
+ toaster.general.success({
+ title: 'Bookmark deleted',
+ text: 'Bookmark has been deleted successfully'
+ });
+
+ store.bookmarks.refresh();
+ })
+}
+
+const truncatText = (desc: string) => truncate(desc, { length: 100 });
+
+</script>
+
+<template>
+ <div class="grid h-full grid-cols-1 gap-0">
+ <div v-for="bm in bookmarks" :key="bm.Id" :id="join(['bm', bm.Id], '-')" class="w-full p-1">
+ <div v-if="readable" class="leading-tight md:leading-normal">
+ <div class="">
+ <a class="bl-link" :href="bm.Url" target="_blank">
+ {{ bm.Name }}
+ </a>
+ </div>
+ <div class="flex flex-row items-center">
+ <span v-for="tag in bm.Tags">
+ <span class="mr-1 text-sm text-teal-500 cursor-pointer dark:text-teal-300"
+ @click="emit('toggleTag', tag)">
+ #{{ tag }}
+ </span>
+ </span>
+ <p class="ml-2 text-sm text-gray-500 truncate dark:text-gray-400 text-ellipsis">
+ {{ bm.Description }}
+ </p>
+ </div>
+ <div class="flex items-center gap-1.5">
+ <span class="text-xs text-gray-500 dark:text-gray-400">
+ {{ formatTimeAgo(new Date(bm.Created), {}, now) }}
+ </span>
+ |
+ <span class="flex flex-row gap-1.5">
+ <button class="text-xs text-gray-700 dark:text-gray-400" @click="copy(bm.Url)">
+ Copy
+ </button>
+ <button class="text-xs text-gray-700 dark:text-gray-400" @click="emit('edit', bm)">
+ Edit
+ </button>
+ <button class="text-xs text-gray-700 dark:text-gray-400" @click="bmDelete(bm)">
+ Delete
+ </button>
+ </span>
+ </div>
+ </div>
+ <div v-else class="leading-tight clean-layout">
+ <div class="flex flex-row">
+ <div class="flex-1">
+ <a class="text-sm font-bold bl-link" :href="bm.Url" target="_blank">
+ {{ bm.Name }}
+ </a>
+ </div>
+ <div class="">
+ <span class="inline-flex gap-1">
+ <button class="text-xs text-gray-700 dark:text-gray-400" @click="copy(bm.Url)">
+ <svg class="w-5 h-5 text-gray-800 dark:text-white" aria-hidden="true"
+ xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"
+ viewBox="0 0 24 24">
+ <path stroke="currentColor" stroke-linejoin="round" stroke-width="2"
+ d="M9 8v3a1 1 0 0 1-1 1H5m11 4h2a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1h-7a1 1 0 0 0-1 1v1m4 3v10a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1v-7.13a1 1 0 0 1 .24-.65L7.7 8.35A1 1 0 0 1 8.46 8H13a1 1 0 0 1 1 1Z" />
+ </svg>
+ </button>
+ <button class="text-xs text-gray-700 dark:text-gray-400" @click="emit('edit', bm)">
+ <svg class="w-5 h-5 text-gray-800 dark:text-white" aria-hidden="true"
+ xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"
+ viewBox="0 0 24 24">
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
+ stroke-width="2"
+ d="m14.304 4.844 2.852 2.852M7 7H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-4.5m2.409-9.91a2.017 2.017 0 0 1 0 2.853l-6.844 6.844L8 14l.713-3.565 6.844-6.844a2.015 2.015 0 0 1 2.852 0Z" />
+ </svg>
+ </button>
+ <button class="text-xs text-gray-700 dark:text-gray-400 " @click="bmDelete(bm)">
+ <svg class="w-5 h-5 text-gray-800 duration-100 ease-in dark:text-white trash"
+ aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
+ fill="none" viewBox="0 0 24 24">
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
+ stroke-width="2"
+ d="M5 7h14m-9 3v8m4-8v8M10 3h4a1 1 0 0 1 1 1v3H9V4a1 1 0 0 1 1-1ZM6 7h12v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7Z" />
+ </svg>
+ </button>
+ </span>
+ </div>
+ </div>
+ <div class="flex flex-row items-start">
+ <p class="text-sm text-gray-500 dark:text-gray-300 max-w-[26rem] flex-auto">
+ {{ truncatText(bm.Description) }}
+ </p>
+ <div class="flex flex-col flex-wrap items-end ml-5 class gap-x-2 max-h-16">
+ <span v-for="tag in bm.Tags">
+ <span class="mr-1 text-xs text-gray-500 duration-75 ease-linear cursor-pointer dark:text-gray-500 hover:text-teal-500 hover:dark:text-teal-400"
+ @click="emit('toggleTag', tag)">
+ {{ tag }}
+ </span>
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<style lang="scss" scoped>
+ .clean-layout {
+ @apply shadow-sm md:px-6 p-3 border rounded-md h-[7rem];
+ @apply bg-white dark:bg-gray-800 dark:border-gray-700 max-w-[40rem];
+
+ button svg{
+ &:hover{
+ @apply text-gray-500 dark:text-gray-300 ease-linear duration-75;
+
+ &.trash{
+ @apply hover:text-red-500;
+ }
+ }
+ }
+ }
+</style> \ No newline at end of file
diff --git a/front-end/src/components/Login/AdminReg.vue b/front-end/src/components/Login/AdminReg.vue
index 8546512..f9bbbb8 100644
--- a/front-end/src/components/Login/AdminReg.vue
+++ b/front-end/src/components/Login/AdminReg.vue
@@ -74,10 +74,22 @@ const onSubmit = async () => {
<input type="password" name="repeat-password" id="repeat-password" class="input" placeholder="••••••••" required
v-model="v$.repeat.$model">
</fieldset>
- <button form="admin-registation" type="submit" class="btn">Register</button>
+ <button type="submit" for="admin-registation" class="flex justify-center btn">
+ <span v-if="waiting" class="mx-auto animate-spin">
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 15 15">
+ <path fill="currentColor" fill-rule="evenodd"
+ d="M8 .5V5H7V.5h1ZM5.146 5.854l-3-3l.708-.708l3 3l-.708.708Zm4-.708l3-3l.708.708l-3 3l-.708-.708Zm.855 1.849L14.5 7l-.002 1l-4.5-.006l.002-1Zm-9.501 0H5v1H.5v-1Zm5.354 2.859l-3 3l-.708-.708l3-3l.708.708Zm6.292 3l-3-3l.708-.708l3 3l-.708.708ZM8 10v4.5H7V10h1Z"
+ clip-rule="evenodd" />
+ </svg>
+ </span>
+ <span v-else>
+ Register
+ </span>
+ </button>
</form>
<p class="py-4 text-sm text-red-500">
- This tab is only visible when the server is in setup mode. You can create as many admin accounts as you like now.
+ This tab is only visible when the server is in setup mode. You can create as many admin accounts as you like
+ now.
</p>
</template>
diff --git a/front-end/src/components/Login/PkiLogin.vue b/front-end/src/components/Login/PkiLogin.vue
index 4515062..d3a635e 100644
--- a/front-end/src/components/Login/PkiLogin.vue
+++ b/front-end/src/components/Login/PkiLogin.vue
@@ -1,12 +1,13 @@
<script setup lang="ts">
import { isEmpty } from 'lodash-es';
-import { apiCall, debugLog, useMessage } from '@vnuge/vnlib.browser';
+import { apiCall, debugLog, useMessage, useWait } from '@vnuge/vnlib.browser';
import { ref } from 'vue'
import { decodeJwt } from 'jose'
import { useStore } from '../../store';
const { setMessage } = useMessage()
const { pkiAuth } = useStore()
+const { waiting } = useWait()
const otp = ref('')
@@ -32,19 +33,27 @@ const onSubmit = () => {
</script>
<template>
- <form id="pki-login-form" class="max-w-sm mx-auto" @submit.prevent="onSubmit">
- <label for="message" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
- Paste your one time password (OTP)
- </label>
- <textarea
- id="message" rows="5"
- v-model="otp"
- class="block p-2.5 w-full text-sm text-gray-900 bg-transparent rounded border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
- placeholder="Enter your OTP"
- >
+ <form id="pki-login-form" class="max-w-sm mx-auto" @submit.prevent="onSubmit">
+ <label for="message" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
+ Paste your one time password (OTP)
+ </label>
+ <textarea id="message" rows="5" v-model="otp"
+ class="block p-2.5 w-full text-sm text-gray-900 bg-transparent rounded border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
+ placeholder="Enter your OTP">
</textarea>
-
- <button type="submit" for="pki-login-form" class="mt-4 btn">Submit</button>
-
- </form>
+
+ <button type="submit" for="pki-login-form" class="flex justify-center mt-4 btn">
+ <span v-if="waiting" class="mx-auto animate-spin">
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 15 15">
+ <path fill="currentColor" fill-rule="evenodd"
+ d="M8 .5V5H7V.5h1ZM5.146 5.854l-3-3l.708-.708l3 3l-.708.708Zm4-.708l3-3l.708.708l-3 3l-.708-.708Zm.855 1.849L14.5 7l-.002 1l-4.5-.006l.002-1Zm-9.501 0H5v1H.5v-1Zm5.354 2.859l-3 3l-.708-.708l3-3l.708.708Zm6.292 3l-3-3l.708-.708l3 3l-.708.708ZM8 10v4.5H7V10h1Z"
+ clip-rule="evenodd" />
+ </svg>
+ </span>
+ <span v-else>
+ Submit
+ </span>
+ </button>
+
+ </form>
</template> \ No newline at end of file
diff --git a/front-end/src/components/Login/UserPass.vue b/front-end/src/components/Login/UserPass.vue
index c47e594..9fd64f4 100644
--- a/front-end/src/components/Login/UserPass.vue
+++ b/front-end/src/components/Login/UserPass.vue
@@ -110,7 +110,18 @@ const onSubmit = async () => {
v-model="v$.password.$model"
>
</fieldset>
- <button type="submit" class="btn">Sign in</button>
+ <button type="submit" class="flex justify-center btn">
+ <span v-if="waiting" class="mx-auto animate-spin">
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 15 15">
+ <path fill="currentColor" fill-rule="evenodd"
+ d="M8 .5V5H7V.5h1ZM5.146 5.854l-3-3l.708-.708l3 3l-.708.708Zm4-.708l3-3l.708.708l-3 3l-.708-.708Zm.855 1.849L14.5 7l-.002 1l-4.5-.006l.002-1Zm-9.501 0H5v1H.5v-1Zm5.354 2.859l-3 3l-.708-.708l3-3l.708.708Zm6.292 3l-3-3l.708-.708l3 3l-.708.708ZM8 10v4.5H7V10h1Z"
+ clip-rule="evenodd" />
+ </svg>
+ </span>
+ <span v-else>
+ Sign in
+ </span>
+ </button>
</form>
</template>
diff --git a/front-end/src/components/global/Dialog.vue b/front-end/src/components/global/Dialog.vue
index 65d9165..18ad0fe 100644
--- a/front-end/src/components/global/Dialog.vue
+++ b/front-end/src/components/global/Dialog.vue
@@ -22,7 +22,7 @@ onClickOutside(dialog, () => get(open) ? cancel() : noop())
class="overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-20 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full">
<div class="fixed inset-0 bg-black/30" aria-hidden="true" />
- <div class="relative w-full max-w-xl max-h-full p-4 mx-auto mt-[8rem] md:mt-32">
+ <div class="relative w-full max-w-xl max-h-full p-4 mx-auto mt-16 md:mt-32">
<!-- Modal content -->
<div class="relative bg-white rounded shadow dark:bg-gray-700" ref="dialog">
<!-- Modal header -->