Add base settings page #74

Merged
jakob.scheid merged 43 commits from feature/base-settings-page into main 2026-05-23 13:16:37 +02:00
17 changed files with 193 additions and 29 deletions
+2 -5
View File
@@ -35,9 +35,7 @@ watch(colorScheme, val => updateColorScheme(val))
> >
<Navbar /> <Navbar />
<div class="main-content"> <router-view class="main-content" />
<router-view />
</div>
<Footer /> <Footer />
</div> </div>
@@ -47,8 +45,7 @@ watch(colorScheme, val => updateColorScheme(val))
.main-content { .main-content {
--main-content-padding-x: 30px; --main-content-padding-x: 30px;
--main-content-padding-y: 40px; --main-content-padding-y: 40px;
padding: var(--main-content-padding-y) var(--main-content-padding-x); --main-content-padding: var(--main-content-padding-y) var(--main-content-padding-x) 0;
width: calc(100% - var(--main-content-padding-x) * 2);
flex-grow: 1; flex-grow: 1;
} }
+20 -4
View File
@@ -15,6 +15,10 @@ limitations under the License.
--> -->
<script setup> <script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const startYear = 2026 const startYear = 2026
const currentYear = new Date().getFullYear() const currentYear = new Date().getFullYear()
@@ -28,20 +32,32 @@ const copyrightPeriod =
<template> <template>
<footer class="global-footer"> <footer class="global-footer">
<div class="footer-segment">
<RouterLink to="settings">
{{ t('preferences.settings') }}
</RouterLink>
</div>
<div class="footer-segment">
<div class="copyright-note"> <div class="copyright-note">
&copy; {{ copyrightPeriod }} Seekra &copy; {{ copyrightPeriod }} Seekra
</div> </div>
</div>
</footer> </footer>
</template> </template>
<style scoped> <style scoped>
.global-footer { .global-footer {
--padding-y: 22px; --padding-y: 16px;
display: flex; }
justify-content: center;
.footer-segment {
padding: var(--padding-y); padding: var(--padding-y);
margin-top: var(--padding-y);
background-color: var(--light-bg); background-color: var(--light-bg);
border-top: 1px solid var(--border); border-top: 1px solid var(--border);
} }
.copyright-note {
display: flex;
justify-content: center;
}
</style> </style>
@@ -36,6 +36,7 @@ const submitSearch = function () {
</script> </script>
<template> <template>
<div>
<form @submit.prevent="submitSearch"> <form @submit.prevent="submitSearch">
<div class="search-wrapper"> <div class="search-wrapper">
<input <input
@@ -47,6 +48,7 @@ const submitSearch = function () {
<button type="submit" class="search-button">{{ t('search.searchBar.submit') }}</button> <button type="submit" class="search-button">{{ t('search.searchBar.submit') }}</button>
</div> </div>
</form> </form>
</div>
</template> </template>
<style scoped> <style scoped>
@@ -28,6 +28,7 @@ searchQueryModel.value = props.searchQuery;
</script> </script>
<template> <template>
<div class="main-content-padding">
<Searchbar class="search-bar" v-model="searchQueryModel" auto-submit /> <Searchbar class="search-bar" v-model="searchQueryModel" auto-submit />
<div class="search-results-error-message-container"> <div class="search-results-error-message-container">
@@ -38,6 +39,7 @@ searchQueryModel.value = props.searchQuery;
</div> </div>
<div class="search-results-container"> <div class="search-results-container">
</div> </div>
</div>
</template> </template>
<style scoped> <style scoped>
@@ -0,0 +1,56 @@
<!--
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script setup>
import LeftSidebarLayout from '@/layouts/LeftSidebarLayout.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
<template>
<div class="settings-page-wrapper">
<header class="header">
<h1>
{{ t('preferences.settings') }}
</h1>
</header>
<LeftSidebarLayout class="layout">
<template #sidebar>
</template>
</LeftSidebarLayout>
</div>
</template>
<style scoped>
.layout {
flex-grow: 1;
}
.settings-page-wrapper {
display: flex;
flex-direction: column;
}
.header {
padding: var(--main-content-padding-y) var(--main-content-padding-x);
}
.header h1 {
margin: 0;
}
</style>
@@ -0,0 +1,28 @@
<!--
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<template>
<nav class="sidebar">
<slot />
</nav>
</template>
<style scoped>
.sidebar {
border-right: 1px solid var(--border);
padding: 20px;
}
</style>
+42
View File
@@ -0,0 +1,42 @@
<!--
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script setup>
import Sidebar from '@/features/sidebar/components/Sidebar.vue';
</script>
<template>
<div class="layout-container">
<Sidebar>
<slot name="sidebar" />
</Sidebar>
<main class="main-content">
<slot />
</main>
</div>
</template>
<style scoped>
.layout-container {
display: grid;
grid-template-columns: min(24%, 280px) 1fr;
border-top: 1px solid var(--border);
}
.main-content {
padding: 20px;
}
</style>
+1
View File
@@ -18,6 +18,7 @@
} }
}, },
"preferences": { "preferences": {
"settings": "Einstellungen",
"colorScheme": { "colorScheme": {
"switch": { "switch": {
"light": "Zum hellen Modus wechseln", "light": "Zum hellen Modus wechseln",
+1
View File
@@ -18,6 +18,7 @@
} }
}, },
"preferences": { "preferences": {
"settings": "Settings",
"colorScheme": { "colorScheme": {
"switch": { "switch": {
"light": "Switch to light mode", "light": "Switch to light mode",
+1
View File
@@ -18,6 +18,7 @@
} }
}, },
"preferences": { "preferences": {
"settings": "Ajustes",
"colorScheme": { "colorScheme": {
"switch": { "switch": {
"light": "Cambiar al modo claro", "light": "Cambiar al modo claro",
+1
View File
@@ -18,6 +18,7 @@
} }
}, },
"preferences": { "preferences": {
"settings": "Paramètres",
"colorScheme": { "colorScheme": {
"switch": { "switch": {
"light": "Passer en mode clair", "light": "Passer en mode clair",
+1
View File
@@ -18,6 +18,7 @@
} }
}, },
"preferences": { "preferences": {
"settings": "Impostazioni",
"colorScheme": { "colorScheme": {
"switch": { "switch": {
"light": "Passa alla modalità chiara", "light": "Passa alla modalità chiara",
+1
View File
@@ -18,6 +18,7 @@
} }
}, },
"preferences": { "preferences": {
"settings": "Configurações",
"colorScheme": { "colorScheme": {
"switch": { "switch": {
"light": "Mudar para modo claro", "light": "Mudar para modo claro",
+10
View File
@@ -15,9 +15,11 @@ limitations under the License.
*/ */
import { createRouter, createWebHistory } from 'vue-router'; import { createRouter, createWebHistory } from 'vue-router';
import { i18n } from '@/i18n';
import SearchView from '../views/SearchView.vue'; import SearchView from '../views/SearchView.vue';
import SearchResultsView from '@/features/search/views/SearchResultsView.vue'; import SearchResultsView from '@/features/search/views/SearchResultsView.vue';
import SettingsView from '@/features/settings/views/SettingsView.vue';
import NotFound from '../views/NotFound.vue'; import NotFound from '../views/NotFound.vue';
const routes = [ const routes = [
@@ -37,6 +39,14 @@ const routes = [
title: (route) => route.query.q title: (route) => route.query.q
} }
}, },
{
path: '/settings',
name: 'settings',
component: SettingsView,
meta: {
title: () => i18n.global.t('preferences.settings')
}
},
{ {
path: '/:pathMatch(.*)*', path: '/:pathMatch(.*)*',
name: 'notFound', name: 'notFound',
+5
View File
@@ -30,3 +30,8 @@ body {
input { input {
color: var(--dark); color: var(--dark);
} }
.main-content-padding {
padding: var(--main-content-padding);
width: calc(100% - var(--main-content-padding-x) * 2);
}
+1 -1
View File
@@ -21,7 +21,7 @@ const { t } = useI18n();
</script> </script>
<template> <template>
<div class="not-found"> <div class="not-found main-content-padding">
<span class="error-message"> <span class="error-message">
{{ t('error.pageNotFound') }} {{ t('error.pageNotFound') }}
</span> </span>
+1 -1
View File
@@ -32,7 +32,7 @@ const submitSearch = function () {
</script> </script>
<template> <template>
<div class="search-content"> <div class="search-content main-content-padding">
<header class="global-header"> <header class="global-header">
<img :src="logo" alt="Seekra" class="header-logo" /> <img :src="logo" alt="Seekra" class="header-logo" />
<span class="slogan"> <span class="slogan">