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>
import { RouterView } from "vue-router";
import Navbar from "@/components/Navbar.vue";
import Footer from "@/components/Footer.vue";
</script>
<template>
<div class="app-layout halftone">
<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>
<RouterView />
</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 DefaultLayout from "@/layouts/DefaultLayout.vue";
import CVLayout from "@/layouts/CVLayout.vue";
import Landing from "@/views/Landing.vue";
const router = createRouter({
@@ -6,63 +8,80 @@ const router = createRouter({
routes: [
{
path: "/",
name: "landing",
component: Landing,
},
{
path: "/stp",
name: "home",
component: () => import("@/views/home/Home.vue"),
component: DefaultLayout,
children: [
{
path: "",
name: "landing",
component: Landing,
},
{
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",
name: "cv",
component: () => import("../views/CV/CV.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"),
component: CVLayout,
children: [
{
path: "",
name: "cv",
component: () => import("@/views/CV/CV.vue"),
},
{
path: "jobs",
name: "job-applications",
component: () => import("@/views/CV/JobApplications.vue"),
},
],
},
],
});

View File

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

View File

@@ -1,6 +1,6 @@
<script setup>
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
import { useRouter, RouterLink } from "vue-router";
import { useAuthStore } from "@/stores/auth";
import { gql } from "@/graphql";
@@ -158,9 +158,12 @@ onMounted(() => {
</script>
<template>
<div class="no-print ja-root">
<div class="ja-root">
<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>
</div>
@@ -259,6 +262,22 @@ onMounted(() => {
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 {
font-size: 1.1rem;
font-weight: 600;