Move job applications to /cv/jobs route and add layout system
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m41s

- Add DefaultLayout and CVLayout with nested routing
- Job applications is now a standalone page at /cv/jobs with a back link
- Remove JobApplications embed from CV.vue

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-13 10:09:22 +01:00
parent a0f99d9fba
commit 0dc1c278c2
6 changed files with 145 additions and 85 deletions

View File

@@ -1,32 +1,7 @@
<script setup> <script setup>
import { RouterView } from "vue-router"; import { RouterView } from "vue-router";
import Navbar from "@/components/Navbar.vue";
import Footer from "@/components/Footer.vue";
</script> </script>
<template> <template>
<div class="app-layout halftone"> <RouterView />
<Navbar class="no-print sticky top-0 z-50" />
<main class="app-content">
<RouterView v-slot="{ Component }">
<Transition name="slide" mode="out-in">
<component :is="Component" :key="$route.path" />
</Transition>
</RouterView>
</main>
<Footer class="no-print sticky bottom-0 z-50" />
</div>
</template> </template>
<style scoped>
.app-layout {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.app-content {
flex: 1;
overflow-y: auto;
}
</style>

View File

@@ -0,0 +1,17 @@
<script setup>
import { RouterView } from "vue-router";
</script>
<template>
<div class="cv-layout">
<RouterView />
</div>
</template>
<style scoped>
.cv-layout {
min-height: 100vh;
background: white;
color: #111;
}
</style>

View File

@@ -0,0 +1,32 @@
<script setup>
import { RouterView } from "vue-router";
import Navbar from "@/components/Navbar.vue";
import Footer from "@/components/Footer.vue";
</script>
<template>
<div class="default-layout halftone">
<Navbar class="no-print sticky top-0 z-50" />
<main class="default-content">
<RouterView v-slot="{ Component }">
<Transition name="slide" mode="out-in">
<component :is="Component" :key="$route.path" />
</Transition>
</RouterView>
</main>
<Footer class="no-print sticky bottom-0 z-50" />
</div>
</template>
<style scoped>
.default-layout {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.default-content {
flex: 1;
overflow-y: auto;
}
</style>

View File

@@ -1,4 +1,6 @@
import { createRouter, createWebHistory } from "vue-router"; import { createRouter, createWebHistory } from "vue-router";
import DefaultLayout from "@/layouts/DefaultLayout.vue";
import CVLayout from "@/layouts/CVLayout.vue";
import Landing from "@/views/Landing.vue"; import Landing from "@/views/Landing.vue";
const router = createRouter({ const router = createRouter({
@@ -6,63 +8,80 @@ const router = createRouter({
routes: [ routes: [
{ {
path: "/", path: "/",
name: "landing", component: DefaultLayout,
component: Landing, children: [
}, {
{ path: "",
path: "/stp", name: "landing",
name: "home", component: Landing,
component: () => import("@/views/home/Home.vue"), },
{
path: "stp",
name: "home",
component: () => import("@/views/home/Home.vue"),
},
{
path: "admin",
name: "admin",
component: () => import("@/views/admin/Admin.vue"),
},
{
path: "bookmarks",
name: "bookmarks",
component: () => import("@/views/Bookmarks.vue"),
},
{
path: "notes/:path(.*)*",
name: "notes",
component: () => import("@/views/Notes.vue"),
},
{
path: "shrines",
name: "shrine links",
component: () => import("@/views/Shrines.vue"),
},
{
path: "shrines/gto",
name: "gto shrine",
component: () => import("@/views/shrines/GTO.vue"),
},
{
path: "shrines/skipskipbenben",
name: "skipskipbenben shrine",
component: () => import("@/views/shrines/Skipskipbenben.vue"),
},
{
path: "shrines/evangelion",
name: "evangelion shrine",
component: () => import("@/views/shrines/Evangelion.vue"),
},
{
path: "shrines/demoman",
name: "demoman shrine",
component: () => import("@/views/shrines/Demoman.vue"),
},
{
path: ":pathMatch(.*)*",
name: "404",
component: () => import("@/views/404.vue"),
},
],
}, },
{ {
path: "/cv", path: "/cv",
name: "cv", component: CVLayout,
component: () => import("../views/CV/CV.vue"), children: [
}, {
{ path: "",
path: "/admin", name: "cv",
name: "admin", component: () => import("@/views/CV/CV.vue"),
component: () => import("../views/admin/Admin.vue"), },
}, {
{ path: "jobs",
path: "/bookmarks", name: "job-applications",
name: "bookmarks", component: () => import("@/views/CV/JobApplications.vue"),
component: () => import("../views/Bookmarks.vue"), },
}, ],
{
path: "/notes/:path(.*)*",
name: "notes",
component: () => import("../views/Notes.vue"),
},
{
path: "/shrines",
name: "shrine links",
component: () => import("../views/Shrines.vue"),
},
{
path: "/shrines/gto",
name: "gto shrine",
component: () => import("../views/shrines/GTO.vue"),
},
{
path: "/shrines/skipskipbenben",
name: "skipskipbenben shrine",
component: () => import("../views/shrines/Skipskipbenben.vue"),
},
{
path: "/shrines/evangelion",
name: "evangelion shrine",
component: () => import("../views/shrines/Evangelion.vue"),
},
{
path: "/shrines/demoman",
name: "demoman shrine",
component: () => import("../views/shrines/Demoman.vue"),
},
{
path: "/:pathMatch(.*)*",
name: "404",
component: () => import("../views/404.vue"),
}, },
], ],
}); });

View File

@@ -4,7 +4,6 @@ import CVGeneral from "./CVGeneral.vue";
import CVBackend from "./CVBackend.vue"; import CVBackend from "./CVBackend.vue";
import CVFrontend from "./CVFrontend.vue"; import CVFrontend from "./CVFrontend.vue";
import CVTemp from "./CVTemp.vue"; import CVTemp from "./CVTemp.vue";
import JobApplications from "./JobApplications.vue";
const CVHospitality = defineAsyncComponent(() => import("./CVHospitality.vue")); const CVHospitality = defineAsyncComponent(() => import("./CVHospitality.vue"));
@@ -39,7 +38,6 @@ function print() {
<Transition name="cv-fade" mode="out-in"> <Transition name="cv-fade" mode="out-in">
<component :is="currentComponent" :key="selected" /> <component :is="currentComponent" :key="selected" />
</Transition> </Transition>
<JobApplications />
</div> </div>
</template> </template>

View File

@@ -1,6 +1,6 @@
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import { useRouter } from "vue-router"; import { useRouter, RouterLink } from "vue-router";
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
import { gql } from "@/graphql"; import { gql } from "@/graphql";
@@ -158,9 +158,12 @@ onMounted(() => {
</script> </script>
<template> <template>
<div class="no-print ja-root"> <div class="ja-root">
<div class="ja-header"> <div class="ja-header">
<h2 class="ja-heading">Job Applications</h2> <div class="ja-header-left">
<RouterLink to="/cv" class="ja-back"> CV</RouterLink>
<h2 class="ja-heading">Job Applications</h2>
</div>
<button class="ja-btn" @click="exportCsv" :disabled="!applications.length">Export CSV</button> <button class="ja-btn" @click="exportCsv" :disabled="!applications.length">Export CSV</button>
</div> </div>
@@ -259,6 +262,22 @@ onMounted(() => {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.ja-header-left {
display: flex;
align-items: center;
gap: 1rem;
}
.ja-back {
font-size: 0.85rem;
color: #555;
text-decoration: none;
}
.ja-back:hover {
color: #111;
}
.ja-heading { .ja-heading {
font-size: 1.1rem; font-size: 1.1rem;
font-weight: 600; font-weight: 600;