Compare commits

..

13 commits
v1.0.0 ... main

38 changed files with 252 additions and 79 deletions

View file

@ -3,3 +3,4 @@ node_modules/
.git/ .git/
.nuxt/ .nuxt/
.env .env
.output/

View file

@ -1,12 +1,12 @@
# ff-webpage # ff-webpage
Feuerwehr Webseite zu Strapi Contnet Management Feuerwehr Webseite zu Strapi Content Management
## Einleitung ## Einleitung
Dieses Repository dient zur dynamischen Erstellung von Webseiten. Es ist ein Frontend-Client, der auf die Daten des [Strapi Content Management Systems (CMS)](https://forgejo.jk-effects.cloud/Ehrenamt/ff-webpage-cms) zugreift. Die Webseite wird mit Nuxt.js erstellt und bietet eine benutzerfreundliche Oberfläche für die Anzeige von Inhalten. Die Webseite ist für Feuerwehren konzipiert und bietet eine einfache Möglichkeit, Informationen zu veröffentlichen und zu verwalten. Dieses Repository dient zur dynamischen Erstellung von Webseiten. Es ist ein Frontend-Client, der auf die Daten des [Strapi Content Management Systems (CMS)](https://forgejo.jk-effects.cloud/Ehrenamt/ff-webpage-cms) zugreift. Die Webseite wird mit Nuxt.js erstellt und bietet eine benutzerfreundliche Oberfläche für die Anzeige von Inhalten. Die Webseite ist für Feuerwehren konzipiert und bietet eine einfache Möglichkeit, Informationen zu veröffentlichen und zu verwalten.
Eine Demo dieser Seite finden Sie unter [https://ff-demo.jk-effects.cloud](https://ff-demo.jk-effects.cloud). Eine Demo dieser Seite finden Sie unter [https://webpage-demo.ff-admin.de](https://webpage-demo.ff-admin.de).
## Installation ## Installation

18
app.vue
View file

@ -2,11 +2,25 @@
<NuxtPage /> <NuxtPage />
</template> </template>
<script setup> <script setup lang="ts">
import type SEOComponent from "./types/component/seoComponent";
import type Global from "./types/single/global";
const runtimeConfig = useRuntimeConfig(); const runtimeConfig = useRuntimeConfig();
const appTitle = runtimeConfig.public.app.title; const appTitle = runtimeConfig.public.app.title;
const { findOne, find } = useStrapi();
const { data: global } = await useAsyncData("global", () => findOne<Global>("global"));
const {
metaTitle, metaDescription, keywords,
} = global.value?.data?.SEO ?? ({} as SEOComponent);
useHead({ useHead({
title: appTitle, title: metaTitle ?? appTitle,
meta: [
{name: "description", content: metaDescription},
{name: "keywords", content: keywords},
]
}); });
</script> </script>

View file

@ -18,9 +18,9 @@
<NuxtPicture <NuxtPicture
v-if="data?.image" v-if="data?.image"
loading="lazy" loading="lazy"
class="w-full h-full sm:w-fit sm:h-[50vh] max-w-full object-cover object-center" class="max-sm:w-full h-full sm:h-[50vh] max-w-full object-cover object-center"
:src="baseUrl + data.image.url" :src="baseUrl + data.image.url"
:imgAttrs="{ class: 'w-full h-full sm:w-fit sm:h-[50vh] object-cover object-center' }" :imgAttrs="{ class: 'h-full sm:h-[50vh] object-cover object-center' }"
/> />
<br v-if="data?.image" /> <br v-if="data?.image" />
<div v-if="data?.content"> <div v-if="data?.content">
@ -34,9 +34,9 @@
<NuxtPicture <NuxtPicture
v-for="img in data.attachment" v-for="img in data.attachment"
loading="lazy" loading="lazy"
class="w-full h-fit sm:w-fit sm:h-48 object-cover object-center" class="max-sm:w-full sm:h-48 object-cover object-center"
:src="baseUrl + img.url" :src="baseUrl + img.url"
:imgAttrs="{ class: 'w-full h-fit sm:w-fit sm:h-48 object-cover object-center' }" :imgAttrs="{ class: 'max-sm:w-full sm:h-48 object-cover object-center' }"
/> />
</div> </div>
</div> </div>

View file

@ -9,6 +9,8 @@
<DynamicZoneFullImage v-else-if="item.__component == 'dynamic-zone.full-image'" :data="item" /> <DynamicZoneFullImage v-else-if="item.__component == 'dynamic-zone.full-image'" :data="item" />
<DynamicZoneFullText v-else-if="item.__component == 'dynamic-zone.full-text'" :data="item" /> <DynamicZoneFullText v-else-if="item.__component == 'dynamic-zone.full-text'" :data="item" />
<DynamicZoneGallery v-else-if="item.__component == 'dynamic-zone.gallery'" :data="item" /> <DynamicZoneGallery v-else-if="item.__component == 'dynamic-zone.gallery'" :data="item" />
<DynamicZoneFileDownload v-else-if="item.__component == 'dynamic-zone.file-download'" :data="item" />
<DynamicZoneEmbedding v-else-if="item.__component == 'dynamic-zone.embedding'" :data="item" />
<SharedList v-else-if="item.__component == 'shared.list'" :data="item" /> <SharedList v-else-if="item.__component == 'shared.list'" :data="item" />
<br /> <br />
</div> </div>

View file

@ -1,13 +1,23 @@
<template> <template>
<div darkgray class="h-48 min-h-fit w-full px-5 py-10 flex-col justify-center items-center flex gap-2"> <footer darkgray class="h-48 min-h-fit w-full px-5 py-10 flex-col justify-center items-center flex gap-2">
<div class="self-stretch py-5 justify-center items-center gap-4 md:gap-10 inline-flex flex-wrap"> <div class="self-stretch py-5 justify-center items-center gap-4 md:gap-10 inline-flex flex-wrap">
<NuxtLink v-for="link in footer.links" :key="link.id" :to="link.URL" :target="link.target" class="text-base"> <div v-for="link in footer.links" :key="link.id" class="contents">
<a v-if="link.URL.startsWith('http')" :href="link.URL" :target="link.target" class="text-base">{{
link.text
}}</a>
<NuxtLink
v-else
:to="`${link.URL.startsWith('/') ? '' : '/'}${link.URL}`"
:target="link.target"
class="text-base"
>
{{ link.text }} {{ link.text }}
</NuxtLink> </NuxtLink>
</div> </div>
</div>
<div class="text-base text-center">@Copyright {{ new Date().getFullYear() }} {{ footer.copyright }}</div> <div class="text-base text-center">@Copyright {{ new Date().getFullYear() }} {{ footer.copyright }}</div>
<div class="text-base text-center">verwaltet von {{ footer.designed_developed_by }}</div> <div class="text-base text-center">verwaltet von {{ footer.designed_developed_by }}</div>
</div> </footer>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View file

@ -1,11 +1,11 @@
<template> <template>
<div class="sticky top-0 h-fit"> <header class="sticky top-0 h-fit z-50">
<div <div
primary primary
class="h-24 min-h-fit w-full px-4 md:px-12 py-2.5 justify-between items-center gap-5 flex overflow-hidden" class="h-24 min-h-fit w-full px-4 md:px-12 py-2.5 justify-between items-center gap-5 flex overflow-hidden"
> >
<NuxtLink to="/"> <NuxtLink to="/">
<img class="h-16 w-fit min-w-fit" :src="baseUrl + navbar.logo.url" /> <img class="h-16" :src="baseUrl + navbar.logo.url" />
</NuxtLink> </NuxtLink>
<div class="md:hidden"> <div class="md:hidden">
<svg <svg
@ -57,7 +57,7 @@
{{ sublink.name }} {{ sublink.name }}
</NuxtLink> </NuxtLink>
</div> </div>
</div> </header>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View file

@ -11,8 +11,13 @@
:imgAttrs="{ class: 'w-full h-56 object-cover object-center' }" :imgAttrs="{ class: 'w-full h-56 object-cover object-center' }"
/> />
<div class="w-full h-44 relative bg-white px-2 py-5 flex flex-col justify-start items-start gap-2 overflow-y-auto"> <div class="w-full h-44 relative bg-white px-2 py-5 flex flex-col justify-start items-start gap-2 overflow-y-auto">
<h1>{{ data?.title }}</h1> <h1>
<p v-if="data?.date" class="w-full text-[#5c5c5c]"> <span v-if="numberOverwrite != undefined" class="w-24 text-center text-black text-4xl my-auto">
{{ numberOverwrite }}.
</span>
{{ data?.title }}
</h1>
<p v-if="numberOverwrite != undefined && data?.date && data.date.includes('T')" class="w-full text-[#5c5c5c]">
{{ {{
new Date(data?.date ?? "").toLocaleString("de-DE", { new Date(data?.date ?? "").toLocaleString("de-DE", {
day: "2-digit", day: "2-digit",
@ -22,6 +27,16 @@
hour: "2-digit", hour: "2-digit",
}) })
}} }}
Uhr
</p>
<p v-else-if="data?.date && data.date.includes('T')" class="w-full text-[#5c5c5c]">
{{
new Date(data?.date ?? "").toLocaleString("de-DE", {
minute: "2-digit",
hour: "2-digit",
})
}}
Uhr
</p> </p>
<p class="w-full text-[#5c5c5c]"> <p class="w-full text-[#5c5c5c]">
{{ data?.description }} {{ data?.description }}
@ -39,6 +54,7 @@ const baseUrl = runtimeConfig.public.strapi.url;
defineProps({ defineProps({
data: Object as PropType<BaseCollection>, data: Object as PropType<BaseCollection>,
numberOverwrite: { type: Number, default: undefined },
allowNavigation: { type: Boolean, default: false }, allowNavigation: { type: Boolean, default: false },
urlOverwrite: { type: String, default: undefined }, urlOverwrite: { type: String, default: undefined },
}); });

View file

@ -16,7 +16,7 @@
</h1> </h1>
<div class="grow shrink basis-0 flex-col justify-center items-center flex"> <div class="grow shrink basis-0 flex-col justify-center items-center flex">
<h1 class="w-full">{{ data?.title }}</h1> <h1 class="w-full">{{ data?.title }}</h1>
<p v-if="numberOverwrite != undefined" class="w-full text-[#5c5c5c]"> <p v-if="numberOverwrite != undefined && data?.date && data.date.includes('T')" class="w-full text-[#5c5c5c]">
{{ {{
new Date(data?.date ?? "").toLocaleString("de-DE", { new Date(data?.date ?? "").toLocaleString("de-DE", {
day: "2-digit", day: "2-digit",
@ -26,14 +26,16 @@
hour: "2-digit", hour: "2-digit",
}) })
}} }}
Uhr
</p> </p>
<p v-else class="w-full text-[#5c5c5c]"> <p v-else-if="data?.date && data.date.includes('T')" class="w-full text-[#5c5c5c]">
{{ {{
new Date(data?.date ?? "").toLocaleString("de-DE", { new Date(data?.date ?? "").toLocaleString("de-DE", {
minute: "2-digit", minute: "2-digit",
hour: "2-digit", hour: "2-digit",
}) })
}} }}
Uhr
</p> </p>
<p class="w-full text-[#5c5c5c]"> <p class="w-full text-[#5c5c5c]">
{{ data?.description }} {{ data?.description }}

View file

@ -1,11 +1,11 @@
<template> <template>
<div class="flex flex-col md:flex-row gap-4"> <div class="flex flex-col md:flex-row gap-8 md:gap-4">
<NuxtPicture <NuxtPicture
loading="lazy" loading="lazy"
class="w-full md:w-1/2 min-w-[50%] h-fit object-cover object-center" class="w-full md:w-1/2 min-w-[50%] object-cover object-center"
:class="data?.image_left ? 'order-0' : 'order-1'" :class="data?.image_left ? 'order-0' : 'order-1'"
:src="baseUrl + data?.image.url" :src="baseUrl + data?.image.url"
:imgAttrs="{ class: 'w-full h-fit object-cover object-center' }" :imgAttrs="{ class: 'w-full h-full object-cover object-center' }"
/> />
<FieldContent :data="data?.text" /> <FieldContent :data="data?.text" />
</div> </div>

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="flex flex-col md:flex-row gap-4"> <div class="flex flex-col md:flex-row gap-8 md:gap-4">
<FieldContent :data="data?.left_side" /> <FieldContent :data="data?.left_side" />
<FieldContent :data="data?.right_side" /> <FieldContent :data="data?.right_side" />
</div> </div>

View file

@ -0,0 +1,15 @@
<template>
<div class="flex flex-col gap-2 w-full min-h-fit max-w-4xl mx-auto">
<h1 class="text-center">{{ data.title }}</h1>
<iframe :src="data.link" class="w-full h-[90vh]"></iframe>
</div>
</template>
<script setup lang="ts">
import type { PropType } from "vue";
import type DynamicZoneEmbedding from "../../types/component/dynamicZoneEmbedding";
const props = defineProps({
data: Object as PropType<DynamicZoneEmbedding>,
});
</script>

View file

@ -0,0 +1,42 @@
<template>
<div v-if="showComponent" class="flex flex-col gap-2 w-full min-h-fit max-w-4xl mx-auto">
<h1 class="text-center">{{ data.title }}</h1>
<iframe v-if="data.file.mime == 'application/pdf'" :src="baseUrl + data.file.url" class="w-full h-[90vh]"></iframe>
<NuxtPicture
v-if="data.file.mime.includes('image')"
loading="lazy"
class="w-full object-cover object-center mx-auto"
:src="baseUrl + data?.file.url"
:imgAttrs="{ class: 'w-full h-full object-cover object-center' }"
/>
<a
v-if="data.enable_download"
:href="baseUrl + data.file.url"
:download="data.file.name"
target="_blank"
class="w-fit text-primary underline"
>
Datei herunterladen
</a>
</div>
</template>
<script setup lang="ts">
import type { PropType } from "vue";
import type DynamicZoneFileDownload from "../../types/component/dynamicZoneFileDownload";
const runtimeConfig = useRuntimeConfig();
const baseUrl = runtimeConfig.public.strapi.url;
const props = defineProps({
data: Object as PropType<DynamicZoneFileDownload>,
});
const showComponent = computed(() => {
if (props.data.file.mime == "application/pdf" || props.data.file.mime.includes("image")) {
return true;
} else {
return props.data.enable_download;
}
});
</script>

View file

@ -1,9 +1,9 @@
<template> <template>
<NuxtPicture <NuxtPicture
loading="lazy" loading="lazy"
class="w-full lg:w-1/2 lg:min-w-[50%] h-fit object-cover object-center mx-auto" class="w-full lg:w-1/2 lg:min-w-[50%] object-cover object-center mx-auto"
:src="baseUrl + data?.image.url" :src="baseUrl + data?.image.url"
:imgAttrs="{ class: 'w-full h-fit object-cover object-center' }" :imgAttrs="{ class: 'w-full h-full object-cover object-center' }"
/> />
</template> </template>

View file

@ -3,9 +3,9 @@
<NuxtPicture <NuxtPicture
v-for="img in data?.images" v-for="img in data?.images"
loading="lazy" loading="lazy"
class="w-full h-fit sm:w-fit sm:h-48 object-cover object-center" class="max-sm:w-full sm:h-48 object-cover object-center"
:src="baseUrl + img.url" :src="baseUrl + img.url"
:imgAttrs="{ class: 'w-full h-fit sm:w-fit sm:h-48 object-cover object-center' }" :imgAttrs="{ class: 'max-sm:w-full sm:h-48 object-cover object-center' }"
/> />
</div> </div>
</template> </template>

View file

@ -26,8 +26,9 @@
primary primary
:to="item.children[0].text.split('->')[0]" :to="item.children[0].text.split('->')[0]"
class="w-fit p-2 px-3 rounded-md mx-auto mt-3" class="w-fit p-2 px-3 rounded-md mx-auto mt-3"
>{{ item.children[0].text.split("->")[1] }}</NuxtLink
> >
{{ item.children[0].text.split("->")[1] }}
</NuxtLink>
</div> </div>
</div> </div>
</template> </template>

View file

@ -23,7 +23,18 @@
:class="data?.lookup.image_item ? 'flex-row flex-wrap justify-center' : ' flex-col'" :class="data?.lookup.image_item ? 'flex-row flex-wrap justify-center' : ' flex-col'"
> >
<div v-for="(item, index) in collection" :key="item.slug" class="contents"> <div v-for="(item, index) in collection" :key="item.slug" class="contents">
<BaseListImageItem v-if="data?.lookup.image_item" :data="item" :allow-navigation="data.enable_detail" /> <p
v-if="data?.lookup.date_list && data?.lookup.numbered_item == false && getDate(index) != ''"
class="w-full text-center"
>
{{ getDate(index) }}
</p>
<BaseListImageItem
v-if="data?.lookup.image_item"
:data="item"
:number-overwrite="data.lookup.numbered_item ? getNumber(index) : undefined"
:allow-navigation="data.enable_detail"
/>
<BaseListItem <BaseListItem
v-else-if="data?.lookup" v-else-if="data?.lookup"
:data="item" :data="item"
@ -33,7 +44,7 @@
</div> </div>
</div> </div>
<div <div
v-if="data?.lookup.date_list" v-if="pagination.pageCount > 1"
class="flex flex-row w-full max-w-4xl mx-auto justify-between items-center select-none pt-4" class="flex flex-row w-full max-w-4xl mx-auto justify-between items-center select-none pt-4"
> >
<p class="text-sm font-normal text-gray-500"> <p class="text-sm font-normal text-gray-500">
@ -56,7 +67,7 @@
v-for="page in displayedPagesNumbers" v-for="page in displayedPagesNumbers"
:key="page" :key="page"
class="flex h-8 w-8 items-center justify-center text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 first:rounded-s-lg last:rounded-e-lg" class="flex h-8 w-8 items-center justify-center text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 first:rounded-s-lg last:rounded-e-lg"
:class="[currentPage == page ? 'font-bold border-primary' : '', page != '.' ? ' cursor-pointer' : '']" :class="[currentPage - 1 == page ? 'font-bold border-primary' : '', page != '.' ? ' cursor-pointer' : '']"
@click="changeTimedPage(page)" @click="changeTimedPage(page)"
> >
{{ typeof page == "number" ? page + 1 : "..." }} {{ typeof page == "number" ? page + 1 : "..." }}
@ -64,7 +75,7 @@
<li <li
class="flex h-8 w-8 items-center justify-center text-gray-500 bg-white border border-gray-300 first:rounded-s-lg last:rounded-e-lg" class="flex h-8 w-8 items-center justify-center text-gray-500 bg-white border border-gray-300 first:rounded-s-lg last:rounded-e-lg"
:class="[ :class="[
currentPage + 1 < pagination.pageCount currentPage < pagination.pageCount
? 'cursor-pointer hover:bg-gray-100 hover:text-gray-700' ? 'cursor-pointer hover:bg-gray-100 hover:text-gray-700'
: 'opacity-50 pointer-events-none', : 'opacity-50 pointer-events-none',
]" ]"
@ -116,7 +127,7 @@ const { data: collections } = await useAsyncData("collection", () =>
sort: "date:desc", sort: "date:desc",
filters: { filters: {
date: { date: {
$between: [`${activeYear.value}-01-01T00:00:00.000Z`, `${activeYear.value}-12-31T23:59:59.999Z`], $between: [`${activeYear.value}-01-01 00:00:00.000000`, `${activeYear.value}-12-31 23:59:59.999`],
}, },
}, },
pagination: { pagination: {
@ -125,7 +136,13 @@ const { data: collections } = await useAsyncData("collection", () =>
withCount: true, withCount: true,
}, },
} }
: {}), : {
pagination: {
page: 1,
pageSize: 10,
withCount: true,
},
}),
}) })
); );
collection.value = collections.value?.data; collection.value = collections.value?.data;
@ -184,19 +201,33 @@ const displayedPagesNumbers = computed(() => {
function getNumber(index: number): number { function getNumber(index: number): number {
if (props.data?.lookup.inverse_count) { if (props.data?.lookup.inverse_count) {
return pagination.value.pageCount - numberOffset.value - index + 1; return pagination.value.total - numberOffset.value - index;
} else { } else {
return numberOffset.value + index + 1; return numberOffset.value + index + 1;
} }
} }
function getDate(index: number): string {
let thisElement = collection.value?.[index];
let beforeElement = collection.value?.[index - 1];
if (thisElement && beforeElement) {
let thisElementDate = new Date(thisElement.date ?? "").toLocaleDateString("de", { month: "long" });
let beforeElementDate = new Date(beforeElement.date ?? "").toLocaleDateString("de", { month: "long" });
if (thisElementDate == beforeElementDate) return "";
else return thisElementDate;
} else if (thisElement) {
return new Date(thisElement.date ?? "").toLocaleDateString("de", { month: "long" });
} else return "";
}
async function changeTimedData(year: number) { async function changeTimedData(year: number) {
activeYear.value = year; activeYear.value = year;
const data = await find<BaseCollection>(props.data?.lookup.collection ?? "", { const data = await find<BaseCollection>(props.data?.lookup.collection ?? "", {
sort: "date:desc", sort: "date:desc",
filters: { filters: {
date: { date: {
$between: [`${activeYear.value}-01-01T00:00:00.000Z`, `${activeYear.value}-12-31T23:59:59.999Z`], $between: [`${activeYear.value}-01-01 00:00:00.000000`, `${activeYear.value}-12-31 23:59:59.999`],
}, },
}, },
pagination: { pagination: {
@ -205,6 +236,7 @@ async function changeTimedData(year: number) {
withCount: true, withCount: true,
}, },
}); });
console.log(data);
collection.value = data?.data; collection.value = data?.data;
pagination.value = (data?.meta.pagination as unknown as { pagination.value = (data?.meta.pagination as unknown as {
page: number; page: number;
@ -223,7 +255,7 @@ async function changeTimedPage(page: number = 1) {
sort: "date:desc", sort: "date:desc",
filters: { filters: {
date: { date: {
$between: [`${activeYear.value}-01-01T00:00:00.000Z`, `${activeYear.value}-12-31T23:59:59.999Z`], $between: [`${activeYear.value}-01-01 00:00:00.000000`, `${activeYear.value}-12-31 23:59:59.999`],
}, },
}, },
pagination: { pagination: {

View file

@ -1,12 +1,17 @@
<template> <template>
<div class="relative h-[calc(100svh-6rem)] max-h-[calc(100svh-6rem)] w-full overflow-hidden"> <div v-if="!hide_backdrop" class="relative h-[calc(100svh-6rem)] max-h-[calc(100svh-6rem)] w-full overflow-hidden">
<NuxtPicture <NuxtPicture
loading="lazy" loading="lazy"
class="w-full h-full object-cover object-center" class="w-full h-full object-cover object-center"
:src="baseUrl + backdrop.url" :src="baseUrl + backdrop.url"
:imgAttrs="{ class: 'w-full h-full object-cover object-center' }" :imgAttrs="{ class: 'w-full h-full object-cover object-center' }"
/> />
<img class="absolute p-4 w-full h-fit sm:h-40 sm:w-fit bottom-5" :src="baseUrl + navbar.logo.url" /> <img class="absolute p-4 max-sm:w-full sm:h-40 bottom-5" :src="baseUrl + navbar.logo.url" />
<img
class="absolute h-5 w-5 left-1/2 -translate-y-1/2 bottom-5 text-gray-400 cursor-pointer"
src="/chevrons-down.svg"
@click="scroll()"
/>
</div> </div>
<Header /> <Header />
<slot /> <slot />
@ -28,5 +33,9 @@ const { data: global } = await useAsyncData("global", () => findOne<Global>("glo
const { navbar } = global.value?.data as unknown as Global; const { navbar } = global.value?.data as unknown as Global;
const { data: homepage } = await useAsyncData("homepage", () => findOne<Homepage>("homepage")); const { data: homepage } = await useAsyncData("homepage", () => findOne<Homepage>("homepage"));
const { backdrop } = homepage.value?.data as unknown as Homepage; const { backdrop, hide_backdrop } = homepage.value?.data as unknown as Homepage;
function scroll() {
window.scrollTo({ top: window.innerHeight - 96, behavior: "smooth" });
}
</script> </script>

View file

@ -10,7 +10,6 @@ export default defineNuxtConfig({
meta: [ meta: [
{ charset: "utf-8" }, { charset: "utf-8" },
{ name: "viewport", content: "width=device-width, initial-scale=1" }, { name: "viewport", content: "width=device-width, initial-scale=1" },
{ hid: "description", name: "description", content: "" },
{ name: "format-detection", content: "telephone=no" }, { name: "format-detection", content: "telephone=no" },
], ],
link: [{ rel: "icon", type: "image/x-icon", href: "/favicon.png" }], link: [{ rel: "icon", type: "image/x-icon", href: "/favicon.png" }],

23
package-lock.json generated
View file

@ -1,14 +1,16 @@
{ {
"name": "nuxt-app", "name": "nuxt-app",
"version": "1.1.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "nuxt-app", "name": "nuxt-app",
"version": "1.1.0",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@nuxt/image": "^1.8.1", "@nuxt/image": "^1.8.1",
"@nuxtjs/strapi": "npm:@nuxtjs/strapi-edge@1.12.0-28818224.f53bdf9", "@nuxtjs/strapi": "^2.0.0",
"nuxt": "^3.13.2", "nuxt": "^3.13.2",
"vue": "latest", "vue": "latest",
"vue-router": "latest" "vue-router": "latest"
@ -17,8 +19,7 @@
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"postcss": "^8.4.47", "postcss": "^8.4.47",
"tailwindcss": "^3.4.14" "tailwindcss": "^3.4.14"
}, }
"version": "1.0.0"
}, },
"node_modules/@alloc/quick-lru": { "node_modules/@alloc/quick-lru": {
"version": "5.2.0", "version": "5.2.0",
@ -1354,10 +1355,9 @@
} }
}, },
"node_modules/@nuxtjs/strapi": { "node_modules/@nuxtjs/strapi": {
"name": "@nuxtjs/strapi-edge", "version": "2.0.0",
"version": "1.12.0-28818224.f53bdf9", "resolved": "https://registry.npmjs.org/@nuxtjs/strapi/-/strapi-2.0.0.tgz",
"resolved": "https://registry.npmjs.org/@nuxtjs/strapi-edge/-/strapi-edge-1.12.0-28818224.f53bdf9.tgz", "integrity": "sha512-6Q34PQd980ruBTaMzVx23V/GP3YeoqWt9NKxVG79d1TJMwpKHCkFqZEN5jI/2csMyncYuzP4ozt2lIi5JYTudA==",
"integrity": "sha512-SdazV9/5qJr/b5F//fveU9DVxBFeYKfB6Gg7wyvZoNUW9z9BSPuQyoCfDdzRkPy+QqdaLhFXPjFJdPNeK/8agA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@nuxt/kit": "^3.13.2", "@nuxt/kit": "^3.13.2",
@ -10923,9 +10923,9 @@
} }
}, },
"@nuxtjs/strapi": { "@nuxtjs/strapi": {
"version": "npm:@nuxtjs/strapi-edge@1.12.0-28818224.f53bdf9", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/@nuxtjs/strapi-edge/-/strapi-edge-1.12.0-28818224.f53bdf9.tgz", "resolved": "https://registry.npmjs.org/@nuxtjs/strapi/-/strapi-2.0.0.tgz",
"integrity": "sha512-SdazV9/5qJr/b5F//fveU9DVxBFeYKfB6Gg7wyvZoNUW9z9BSPuQyoCfDdzRkPy+QqdaLhFXPjFJdPNeK/8agA==", "integrity": "sha512-6Q34PQd980ruBTaMzVx23V/GP3YeoqWt9NKxVG79d1TJMwpKHCkFqZEN5jI/2csMyncYuzP4ozt2lIi5JYTudA==",
"requires": { "requires": {
"@nuxt/kit": "^3.13.2", "@nuxt/kit": "^3.13.2",
"defu": "^6.1.4", "defu": "^6.1.4",
@ -16597,6 +16597,5 @@
"readable-stream": "^4.0.0" "readable-stream": "^4.0.0"
} }
} }
}, }
"version": "1.0.0"
} }

View file

@ -4,14 +4,14 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "nuxt build", "build": "nuxt build",
"dev": "nuxt dev", "dev": "nuxt dev --host",
"generate": "nuxt generate", "generate": "nuxt generate",
"preview": "nuxt preview", "preview": "nuxt preview",
"postinstall": "nuxt prepare" "postinstall": "nuxt prepare"
}, },
"dependencies": { "dependencies": {
"@nuxt/image": "^1.8.1", "@nuxt/image": "^1.8.1",
"@nuxtjs/strapi": "npm:@nuxtjs/strapi-edge@1.12.0-28818224.f53bdf9", "@nuxtjs/strapi": "^2.0.0",
"nuxt": "^3.13.2", "nuxt": "^3.13.2",
"vue": "latest", "vue": "latest",
"vue-router": "latest" "vue-router": "latest"
@ -21,5 +21,5 @@
"postcss": "^8.4.47", "postcss": "^8.4.47",
"tailwindcss": "^3.4.14" "tailwindcss": "^3.4.14"
}, },
"version": "1.0.0" "version": "1.1.0"
} }

1
public/chevrons-down.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevrons-down"><polyline points="7 13 12 18 17 13"></polyline><polyline points="7 6 12 11 17 6"></polyline></svg>

After

Width:  |  Height:  |  Size: 317 B

View file

@ -1,4 +1,4 @@
import type BaseImage from "../component/baseImage"; import type BaseFile from "../component/baseFile";
import type BaseCollection from "./baseCollection"; import type BaseCollection from "./baseCollection";
export default interface Article extends BaseCollection {} export default interface Article extends BaseCollection {}

View file

@ -1,4 +1,4 @@
import type BaseImage from "../component/baseImage"; import type BaseFile from "../component/baseFile";
import type ContentField from "../field/content"; import type ContentField from "../field/content";
export default interface BaseCollection { export default interface BaseCollection {
@ -14,6 +14,6 @@ export default interface BaseCollection {
description: string; description: string;
date: string | undefined; date: string | undefined;
content: ContentField | undefined; content: ContentField | undefined;
image: BaseImage | undefined; image: BaseFile | undefined;
attachment: Array<BaseImage>; attachment: Array<BaseFile>;
} }

View file

@ -1,4 +1,4 @@
import type BaseImage from "../component/baseImage"; import type BaseFile from "../component/baseFile";
import type BaseCollection from "./baseCollection"; import type BaseCollection from "./baseCollection";
export default interface Vehicle extends BaseCollection { export default interface Vehicle extends BaseCollection {

View file

@ -4,7 +4,9 @@ import type DynamicZoneEmphasiseArticle from "./dynamicZoneEmphasiseArticle";
import type DynamicZoneFullImage from "./dynamicZoneFullImage"; import type DynamicZoneFullImage from "./dynamicZoneFullImage";
import type DynamicZoneFullText from "./dynamicZoneFullText"; import type DynamicZoneFullText from "./dynamicZoneFullText";
import type DynamicZoneGallery from "./dynamicZoneGallery"; import type DynamicZoneGallery from "./dynamicZoneGallery";
import type DynamicZoneFileDownload from "./dynamicZoneFileDownload";
import type SharedList from "./sharedList"; import type SharedList from "./sharedList";
import type DynamicZoneEmbedding from "./dynamicZoneEmbedding";
export default interface BaseComponent { export default interface BaseComponent {
__component: ComponentNames; __component: ComponentNames;
@ -18,7 +20,9 @@ export type ComponentNames =
| "dynamic-zone.full-image" | "dynamic-zone.full-image"
| "dynamic-zone.emphasise-article" | "dynamic-zone.emphasise-article"
| "dynamic-zone.dual-column-text" | "dynamic-zone.dual-column-text"
| "dynamic-zone.column-image-text"; | "dynamic-zone.column-image-text"
| "dynamic-zone.file-download"
| "dynamic-zone.embedding";
export type ComponentTypes = export type ComponentTypes =
| SharedList | SharedList
@ -27,4 +31,6 @@ export type ComponentTypes =
| DynamicZoneFullImage | DynamicZoneFullImage
| DynamicZoneEmphasiseArticle | DynamicZoneEmphasiseArticle
| DynamicZoneDualColumnText | DynamicZoneDualColumnText
| DynamicZoneColumnImageText; | DynamicZoneColumnImageText
| DynamicZoneFileDownload
| DynamicZoneEmbedding;

View file

@ -1,4 +1,4 @@
export default interface BaseImage { export default interface BaseFile {
id: number; id: number;
documentId: string; documentId: string;
name: string; name: string;

View file

@ -1,10 +1,10 @@
import type ContentField from "../field/content"; import type ContentField from "../field/content";
import type BaseComponent from "./baseComponent"; import type BaseComponent from "./baseComponent";
import type BaseImage from "./baseImage"; import type BaseFile from "./baseFile";
export default interface DynamicZoneColumnImageText extends BaseComponent { export default interface DynamicZoneColumnImageText extends BaseComponent {
__component: "dynamic-zone.column-image-text"; __component: "dynamic-zone.column-image-text";
text: ContentField; text: ContentField;
image_left: boolean; image_left: boolean;
image: BaseImage; image: BaseFile;
} }

View file

@ -0,0 +1,7 @@
import type BaseComponent from "./baseComponent";
export default interface DynamicZoneEmbedding extends BaseComponent {
__component: "dynamic-zone.embedding";
title: string;
link: string;
}

View file

@ -0,0 +1,9 @@
import type BaseComponent from "./baseComponent";
import type BaseFile from "./baseFile";
export default interface DynamicZoneFileDownload extends BaseComponent {
__component: "dynamic-zone.file-download";
enable_download: boolean;
title: string;
file: BaseFile;
}

View file

@ -1,7 +1,7 @@
import type BaseComponent from "./baseComponent"; import type BaseComponent from "./baseComponent";
import type BaseImage from "./baseImage"; import type BaseFile from "./baseFile";
export default interface DynamicZoneFullImage extends BaseComponent { export default interface DynamicZoneFullImage extends BaseComponent {
__component: "dynamic-zone.full-image"; __component: "dynamic-zone.full-image";
image: BaseImage; image: BaseFile;
} }

View file

@ -1,7 +1,7 @@
import type BaseComponent from "./baseComponent"; import type BaseComponent from "./baseComponent";
import type BaseImage from "./baseImage"; import type BaseFile from "./baseFile";
export default interface DynamicZoneGallery extends BaseComponent { export default interface DynamicZoneGallery extends BaseComponent {
__component: "dynamic-zone.gallery"; __component: "dynamic-zone.gallery";
images: Array<BaseImage>; images: Array<BaseFile>;
} }

View file

@ -1,8 +1,8 @@
import type BaseImage from "./baseImage"; import type BaseFile from "./baseFile";
import type NavbarItem from "./itemsNavbarItem"; import type NavbarItem from "./itemsNavbarItem";
export default interface Navbar { export default interface Navbar {
id: number; id: number;
logo: BaseImage; logo: BaseFile;
navbar_items: NavbarItem[]; navbar_items: NavbarItem[];
} }

View file

@ -0,0 +1,5 @@
export default interface SEOComponent {
metaTitle: string;
metaDescription: string;
keywords: string;
}

View file

@ -1,7 +1,7 @@
import type BaseImage from "./baseImage"; import type BaseFile from "./baseFile";
export default interface SharedHero { export default interface SharedHero {
id: number; id: number;
title: string; title: string;
banner: BaseImage; banner: BaseFile;
} }

View file

@ -12,7 +12,7 @@ export default interface ContentField
} }
| { | {
type: "code"; type: "code";
children: Array<{ type: "list-item"; children: Array<TypeField> }>; children: Array<TextField>;
language: "plaintext"; language: "plaintext";
} }
> {} > {}

View file

@ -1,5 +1,6 @@
import type Footer from "../component/globalFooter"; import type Footer from "../component/globalFooter";
import type Navbar from "../component/globalNavbar"; import type Navbar from "../component/globalNavbar";
import type SEOComponent from "../component/seoComponent";
export default interface Global { export default interface Global {
id: number; id: number;
@ -10,4 +11,5 @@ export default interface Global {
locale: string; locale: string;
navbar: Navbar; navbar: Navbar;
footer: Footer; footer: Footer;
SEO: SEOComponent;
} }

View file

@ -1,4 +1,4 @@
import type BaseImage from "../component/baseImage"; import type BaseFile from "../component/baseFile";
import type { ComponentTypes } from "../component/baseComponent"; import type { ComponentTypes } from "../component/baseComponent";
export default interface Homepage { export default interface Homepage {
@ -8,6 +8,7 @@ export default interface Homepage {
updatedAt: string; updatedAt: string;
publishedAt: string; publishedAt: string;
locale: string; locale: string;
backdrop: BaseImage; backdrop: BaseFile;
hide_backdrop: boolean;
content: Array<ComponentTypes>; content: Array<ComponentTypes>;
} }