Add inline admin create forms to home page components
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 28s

Lazy-load create forms (Post, Activity, Favorite, Rowing) directly
into their corresponding home components with an admin-only toggle
button, replacing the need to navigate to the admin page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-13 12:13:13 +01:00
parent 66f32cdbd2
commit a911e6ca69
8 changed files with 78 additions and 10 deletions

View File

@@ -4,6 +4,8 @@ import Button from "@/components/input/Button.vue";
import { ref } from "vue"; import { ref } from "vue";
import { gql } from "@/graphql"; import { gql } from "@/graphql";
const emit = defineEmits(["done", "cancel"]);
const type = ref(""); const type = ref("");
const name = ref(""); const name = ref("");
const link = ref(""); const link = ref("");
@@ -18,6 +20,7 @@ async function post() {
name.value = ""; name.value = "";
link.value = ""; link.value = "";
console.log(data.createActivity); console.log(data.createActivity);
emit("done");
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }
@@ -31,5 +34,6 @@ async function post() {
<input type="text" v-model="name" placeholder="Name" @keyup.enter="post" /> <input type="text" v-model="name" placeholder="Name" @keyup.enter="post" />
<input type="text" v-model="link" placeholder="Link" @keyup.enter="post" /> <input type="text" v-model="link" placeholder="Link" @keyup.enter="post" />
<Button @click="post">Upload</Button> <Button @click="post">Upload</Button>
<Button @click="emit('cancel')">Cancel</Button>
</div> </div>
</template> </template>

View File

@@ -4,6 +4,8 @@ import Button from "@/components/input/Button.vue";
import { ref } from "vue"; import { ref } from "vue";
import { gql } from "@/graphql"; import { gql } from "@/graphql";
const emit = defineEmits(["done", "cancel"]);
const type = ref(""); const type = ref("");
const name = ref(""); const name = ref("");
const link = ref(""); const link = ref("");
@@ -18,6 +20,7 @@ async function post() {
name.value = ""; name.value = "";
link.value = ""; link.value = "";
console.log(data.createFavorite); console.log(data.createFavorite);
emit("done");
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }
@@ -31,5 +34,6 @@ async function post() {
<input type="text" v-model="name" placeholder="Name" @keyup.enter="post" /> <input type="text" v-model="name" placeholder="Name" @keyup.enter="post" />
<input type="text" v-model="link" placeholder="Link" @keyup.enter="post" /> <input type="text" v-model="link" placeholder="Link" @keyup.enter="post" />
<Button @click="post">Upload</Button> <Button @click="post">Upload</Button>
<Button @click="emit('cancel')">Cancel</Button>
</div> </div>
</template> </template>

View File

@@ -3,6 +3,8 @@ import Button from "@/components/input/Button.vue";
import { ref } from "vue"; import { ref } from "vue";
import { gql } from "@/graphql"; import { gql } from "@/graphql";
const emit = defineEmits(["done", "cancel"]);
const title = ref(""); const title = ref("");
const content = ref(""); const content = ref("");
@@ -15,6 +17,7 @@ async function post() {
title.value = ""; title.value = "";
content.value = ""; content.value = "";
console.log(data.createPost); console.log(data.createPost);
emit("done");
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }
@@ -31,6 +34,6 @@ async function post() {
placeholder="Content" placeholder="Content"
></textarea> ></textarea>
<Button @click="post">Upload</Button> <Button @click="post">Upload</Button>
<!-- make textarea take up most the space --> <Button @click="emit('cancel')">Cancel</Button>
</div> </div>
</template> </template>

View File

@@ -3,6 +3,8 @@ import Button from "@/components/input/Button.vue";
import { ref } from "vue"; import { ref } from "vue";
import axios from "axios"; import axios from "axios";
const emit = defineEmits(["done", "cancel"]);
const images = ref([]); const images = ref([]);
const results = ref([]); const results = ref([]);
@@ -35,6 +37,7 @@ async function submit() {
); );
images.value = []; images.value = [];
emit("done");
} }
</script> </script>
@@ -43,6 +46,7 @@ async function submit() {
<h1>Create Rowing</h1> <h1>Create Rowing</h1>
<input type="file" accept="image/jpeg,image/png,image/gif,image/webp" multiple @change="onFileChange" /> <input type="file" accept="image/jpeg,image/png,image/gif,image/webp" multiple @change="onFileChange" />
<Button @click="submit">Upload</Button> <Button @click="submit">Upload</Button>
<Button @click="emit('cancel')">Cancel</Button>
<div v-for="r in results" :key="r.name"> <div v-for="r in results" :key="r.name">
<span class="text-primary">{{ r.name }}: </span> <span class="text-primary">{{ r.name }}: </span>
<span :class="r.ok ? 'text-secondary' : 'text-red-500'">{{ r.status }}</span> <span :class="r.ok ? 'text-secondary' : 'text-red-500'">{{ r.status }}</span>

View File

@@ -3,15 +3,29 @@ import AutoScroll from "@/components/util/AutoScroll.vue";
import LinkTable from "@/components/util/LinkTable.vue"; import LinkTable from "@/components/util/LinkTable.vue";
import Header from "@/components/text/Header.vue"; import Header from "@/components/text/Header.vue";
import { ref, defineAsyncComponent } from "vue";
import { useActivityStore } from "@/stores/activity"; import { useActivityStore } from "@/stores/activity";
import { useAuthStore } from "@/stores/auth";
const CreateActivity = defineAsyncComponent(() => import("@/views/admin/CreateActivity.vue"));
const activityStore = useActivityStore(); const activityStore = useActivityStore();
const authStore = useAuthStore();
const showCreate = ref(false);
</script> </script>
<template> <template>
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
<Header>Consumption</Header> <Header>
<AutoScroll class="flex-1 w-full"> <span class="flex items-center justify-between w-full">
{{ showCreate ? "Create Activity" : "Consumption" }}
<button v-if="authStore.user.admin" class="text-sm px-1" @click="showCreate = !showCreate">
{{ showCreate ? "x" : "+" }}
</button>
</span>
</Header>
<CreateActivity v-if="showCreate" class="flex-1 w-full p-1" @done="showCreate = false" @cancel="showCreate = false" />
<AutoScroll v-if="!showCreate" class="flex-1 w-full">
<LinkTable variant="table" class="w-full" :items="activityStore.activity" /> <LinkTable variant="table" class="w-full" :items="activityStore.activity" />
</AutoScroll> </AutoScroll>
</div> </div>

View File

@@ -3,15 +3,29 @@ import Header from "@/components/text/Header.vue";
import LinkTable from "@/components/util/LinkTable.vue"; import LinkTable from "@/components/util/LinkTable.vue";
import AutoScroll from "@/components/util/AutoScroll.vue"; import AutoScroll from "@/components/util/AutoScroll.vue";
import { ref, defineAsyncComponent } from "vue";
import { useFavoritesStore } from "@/stores/favorites"; import { useFavoritesStore } from "@/stores/favorites";
import { useAuthStore } from "@/stores/auth";
const CreateFavorite = defineAsyncComponent(() => import("@/views/admin/CreateFavorite.vue"));
const favoritesStore = useFavoritesStore(); const favoritesStore = useFavoritesStore();
const authStore = useAuthStore();
const showCreate = ref(false);
</script> </script>
<template> <template>
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
<Header>favs</Header> <Header>
<AutoScroll class="w-full flex-1"> <span class="flex items-center justify-between w-full">
{{ showCreate ? "Create Favorite" : "favs" }}
<button v-if="authStore.user.admin" class="text-sm px-1" @click="showCreate = !showCreate">
{{ showCreate ? "x" : "+" }}
</button>
</span>
</Header>
<CreateFavorite v-if="showCreate" class="w-full flex-1 p-1" @done="showCreate = false" @cancel="showCreate = false" />
<AutoScroll v-if="!showCreate" class="w-full flex-1">
<LinkTable <LinkTable
variant="table" variant="table"
class="w-full" class="w-full"

View File

@@ -3,14 +3,17 @@ import Button from "@/components/input/Button.vue";
import Markdown from "@/components/util/Markdown.vue"; import Markdown from "@/components/util/Markdown.vue";
import Header from "@/components/text/Header.vue"; import Header from "@/components/text/Header.vue";
import { ref, computed, onBeforeMount } from "vue"; import { ref, computed, defineAsyncComponent } from "vue";
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
import { usePostsStore } from "@/stores/posts"; import { usePostsStore } from "@/stores/posts";
const CreatePost = defineAsyncComponent(() => import("@/views/admin/CreatePost.vue"));
const authStore = useAuthStore(); const authStore = useAuthStore();
const postsStore = usePostsStore(); const postsStore = usePostsStore();
const idx = ref(0); const idx = ref(0);
const showCreate = ref(false);
const leftCap = computed(() => idx.value === 0); const leftCap = computed(() => idx.value === 0);
const rightCap = computed(() => idx.value === postsStore.postsCount - 1); const rightCap = computed(() => idx.value === postsStore.postsCount - 1);
@@ -39,8 +42,17 @@ function deletePost() {
<template> <template>
<div class="flex flex-col flex-1 min-h-0"> <div class="flex flex-col flex-1 min-h-0">
<Header>{{ post.title }}</Header> <Header>
<span class="flex items-center justify-between w-full">
{{ showCreate ? "Create Post" : post.title }}
<button v-if="authStore.user.admin" class="text-sm px-1" @click="showCreate = !showCreate">
{{ showCreate ? "x" : "+" }}
</button>
</span>
</Header>
<CreatePost v-if="showCreate" class="flex-1 min-h-0 p-1" @done="showCreate = false" @cancel="showCreate = false" />
<div <div
v-if="!showCreate"
class="flex flex-col flex-1 min-h-0 p-1 overflow-auto text-left items-start justify-start" class="flex flex-col flex-1 min-h-0 p-1 overflow-auto text-left items-start justify-start"
> >
<small <small

View File

@@ -1,11 +1,16 @@
<script setup> <script setup>
import { ref, computed } from "vue"; import { ref, computed, defineAsyncComponent } from "vue";
import Header from "@/components/text/Header.vue"; import Header from "@/components/text/Header.vue";
import { useHomeDataStore } from "@/stores/homeData"; import { useHomeDataStore } from "@/stores/homeData";
import { useAuthStore } from "@/stores/auth";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
const CreateRowing = defineAsyncComponent(() => import("@/views/admin/CreateRowing.vue"));
const store = useHomeDataStore(); const store = useHomeDataStore();
const authStore = useAuthStore();
const { loaded, error, rowingSessions } = storeToRefs(store); const { loaded, error, rowingSessions } = storeToRefs(store);
const showCreate = ref(false);
const rows = computed(() => rowingSessions.value.slice().reverse()); const rows = computed(() => rowingSessions.value.slice().reverse());
const loading = computed(() => !loaded.value); const loading = computed(() => !loaded.value);
@@ -109,9 +114,17 @@ function formatValue(key, val) {
<template> <template>
<div class="flex flex-col h-full overflow-hidden"> <div class="flex flex-col h-full overflow-hidden">
<Header>Rowing</Header> <Header>
<span class="flex items-center justify-between w-full">
{{ showCreate ? "Upload Rowing" : "Rowing" }}
<button v-if="authStore.user.admin" class="text-sm px-1" @click="showCreate = !showCreate">
{{ showCreate ? "x" : "+" }}
</button>
</span>
</Header>
<div v-if="loading" class="flex-1 flex items-center justify-center"> <CreateRowing v-if="showCreate" class="flex-1 p-1" @done="showCreate = false" @cancel="showCreate = false" />
<div v-else-if="loading" class="flex-1 flex items-center justify-center">
<p>Loading...</p> <p>Loading...</p>
</div> </div>
<div v-else-if="error" class="flex-1 flex items-center justify-center"> <div v-else-if="error" class="flex-1 flex items-center justify-center">