feature: admin menu

This commit is contained in:
Benno Tielen 2025-02-10 14:54:43 +01:00
parent 8f90781acc
commit 81614e4449
15 changed files with 169 additions and 26 deletions

View file

@ -9,6 +9,8 @@ import { HR } from '@/components/HorizontalRule/HorizontalRule'
import Image from 'next/image' import Image from 'next/image'
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
import { Blocks } from '@/compositions/Blocks/Blocks' import { Blocks } from '@/compositions/Blocks/Blocks'
import { isAuthenticated } from '@/utils/auth'
import { AdminMenu } from '@/components/AdminMenu/AdminMenu'
async function fetchBlog(id: string) { async function fetchBlog(id: string) {
const res = await fetch(`http://localhost:3000/api/blog/${id}`) const res = await fetch(`http://localhost:3000/api/blog/${id}`)
@ -21,6 +23,7 @@ export default async function BlogPage({ params }: { params: Promise<{id: string
const id = (await params).id; const id = (await params).id;
const data = await fetchBlog(id) as Blog; const data = await fetchBlog(id) as Blog;
const url = typeof data.photo === 'object' && data.photo?.sizes?.banner?.url; const url = typeof data.photo === 'object' && data.photo?.sizes?.banner?.url;
const authenticated = await isAuthenticated();
if(!data) { if(!data) {
notFound(); notFound();
@ -51,6 +54,11 @@ export default async function BlogPage({ params }: { params: Promise<{id: string
</Section> </Section>
<Blocks content={data.content} /> <Blocks content={data.content} />
<AdminMenu
collection={"worship"}
id={id}
isAuthenticated={authenticated}
/>
</> </>
) )

View file

@ -6,6 +6,8 @@ import { P } from '@/components/Text/Paragraph'
import { PageHeader } from '@/compositions/PageHeader/PageHeader' import { PageHeader } from '@/compositions/PageHeader/PageHeader'
import { NextPrevButtons } from '@/components/NextPrevButtons/NextPrevButtons' import { NextPrevButtons } from '@/components/NextPrevButtons/NextPrevButtons'
import { notFound } from 'next/navigation' import { notFound } from 'next/navigation'
import { isAuthenticated } from '@/utils/auth'
import { AdminMenu } from '@/components/AdminMenu/AdminMenu'
const months: Record<string, string> = { const months: Record<string, string> = {
"01": "Januar", "01": "Januar",
@ -105,6 +107,8 @@ export default async function PrayerIntentionPage({ params }: { params: Promise<
notFound(); notFound();
} }
const authenticated = await isAuthenticated()
return ( return (
<> <>
<PageHeader <PageHeader
@ -138,6 +142,11 @@ export default async function PrayerIntentionPage({ params }: { params: Promise<
}} }}
/> />
</Section> </Section>
<AdminMenu
collection={"popePrayerIntentions"}
id={prayer.id}
isAuthenticated={authenticated}
/>
</> </>
) )

View file

@ -6,6 +6,8 @@ import { fetchParish } from '@/fetch/parish'
import { fetchLastAnnouncement } from '@/fetch/announcement' import { fetchLastAnnouncement } from '@/fetch/announcement'
import { transformGallery } from '@/utils/dto/gallery' import { transformGallery } from '@/utils/dto/gallery'
import { fetchLastCalendar } from '@/fetch/calendar' import { fetchLastCalendar } from '@/fetch/calendar'
import { isAuthenticated } from '@/utils/auth'
import { AdminMenu } from '@/components/AdminMenu/AdminMenu'
export default async function ParishPage ({ params }: { params: Promise<{slug: string}>}) { export default async function ParishPage ({ params }: { params: Promise<{slug: string}>}) {
@ -32,20 +34,29 @@ export default async function ParishPage ({ params }: { params: Promise<{slug: s
const worship = await fetchWorship({ locations: churchIds }) const worship = await fetchWorship({ locations: churchIds })
const announcement = await fetchLastAnnouncement(id); const announcement = await fetchLastAnnouncement(id);
const calendar = await fetchLastCalendar(id); const calendar = await fetchLastCalendar(id);
const authenticated = await isAuthenticated();
return ( return (
<Parish <>
title={name} <Parish
slug={slug} title={name}
image={typeof photo === "string" ? photo : photo.url || "notfound"} slug={slug}
description={description} image={typeof photo === "string" ? photo : photo.url || "notfound"}
history={history} description={description}
contactPersons={contactPersons || []} history={history}
contact={contact} contactPersons={contactPersons || []}
events={events?.docs || []} contact={contact}
worship={worship?.docs || []} events={events?.docs || []}
announcement={announcement && typeof announcement.document === "object" ? announcement.document.url || undefined : undefined} worship={worship?.docs || []}
calendar={calendar && typeof calendar.document === "object" ? calendar.document.url || undefined : undefined} announcement={announcement && typeof announcement.document === "object" ? announcement.document.url || undefined : undefined}
gallery={gallery ? transformGallery(gallery) : undefined} calendar={calendar && typeof calendar.document === "object" ? calendar.document.url || undefined : undefined}
/> gallery={gallery ? transformGallery(gallery) : undefined}
/>
<AdminMenu
collection={"parish"}
id={id}
isAuthenticated={authenticated}
/>
</>
) )
} }

View file

@ -1,6 +1,8 @@
import { notFound } from 'next/navigation' import { notFound } from 'next/navigation'
import { Worship as WorshipType } from '@/payload-types' import { Worship as WorshipType } from '@/payload-types'
import { Worship } from '@/pageComponents/Worship/Worship' import { Worship } from '@/pageComponents/Worship/Worship'
import { isAuthenticated } from '@/utils/auth'
import { AdminMenu } from '@/components/AdminMenu/AdminMenu'
export default async function WorshipPage({ params }: { params: Promise<{id: string}>}) { export default async function WorshipPage({ params }: { params: Promise<{id: string}>}) {
@ -9,10 +11,20 @@ export default async function WorshipPage({ params }: { params: Promise<{id: str
if (!res.ok) { if (!res.ok) {
notFound() notFound()
} }
const authenticated = await isAuthenticated();
const worship = await res.json() as WorshipType; const worship = await res.json() as WorshipType;
return ( return (
<Worship worship={worship} /> <>
<Worship
worship={worship}
/>
<AdminMenu
collection={"worship"}
id={worship.id}
isAuthenticated={authenticated}
/>
</>
) )
} }

View file

@ -9,6 +9,8 @@ import { EventRow } from '@/components/EventRow/EventRow'
import Error from '@/pages/_error' import Error from '@/pages/_error'
import { fetchWorship } from '@/fetch/worship' import { fetchWorship } from '@/fetch/worship'
import { tranformWorship } from '@/utils/dto/worship' import { tranformWorship } from '@/utils/dto/worship'
import { AdminMenu } from '@/components/AdminMenu/AdminMenu'
import { isAuthenticated } from '@/utils/auth'
export default async function WorshipPage({searchParams}: { export default async function WorshipPage({searchParams}: {
@ -20,6 +22,7 @@ export default async function WorshipPage({searchParams}: {
if (!week) { if (!week) {
week = weekNumber(moment()); week = weekNumber(moment());
} }
const authenticated = await isAuthenticated();
const fromDate = moment(week, true); const fromDate = moment(week, true);
@ -76,6 +79,10 @@ export default async function WorshipPage({searchParams}: {
} }
</Container> </Container>
</Section> </Section>
<AdminMenu
collection={"worship"}
isAuthenticated={authenticated}
/>
{events.length > 0 && {events.length > 0 &&
<Section padding={"small"}> <Section padding={"small"}>

View file

@ -15,6 +15,8 @@ import { Row } from '@/components/Flex/Row'
import { RawHTML } from '@/components/RawHTML/RawHTML' import { RawHTML } from '@/components/RawHTML/RawHTML'
import { Blocks } from '@/compositions/Blocks/Blocks' import { Blocks } from '@/compositions/Blocks/Blocks'
import { getPhoto } from '@/utils/dto/gallery' import { getPhoto } from '@/utils/dto/gallery'
import { isAuthenticated } from '@/utils/auth'
import { AdminMenu } from '@/components/AdminMenu/AdminMenu'
export default async function GroupPage({ params }: { params: Promise<{slug: string}>}) { export default async function GroupPage({ params }: { params: Promise<{slug: string}>}) {
@ -29,6 +31,7 @@ export default async function GroupPage({ params }: { params: Promise<{slug: str
const media = getPhoto("tablet", photo) const media = getPhoto("tablet", photo)
const events = await fetchEvents({groupId: id}) const events = await fetchEvents({groupId: id})
const authenticated = await isAuthenticated();
return ( return (
<> <>
@ -90,6 +93,12 @@ export default async function GroupPage({ params }: { params: Promise<{slug: str
{ content && content.length > 0 && { content && content.length > 0 &&
<Blocks content={content} /> <Blocks content={content} />
} }
<AdminMenu
collection={"group"}
id={id}
isAuthenticated={authenticated}
/>
</> </>
) )
} }

View file

@ -60,7 +60,7 @@ export default function RootLayout({
items: [ items: [
{ {
title: "Kathoccino", title: "Kathoccino",
description: "Brunchgruppe für Jungerwachsene", description: "Brunchgruppe für junge Erwachsene",
href: "/gruppe/kathocchino" href: "/gruppe/kathocchino"
}, },
{ {
@ -127,7 +127,7 @@ export default function RootLayout({
}, },
groups: [ groups: [
{ {
title: "Sakramenten", title: "Sakramente",
items: [ items: [
{ {
title: "Taufe", title: "Taufe",
@ -146,7 +146,7 @@ export default function RootLayout({
}, },
{ {
title: "Ehe", title: "Ehe",
description: "Bund in Liebe, Treue", description: "Bund in Liebe und Treue",
href: "/sakramente/ehe" href: "/sakramente/ehe"
}, },
{ {
@ -196,7 +196,7 @@ export default function RootLayout({
items: [ items: [
{ {
title: "Alphakurs", title: "Alphakurs",
description: "Freude am glauben entdecken", description: "Freude am Glauben entdecken",
href: "/gruppe/alphakurs" href: "/gruppe/alphakurs"
}, },
{ {
@ -228,11 +228,11 @@ export default function RootLayout({
{ {
href: '/gruppe/waermestube', href: '/gruppe/waermestube',
title: 'Wärmestube', title: 'Wärmestube',
description: 'Kälteschutz für Bedurftigen', description: 'Kälteschutz für Bedurftige',
}, },
{ {
href: '/gruppe/essen-ist-fertig', href: '/gruppe/essen-ist-fertig',
title: 'Essen ist Fertig', title: 'Essen ist fertig',
description: 'Essensausgabe Neukölln', description: 'Essensausgabe Neukölln',
}, },
{ {
@ -252,8 +252,8 @@ export default function RootLayout({
}, },
{ {
href: '/gruppe/die-unterstuetzer', href: '/gruppe/die-unterstuetzer',
title: 'Anpacken & gutes tun', title: 'Anpacken & Gutes tun',
description: 'Kinderbertreuung, Hilfe bei.., Kirchenreinigung', description: 'Hilfe bei Kirchenreinigung und anderem',
}, },
] ]
} }

View file

@ -3,6 +3,8 @@ import { Event } from '@/payload-types'
import { EventPage } from '@/pageComponents/Event/Event' import { EventPage } from '@/pageComponents/Event/Event'
import { stringify } from 'qs-esm' import { stringify } from 'qs-esm'
import { getPhoto } from '@/utils/dto/gallery' import { getPhoto } from '@/utils/dto/gallery'
import { cookies } from 'next/headers'
import { isAuthenticated } from '@/utils/auth'
export default async function Page({ params }: { params: Promise<{id: string}>}) { export default async function Page({ params }: { params: Promise<{id: string}>}) {
@ -33,12 +35,15 @@ export default async function Page({ params }: { params: Promise<{id: string}>})
notFound() notFound()
} }
const authenticated = await isAuthenticated();
const event = await res.json() as Event; const event = await res.json() as Event;
const group = Array.isArray(event.group) && event.group.length > 0 && typeof event.group[0] == "object" ? event.group[0].slug : undefined; const group = Array.isArray(event.group) && event.group.length > 0 && typeof event.group[0] == "object" ? event.group[0].slug : undefined;
const photo = getPhoto("tablet", event.photo); const photo = getPhoto("tablet", event.photo);
return ( return (
<EventPage <EventPage
id={event.id}
title={event.title} title={event.title}
date={event.date} date={event.date}
createdAt={event.createdAt} createdAt={event.createdAt}
@ -52,6 +57,7 @@ export default async function Page({ params }: { params: Promise<{id: string}>})
rsvpLink={event.rsvpLink || undefined} rsvpLink={event.rsvpLink || undefined}
flyer={typeof event.flyer === 'object' ? event.flyer || undefined : undefined} flyer={typeof event.flyer === 'object' ? event.flyer || undefined : undefined}
photo={photo} photo={photo}
isAuthenticated={authenticated}
/> />
) )
} }

View file

@ -13,12 +13,15 @@ import { Row } from '@/components/Flex/Row'
import { Col } from '@/components/Flex/Col' import { Col } from '@/components/Flex/Col'
import { highlightLink } from '@/utils/dto/highlight' import { highlightLink } from '@/utils/dto/highlight'
import Error from '@/pages/_error' import Error from '@/pages/_error'
import { AdminMenu } from '@/components/AdminMenu/AdminMenu'
import { isAuthenticated } from '@/utils/auth'
export default async function EventsPage({searchParams}: { export default async function EventsPage({searchParams}: {
searchParams: Promise<{ week: string | undefined }> searchParams: Promise<{ week: string | undefined }>
}) { }) {
const authenticated = await isAuthenticated();
const query = await searchParams; const query = await searchParams;
const limit = 100; const limit = 100;
let week = query.week; let week = query.week;
@ -116,6 +119,10 @@ export default async function EventsPage({searchParams}: {
</Row> </Row>
</Container> </Container>
</Section> </Section>
<AdminMenu
collection={"event"}
isAuthenticated={authenticated}
/>
{/*prevents bots indexing till infinity*/} {/*prevents bots indexing till infinity*/}
{ events.length > 0 && { events.length > 0 &&

View file

@ -0,0 +1,26 @@
import styles from "./styles.module.scss"
type AdminEditButtonProps = {
collection: string,
id?: string,
isAuthenticated: boolean,
}
export const AdminMenu = ({ collection, id, isAuthenticated}: AdminEditButtonProps) => {
if(!isAuthenticated)
return null;
return (
<div className={styles.menu}>
<a href={`/admin`}>Admin</a>
<a href={`/admin/collections/${collection}/create`}>Neu erstellen</a>
{id &&
<a href={`/admin/collections/${collection}/${id}`}>Bearbeiten</a>
}
<a href={`/admin/logout`}>Abmelden</a>
</div>
)
}

View file

@ -0,0 +1,19 @@
.menu {
position: fixed;
bottom: 0;
width: 100%;
box-sizing: border-box;
left: 0;
background-color: rgba(238, 238, 238, 0.60);
backdrop-filter: blur(8px);
display: flex;
gap: 20px;
justify-content: flex-end;
padding-right: 20px;
}
.menu a {
padding: 10px 0;
font-size: 14px;
color: #23362c;
}

View file

@ -31,7 +31,7 @@ export const Footer = () => {
</p> </p>
<ul className={styles.list}> <ul className={styles.list}>
<li><Link href={"/kontakt"}>Kontakt</Link></li> <li><Link href={"/kontakt"}>Kontakt</Link></li>
<li><Link href={"/gottesdienst"}>Gottesdiensten</Link></li> <li><Link href={"/gottesdienst"}>Gottesdienste</Link></li>
<li><Link href={"/datenschutz"}>Datenschutz</Link></li> <li><Link href={"/datenschutz"}>Datenschutz</Link></li>
<li><Link href={"/schutzkonzept"}>Schutzkonzept</Link></li> <li><Link href={"/schutzkonzept"}>Schutzkonzept</Link></li>
<li><Link href={"/impressum"}>Impressum</Link></li> <li><Link href={"/impressum"}>Impressum</Link></li>

View file

@ -11,6 +11,7 @@ export default meta
export const Default: Story = { export const Default: Story = {
args: { args: {
id: "testid",
title: "Sing & Pray", title: "Sing & Pray",
date: "2024-12-02T09:21:19Z", date: "2024-12-02T09:21:19Z",
createdAt: "2024-12-02T09:21:19Z", createdAt: "2024-12-02T09:21:19Z",

View file

@ -15,8 +15,10 @@ import { StaticImageData } from 'next/image'
import { locationString } from '@/utils/dto/location' import { locationString } from '@/utils/dto/location'
import { ContactPerson2 } from '@/components/ContactPerson2/ContactPerson2' import { ContactPerson2 } from '@/components/ContactPerson2/ContactPerson2'
import { getPhoto } from '@/utils/dto/gallery' import { getPhoto } from '@/utils/dto/gallery'
import { AdminMenu } from '@/components/AdminMenu/AdminMenu'
type EventProps = { type EventProps = {
id: string,
title: string, title: string,
date: string, date: string,
createdAt: string, createdAt: string,
@ -29,11 +31,13 @@ type EventProps = {
group?: string, group?: string,
flyer?: Document, flyer?: Document,
photo?: StaticImageData, photo?: StaticImageData,
rsvpLink?: string rsvpLink?: string,
isAuthenticated: boolean
} }
export function EventPage( export function EventPage(
{ {
id,
title, title,
date, date,
createdAt, createdAt,
@ -46,7 +50,8 @@ export function EventPage(
flyer, flyer,
group, group,
photo, photo,
rsvpLink rsvpLink,
isAuthenticated
}: EventProps }: EventProps
) { ) {
const published = useDate(createdAt) const published = useDate(createdAt)
@ -165,6 +170,11 @@ export function EventPage(
</Section> </Section>
<Section/> <Section/>
<AdminMenu
collection={"event"}
id={id}
isAuthenticated={isAuthenticated}
/>
</> </>
) )
} }

18
src/utils/auth.ts Normal file
View file

@ -0,0 +1,18 @@
import { cookies } from 'next/headers'
/**
* Check if the current user is (trying to be) authenticated by
* checking if the `payload-token` is present.
*
* Warning: DO NOT USE THIS FUNCTION TO SECURE PARTS OF THE APPLICATION
*/
export const isAuthenticated = async (): Promise<boolean> => {
const cookieStore = await cookies();
const token = cookieStore.get("payload-token");
if(typeof token === "undefined") {
return false;
}
return true;
}