Merge pull request 'Add search form submit #32' (#45) from feature/search-form-submit into main

Reviewed-on: #45
Reviewed-by: Jakob Gregory
This commit was merged in pull request #45.
This commit is contained in:
2026-05-14 11:29:18 +02:00
8 changed files with 208 additions and 29 deletions
+7
View File
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
+4 -5
View File
@@ -31,11 +31,10 @@ import Footer from './features/footer/components/Footer.vue';
<style scoped> <style scoped>
.main-content { .main-content {
display: flex; --main-content-padding-x: 30px;
flex-direction: column; --main-content-padding-y: 40px;
align-items: center; padding: var(--main-content-padding-y) var(--main-content-padding-x);
width: 100%; width: calc(100% - var(--main-content-padding-x) * 2);
gap: 60px;
flex-grow: 1; flex-grow: 1;
} }
</style> </style>
+3 -1
View File
@@ -16,7 +16,9 @@ limitations under the License.
<template> <template>
<nav class="global-nav"> <nav class="global-nav">
<span id="logo">Seekra</span> <RouterLink to="/" class="link button link">
<span id="logo">Seekra</span>
</RouterLink>
<ul class="right-links"> <ul class="right-links">
</ul> </ul>
</nav> </nav>
+31 -6
View File
@@ -14,12 +14,37 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<script setup>
const searchQuery = defineModel();
import { useRouter } from 'vue-router';
const router = useRouter()
const props = defineProps(['autoSubmit'])
const submitSearch = function () {
if (props.autoSubmit !== undefined) {
router.push({
name: 'searchResults',
query: { q: searchQuery.value }
});
};
}
</script>
<template> <template>
<div class="search-wrapper"> <form @submit.prevent="submitSearch">
<input type="search" name="search" placeholder="Search..." required /> <div class="search-wrapper">
<button type="submit" class="search-button">Search</button> <input
</div> v-model="searchQuery"
</template> type="search"
placeholder="Search..."
required
/>
<button type="submit" class="search-button">Search</button>
</div>
</form>
</template>
<style scoped> <style scoped>
.search-wrapper { .search-wrapper {
@@ -53,7 +78,7 @@ limitations under the License.
border: none; border: none;
padding: var(--submit-button-padding-y) 20px; padding: var(--submit-button-padding-y) 20px;
background: var(--primary-color); background: var(--primary-color);
color: white; color: var(--white);
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
} }
@@ -0,0 +1,86 @@
<script setup>
import Searchbar from '@/features/search/components/Searchbar.vue';
const props = defineProps(['searchQuery']);
const searchQueryModel = defineModel();
searchQueryModel.value = props.searchQuery;
</script>
<template>
<Searchbar class="search-bar" v-model="searchQueryModel" auto-submit />
<div class="search-results-error-message-container">
<div class="search-results-error-message">
<p>Search is not available right now.</p>
<p>Please try again to another time.</p>
</div>
</div>
<div class="search-results-container">
</div>
</template>
<style scoped>
.search-bar {
width: 50%;
}
@media (max-width: 67.5rem) {
.search-bar {
width: 100%;
}
}
.search-results-container {
margin-top: 56px;
}
.search-results-error-message-container {
--error-message-height: 100px;
--error-message-padding: 2em;
display: flex;
justify-content: center;
width: calc(100% - 2 * var(--main-content-padding-x));
position: absolute;
top: calc(50vh - 0.5 * var(--error-message-height) - var(--error-message-padding));
}
.search-results-error-message {
padding: var(--error-message-padding);
font-size: 18px;
box-shadow: 0px 0px 42px #cdcdcdb3;
border-radius: 28px;
text-align: center;
background-color: #f7f7f7;
animation: fade-in 0.8s ease-out;
height: var(--error-message-height);
display: flex;
justify-content: center;
flex-direction: column;
outline: 1px solid var(--light-d-2);
}
@media (max-width: 48rem) {
.search-results-error-message-container {
--error-message-height: 150px;
}
.search-results-error-message {
padding: 1em;
width: 100%;
}
}
@keyframes fade-in {
0% {
opacity: 0;
}
10% {
opacity: 0;
transform: scale(1.02);
}
100% {
opacity: 100;
transform: scale(1);
}
}
</style>
+32 -6
View File
@@ -14,21 +14,47 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router';
import SearchView from '../views/SearchView.vue' import SearchView from '../views/SearchView.vue';
import SearchResultsView from '@/features/search/views/SearchResultsView.vue';
const routes = [ const routes = [
{ {
path: '/', path: '/',
name: 'search', name: 'startPage',
component: SearchView component: SearchView
},
{
path: '/search',
name: 'searchResults',
component: SearchResultsView,
props: route => ({
searchQuery: route.query.q
}),
meta: {
title: (route) => route.query.q
}
} }
] ];
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(),
routes routes
}) });
export default router // set page title
router.afterEach(to => {
const title =
typeof to.meta.title === 'function'
? to.meta.title(to)
: to.meta.title;
if (title) {
document.title = `${title} - Seekra`;
} else {
document.title = 'Seekra';
};
});
export default router;
+8
View File
@@ -23,4 +23,12 @@ body {
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
}
.link {
text-decoration: none;
}
.link:hover:not(.button-link) {
text-decoration: underline;
} }
+37 -11
View File
@@ -15,23 +15,43 @@ limitations under the License.
--> -->
<script setup> <script setup>
import Searchbar from '../features/search/components/Searchbar.vue' import Searchbar from '../features/search/components/Searchbar.vue';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter();
const searchQuery = ref('');
const submitSearch = function () {
};
</script> </script>
<template> <template>
<header class="global-header"> <div class="search-content">
<h1>Seekra</h1> <header class="global-header">
<span class="slogan"> <h1>Seekra</h1>
Built to search. <span class="slogan">
</span> Built to search.
</header> </span>
</header>
<form id="search-form"> <div class="search-container">
<Searchbar /> <Searchbar v-model="searchQuery" ref="searchbar" class="search-bar" auto-submit />
</form> </div>
</div>
</template> </template>
<style scoped> <style scoped>
.search-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 60px;
}
.global-header { .global-header {
text-align: center; text-align: center;
display: flex; display: flex;
@@ -61,8 +81,14 @@ import Searchbar from '../features/search/components/Searchbar.vue'
font-size: small; font-size: small;
} }
#search-form { .search-container {
width: 70%; width: 70%;
max-width: 624px; max-width: 624px;
} }
@media (max-width: 67.5rem) {
.search-container {
width: 100%;
}
}
</style> </style>