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 />
<div class="main-content">
<router-view />
</div>
<router-view class="main-content" />
<Footer />
</div>
@@ -47,8 +45,7 @@ watch(colorScheme, val => updateColorScheme(val))
.main-content {
--main-content-padding-x: 30px;
--main-content-padding-y: 40px;
padding: var(--main-content-padding-y) var(--main-content-padding-x);
width: calc(100% - var(--main-content-padding-x) * 2);
--main-content-padding: var(--main-content-padding-y) var(--main-content-padding-x) 0;
flex-grow: 1;
}
+22 -6
View File
@@ -15,6 +15,10 @@ limitations under the License.
-->
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const startYear = 2026
const currentYear = new Date().getFullYear()
@@ -28,20 +32,32 @@ const copyrightPeriod =
<template>
<footer class="global-footer">
<div class="copyright-note">
&copy; {{ copyrightPeriod }} Seekra
<div class="footer-segment">
<RouterLink to="settings">
{{ t('preferences.settings') }}
</RouterLink>
</div>
<div class="footer-segment">
<div class="copyright-note">
&copy; {{ copyrightPeriod }} Seekra
</div>
</div>
</footer>
</template>
<style scoped>
.global-footer {
--padding-y: 22px;
display: flex;
justify-content: center;
--padding-y: 16px;
}
.footer-segment {
padding: var(--padding-y);
margin-top: var(--padding-y);
background-color: var(--light-bg);
border-top: 1px solid var(--border);
}
.copyright-note {
display: flex;
justify-content: center;
}
</style>
+10 -8
View File
@@ -36,18 +36,20 @@ const submitSearch = function () {
</script>
<template>
<form @submit.prevent="submitSearch">
<div class="search-wrapper">
<input
<div>
<form @submit.prevent="submitSearch">
<div class="search-wrapper">
<input
v-model="searchQuery"
type="search"
:placeholder="t('search.searchBar.placeholder')"
required
/>
<button type="submit" class="search-button">{{ t('search.searchBar.submit') }}</button>
</div>
</form>
</template>
/>
<button type="submit" class="search-button">{{ t('search.searchBar.submit') }}</button>
</div>
</form>
</div>
</template>
<style scoped>
.search-wrapper {
@@ -28,15 +28,17 @@ searchQueryModel.value = props.searchQuery;
</script>
<template>
<Searchbar class="search-bar" v-model="searchQueryModel" auto-submit />
<div class="main-content-padding">
<Searchbar class="search-bar" v-model="searchQueryModel" auto-submit />
<div class="search-results-error-message-container">
<div class="search-results-error-message">
<p>{{ t('search.error.searchNotAvailable') }}</p>
<p>{{ t('error.tryAgainToAnotherTime') }}</p>
<div class="search-results-error-message-container">
<div class="search-results-error-message">
<p>{{ t('search.error.searchNotAvailable') }}</p>
<p>{{ t('error.tryAgainToAnotherTime') }}</p>
</div>
</div>
<div class="search-results-container">
</div>
</div>
<div class="search-results-container">
</div>
</template>
@@ -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": {
"settings": "Einstellungen",
"colorScheme": {
"switch": {
"light": "Zum hellen Modus wechseln",
+1
View File
@@ -18,6 +18,7 @@
}
},
"preferences": {
"settings": "Settings",
"colorScheme": {
"switch": {
"light": "Switch to light mode",
+1
View File
@@ -18,6 +18,7 @@
}
},
"preferences": {
"settings": "Ajustes",
"colorScheme": {
"switch": {
"light": "Cambiar al modo claro",
+1
View File
@@ -18,6 +18,7 @@
}
},
"preferences": {
"settings": "Paramètres",
"colorScheme": {
"switch": {
"light": "Passer en mode clair",
+1
View File
@@ -18,6 +18,7 @@
}
},
"preferences": {
"settings": "Impostazioni",
"colorScheme": {
"switch": {
"light": "Passa alla modalità chiara",
+1
View File
@@ -18,6 +18,7 @@
}
},
"preferences": {
"settings": "Configurações",
"colorScheme": {
"switch": {
"light": "Mudar para modo claro",
+10
View File
@@ -15,9 +15,11 @@ limitations under the License.
*/
import { createRouter, createWebHistory } from 'vue-router';
import { i18n } from '@/i18n';
import SearchView from '../views/SearchView.vue';
import SearchResultsView from '@/features/search/views/SearchResultsView.vue';
import SettingsView from '@/features/settings/views/SettingsView.vue';
import NotFound from '../views/NotFound.vue';
const routes = [
@@ -37,6 +39,14 @@ const routes = [
title: (route) => route.query.q
}
},
{
path: '/settings',
name: 'settings',
component: SettingsView,
meta: {
title: () => i18n.global.t('preferences.settings')
}
},
{
path: '/:pathMatch(.*)*',
name: 'notFound',
+5
View File
@@ -30,3 +30,8 @@ body {
input {
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>
<template>
<div class="not-found">
<div class="not-found main-content-padding">
<span class="error-message">
{{ t('error.pageNotFound') }}
</span>
+1 -1
View File
@@ -32,7 +32,7 @@ const submitSearch = function () {
</script>
<template>
<div class="search-content">
<div class="search-content main-content-padding">
<header class="global-header">
<img :src="logo" alt="Seekra" class="header-logo" />
<span class="slogan">