initial release
This commit is contained in:
parent
e156de22c3
commit
b3ac11a85e
37 changed files with 3649 additions and 669 deletions
110
frontend/src/composables/usePages.ts
Normal file
110
frontend/src/composables/usePages.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
import { ref } from "vue";
|
||||
import type { Page } from "@/types/segment";
|
||||
import { useAdmin } from "@/composables/useAdmin";
|
||||
|
||||
const pages = ref<Page[]>([]);
|
||||
const loading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
export function usePages() {
|
||||
const { authHeaders } = useAdmin();
|
||||
|
||||
async function fetchPages(): Promise<void> {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const res = await fetch("/api/pages/");
|
||||
if (!res.ok) throw new Error(`Failed to fetch pages: ${res.status}`);
|
||||
pages.value = (await res.json()) as Page[];
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : "Unknown error";
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function createPage(name: string, slug: string): Promise<Page | null> {
|
||||
try {
|
||||
const res = await fetch("/api/pages/", {
|
||||
method: "POST",
|
||||
headers: { ...authHeaders.value, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name, slug }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const data = (await res.json()) as { detail?: string };
|
||||
throw new Error(data.detail ?? `Create failed: ${res.status}`);
|
||||
}
|
||||
const page = (await res.json()) as Page;
|
||||
await fetchPages();
|
||||
return page;
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : "Unknown error";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function updatePage(id: string, data: { name?: string; slug?: string }): Promise<Page | null> {
|
||||
try {
|
||||
const res = await fetch(`/api/pages/${id}`, {
|
||||
method: "PATCH",
|
||||
headers: { ...authHeaders.value, "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const body = (await res.json()) as { detail?: string };
|
||||
throw new Error(body.detail ?? `Update failed: ${res.status}`);
|
||||
}
|
||||
const page = (await res.json()) as Page;
|
||||
await fetchPages();
|
||||
return page;
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : "Unknown error";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function deletePage(id: string): Promise<{ ok: boolean; error?: string }> {
|
||||
try {
|
||||
const res = await fetch(`/api/pages/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: authHeaders.value,
|
||||
});
|
||||
if (!res.ok) {
|
||||
const body = (await res.json()) as { detail?: string };
|
||||
return { ok: false, error: body.detail ?? `Delete failed: ${res.status}` };
|
||||
}
|
||||
await fetchPages();
|
||||
return { ok: true };
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : "Unknown error";
|
||||
return { ok: false, error: msg };
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleHidden(id: string, hidden: boolean): Promise<void> {
|
||||
try {
|
||||
await fetch(`/api/pages/${id}`, {
|
||||
method: "PATCH",
|
||||
headers: { ...authHeaders.value, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ is_hidden: hidden }),
|
||||
});
|
||||
await fetchPages();
|
||||
} catch { /* Silently fail */ }
|
||||
}
|
||||
|
||||
async function reorderPages(ids: string[]): Promise<void> {
|
||||
try {
|
||||
const res = await fetch("/api/pages/reorder", {
|
||||
method: "PUT",
|
||||
headers: { ...authHeaders.value, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ page_ids: ids }),
|
||||
});
|
||||
if (!res.ok) throw new Error(`Reorder failed: ${res.status}`);
|
||||
pages.value = (await res.json()) as Page[];
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
return { pages, loading, error, fetchPages, createPage, updatePage, deletePage, reorderPages, toggleHidden };
|
||||
}
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
import { ref, onMounted } from "vue";
|
||||
import type { Segment, SiteInfo } from "@/types/segment";
|
||||
import { ref } from "vue";
|
||||
import type { Segment } from "@/types/segment";
|
||||
|
||||
export function useSegments() {
|
||||
export function useSegments(pageId: string) {
|
||||
const segments = ref<Segment[]>([]);
|
||||
const siteTitle = ref("Handin");
|
||||
const loading = ref(true);
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
|
|
@ -11,18 +10,10 @@ export function useSegments() {
|
|||
loading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const [siteRes, segmentsRes] = await Promise.all([
|
||||
fetch("/api/site/"),
|
||||
fetch("/api/segments/"),
|
||||
]);
|
||||
if (!siteRes.ok) throw new Error(`Site fetch failed: ${siteRes.status}`);
|
||||
if (!segmentsRes.ok) throw new Error(`Segments fetch failed: ${segmentsRes.status}`);
|
||||
|
||||
const siteData = (await siteRes.json()) as SiteInfo;
|
||||
const segmentsData = (await segmentsRes.json()) as Segment[];
|
||||
|
||||
siteTitle.value = siteData.title;
|
||||
segments.value = segmentsData.sort((a, b) => a.sort_order - b.sort_order);
|
||||
const res = await fetch(`/api/segments/?page_id=${encodeURIComponent(pageId)}`);
|
||||
if (!res.ok) throw new Error(`Segments fetch failed: ${res.status}`);
|
||||
const data = (await res.json()) as Segment[];
|
||||
segments.value = data.sort((a, b) => a.sort_order - b.sort_order);
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : "Unknown error";
|
||||
} finally {
|
||||
|
|
@ -30,9 +21,5 @@ export function useSegments() {
|
|||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void refresh();
|
||||
});
|
||||
|
||||
return { segments, siteTitle, loading, error, refresh };
|
||||
return { segments, loading, error, refresh };
|
||||
}
|
||||
|
|
|
|||
57
frontend/src/composables/useTeamMembers.ts
Normal file
57
frontend/src/composables/useTeamMembers.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { ref } from "vue";
|
||||
import type { TeamMember } from "@/types/segment";
|
||||
import { useAdmin } from "@/composables/useAdmin";
|
||||
|
||||
export function useTeamMembers() {
|
||||
const { authHeaders } = useAdmin();
|
||||
const members = ref<TeamMember[]>([]);
|
||||
const loading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
async function fetchMembers(): Promise<void> {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const res = await fetch("/api/team/");
|
||||
if (!res.ok) throw new Error(`Failed to fetch team: ${res.status}`);
|
||||
members.value = (await res.json()) as TeamMember[];
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : "Unknown error";
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function addMember(name: string, student_number: string): Promise<TeamMember | null> {
|
||||
try {
|
||||
const res = await fetch("/api/team/", {
|
||||
method: "POST",
|
||||
headers: { ...authHeaders.value, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name, student_number }),
|
||||
});
|
||||
if (!res.ok) throw new Error(`Add failed: ${res.status}`);
|
||||
const member = (await res.json()) as TeamMember;
|
||||
await fetchMembers();
|
||||
return member;
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : "Unknown error";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteMember(id: string): Promise<boolean> {
|
||||
try {
|
||||
const res = await fetch(`/api/team/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: authHeaders.value,
|
||||
});
|
||||
if (!res.ok) return false;
|
||||
await fetchMembers();
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return { members, loading, error, fetchMembers, addMember, deleteMember };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue