@opengis/cms 0.0.21 → 0.0.22
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/package.json +2 -2
- package/src/App.css +52 -0
- package/src/App.vue +62 -0
- package/src/assets/image.png +0 -0
- package/src/assets/main.css +3 -0
- package/src/assets/tailwind-3.4.17.js +113 -0
- package/src/components/LanguageSwitcher.vue +73 -0
- package/src/components/SettingsCard.vue +40 -0
- package/src/components/builder/CreateForm.vue +128 -0
- package/src/components/builder/formTypeSchema.js +145 -0
- package/src/components/builder/tabs/index.ts +9 -0
- package/src/components/builder/tabs/vs-builder-edit.vue +133 -0
- package/src/components/builder/tabs/vs-builder-monaco.vue +29 -0
- package/src/components/builder/tabs/vs-builder-preview.vue +39 -0
- package/src/components/builder/vs-builder-datatable-controls.vue +138 -0
- package/src/components/builder/vs-builder-datatable-form.vue +80 -0
- package/src/components/builder/vs-builder-datatable.vue +191 -0
- package/src/components/builder/vs-builder-list-item.vue +110 -0
- package/src/components/collections/CollectionsBreadcrumb.vue +52 -0
- package/src/components/collections/CollectionsGrid.vue +176 -0
- package/src/components/collections/ContentBlock.vue +75 -0
- package/src/components/collections/formWrapper.vue +156 -0
- package/src/components/dashboard/ContentItem.vue +82 -0
- package/src/components/dashboard/DashboardHeader.vue +33 -0
- package/src/components/dashboard/QuickActions.vue +84 -0
- package/src/components/dashboard/RecentContent.vue +54 -0
- package/src/components/dashboard/StatCard.vue +63 -0
- package/src/components/dashboard/StatsGrid.vue +28 -0
- package/src/components/form-components/MonacoEditor.vue +104 -0
- package/src/components/form-components/VsFormMeta.vue +40 -0
- package/src/components/form-components/VsFormTags.vue +150 -0
- package/src/components/form-components/custom-datatable/vs-form-custom-datatable-add.vue +84 -0
- package/src/components/form-components/custom-datatable/vs-form-custom-datatable-controls.vue +106 -0
- package/src/components/form-components/index.js +23 -0
- package/src/components/form-components/reference/vs-form-reference-add.vue +92 -0
- package/src/components/form-components/reference/vs-form-reference-controls.vue +101 -0
- package/src/components/form-components/reference-list/referenceOptionList.js +78 -0
- package/src/components/form-components/reference-list/vs-form-reference-add.vue +145 -0
- package/src/components/form-components/reference-list/vs-form-reference-choce.vue +39 -0
- package/src/components/form-components/reference-list/vs-form-reference-controls.vue +110 -0
- package/src/components/form-components/reference-skeleton/about-skeleton.vue +37 -0
- package/src/components/form-components/reference-skeleton/banner-skeleton.vue +29 -0
- package/src/components/form-components/reference-skeleton/body-skeleton.vue +56 -0
- package/src/components/form-components/reference-skeleton/cards-skeleton.vue +47 -0
- package/src/components/form-components/reference-skeleton/documents-skeleton.vue +64 -0
- package/src/components/form-components/reference-skeleton/faq-skeleton.vue +64 -0
- package/src/components/form-components/reference-skeleton/form-skeleton.vue +41 -0
- package/src/components/form-components/reference-skeleton/index.js +36 -0
- package/src/components/form-components/reference-skeleton/infoLine-skeleton.vue +37 -0
- package/src/components/form-components/reference-skeleton/news-skeleton.vue +54 -0
- package/src/components/form-components/reference-skeleton/slider-skeleton.vue +41 -0
- package/src/components/form-components/reference-skeleton/tabs-skeleton.vue +40 -0
- package/src/components/form-components/reference-skeleton/team-skeleton.vue +103 -0
- package/src/components/form-components/reference-skeleton/usefulLinks-skeleton.vue +52 -0
- package/src/components/form-components/reference-skeleton/video-skeleton.vue +36 -0
- package/src/components/form-components/testReferenceTypes.js +773 -0
- package/src/components/form-components/vs-form-color-picker.vue +29 -0
- package/src/components/form-components/vs-form-custom-datatable.vue +214 -0
- package/src/components/form-components/vs-form-integer.vue +86 -0
- package/src/components/form-components/vs-form-key-value.vue +201 -0
- package/src/components/form-components/vs-form-marcdown-md.vue +3 -0
- package/src/components/form-components/vs-form-media-select.vue +780 -0
- package/src/components/form-components/vs-form-reference-list.vue +97 -0
- package/src/components/form-components/vs-form-reference.vue +59 -0
- package/src/components/form-components/vs-form-relation.vue +30 -0
- package/src/components/form-components/vs-form-reletion-link.vue +34 -0
- package/src/components/form-components/vs-form-select-collection.vue +0 -0
- package/src/components/form-components/vs-form-slug.vue +72 -0
- package/src/components/form-components/vs-form-tiptap.vue +7 -0
- package/src/components/form-components/vs-richtext-md.vue +3 -0
- package/src/components/icons/BellIcon.vue +17 -0
- package/src/components/icons/GlobeIcon.vue +18 -0
- package/src/components/icons/KeyIcon.vue +20 -0
- package/src/components/icons/PaletteIcon.vue +22 -0
- package/src/components/icons/SettingsIcon.vue +19 -0
- package/src/components/icons/ShieldIcon.vue +18 -0
- package/src/components/icons/UsersIcon.vue +19 -0
- package/src/components/icons/icon-chevron-right.vue +16 -0
- package/src/components/icons/icon-drag.vue +20 -0
- package/src/components/icons/icon-file-text.vue +21 -0
- package/src/components/icons/icon-folder.vue +18 -0
- package/src/components/icons/icon-grid.vue +17 -0
- package/src/components/icons/icon-group.vue +19 -0
- package/src/components/icons/icon-home.vue +16 -0
- package/src/components/icons/icon-image.vue +18 -0
- package/src/components/icons/icon-list.vue +20 -0
- package/src/components/icons/icon-more.vue +17 -0
- package/src/components/icons/icon-plus.vue +17 -0
- package/src/components/icons-types/icon-array.vue +22 -0
- package/src/components/icons-types/icon-boolean.vue +18 -0
- package/src/components/icons-types/icon-datalist.vue +22 -0
- package/src/components/icons-types/icon-date.vue +20 -0
- package/src/components/icons-types/icon-datetime.vue +20 -0
- package/src/components/icons-types/icon-file.vue +21 -0
- package/src/components/icons-types/icon-gallery.vue +18 -0
- package/src/components/icons-types/icon-image.vue +19 -0
- package/src/components/icons-types/icon-integer.vue +20 -0
- package/src/components/icons-types/icon-merkdown.vue +18 -0
- package/src/components/icons-types/icon-multiselect.vue +22 -0
- package/src/components/icons-types/icon-number.vue +20 -0
- package/src/components/icons-types/icon-radio.vue +22 -0
- package/src/components/icons-types/icon-reference-list.vue +22 -0
- package/src/components/icons-types/icon-reference.vue +20 -0
- package/src/components/icons-types/icon-relation.vue +22 -0
- package/src/components/icons-types/icon-richtext.vue +18 -0
- package/src/components/icons-types/icon-select.vue +22 -0
- package/src/components/icons-types/icon-slug.vue +19 -0
- package/src/components/icons-types/icon-text.vue +19 -0
- package/src/components/icons-types/index.js +43 -0
- package/src/components/layout/Layout.vue +67 -0
- package/src/components/layout/Sidebar.vue +128 -0
- package/src/components/media/FileUploadProgress.vue +29 -0
- package/src/components/media/MediaBreadcrumb.vue +42 -0
- package/src/components/media/MediaCreateFolder.vue +59 -0
- package/src/components/media/MediaFileInfo.vue +148 -0
- package/src/components/media/MediaGrid.vue +148 -0
- package/src/components/media/MediaList.vue +148 -0
- package/src/components/media/MediaViewControls.vue +38 -0
- package/src/components/media/TypeTag.vue +23 -0
- package/src/components/menu/AddNewItemInTree.vue +75 -0
- package/src/components/menu/MenuBody.vue +149 -0
- package/src/components/menu/MenuItem.vue +73 -0
- package/src/components/menu/MenuList.vue +101 -0
- package/src/components/referencec/index.ts +7 -0
- package/src/components/referencec/vs-reference-faq.vue +61 -0
- package/src/components/referencec/vs-reference-user-card.vue +40 -0
- package/src/components/settings/NotificationSettings.vue +32 -0
- package/src/components/settings/SettingsTable.vue +50 -0
- package/src/components/settings/SettingsTitle.vue +33 -0
- package/src/components/settings/SettingsToggleItem.vue +25 -0
- package/src/components/sidebar/DropdownMenu.vue +34 -0
- package/src/components/sidebar/SettingsSidebar.vue +121 -0
- package/src/components/sidebar/SidebarFooter.vue +52 -0
- package/src/components/sidebar/SidebarHeader.vue +57 -0
- package/src/components/sidebar/SidebarMenu.vue +78 -0
- package/src/components/ui/EmptyData.vue +76 -0
- package/src/components/ui/UniversalTable.vue +310 -0
- package/src/components/ui/UniversalTableFilters.vue +0 -0
- package/src/components/ui/UniversalTablePagination.vue +118 -0
- package/src/components/ui/VsPreview.vue +75 -0
- package/src/composables/useCollectionView.ts +21 -0
- package/src/composables/useDebounce.ts +26 -0
- package/src/composables/useMonaco.ts +28 -0
- package/src/composables/useTheme.ts +40 -0
- package/src/content/test-slug/metadata.json +1 -0
- package/src/i18n.ts +75 -0
- package/src/index.css +3 -0
- package/src/locales/en.json +778 -0
- package/src/locales/uk.json +797 -0
- package/src/main.ts +41 -0
- package/src/pages/Dashboard.vue +168 -0
- package/src/pages/EmailPage.vue +183 -0
- package/src/pages/FeedbackPage.vue +232 -0
- package/src/pages/MediaPage.vue +372 -0
- package/src/pages/TagsPage.vue +207 -0
- package/src/pages/builder/BuilderPage.vue +195 -0
- package/src/pages/builder/EditCollectionPage.vue +163 -0
- package/src/pages/collections/ArticlesPage.vue +385 -0
- package/src/pages/collections/CollectionsPage.vue +146 -0
- package/src/pages/collections/SingletonsPage.vue +119 -0
- package/src/pages/collections/contentForm.vue +484 -0
- package/src/pages/collections/schema/seo.ts +27 -0
- package/src/pages/menu/MenuAddPage.vue +123 -0
- package/src/pages/menu/MenuItemPage.vue +183 -0
- package/src/pages/menu/MenuPage.vue +133 -0
- package/src/pages/settings/ApiKeys.vue +75 -0
- package/src/pages/settings/Appearance.vue +80 -0
- package/src/pages/settings/Logs.vue +260 -0
- package/src/pages/settings/PermissionsPage.vue +237 -0
- package/src/pages/settings/Settings.vue +186 -0
- package/src/pages/settings/Users.vue +109 -0
- package/src/pages/settings/general.vue +154 -0
- package/src/pages/settings/generalScheme.js +132 -0
- package/src/pages/users/AddUser.vue +106 -0
- package/src/pages/users/UsersPage.vue +98 -0
- package/src/props/builder.ts +67 -0
- package/src/props/content.ts +56 -0
- package/src/props/media.ts +63 -0
- package/src/router/index.ts +181 -0
- package/src/types/fastify-auth.d.ts +4 -0
- package/src/utils/getField.js +270 -0
- package/src/utils/translit.js +19 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="space-y-6 max-w-7xl mx-auto">
|
|
3
|
+
<div class="flex items-center justify-between">
|
|
4
|
+
<div class="flex items-center gap-4">
|
|
5
|
+
<button
|
|
6
|
+
@click="router.back()"
|
|
7
|
+
class="p-2 text-gray-500 rounded-full hover:text-gray-900 dark:text-gray-400 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
8
|
+
>
|
|
9
|
+
<ArrowLeft class="w-5 h-5" />
|
|
10
|
+
</button>
|
|
11
|
+
<div class="flex items-center gap-2">
|
|
12
|
+
<h1 class="text-2xl font-semibold text-gray-900 dark:text-white">
|
|
13
|
+
{{ $t("menu.editMenu") }}
|
|
14
|
+
</h1>
|
|
15
|
+
<a :href="`https://cms.opengis.info/${locale}/guides/menu/`" target="_blank" :title="$t('guide.menu')">
|
|
16
|
+
<HelpCircle class="w-5 h-5" />
|
|
17
|
+
</a>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="flex items-center space-x-3">
|
|
21
|
+
<button
|
|
22
|
+
@click="handleSubmit"
|
|
23
|
+
class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium h-9 px-4 py-2 bg-blue-600 text-white shadow-md transition-all duration-200 transform
|
|
24
|
+
hover:bg-blue-700 hover:shadow-lg hover:scale-105
|
|
25
|
+
focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring
|
|
26
|
+
disabled:pointer-events-none disabled:opacity-50"
|
|
27
|
+
>
|
|
28
|
+
<Save class="w-4 h-4 mr-2" />
|
|
29
|
+
{{ $t("common.actions.save") }}
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="max-w-[1000px] flex flex-col" v-if="isForm">
|
|
34
|
+
<div class="flex ml-2 mb-6 w-min border border-gray-200 dark:border-gray-700 rounded-full overflow-hidden">
|
|
35
|
+
<button
|
|
36
|
+
@click="isSortable = false"
|
|
37
|
+
:class="{'bg-gray-200 text-gray-900 dark:bg-gray-700 dark:text-white': !isSortable}"
|
|
38
|
+
class="text-gray-500 hover:text-gray-900 dark:hover:text-white hover:bg-gray-200 dark:hover:bg-gray-700 whitespace-nowrap px-5 py-2 w-[200px] text-sm font-medium"
|
|
39
|
+
>{{ $t("menu.textEditor") }}</button>
|
|
40
|
+
<button
|
|
41
|
+
@click="isSortable = true"
|
|
42
|
+
:class="{'bg-gray-200 text-gray-900 dark:bg-gray-700 dark:text-white': isSortable}"
|
|
43
|
+
class="text-gray-500 hover:text-gray-900 dark:hover:text-white hover:bg-gray-200 dark:hover:bg-gray-700 whitespace-nowrap px-5 py-2 w-[200px] text-sm font-medium"
|
|
44
|
+
>{{ $t("menu.listTree") }}</button>
|
|
45
|
+
</div>
|
|
46
|
+
<MenuBody v-model="formValue" v-if="isSortable " />
|
|
47
|
+
<VForm v-else :schema="scheme" v-model="formValue" v-model:form="form" />
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<script setup lang="ts">
|
|
53
|
+
import MenuBody from "../../components/menu/MenuBody.vue";
|
|
54
|
+
import MonacoEditor from "../../components/form-components/MonacoEditor.vue";
|
|
55
|
+
import { ref, computed, getCurrentInstance, onMounted, watch } from "vue";
|
|
56
|
+
import { useRouter, useRoute } from "vue-router";
|
|
57
|
+
import { ArrowLeft, Save, HelpCircle } from "lucide-vue-next";
|
|
58
|
+
import { useI18n } from "vue-i18n";
|
|
59
|
+
import { dump as json2yml } from 'js-yaml';
|
|
60
|
+
import { notify } from '@opengis/core';
|
|
61
|
+
import { VForm, inputs } from '@opengis/form'
|
|
62
|
+
inputs['vs-input-monaco-editor'] = MonacoEditor
|
|
63
|
+
|
|
64
|
+
const { t, locale } = useI18n();
|
|
65
|
+
|
|
66
|
+
const router = useRouter();
|
|
67
|
+
const route = useRoute();
|
|
68
|
+
|
|
69
|
+
const scheme = computed(() => [
|
|
70
|
+
{
|
|
71
|
+
key: "name",
|
|
72
|
+
title: t("menu.form.name"),
|
|
73
|
+
type: "text",
|
|
74
|
+
placeholder: t("menu.form.name"),
|
|
75
|
+
required: true,
|
|
76
|
+
validators: ["required"],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
key: "description",
|
|
80
|
+
title: t("menu.form.description"),
|
|
81
|
+
type: "text",
|
|
82
|
+
placeholder: t("menu.form.description"),
|
|
83
|
+
required: true,
|
|
84
|
+
validators: ["required"],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
key: "locale",
|
|
88
|
+
title: t("menu.form.language"),
|
|
89
|
+
type: "select",
|
|
90
|
+
placeholder: t("menu.form.language"),
|
|
91
|
+
options: [
|
|
92
|
+
{text: "Ukrainian", id: "uk"},
|
|
93
|
+
{text: "English", id: "en"},
|
|
94
|
+
],
|
|
95
|
+
required: true,
|
|
96
|
+
validators: ["required"],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
key: "content",
|
|
100
|
+
title: t("menu.form.content"),
|
|
101
|
+
type: "monaco-editor",
|
|
102
|
+
height: "500px",
|
|
103
|
+
language: "yaml",
|
|
104
|
+
theme: "vs-light",
|
|
105
|
+
},
|
|
106
|
+
]);
|
|
107
|
+
const formValue = ref<Record<string, any> | null>(null);
|
|
108
|
+
const form = ref<any>({});
|
|
109
|
+
const isForm = ref(false);
|
|
110
|
+
const isSortable = ref(false);
|
|
111
|
+
|
|
112
|
+
const formateData = (menuArray: any[]) => {
|
|
113
|
+
const result: Record<string, any> = {};
|
|
114
|
+
|
|
115
|
+
for (const item of menuArray) {
|
|
116
|
+
const { title, value, children } = item;
|
|
117
|
+
|
|
118
|
+
if (children && children.length > 0) {
|
|
119
|
+
// Якщо є дочірні елементи, рекурсивно обробляємо їх
|
|
120
|
+
result[title] = formateData(children);
|
|
121
|
+
} else {
|
|
122
|
+
// Якщо немає дочірніх елементів, зберігаємо value
|
|
123
|
+
result[title] = value;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const handleSubmit = async () => {
|
|
131
|
+
let data = {...(formValue.value || {})};
|
|
132
|
+
let result = await form.value.validate();
|
|
133
|
+
if (result) {
|
|
134
|
+
notify({
|
|
135
|
+
type: "warning",
|
|
136
|
+
title: t("common.actions.warning"),
|
|
137
|
+
message: t("menu.createMenuFailed"),
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (Array.isArray(data.items)) {
|
|
143
|
+
data.items = formateData(data.items);
|
|
144
|
+
data.content = json2yml(data.items);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
await fetch(`/api/cms-menu/${route.params.id}`, {
|
|
149
|
+
method: "PUT",
|
|
150
|
+
headers: {
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
},
|
|
153
|
+
body: JSON.stringify(data),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
notify({
|
|
157
|
+
title: t("menu.menuUpdated"),
|
|
158
|
+
type: "success",
|
|
159
|
+
message: t("menu.menuUpdated"),
|
|
160
|
+
});
|
|
161
|
+
router.push("/menu");
|
|
162
|
+
} catch (error) {
|
|
163
|
+
notify({
|
|
164
|
+
title: t("menu.menuUpdateError"),
|
|
165
|
+
type: "error",
|
|
166
|
+
message: t("menu.menuUpdateError"),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const fetchMenu = async () => {
|
|
172
|
+
try {
|
|
173
|
+
const response = await fetch(`/api/cms-menu/${route.params.id}`);
|
|
174
|
+
const data = await response.json();
|
|
175
|
+
formValue.value = data;
|
|
176
|
+
isForm.value = true;
|
|
177
|
+
} catch (error) {
|
|
178
|
+
isForm.value = false;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
onMounted(fetchMenu);
|
|
183
|
+
</script>
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="space-y-6 max-w-7xl mx-auto">
|
|
3
|
+
<div
|
|
4
|
+
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
|
|
5
|
+
>
|
|
6
|
+
<div>
|
|
7
|
+
<div class="flex items-center gap-2">
|
|
8
|
+
<h1 class="text-3xl font-bold text-slate-800 dark:text-slate-100 mb-2">{{ $t("menu.title") }}</h1>
|
|
9
|
+
<a :href="`https://cms.opengis.info/${locale}/guides/menu/`" target="_blank" :title="$t('guide.menu')">
|
|
10
|
+
<HelpCircle class="w-5 h-5" />
|
|
11
|
+
</a>
|
|
12
|
+
</div>
|
|
13
|
+
<p class="text-slate-600 dark:text-slate-300">{{ $t("menu.description") }}</p>
|
|
14
|
+
</div>
|
|
15
|
+
<router-link
|
|
16
|
+
to="/menu/create"
|
|
17
|
+
class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium h-9 px-4 py-2 bg-blue-600 text-white shadow-md transition-all duration-200 transform
|
|
18
|
+
hover:bg-blue-700 hover:shadow-lg hover:scale-105"
|
|
19
|
+
>
|
|
20
|
+
<Plus class="w-4 h-4 mr-2" />
|
|
21
|
+
{{ $t("menu.menuAdd") }}
|
|
22
|
+
</router-link>
|
|
23
|
+
</div>
|
|
24
|
+
<div
|
|
25
|
+
class="overflow-hidden bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700"
|
|
26
|
+
>
|
|
27
|
+
<div v-if="filterScheme?.length > 0" class="p-4 border-b border-gray-200 sm:p-6 dark:border-gray-700">
|
|
28
|
+
<VsFilter
|
|
29
|
+
|
|
30
|
+
:schema="filterScheme"
|
|
31
|
+
view="inline"
|
|
32
|
+
@change="handleChange"
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<UniversalTable
|
|
37
|
+
:rows="singletons"
|
|
38
|
+
:columns="columns"
|
|
39
|
+
:query="searchQuery"
|
|
40
|
+
:filterFn="filterFn"
|
|
41
|
+
@edit="handleEditSingleton"
|
|
42
|
+
:onDelete="handleDeleteSingleton"
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<script setup lang="ts">
|
|
49
|
+
import { ref, onMounted, getCurrentInstance } from "vue";
|
|
50
|
+
import { useRouter } from "vue-router";
|
|
51
|
+
import { Plus, Search, Filter, HelpCircle } from "lucide-vue-next";
|
|
52
|
+
import UniversalTable from "../../components/ui/UniversalTable.vue";
|
|
53
|
+
import { useI18n } from "vue-i18n";
|
|
54
|
+
import VsFilter from '@opengis/filter';
|
|
55
|
+
import { notify } from '@opengis/core';
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
const { t, locale } = useI18n();
|
|
59
|
+
|
|
60
|
+
interface Singleton {
|
|
61
|
+
menu_id: string;
|
|
62
|
+
name: string;
|
|
63
|
+
description: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const router = useRouter();
|
|
67
|
+
const searchQuery = ref("");
|
|
68
|
+
const singletons = ref<any[]>([]);
|
|
69
|
+
const filterScheme = ref<any[]>([]);
|
|
70
|
+
const filterValue = ref<string>('');
|
|
71
|
+
const columns = ref([
|
|
72
|
+
{ name: "name", title: t("menu.form.name"), type: "text" },
|
|
73
|
+
{ name: "description", title: t("menu.form.description"), type: "text" },
|
|
74
|
+
{ name: "locale", title: t("menu.form.language"), type: "select", data: [{ text: "uk", id: "uk" }, { text: "en", id: "en" }] },
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
const fetchSingletons = async () => {
|
|
78
|
+
const res = await fetch("/api/cms-menu");
|
|
79
|
+
const data = await res.json();
|
|
80
|
+
const rows = data.rows;
|
|
81
|
+
singletons.value = rows;
|
|
82
|
+
|
|
83
|
+
// Clean filter schema to only include valid IFilterItem properties
|
|
84
|
+
const cleanFilters = (data.filters || []).map((filter: any) => {
|
|
85
|
+
const { extra, title, ...cleanFilter } = filter;
|
|
86
|
+
return cleanFilter;
|
|
87
|
+
});
|
|
88
|
+
filterScheme.value = cleanFilters;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const handleEditSingleton = (singleton: Singleton) => {
|
|
92
|
+
router.push(`/menu/${singleton?.menu_id}`);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const handleDeleteSingleton = async (singleton: Singleton) => {
|
|
96
|
+
try {
|
|
97
|
+
await fetch(`/api/cms-menu/${singleton?.menu_id}`, { method: 'DELETE' });
|
|
98
|
+
await notify({
|
|
99
|
+
title: t("menu.menuDeleted"),
|
|
100
|
+
type: "success",
|
|
101
|
+
message: t("menu.menuDeleted"),
|
|
102
|
+
});
|
|
103
|
+
await fetchSingletons();
|
|
104
|
+
} catch (error) {
|
|
105
|
+
notify({
|
|
106
|
+
title: t("menu.menuDeleteError"),
|
|
107
|
+
type: "error",
|
|
108
|
+
message: t("menu.menuDeleteError"),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const filterFn = (row: any, query: string) => {
|
|
114
|
+
if (!query) return true;
|
|
115
|
+
const q = query.toLowerCase();
|
|
116
|
+
return columns.value.some((col: any) => {
|
|
117
|
+
const val = row[col.name];
|
|
118
|
+
return val && val.toString().toLowerCase().includes(q);
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const handleChange = (filters: any) => {
|
|
123
|
+
const filterStr = Object.entries(filters?.data)
|
|
124
|
+
.filter(([, v]) => v != null)
|
|
125
|
+
.map(([key, val]) => `${key}=${val}`)
|
|
126
|
+
.join('|');
|
|
127
|
+
|
|
128
|
+
filterValue.value = filterStr;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
onMounted(fetchSingletons);
|
|
133
|
+
</script>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="w-full max-w-7xl mx-auto space-y-6">
|
|
3
|
+
<SettingsTitle :title="$t('settings.api-keys.apiKeys')" :description="$t('settings.api-keys.apiKeysDescription')" />
|
|
4
|
+
<div class="space-y-6 max-w-7xl mx-auto">
|
|
5
|
+
<SettingsTable :icon="Key" :title="$t('settings.api-keys.apiKeys')" :color="'purple'">
|
|
6
|
+
<template #content>
|
|
7
|
+
<div v-for="key in apiKeys" :key="key.name" class="flex items-center justify-between p-4 border border-slate-200 dark:border-slate-600 rounded-lg bg-slate-50 dark:bg-slate-700">
|
|
8
|
+
<div>
|
|
9
|
+
<h3 class="font-medium text-slate-800 dark:text-slate-100">{{ key.label }}</h3>
|
|
10
|
+
<p class="text-sm text-slate-500 dark:text-slate-400">{{ key.value }}</p>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="flex space-x-2">
|
|
13
|
+
<button class="inline-flex items-center justify-center whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border shadow-sm hover:text-accent-foreground h-8 rounded-md px-3 text-xs bg-white dark:bg-slate-700 border-slate-300 dark:border-slate-600 text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-600">
|
|
14
|
+
<Eye class="w-4 h-4" />
|
|
15
|
+
</button>
|
|
16
|
+
<button class="inline-flex items-center justify-center whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border shadow-sm hover:text-accent-foreground h-8 rounded-md px-3 text-xs bg-white dark:bg-slate-700 border-slate-300 dark:border-slate-600 text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-600">
|
|
17
|
+
<RefreshCcw class="w-4 h-4" />
|
|
18
|
+
</button>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<button class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none text-white focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 text-primary-foreground shadow h-9 px-4 py-2 mt-4 bg-purple-600 hover:bg-purple-700">
|
|
22
|
+
{{ $t('settings.api-keys.generateNewKey') }}
|
|
23
|
+
</button>
|
|
24
|
+
</template>
|
|
25
|
+
</SettingsTable>
|
|
26
|
+
<SettingsTable v-model="notificationSettings" :icon="Shield" :title="$t('settings.api-keys.apiSecurity')" :color="'purple'" />
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<script setup>
|
|
32
|
+
import { Eye, RefreshCcw, Shield, Key } from 'lucide-vue-next';
|
|
33
|
+
import { ref } from 'vue';
|
|
34
|
+
import SettingsTitle from '@/components/settings/SettingsTitle.vue';
|
|
35
|
+
import SettingsTable from '@/components/settings/SettingsTable.vue';
|
|
36
|
+
import { useI18n } from 'vue-i18n';
|
|
37
|
+
|
|
38
|
+
const { t } = useI18n();
|
|
39
|
+
|
|
40
|
+
const apiKeys = [
|
|
41
|
+
{
|
|
42
|
+
name: 'production',
|
|
43
|
+
label: t('settings.api-keys.productionApiKey'),
|
|
44
|
+
value: 'pk_live_••••••••••••••••',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'development',
|
|
48
|
+
label: t('settings.api-keys.developmentApiKey'),
|
|
49
|
+
value: 'pk_test_••••••••••••••••',
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const notificationSettings = ref([
|
|
54
|
+
{
|
|
55
|
+
key: 'rateLimiting',
|
|
56
|
+
label: t('settings.api-keys.rateLimiting'),
|
|
57
|
+
description: t('settings.api-keys.rateLimitingDescription'),
|
|
58
|
+
checked: true,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
key: 'corsProtection',
|
|
62
|
+
label: t('settings.api-keys.corsProtection'),
|
|
63
|
+
description: t('settings.api-keys.corsProtectionDescription'),
|
|
64
|
+
checked: true,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
key: 'apiLogging',
|
|
68
|
+
label: t('settings.api-keys.apiLogging'),
|
|
69
|
+
description: t('settings.api-keys.apiLoggingDescription'),
|
|
70
|
+
checked: false,
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
</script>
|
|
75
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="w-full max-w-7xl mx-auto space-y-6">
|
|
3
|
+
<SettingsTitle :title="t('settings.appearance.appearance')" :description="t('settings.appearance.appearanceDescription')" />
|
|
4
|
+
<SettingsTable v-model="appearanceSettings" :icon="Palette" :title="t('settings.appearance.appearance')" :color="'pink'">
|
|
5
|
+
<template #content>
|
|
6
|
+
<div>
|
|
7
|
+
<label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-slate-700 dark:text-slate-300">
|
|
8
|
+
{{ $t('settings.appearance.colorTheme') }}
|
|
9
|
+
</label>
|
|
10
|
+
<div class="grid grid-cols-3 gap-3 mt-2">
|
|
11
|
+
<button
|
|
12
|
+
v-for="theme in colorTheme"
|
|
13
|
+
:key="theme.name"
|
|
14
|
+
class="p-3 rounded-lg text-center"
|
|
15
|
+
:class="theme.name === color ? theme.color : 'border border-slate-200 dark:border-slate-600 bg-white dark:bg-slate-700 hover:border-slate-300 dark:hover:border-slate-500'"
|
|
16
|
+
@click="toggleColor(theme.name)"
|
|
17
|
+
>
|
|
18
|
+
<div class="w-full h-8 rounded mb-2" :class="theme.bg"></div>
|
|
19
|
+
<span class="text-sm font-medium text-slate-700 dark:text-slate-300">{{ theme.name }}</span>
|
|
20
|
+
</button>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
</SettingsTable>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup>
|
|
29
|
+
import { ref } from 'vue';
|
|
30
|
+
import SettingsTitle from '@/components/settings/SettingsTitle.vue';
|
|
31
|
+
import SettingsTable from '@/components/settings/SettingsTable.vue';
|
|
32
|
+
import { Palette } from 'lucide-vue-next';
|
|
33
|
+
import { useTheme } from '@/composables/useTheme';
|
|
34
|
+
import { useI18n } from 'vue-i18n';
|
|
35
|
+
|
|
36
|
+
const { t } = useI18n();
|
|
37
|
+
|
|
38
|
+
const { color, toggleColor } = useTheme();
|
|
39
|
+
|
|
40
|
+
const colorTheme = [
|
|
41
|
+
{
|
|
42
|
+
name: 'blue',
|
|
43
|
+
color: 'border-2 bg-blue-50 border-blue-500 dark:bg-blue-900/20',
|
|
44
|
+
bg: 'bg-blue-600',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'green',
|
|
48
|
+
color: 'border-2 bg-green-50 border-green-500 dark:bg-green-900/20',
|
|
49
|
+
bg: 'bg-green-600',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'purple',
|
|
53
|
+
color: 'border-2 bg-purple-50 border-purple-500 dark:bg-purple-900/20',
|
|
54
|
+
bg: 'bg-purple-600',
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const appearanceSettings = ref([
|
|
59
|
+
{
|
|
60
|
+
key: "darkMode",
|
|
61
|
+
label: t('settings.appearance.darkMode'),
|
|
62
|
+
description: t('settings.appearance.darkModeDescription'),
|
|
63
|
+
checked: true,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
key: "compactMode",
|
|
67
|
+
label: t('settings.appearance.compactMode'),
|
|
68
|
+
description: t('settings.appearance.compactModeDescription'),
|
|
69
|
+
checked: false,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
key: "animations",
|
|
73
|
+
label: t('settings.appearance.animations'),
|
|
74
|
+
description: t('settings.appearance.animationsDescription'),
|
|
75
|
+
checked: false,
|
|
76
|
+
},
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
</script>
|