Transferred and translated the frontend code from L10 to L12
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run

This commit is contained in:
2025-05-05 19:01:27 +02:00
parent 98205954c3
commit e15d3ae146
96 changed files with 6398 additions and 701 deletions
@@ -0,0 +1,66 @@
<script setup>
import { defineProps, ref } from 'vue';
import { v4 as uuidv4 } from 'uuid';
const props = defineProps({
title: String,
blocks: Array
});
const open = ref(false)
const clone = (block) => {
return {
...block,
uuid: uuidv4()
};
};
function handleDragStart(event, block) {
event.dataTransfer.setData('application/json', JSON.stringify(block));
}
</script>
<template>
<div id="accordion-blocks" data-accordion="collapse">
<div class="border border-gray-200 dark:border-gray-700 rounded-md">
<h2>
<button
@click="open = !open"
type="button"
class="flex items-center justify-between w-full px-4 py-2 font-medium text-left text-gray-800 dark:text-gray-200 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700"
>
{{ title }}
<svg
:class="{ 'rotate-180': open }"
class="w-4 h-4 transform transition-transform duration-300"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
</h2>
<div v-show="open" class="px-4 pb-4 space-y-4 border-t border-gray-200 dark:border-gray-700">
<div
v-for="block in blocks"
:key="block.uuid"
class="cursor-move border rounded p-2 bg-white dark:bg-gray-700 shadow hover:bg-gray-50 dark:hover:bg-gray-600"
draggable="true"
@dragstart="handleDragStart($event, block)"
>
<span class="block text-xs font-medium text-gray-900 dark:text-white">{{ block.description }}</span>
<img :src="block.thumbUrl" class="mt-2 w-full object-cover rounded" />
</div>
</div>
</div>
</div>
</template>
<style scoped>
.collapse {
transition: all 0.3s ease;
}
</style>
@@ -0,0 +1,23 @@
<script setup>
import { defineProps } from 'vue';
// Import all block components explicitly
const { block } = defineProps({
block: {
type: Object,
required: true
}
});
const components = {
};
</script>
<template>
<component :is="components[block.componentName]" :block="block" />
</template>
<style scoped>
/* Optional styles */
</style>
@@ -0,0 +1,128 @@
<script setup>
import { ref, computed } from 'vue'
import Icon from '@/Components/Icon.vue'
import HeroLogoText from '@/Components/Blocks/Heroes/HeroLogoText.vue';
import HeroLogoTextOptions from '@/Components/Blocks/Heroes/HeroLogoTextOptions.vue';
import FAQList from '@/Components/Blocks/FAQ/FAQList.vue';
import FAQListOptions from '@/Components/Blocks/FAQ/FAQListOptions.vue';
import FAQCollapse from '@/Components/Blocks/FAQ/FAQCollapse.vue';
import FAQCollapseOptions from '@/Components/Blocks/FAQ/FAQCollapseOptions.vue';
import FAQCondensed from '@/Components/Blocks/FAQ/FAQCondensed.vue';
import FAQCondensedOptions from '@/Components/Blocks/FAQ/FAQCondensedOptions.vue';
import TwoColumnText from '@/Components/Blocks/Text/TwoColumnText.vue';
import TwoColumnTextOptions from '@/Components/Blocks/Text/TwoColumnTextOptions.vue';
import FrontPageMenuHeader from '@/Components/Blocks/Statics/FrontPageMenuHeader.vue';
import FrontPageMenuHeaderOptions from '@/Components/Blocks/Statics/FrontPageMenuHeaderOptions.vue';
import FrontPageSponsors from '@/Components/Blocks/Statics/FrontPageSponsors.vue';
import FrontPageSponsorsOptions from '@/Components/Blocks/Statics/FrontPageSponsorsOptions.vue';
import FrontPageMembershipInfo from '@/Components/Blocks/Statics/FrontPageMembershipInfo.vue';
import FrontPageMembershipInfoOptions from '@/Components/Blocks/Statics/FrontPageMembershipInfoOptions.vue';
import FrontPageNewsList from '@/Components/Blocks/Statics/FrontPageNewsList.vue';
import FrontPageNewsListOptions from '@/Components/Blocks/Statics/FrontPageNewsListOptions.vue';
import FrontPageEventsList from '@/Components/Blocks/Statics/FrontPageEventsList.vue';
import FrontPageEventsListOptions from '@/Components/Blocks/Statics/FrontPageEventsListOptions.vue';
import PageFooter from '@/Components/Blocks/Statics/PageFooter.vue';
const components = {
HeroLogoText,
HeroLogoTextOptions,
FAQList,
FAQListOptions,
FAQCollapse,
FAQCollapseOptions,
FAQCondensed,
FAQCondensedOptions,
TwoColumnText,
TwoColumnTextOptions,
FrontPageMenuHeader,
FrontPageMenuHeaderOptions,
FrontPageSponsors,
FrontPageSponsorsOptions,
FrontPageMembershipInfo,
FrontPageMembershipInfoOptions,
FrontPageNewsList,
FrontPageNewsListOptions,
FrontPageEventsList,
FrontPageEventsListOptions,
PageFooter
}
const props = defineProps({
block: {
type: Object,
required: true,
},
images: {
type: Array
}
});
const emit = defineEmits(['delete']);
const showDrawer = ref(false);
const toggleDrawer = () => {
showDrawer.value = !showDrawer.value;
emit('drawer-state', showDrawer.value);
};
const isStatic = computed(() => props.block.static === true);
</script>
<template>
<div class="relative group border rounded-md p-4 bg-black dark:bg-gray-800">
<!-- Render the actual block -->
<component :images="images" :is="components[block.componentName]" :block="block" />
<!-- Floating toolbar -->
<div class="absolute top-0 left-0 flex flex-col space-y-1 p-1 bg-gray-700 rounded-tr-md rounded-br-md text-white z-10">
<!-- Drag handle -->
<div class="cursor-move handle p-1 hover:bg-gray-600 rounded">
<Icon name="move" class="h-4 w-4" />
</div>
<!-- Open options drawer -->
<button :disabled="isStatic" @click="toggleDrawer" class="p-1 hover:bg-gray-600 rounded">
<Icon name="settings" class="h-4 w-4" />
</button>
<!-- Delete block -->
<button @click="emit('delete')" class="p-1 hover:bg-red-600 text-red-300 rounded">
<Icon name="trash" class="h-4 w-4" />
</button>
</div>
</div>
<!-- Options drawer -->
<teleport to="body">
<div v-if="showDrawer" class="fixed inset-0 z-50 bg-black/50 flex">
<div class="bg-white dark:bg-gray-800 w-96 max-w-full h-full shadow-lg p-4 overflow-y-auto">
<div class="flex justify-between items-center mb-4">
<h2 class="text-lg font-semibold text-gray-800 dark:text-white">Blokkinnstillinger</h2>
<button @click="toggleDrawer" class="text-gray-500 hover:text-red-500 dark:text-gray-300 dark:hover:text-red-400">
✕
</button>
</div>
<component
:is="components[block.optionsComponentName]"
:block="block"
:images="images"
/>
</div>
<div class="flex-1" @click="toggleDrawer"></div>
</div>
</teleport>
</template>
<style scoped>
/* Optional styling tweaks */
</style>
@@ -0,0 +1,33 @@
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
block: {
type: Object,
required: true,
},
});
const faqs = props.block.faqs || [
{ question: 'Hva er dette?', answer: 'Dette er et eksempel på et spørsmål og svar.' },
{ question: 'Hvordan fungerer det?', answer: 'Du legger til innhold og publiserer siden.' },
];
</script>
<template>
<section class="relative py-20 md:py-24 overflow-hidden mx-auto max-w-[1280px] bg-black">
<div class="container mx-auto px-6 lg:px-20">
<h2 class="text-amber-600 text-xl md:text-2xl font-serif mb-6">{{ block.title || 'Vanlige spørsmål' }}</h2>
<div class="space-y-4">
<details v-for="(faq, index) in faqs" :key="index" class="bg-gray-700 p-4 rounded-md shadow">
<summary class="cursor-pointer font-medium text-white">{{ faq.question }}</summary>
<p class="mt-2 text-gray-300">{{ faq.answer }}</p>
</details>
</div>
</div>
</section>
</template>
<style scoped>
/* Optional collapse styles */
</style>
@@ -0,0 +1,103 @@
<script setup>
import Icon from '@/Components/Icon.vue';
import { toRef, ref } from 'vue';
const props = defineProps({
block: {
type: Object,
required: true,
},
});
const emit = defineEmits(['update:block']);
const local = toRef(props, 'block');
// Drag state
const draggedIndex = ref(null);
function handleDragStart(event, index) {
draggedIndex.value = index;
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', index);
};
function handleDragOver(event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
};
function handleDrop(event, index) {
event.preventDefault();
const fromIndex = draggedIndex.value;
if (fromIndex === null || fromIndex === index) return;
const item = local.value.faqs.splice(fromIndex, 1)[0];
local.value.faqs.splice(index, 0, item);
draggedIndex.value = null;
};
function addFaq() {
local.value.faqs.push({
question: '',
answer: '',
});
}
function removeFaq(index) {
local.value.faqs.splice(index, 1);
}
</script>
<template>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tittel for seksjonen</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.title"
/>
</div>
<div class="space-y-4">
<div
v-for="(faq, index) in local.faqs"
:key="index"
class="flex items-start gap-2 p-4 rounded border dark:border-gray-600 bg-gray-50 dark:bg-gray-800"
draggable="true"
@dragstart="handleDragStart($event, index)"
@dragover="handleDragOver"
@drop="handleDrop($event, index)"
>
<div class="cursor-move mt-1 text-gray-500 dark:text-gray-400">
<Icon name="move" class="w-4 h-4" />
</div>
<div class="flex-1 space-y-2">
<div class="flex justify-between items-center">
<span class="text-sm font-semibold text-gray-700 dark:text-gray-300">FAQ {{ index + 1 }}</span>
<button @click="removeFaq(index)" class="text-red-500 hover:underline text-xs">Fjern</button>
</div>
<div class="mb-2">
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400">Spørsmål</label>
<input type="text" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white" v-model="faq.question" />
</div>
<div>
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400">Svar</label>
<textarea rows="2" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white" v-model="faq.answer"></textarea>
</div>
</div>
</div>
</div>
<button @click="addFaq" class="mt-2 px-4 py-2 bg-blue-600 text-white text-sm rounded hover:bg-blue-700">
Legg til ny FAQ
</button>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,33 @@
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
block: {
type: Object,
required: true,
},
});
const faqs = props.block.faqs || [
{ question: 'Hva er dette?', answer: 'Dette er et eksempel på et spørsmål og svar.' },
{ question: 'Hvordan fungerer det?', answer: 'Du legger til innhold og publiserer siden.' },
];
</script>
<template>
<section class="relative py-20 md:py-24 overflow-hidden mx-auto max-w-[1280px] bg-black">
<div class="container mx-auto px-6 lg:px-20">
<h2 class="text-amber-600 text-xl md:text-2xl font-serif mb-6">{{ block.title || 'Vanlige spørsmål' }}</h2>
<div class="space-y-4">
<details v-for="(faq, index) in faqs" :key="index" class="bg-gray-700 p-4 rounded-md shadow">
<summary class="cursor-pointer font-medium text-white">{{ faq.question }}</summary>
<p class="mt-2 text-gray-300">{{ faq.answer }}</p>
</details>
</div>
</div>
</section>
</template>
<style scoped>
/* Optional collapse styles */
</style>
@@ -0,0 +1,117 @@
<script setup>
import { defineProps, ref, nextTick, onMounted } from 'vue';
import { gsap } from 'gsap';
import { initFlowbite } from 'flowbite';
import { marked } from 'marked';
onMounted(() => {
initFlowbite();
faqs[0].categoryContent[0].state = true;
});
const faqArticleContainer = ref(null);
const props = defineProps({
block: {
type: Object,
required: true,
},
});
const faqs = props.block.faqs || [
{
categoryName: 'Kategori 1',
categoryContent: [
{ question: 'Spørsmål 1', answer: 'Dette er svar på spørsmål 1', state: false },
{ question: 'Spørsmål 2', answer: 'Dette er svar på spørsmål 2', state: false },
{ question: 'Spørsmål 3', answer: 'Dette er svar på spørsmål 3', state: false }
]
},
{
categoryName: 'Kategori 2',
categoryContent: [
{ question: 'Spørsmål 4', answer: 'Dette er svar på spørsmål 4', state: false },
{ question: 'Spørsmål 5', answer: 'Dette er svar på spørsmål 5', state: false },
{ question: 'Spørsmål 6', answer: 'Dette er svar på spørsmål 6', state: false }
]
}
];
const setFalseExcept = (categoryIndex, qaIndex) => {
faqs.forEach((category, cIdx) => {
category.categoryContent.forEach((qa, qIdx) => {
qa.state = cIdx === categoryIndex && qIdx === qaIndex;
});
});
};
const showFaq = (categoryIndex, qaIndex) => {
gsap.to(faqArticleContainer.value, {y: 200, opacity: 0, onComplete: () => {
setFalseExcept(categoryIndex, qaIndex);
// Wait for the DOM to update
nextTick().then(() => {
// Animate new articles in from the right
gsap.fromTo(faqArticleContainer.value, {y: 200, opacity: 0}, {y: 0, opacity: 1});
});
}});
};
const stateOf = (categoryIndex, qaIndex) => {
return faqs[categoryIndex].categoryContent[qaIndex]?.state;
};
</script>
<template>
<section class="relative py-20 md:py-24 overflow-hidden mx-auto max-w-[1280px] bg-black">
<div class="relative container px-4 mx-auto">
<div class="max-w-7xl mx-auto">
<div class="text-center mb-20">
<span v-if="block.showlabel" class="inline-block py-1 px-3 mb-4 text-xs font-semibold text-orange-900 bg-orange-50 rounded-full">
{{ block.label || 'Skriv inn en tekst for merkelappen' }}
</span>
<h1 v-if="block.showtitle" class="font-heading text-5xl xs:text-6xl md:text-7xl font-bold text-gray-500">
<span>{{ block.title || 'Overskrift' }}&nbsp;</span>
<span class="font-serif italic font-normal">{{ block.titlecursive || 'Overskriftkursiv' }}</span>
</h1>
</div>
<div class="flex flex-wrap -mx-4 -mb-8">
<div class="w-full lg:w-1/3 px-4 mb-15 lg:mb-0">
<div class="flex flex-wrap -mx-2 lg:flex-col lg:max-w-sm border-b lg:border-b-0 lg:border-r border-gray-100">
<div v-for="(faq, index) in faqs" :key="index" class="w-full md:w-1/2 lg:w-full px-2 mb-15">
<h4 class="text-2xl font-semibold mb-6 text-amber-600">{{ faq.categoryName }}</h4>
<ul class="mb-6">
<li v-for="(category, catIndex) in faq.categoryContent" :key="catIndex" class="mb-6">
<button type="button" @click="showFaq(index, catIndex)" class="flex items-center text-lg hover:text-amber-700" :class="stateOf(index, catIndex) ? 'text-amber-700 font-semibold' : 'text-white font-normal'">
<svg width="12" height="12" viewbox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="6" cy="6" r="5" stroke="#C3C6CE" stroke-width="2"></circle>
</svg>
<span class="ml-3">
{{ category.question }}
</span>
</button>
</li>
</ul>
</div>
</div>
</div>
<div class="w-full lg:w-2/3 px-4" ref="faqArticleContainer">
<template v-for="(faq, index) in faqs" :key="index">
<template v-for="(category, catIndex) in faq.categoryContent" :key="catIndex">
<div v-show="stateOf(index, catIndex)" class="max-w-xl xl:max-w-3xl mx-auto lg:mr-0">
<h2 class="text-5xl font-semibold text-gray-500 mb-14">{{ category.question }}</h2>
<div v-if="category.answer" v-html="marked(category.answer)" class="text-gray-300">
</div>
</div>
</template>
</template>
</div>
</div>
</div>
</div>
</section>
</template>
<style scoped>
/* Optional FAQ list styles */
</style>
@@ -0,0 +1,285 @@
<script setup>
import Icon from '@/Components/Icon.vue'
import { ref, toRef, watch } from 'vue';
const props = defineProps({
block: { type: Object, required: true }
});
const emit = defineEmits(['update:block']);
const local = toRef(props, 'block');
const collapsedCategories = ref([]);
watch(local, (val) => {
emit('update:block', val);
}, { deep: true });
watch(() => local.value.faqs, (faqs) => {
if (collapsedCategories.value.length !== faqs.length) {
collapsedCategories.value = faqs.map((_, i) =>
collapsedCategories.value[i] ?? true
);
}
}, { immediate: true, deep: true });
function toggleCategoryCollapse(index) {
collapsedCategories.value[index] = !collapsedCategories.value[index];
};
// Drag state for categories
const draggedCategoryIndex = ref(null);
function handleDragStartCategory(event, index) {
draggedCategoryIndex.value = index;
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', index);
};
function handleDragOverCategory(event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
};
function handleDropCategory(event, index) {
event.preventDefault();
const fromIndex = draggedCategoryIndex.value;
if (fromIndex === null || fromIndex === index) return;
const item = local.value.faqs.splice(fromIndex, 1)[0];
local.value.faqs.splice(index, 0, item);
draggedCategoryIndex.value = null;
};
// End drag state categories
// Add and remove questions
function addFaqCategory() {
local.value.faqs = local.value.faqs || [];
local.value.faqs.push({ categoryName: '', categoryContent: [{}] });
};
function removeFaqCategory(index) {
local.value.faqs.splice(index, 1);
};
// End add and remove categories
// Drag state for questions & answers
const draggedQAIndex = ref(null);
const draggedCategoryForQA = ref(null);
function handleDragStartQA(event, indexQA, categoryIndex) {
draggedQAIndex.value = indexQA;
draggedCategoryForQA.value = categoryIndex;
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', indexQA);
}
function handleDragOverQA(event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}
function handleDropQA(event, indexQA, categoryIndex) {
event.preventDefault();
const fromIndex = draggedQAIndex.value;
const fromCategoryIndex = draggedCategoryForQA.value;
if (
fromIndex === null ||
fromCategoryIndex === null ||
(fromIndex === indexQA && fromCategoryIndex === categoryIndex)
) return;
const sourceCategory = local.value.faqs[fromCategoryIndex];
const targetCategory = local.value.faqs[categoryIndex];
const item = sourceCategory.categoryContent.splice(fromIndex, 1)[0];
targetCategory.categoryContent.splice(indexQA, 0, item);
draggedQAIndex.value = null;
draggedCategoryForQA.value = null;
}
// End drag state for questions & answers
// Add and remove questions & answers
function addQA(categoryIndex) {
local.value.faqs[categoryIndex].categoryContent.push({ question: '', answer: '', state: false });
}
function removeQA(indexQA, categoryIndex) {
local.value.faqs[categoryIndex].categoryContent.splice(indexQA, 1);
}
// End add and remove
</script>
<template>
<div class="space-y-4">
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">DOM ID</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.anchorName"
/>
</div>
<div class="mb-2">
<div class="flex">
<div class="flex items-center h-5">
<input
id="label-checkbox"
aria-describedby="label-checkbox-text"
type="checkbox"
v-model="local.showlabel"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
>
</div>
<div class="ms-2 text-sm">
<label for="label-checkbox" class="font-medium text-gray-900 dark:text-gray-300">Vis merkelapp</label>
<p id="label-checkbox-text" class="text-xs font-normal text-gray-500 dark:text-gray-300">Skru merkelappen øverst på komponenten av, eller på</p>
</div>
</div>
</div>
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Merkelapp tekst</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.label"
:disabled="!local.showlabel"
/>
</div>
<div class="mb-2">
<div class="flex">
<div class="flex items-center h-5">
<input
id="title-checkbox"
aria-describedby="title-checkbox-text"
type="checkbox"
v-model="local.showtitle"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
>
</div>
<div class="ms-2 text-sm">
<label for="title-checkbox" class="font-medium text-gray-900 dark:text-gray-300">Vis tittel</label>
<p id="title-checkbox-text" class="text-xs font-normal text-gray-500 dark:text-gray-300">Skru tittelen på komponenten av, eller på</p>
</div>
</div>
</div>
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tittel tekst</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.title"
:disabled="!local.showtitle"
/>
</div>
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tittel kursiv tekst</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.titlecursive"
:disabled="!local.showtitle"
/>
</div>
<div class="space-y-4">
<div
v-for="(faq, index) in local.faqs"
:key="index"
class="flex items-start gap-2 p-4 rounded border dark:border-gray-600 bg-gray-50 dark:bg-gray-800"
draggable="true"
@dragstart="handleDragStartCategory($event, index)"
@dragover="handleDragOverCategory"
@drop="handleDropCategory($event, index)"
>
<!-- Drag handle -->
<div class="cursor-move mt-1 text-gray-500 dark:text-gray-400">
<Icon name="move" class="w-4 h-4" />
</div>
<!-- Content -->
<div class="flex-1 space-y-2">
<div class="flex justify-between items-center">
<span class="text-sm font-semibold text-gray-700 dark:text-gray-300">FAQ Kategori {{ index + 1 }}</span>
<div class="flex items-center gap-2">
<button
@click="toggleCategoryCollapse(index)"
class="text-blue-500 text-xs hover:underline"
>
{{ collapsedCategories[index] ? 'Vis' : 'Skjul' }}
</button>
<button @click="removeFaqCategory(index)" class="text-red-500 hover:underline text-xs">
Fjern
</button>
</div>
</div>
<div class="mb-2">
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400">Kategori tittel</label>
<input type="text" class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white" v-model="faq.categoryName" />
</div>
<div v-show="!collapsedCategories[index]">
<h5 class="text-xs font-semibold text-gray-600 dark:text-gray-400">Spørsmål & Svar</h5>
<div
v-for="(qa, indexQA) in faq.categoryContent"
:key="indexQA"
class="flex items-start mt-2 gap-2 p-4 rounded border dark:border-gray-600 bg-gray-50 dark:bg-gray-800"
draggable="true"
@dragstart="handleDragStartQA($event, indexQA, index)"
@dragover="handleDragOverQA"
@drop="handleDropQA($event, indexQA, index)"
>
<!-- Drag handle -->
<div class="cursor-move mt-1 text-gray-500 dark:text-gray-400">
<Icon name="move" class="w-4 h-4" />
</div>
<div class="flex-1 space-y-2">
<div class="flex justify-between items-center">
<span class="text-sm font-semibold text-gray-700 dark:text-gray-300">"{{ faq.categoryName }}" spørsmål {{ indexQA + 1 }}</span>
<button @click="removeQA(indexQA, index)" class="text-red-500 hover:underline text-xs">Fjern</button>
</div>
<div class="mb-2">
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400">Spørsmål</label>
<input
type="text"
v-model="qa.question"
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
/>
</div>
<div class="mb-2">
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400">Svar</label>
<textarea
rows="3"
v-model="qa.answer"
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
></textarea>
</div>
</div>
</div>
<button
@click="addQA(index)"
class="mt-2 px-3 py-1 bg-green-600 text-white text-xs rounded hover:bg-green-700"
>
Legg til spørsmål & svar
</button>
</div>
</div>
</div>
<button @click="addFaqCategory" class="mt-2 px-4 py-2 bg-blue-600 text-white text-sm rounded hover:bg-blue-700">
Legg til ny kategori
</button>
</div>
</div>
</template>
@@ -0,0 +1,117 @@
<script setup>
import { defineProps, ref, nextTick, onMounted } from 'vue';
import { gsap } from 'gsap';
import { initFlowbite } from 'flowbite';
import { marked } from 'marked';
onMounted(() => {
initFlowbite();
faqs[0].categoryContent[0].state = true;
});
const faqArticleContainer = ref(null);
const props = defineProps({
block: {
type: Object,
required: true,
},
});
const faqs = props.block.faqs || [
{
categoryName: 'Kategori 1',
categoryContent: [
{ question: 'Spørsmål 1', answer: 'Dette er svar på spørsmål 1', state: false },
{ question: 'Spørsmål 2', answer: 'Dette er svar på spørsmål 2', state: false },
{ question: 'Spørsmål 3', answer: 'Dette er svar på spørsmål 3', state: false }
]
},
{
categoryName: 'Kategori 2',
categoryContent: [
{ question: 'Spørsmål 4', answer: 'Dette er svar på spørsmål 4', state: false },
{ question: 'Spørsmål 5', answer: 'Dette er svar på spørsmål 5', state: false },
{ question: 'Spørsmål 6', answer: 'Dette er svar på spørsmål 6', state: false }
]
}
];
const setFalseExcept = (categoryIndex, qaIndex) => {
faqs.forEach((category, cIdx) => {
category.categoryContent.forEach((qa, qIdx) => {
qa.state = cIdx === categoryIndex && qIdx === qaIndex;
});
});
};
const showFaq = (categoryIndex, qaIndex) => {
gsap.to(faqArticleContainer.value, {y: 200, opacity: 0, onComplete: () => {
setFalseExcept(categoryIndex, qaIndex);
// Wait for the DOM to update
nextTick().then(() => {
// Animate new articles in from the right
gsap.fromTo(faqArticleContainer.value, {y: 200, opacity: 0}, {y: 0, opacity: 1});
});
}});
};
const stateOf = (categoryIndex, qaIndex) => {
return faqs[categoryIndex].categoryContent[qaIndex]?.state;
};
</script>
<template>
<section :id="block.anchorName" class="relative py-20 md:py-24 overflow-hidden mx-auto max-w-[1280px] bg-black">
<div class="relative container px-4 mx-auto">
<div class="max-w-7xl mx-auto">
<div class="text-center mb-20">
<span v-if="block.showlabel" class="inline-block py-1 px-3 mb-4 text-xs font-semibold text-orange-900 bg-orange-50 rounded-full">
{{ block.label || 'Skriv inn en tekst for merkelappen' }}
</span>
<h1 v-if="block.showtitle" class="font-heading text-5xl xs:text-6xl md:text-7xl font-bold text-gray-500">
<span>{{ block.title || 'Overskrift' }}&nbsp;</span>
<span class="font-serif italic font-normal">{{ block.titlecursive || 'Overskriftkursiv' }}</span>
</h1>
</div>
<div class="flex flex-wrap -mx-4 -mb-8">
<div class="w-full lg:w-1/3 px-4 mb-15 lg:mb-0">
<div class="flex flex-wrap -mx-2 lg:flex-col lg:max-w-sm border-b lg:border-b-0 lg:border-r border-gray-100">
<div v-for="(faq, index) in faqs" :key="index" class="w-full md:w-1/2 lg:w-full px-2 mb-15">
<h4 class="text-2xl font-semibold mb-6 text-amber-600">{{ faq.categoryName }}</h4>
<ul class="mb-6">
<li v-for="(category, catIndex) in faq.categoryContent" :key="catIndex" class="mb-6">
<button type="button" @click="showFaq(index, catIndex)" class="flex items-center text-lg hover:text-amber-700" :class="stateOf(index, catIndex) ? 'text-amber-700 font-semibold' : 'text-white font-normal'">
<svg width="12" height="12" viewbox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="6" cy="6" r="5" stroke="#C3C6CE" stroke-width="2"></circle>
</svg>
<span class="ml-3">
{{ category.question }}
</span>
</button>
</li>
</ul>
</div>
</div>
</div>
<div class="w-full lg:w-2/3 px-4" ref="faqArticleContainer">
<template v-for="(faq, index) in faqs" :key="index">
<template v-for="(category, catIndex) in faq.categoryContent" :key="catIndex">
<div v-show="stateOf(index, catIndex)" class="max-w-xl xl:max-w-3xl mx-auto lg:mr-0">
<h2 class="text-5xl font-semibold text-gray-500 mb-14">{{ category.question }}</h2>
<div v-if="category.answer" v-html="marked(category.answer)" class="text-gray-300">
</div>
</div>
</template>
</template>
</div>
</div>
</div>
</div>
</section>
</template>
<style scoped>
/* Optional FAQ list styles */
</style>
@@ -0,0 +1,33 @@
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
block: {
type: Object,
required: true,
},
});
const faqs = props.block.faqs || [
{ question: 'Hva er dette?', answer: 'Dette er et eksempel på et spørsmål og svar.' },
{ question: 'Hvordan fungerer det?', answer: 'Du legger til innhold og publiserer siden.' },
];
</script>
<template>
<section class="relative py-20 md:py-24 overflow-hidden mx-auto max-w-[1280px] bg-black">
<div class="container mx-auto px-6 lg:px-20">
<h2 class="text-amber-600 text-xl md:text-2xl font-serif mb-6">{{ block.title || 'Vanlige spørsmål' }}</h2>
<ul class="space-y-4">
<li v-for="(faq, index) in faqs" :key="index" class="border-b pb-4">
<h3 class="text-lg font-semibold text-gray-200">{{ faq.question }}</h3>
<p class="text-gray-300">{{ faq.answer }}</p>
</li>
</ul>
</div>
</section>
</template>
<style scoped>
/* Optional FAQ list styles */
</style>
@@ -0,0 +1,99 @@
<script setup>
import Icon from '@/Components/Icon.vue'
import { ref, toRef, watch } from 'vue';
const props = defineProps({
block: { type: Object, required: true }
});
const emit = defineEmits(['update:block']);
const local = toRef(props, 'block');
// Drag state
const draggedIndex = ref(null);
function handleDragStart(event, index) {
draggedIndex.value = index;
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', index);
};
function handleDragOver(event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
};
function handleDrop(event, index) {
event.preventDefault();
const fromIndex = draggedIndex.value;
if (fromIndex === null || fromIndex === index) return;
const item = local.value.faqs.splice(fromIndex, 1)[0];
local.value.faqs.splice(index, 0, item);
draggedIndex.value = null;
};
// End drag state
watch(local, (val) => {
emit('update:block', val);
}, { deep: true });
function addFaq() {
local.value.faqs = local.value.faqs || [];
local.value.faqs.push({ question: '', answer: '' });
};
function removeFaq(index) {
local.value.faqs.splice(index, 1);
};
</script>
<template>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tittel for seksjonen</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.title"
/>
</div>
<div class="space-y-4">
<div
v-for="(faq, index) in local.faqs"
:key="index"
class="flex items-start gap-2 p-4 rounded border dark:border-gray-600 bg-gray-50 dark:bg-gray-800"
draggable="true"
@dragstart="handleDragStart($event, index)"
@dragover="handleDragOver"
@drop="handleDrop($event, index)"
>
<!-- Drag handle -->
<div class="cursor-move mt-1 text-gray-500 dark:text-gray-400">
<Icon name="move" class="w-4 h-4" />
</div>
<!-- Content -->
<div class="flex-1 space-y-2">
<div class="flex justify-between items-center">
<span class="text-sm font-semibold text-gray-700 dark:text-gray-300">FAQ {{ index + 1 }}</span>
<button @click="removeFaq(index)" class="text-red-500 hover:underline text-xs">Fjern</button>
</div>
<div class="mb-2">
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400">Spørsmål</label>
<input type="text" class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white" v-model="faq.question" />
</div>
<div>
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400">Svar</label>
<textarea rows="2" class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white" v-model="faq.answer"></textarea>
</div>
</div>
</div>
</div>
</div>
</template>
@@ -0,0 +1,33 @@
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
block: {
type: Object,
required: true,
},
});
const faqs = props.block.faqs || [
{ question: 'Hva er dette?', answer: 'Dette er et eksempel på et spørsmål og svar.' },
{ question: 'Hvordan fungerer det?', answer: 'Du legger til innhold og publiserer siden.' },
];
</script>
<template>
<section class="relative py-20 md:py-24 overflow-hidden mx-auto max-w-[1280px] bg-black">
<div class="container mx-auto px-6 lg:px-20">
<h2 class="text-amber-600 text-xl md:text-2xl font-serif mb-6">{{ block.title || 'Vanlige spørsmål' }}</h2>
<ul class="space-y-4">
<li v-for="(faq, index) in faqs" :key="index" class="border-b pb-4">
<h3 class="text-lg font-semibold text-gray-200">{{ faq.question }}</h3>
<p class="text-gray-300">{{ faq.answer }}</p>
</li>
</ul>
</div>
</section>
</template>
<style scoped>
/* Optional FAQ list styles */
</style>
@@ -0,0 +1,110 @@
<script setup>
import { onMounted, defineProps } from 'vue';
import { Link, router } from '@inertiajs/vue3';
import { initFlowbite } from 'flowbite';
import ArcticLogo from '@/Components/ClubLogo.vue';
onMounted(() => {
initFlowbite();
});
const props = defineProps({
block: {
type: Object,
required: true,
},
});
const texts = props.block.texts || [
{ paragraph: 'Trenger du et enkelt og fleksibelt system for å organisere din klubb og klubbaktiviteter, uten å knekke lommeboka? Ønsker du et partnerskap med en utvikler med tilhold i Norge og som er lett tilgjengelig dersom du har behov for oppfølging? Kontakt oss for mer informasjon om hvordan vi kan hjelpe deg og din klubb' }
];
</script>
<template>
<section class="relative py-20 md:py-24 overflow-hidden mx-auto max-w-[1280px] bg-black">
<div class="relative container px-4 mx-auto">
<div class="max-w-2xl lg:max-w-3xl xl:max-w-5xl xl:pr-20">
<h1 v-show="block.showtitle" class="font-heading text-4xl sm:text-5xl lg:text-6xl xl:text-7xl font-bold text-gray-500 mb-10">
<span>{{ block.title || 'Overskrift' }}&nbsp;</span>
<span class="font-serif italic font-normal">{{ block.titlecursive || 'Overskriftkursiv' }}</span>
</h1>
<p
v-for="(text, index) in texts"
:key="index"
class="max-w-xl text-base sm:text-xl text-gray-300 font-semibold"
:class="{
'mt-4': index > 0,
'mb-10': index === texts.length - 1
}"
>
{{ text.paragraph }}
</p>
<div class="lg:flex items-center">
<a v-if="props.block.showbutton && props.block.buttonaction === 'none'" class="relative group inline-block mb-12 lg:mb-0 md:mr-10 lg:mr-18 py-4 px-6 text-white font-semibold bg-orange-900 rounded-full overflow-hidden" href="#">
<div class="absolute top-0 right-full w-full h-full bg-gray-900 transform group-hover:translate-x-full group-hover:scale-102 transition duration-500"></div>
<div class="relative flex items-center justify-center">
<span class="mr-4">{{ props.block.buttontext }}</span>
<span>
<svg width="8" height="12" viewbox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.83 5.28999L2.59 1.04999C2.49704 0.956266 2.38644 0.881872 2.26458 0.831103C2.14272 0.780334 2.01202 0.754196 1.88 0.754196C1.74799 0.754196 1.61729 0.780334 1.49543 0.831103C1.37357 0.881872 1.26297 0.956266 1.17 1.04999C0.983753 1.23736 0.879211 1.49081 0.879211 1.75499C0.879211 2.01918 0.983753 2.27263 1.17 2.45999L4.71 5.99999L1.17 9.53999C0.983753 9.72736 0.879211 9.98081 0.879211 10.245C0.879211 10.5092 0.983753 10.7626 1.17 10.95C1.26344 11.0427 1.37426 11.116 1.4961 11.1658C1.61794 11.2155 1.7484 11.2408 1.88 11.24C2.01161 11.2408 2.14207 11.2155 2.26391 11.1658C2.38575 11.116 2.49656 11.0427 2.59 10.95L6.83 6.70999C6.92373 6.61703 6.99813 6.50643 7.04889 6.38457C7.09966 6.26271 7.1258 6.13201 7.1258 5.99999C7.1258 5.86798 7.09966 5.73728 7.04889 5.61542C6.99813 5.49356 6.92373 5.38296 6.83 5.28999Z" fill="#FAFBFC"></path>
</svg>
</span>
</div>
</a>
<a v-else-if="props.block.showbutton && props.block.buttonaction === 'external_link'" class="relative group inline-block mb-12 lg:mb-0 md:mr-10 lg:mr-18 py-4 px-6 text-white font-semibold bg-orange-900 rounded-full overflow-hidden" :href="props.block.buttonlink" target="_new">
<div class="absolute top-0 right-full w-full h-full bg-gray-900 transform group-hover:translate-x-full group-hover:scale-102 transition duration-500"></div>
<div class="relative flex items-center justify-center">
<span class="mr-4">{{ props.block.buttontext }}</span>
<span>
<svg width="8" height="12" viewbox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.83 5.28999L2.59 1.04999C2.49704 0.956266 2.38644 0.881872 2.26458 0.831103C2.14272 0.780334 2.01202 0.754196 1.88 0.754196C1.74799 0.754196 1.61729 0.780334 1.49543 0.831103C1.37357 0.881872 1.26297 0.956266 1.17 1.04999C0.983753 1.23736 0.879211 1.49081 0.879211 1.75499C0.879211 2.01918 0.983753 2.27263 1.17 2.45999L4.71 5.99999L1.17 9.53999C0.983753 9.72736 0.879211 9.98081 0.879211 10.245C0.879211 10.5092 0.983753 10.7626 1.17 10.95C1.26344 11.0427 1.37426 11.116 1.4961 11.1658C1.61794 11.2155 1.7484 11.2408 1.88 11.24C2.01161 11.2408 2.14207 11.2155 2.26391 11.1658C2.38575 11.116 2.49656 11.0427 2.59 10.95L6.83 6.70999C6.92373 6.61703 6.99813 6.50643 7.04889 6.38457C7.09966 6.26271 7.1258 6.13201 7.1258 5.99999C7.1258 5.86798 7.09966 5.73728 7.04889 5.61542C6.99813 5.49356 6.92373 5.38296 6.83 5.28999Z" fill="#FAFBFC"></path>
</svg>
</span>
</div>
</a>
<Link v-else-if="props.block.showbutton && props.block.buttonaction === 'newslink'" class="relative group inline-block mb-12 lg:mb-0 md:mr-10 lg:mr-18 py-4 px-6 text-white font-semibold bg-orange-900 rounded-full overflow-hidden" :href="route('newsArticle', props.block.buttonlink)">
<div class="absolute top-0 right-full w-full h-full bg-gray-900 transform group-hover:translate-x-full group-hover:scale-102 transition duration-500"></div>
<div class="relative flex items-center justify-center">
<span class="mr-4">{{ props.block.buttontext }}</span>
<span>
<svg width="8" height="12" viewbox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.83 5.28999L2.59 1.04999C2.49704 0.956266 2.38644 0.881872 2.26458 0.831103C2.14272 0.780334 2.01202 0.754196 1.88 0.754196C1.74799 0.754196 1.61729 0.780334 1.49543 0.831103C1.37357 0.881872 1.26297 0.956266 1.17 1.04999C0.983753 1.23736 0.879211 1.49081 0.879211 1.75499C0.879211 2.01918 0.983753 2.27263 1.17 2.45999L4.71 5.99999L1.17 9.53999C0.983753 9.72736 0.879211 9.98081 0.879211 10.245C0.879211 10.5092 0.983753 10.7626 1.17 10.95C1.26344 11.0427 1.37426 11.116 1.4961 11.1658C1.61794 11.2155 1.7484 11.2408 1.88 11.24C2.01161 11.2408 2.14207 11.2155 2.26391 11.1658C2.38575 11.116 2.49656 11.0427 2.59 10.95L6.83 6.70999C6.92373 6.61703 6.99813 6.50643 7.04889 6.38457C7.09966 6.26271 7.1258 6.13201 7.1258 5.99999C7.1258 5.86798 7.09966 5.73728 7.04889 5.61542C6.99813 5.49356 6.92373 5.38296 6.83 5.28999Z" fill="#FAFBFC"></path>
</svg>
</span>
</div>
</Link>
<Link v-else-if="props.block.showbutton && props.block.buttonaction === 'eventlink'" class="relative group inline-block mb-12 lg:mb-0 md:mr-10 lg:mr-18 py-4 px-6 text-white font-semibold bg-orange-900 rounded-full overflow-hidden" :href="route('event', props.block.buttonlink)">
<div class="absolute top-0 right-full w-full h-full bg-gray-900 transform group-hover:translate-x-full group-hover:scale-102 transition duration-500"></div>
<div class="relative flex items-center justify-center">
<span class="mr-4">{{ props.block.buttontext }}</span>
<span>
<svg width="8" height="12" viewbox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.83 5.28999L2.59 1.04999C2.49704 0.956266 2.38644 0.881872 2.26458 0.831103C2.14272 0.780334 2.01202 0.754196 1.88 0.754196C1.74799 0.754196 1.61729 0.780334 1.49543 0.831103C1.37357 0.881872 1.26297 0.956266 1.17 1.04999C0.983753 1.23736 0.879211 1.49081 0.879211 1.75499C0.879211 2.01918 0.983753 2.27263 1.17 2.45999L4.71 5.99999L1.17 9.53999C0.983753 9.72736 0.879211 9.98081 0.879211 10.245C0.879211 10.5092 0.983753 10.7626 1.17 10.95C1.26344 11.0427 1.37426 11.116 1.4961 11.1658C1.61794 11.2155 1.7484 11.2408 1.88 11.24C2.01161 11.2408 2.14207 11.2155 2.26391 11.1658C2.38575 11.116 2.49656 11.0427 2.59 10.95L6.83 6.70999C6.92373 6.61703 6.99813 6.50643 7.04889 6.38457C7.09966 6.26271 7.1258 6.13201 7.1258 5.99999C7.1258 5.86798 7.09966 5.73728 7.04889 5.61542C6.99813 5.49356 6.92373 5.38296 6.83 5.28999Z" fill="#FAFBFC"></path>
</svg>
</span>
</div>
</Link>
<Link v-else-if="props.block.showbutton && props.block.buttonaction === 'pagelink'" class="relative group inline-block mb-12 lg:mb-0 md:mr-10 lg:mr-18 py-4 px-6 text-white font-semibold bg-orange-900 rounded-full overflow-hidden" :href="route('page.view', props.block.buttonlink)">
<div class="absolute top-0 right-full w-full h-full bg-gray-900 transform group-hover:translate-x-full group-hover:scale-102 transition duration-500"></div>
<div class="relative flex items-center justify-center">
<span class="mr-4">{{ props.block.buttontext }}</span>
<span>
<svg width="8" height="12" viewbox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.83 5.28999L2.59 1.04999C2.49704 0.956266 2.38644 0.881872 2.26458 0.831103C2.14272 0.780334 2.01202 0.754196 1.88 0.754196C1.74799 0.754196 1.61729 0.780334 1.49543 0.831103C1.37357 0.881872 1.26297 0.956266 1.17 1.04999C0.983753 1.23736 0.879211 1.49081 0.879211 1.75499C0.879211 2.01918 0.983753 2.27263 1.17 2.45999L4.71 5.99999L1.17 9.53999C0.983753 9.72736 0.879211 9.98081 0.879211 10.245C0.879211 10.5092 0.983753 10.7626 1.17 10.95C1.26344 11.0427 1.37426 11.116 1.4961 11.1658C1.61794 11.2155 1.7484 11.2408 1.88 11.24C2.01161 11.2408 2.14207 11.2155 2.26391 11.1658C2.38575 11.116 2.49656 11.0427 2.59 10.95L6.83 6.70999C6.92373 6.61703 6.99813 6.50643 7.04889 6.38457C7.09966 6.26271 7.1258 6.13201 7.1258 5.99999C7.1258 5.86798 7.09966 5.73728 7.04889 5.61542C6.99813 5.49356 6.92373 5.38296 6.83 5.28999Z" fill="#FAFBFC"></path>
</svg>
</span>
</div>
</Link>
<div v-show="props.block.showlogo" class="flex items-center">
<ArcticLogo class="w-14 h-14" />
<div class="flex-col items-center justify-center text-center">
<p class="font-topsecret text-white text-lg ml-2">Østfold Milsim</p>
<p class="font-topsecret text-white text-lg ml-2">Airsoftklubb</p>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
@@ -0,0 +1,226 @@
<script setup>
import Icon from '@/Components/Icon.vue';
import { toRef, ref, onMounted } from 'vue';
import { initFlowbite } from 'flowbite';
onMounted(() => {
initFlowbite();
});
const props = defineProps({
block: {
type: Object,
required: true,
},
});
const emit = defineEmits(['update:block']);
const local = toRef(props, 'block');
// Drag state for paragraphs
const draggedIndex = ref(null);
function handleDragStart(event, index) {
draggedIndex.value = index;
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', index);
};
function handleDragOver(event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
};
function handleDrop(event, index) {
event.preventDefault();
const fromIndex = draggedIndex.value;
if (fromIndex === null || fromIndex === index) return;
const item = local.value.texts.splice(fromIndex, 1)[0];
local.value.texts.splice(index, 0, item);
draggedIndex.value = null;
};
function add() {
local.value.texts.push({
paragraph: ''
});
};
function remove(index) {
local.value.texts.splice(index, 1);
}
// End drag state functions for paragraphs
const texts = props.block.texts || [
{ paragraph: 'Trenger du et enkelt og fleksibelt system for å organisere din klubb og klubbaktiviteter, uten å knekke lommeboka? Ønsker du et partnerskap med en utvikler med tilhold i Norge og som er lett tilgjengelig dersom du har behov for oppfølging? Kontakt oss for mer informasjon om hvordan vi kan hjelpe deg og din klubb' }
];
</script>
<template>
<div class="space-y-4">
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">DOM ID</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.anchorName"
/>
</div>
<div class="mb-2">
<div class="flex">
<div class="flex items-center h-5">
<input
id="title-checkbox"
aria-describedby="title-checkbox-text"
type="checkbox"
v-model="local.showtitle"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
>
</div>
<div class="ms-2 text-sm">
<label for="title-checkbox" class="font-medium text-gray-900 dark:text-gray-300">Vis tittel</label>
<p id="title-checkbox-text" class="text-xs font-normal text-gray-500 dark:text-gray-300">Skru tittelen på komponenten av, eller på</p>
</div>
</div>
</div>
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tittel tekst</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.title"
:disabled="!local.showtitle"
/>
</div>
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tittel kursiv tekst</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.titlecursive"
:disabled="!local.showtitle"
/>
</div>
<div class="mb-2">
<div class="flex">
<div class="flex items-center h-5">
<input
id="button-checkbox"
aria-describedby="button-checkbox-text"
type="checkbox"
v-model="local.showbutton"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
>
</div>
<div class="ms-2 text-sm">
<label for="button-checkbox" class="font-medium text-gray-900 dark:text-gray-300">Vis knapp</label>
<p id="button-checkbox-text" class="text-xs font-normal text-gray-500 dark:text-gray-300">Skru handlingsknappen for komponenten av, eller på</p>
</div>
</div>
</div>
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tittel knapp</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.buttontext"
:disabled="!local.showbutton"
/>
</div>
<div class="mb-2">
<label for="actiontype" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Velg knapp handling</label>
<select
id="actiontype"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
v-model="local.buttonaction"
:disabled="!local.showbutton"
>
<option value="none">Inaktiv</option>
<option value="newslink">Lenke til nyhetsartikkel</option>
<option value="eventlink">Lenke til arrangement</option>
<option value="pagelink">Lenke til en annen side</option>
<option value="external_link">Ekstern lenke</option>
</select>
</div>
<div v-show="local.buttonaction != 'none'" class="mb-2">
<label for="address-text" class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">Adresse</label>
<input
type="email"
id="address-text"
aria-describedby="address-text-explanation"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
:placeholder="local.buttonaction === 'newslink' ? 'slug-til-nyhetsartikkel'
: local.buttonaction === 'eventlink' ? 'slug-til-arrangement'
: local.buttonaction === 'pagelink' ? 'slug-til-side'
: local.buttonaction === 'external_link' ? 'https://ekstern.lenke.no'
: ''"
:disabled="!local.showbutton"
v-model="local.buttonlink"
>
<p id="address-text-explanation" class="mt-2 text-sm text-gray-500 dark:text-gray-400">Skriv inn enten identifikatoren (slug) til den interne lenken, eller full nettsideadresse hvis det er en ekstern lenke</p>
</div>
<div class="mb-2">
<div class="flex">
<div class="flex items-center h-5">
<input
id="logo-checkbox"
aria-describedby="logo-checkbox-text"
type="checkbox"
v-model="local.showlogo"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
>
</div>
<div class="ms-2 text-sm">
<label for="logo-checkbox" class="font-medium text-gray-900 dark:text-gray-300">Vis logo</label>
<p id="logo-checkbox-text" class="text-xs font-normal text-gray-500 dark:text-gray-300">Skru logovisning i komponenten av, eller på</p>
</div>
</div>
</div>
<div class="space-y-4">
<h3 class="font-bold text-sm text-gray-800 dark:text-white">
Tekstparagrafer
</h3>
</div>
<div class="space-y-4">
<div
v-for="(text, index) in local.texts"
:key="index"
class="flex items-start gap-2 p-4 rounded border dark:border-gray-600 bg-gray-50 dark:bg-gray-800"
draggable="true"
@dragstart="handleDragStart($event, index)"
@dragover="handleDragOver"
@drop="handleDrop($event, index)"
>
<div class="cursor-move mt-1 text-gray-500 dark:text-gray-400">
<Icon name="move" class="w-4 h-4" />
</div>
<div class="flex-1 space-y-2">
<div class="flex justify-between items-center">
<span class="text-sm font-semibold text-gray-700 dark:text-gray-300">Paragraf {{ index + 1 }}</span>
<button @click="remove(index)" class="text-red-500 hover:underline text-xs">Fjern</button>
</div>
<div>
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400">Tekst</label>
<textarea rows="2" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white" v-model="text.paragraph"></textarea>
</div>
</div>
</div>
</div>
<button @click="add" class="mt-2 px-4 py-2 bg-blue-600 text-white text-sm rounded hover:bg-blue-700">
Legg til ny paragraf
</button>
</div>
</template>
<style scoped>
/* Optional FAQ list styles */
</style>
@@ -0,0 +1,110 @@
<script setup>
import { onMounted, defineProps } from 'vue';
import { Link, router } from '@inertiajs/vue3';
import { initFlowbite } from 'flowbite';
import ArcticLogo from '@/Components/ArcticFooterLogo.vue';
onMounted(() => {
initFlowbite();
});
const props = defineProps({
block: {
type: Object,
required: true,
},
});
const texts = props.block.texts || [
{ paragraph: 'Trenger du et enkelt og fleksibelt system for å organisere din klubb og klubbaktiviteter, uten å knekke lommeboka? Ønsker du et partnerskap med en utvikler med tilhold i Norge og som er lett tilgjengelig dersom du har behov for oppfølging? Kontakt oss for mer informasjon om hvordan vi kan hjelpe deg og din klubb' }
];
</script>
<template>
<section :id="block.anchorName" class="relative py-20 md:py-24 overflow-hidden mx-auto max-w-[1280px] bg-black">
<div class="relative container px-4 mx-auto">
<div class="max-w-2xl lg:max-w-3xl xl:max-w-5xl xl:pr-20">
<h1 v-show="block.showtitle" class="font-heading text-4xl sm:text-5xl lg:text-6xl xl:text-7xl font-bold text-gray-500 mb-10">
<span>{{ block.title || 'Overskrift' }}&nbsp;</span>
<span class="font-serif italic font-normal">{{ block.titlecursive || 'Overskriftkursiv' }}</span>
</h1>
<p
v-for="(text, index) in texts"
:key="index"
class="max-w-xl text-base sm:text-xl text-gray-300 font-semibold"
:class="{
'mt-4': index > 0,
'mb-10': index === texts.length - 1
}"
>
{{ text.paragraph }}
</p>
<div class="lg:flex items-center">
<a v-if="props.block.showbutton && props.block.buttonaction === 'none'" class="relative group inline-block mb-12 lg:mb-0 md:mr-10 lg:mr-18 py-4 px-6 text-white font-semibold bg-orange-900 rounded-full overflow-hidden" href="#">
<div class="absolute top-0 right-full w-full h-full bg-gray-900 transform group-hover:translate-x-full group-hover:scale-102 transition duration-500"></div>
<div class="relative flex items-center justify-center">
<span class="mr-4">{{ props.block.buttontext }}</span>
<span>
<svg width="8" height="12" viewbox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.83 5.28999L2.59 1.04999C2.49704 0.956266 2.38644 0.881872 2.26458 0.831103C2.14272 0.780334 2.01202 0.754196 1.88 0.754196C1.74799 0.754196 1.61729 0.780334 1.49543 0.831103C1.37357 0.881872 1.26297 0.956266 1.17 1.04999C0.983753 1.23736 0.879211 1.49081 0.879211 1.75499C0.879211 2.01918 0.983753 2.27263 1.17 2.45999L4.71 5.99999L1.17 9.53999C0.983753 9.72736 0.879211 9.98081 0.879211 10.245C0.879211 10.5092 0.983753 10.7626 1.17 10.95C1.26344 11.0427 1.37426 11.116 1.4961 11.1658C1.61794 11.2155 1.7484 11.2408 1.88 11.24C2.01161 11.2408 2.14207 11.2155 2.26391 11.1658C2.38575 11.116 2.49656 11.0427 2.59 10.95L6.83 6.70999C6.92373 6.61703 6.99813 6.50643 7.04889 6.38457C7.09966 6.26271 7.1258 6.13201 7.1258 5.99999C7.1258 5.86798 7.09966 5.73728 7.04889 5.61542C6.99813 5.49356 6.92373 5.38296 6.83 5.28999Z" fill="#FAFBFC"></path>
</svg>
</span>
</div>
</a>
<a v-else-if="props.block.showbutton && props.block.buttonaction === 'external_link'" class="relative group inline-block mb-12 lg:mb-0 md:mr-10 lg:mr-18 py-4 px-6 text-white font-semibold bg-orange-900 rounded-full overflow-hidden" :href="props.block.buttonlink" target="_new">
<div class="absolute top-0 right-full w-full h-full bg-gray-900 transform group-hover:translate-x-full group-hover:scale-102 transition duration-500"></div>
<div class="relative flex items-center justify-center">
<span class="mr-4">{{ props.block.buttontext }}</span>
<span>
<svg width="8" height="12" viewbox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.83 5.28999L2.59 1.04999C2.49704 0.956266 2.38644 0.881872 2.26458 0.831103C2.14272 0.780334 2.01202 0.754196 1.88 0.754196C1.74799 0.754196 1.61729 0.780334 1.49543 0.831103C1.37357 0.881872 1.26297 0.956266 1.17 1.04999C0.983753 1.23736 0.879211 1.49081 0.879211 1.75499C0.879211 2.01918 0.983753 2.27263 1.17 2.45999L4.71 5.99999L1.17 9.53999C0.983753 9.72736 0.879211 9.98081 0.879211 10.245C0.879211 10.5092 0.983753 10.7626 1.17 10.95C1.26344 11.0427 1.37426 11.116 1.4961 11.1658C1.61794 11.2155 1.7484 11.2408 1.88 11.24C2.01161 11.2408 2.14207 11.2155 2.26391 11.1658C2.38575 11.116 2.49656 11.0427 2.59 10.95L6.83 6.70999C6.92373 6.61703 6.99813 6.50643 7.04889 6.38457C7.09966 6.26271 7.1258 6.13201 7.1258 5.99999C7.1258 5.86798 7.09966 5.73728 7.04889 5.61542C6.99813 5.49356 6.92373 5.38296 6.83 5.28999Z" fill="#FAFBFC"></path>
</svg>
</span>
</div>
</a>
<Link v-else-if="props.block.showbutton && props.block.buttonaction === 'newslink'" class="relative group inline-block mb-12 lg:mb-0 md:mr-10 lg:mr-18 py-4 px-6 text-white font-semibold bg-orange-900 rounded-full overflow-hidden" :href="route('newsArticle', props.block.buttonlink)">
<div class="absolute top-0 right-full w-full h-full bg-gray-900 transform group-hover:translate-x-full group-hover:scale-102 transition duration-500"></div>
<div class="relative flex items-center justify-center">
<span class="mr-4">{{ props.block.buttontext }}</span>
<span>
<svg width="8" height="12" viewbox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.83 5.28999L2.59 1.04999C2.49704 0.956266 2.38644 0.881872 2.26458 0.831103C2.14272 0.780334 2.01202 0.754196 1.88 0.754196C1.74799 0.754196 1.61729 0.780334 1.49543 0.831103C1.37357 0.881872 1.26297 0.956266 1.17 1.04999C0.983753 1.23736 0.879211 1.49081 0.879211 1.75499C0.879211 2.01918 0.983753 2.27263 1.17 2.45999L4.71 5.99999L1.17 9.53999C0.983753 9.72736 0.879211 9.98081 0.879211 10.245C0.879211 10.5092 0.983753 10.7626 1.17 10.95C1.26344 11.0427 1.37426 11.116 1.4961 11.1658C1.61794 11.2155 1.7484 11.2408 1.88 11.24C2.01161 11.2408 2.14207 11.2155 2.26391 11.1658C2.38575 11.116 2.49656 11.0427 2.59 10.95L6.83 6.70999C6.92373 6.61703 6.99813 6.50643 7.04889 6.38457C7.09966 6.26271 7.1258 6.13201 7.1258 5.99999C7.1258 5.86798 7.09966 5.73728 7.04889 5.61542C6.99813 5.49356 6.92373 5.38296 6.83 5.28999Z" fill="#FAFBFC"></path>
</svg>
</span>
</div>
</Link>
<Link v-else-if="props.block.showbutton && props.block.buttonaction === 'eventlink'" class="relative group inline-block mb-12 lg:mb-0 md:mr-10 lg:mr-18 py-4 px-6 text-white font-semibold bg-orange-900 rounded-full overflow-hidden" :href="route('event', props.block.buttonlink)">
<div class="absolute top-0 right-full w-full h-full bg-gray-900 transform group-hover:translate-x-full group-hover:scale-102 transition duration-500"></div>
<div class="relative flex items-center justify-center">
<span class="mr-4">{{ props.block.buttontext }}</span>
<span>
<svg width="8" height="12" viewbox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.83 5.28999L2.59 1.04999C2.49704 0.956266 2.38644 0.881872 2.26458 0.831103C2.14272 0.780334 2.01202 0.754196 1.88 0.754196C1.74799 0.754196 1.61729 0.780334 1.49543 0.831103C1.37357 0.881872 1.26297 0.956266 1.17 1.04999C0.983753 1.23736 0.879211 1.49081 0.879211 1.75499C0.879211 2.01918 0.983753 2.27263 1.17 2.45999L4.71 5.99999L1.17 9.53999C0.983753 9.72736 0.879211 9.98081 0.879211 10.245C0.879211 10.5092 0.983753 10.7626 1.17 10.95C1.26344 11.0427 1.37426 11.116 1.4961 11.1658C1.61794 11.2155 1.7484 11.2408 1.88 11.24C2.01161 11.2408 2.14207 11.2155 2.26391 11.1658C2.38575 11.116 2.49656 11.0427 2.59 10.95L6.83 6.70999C6.92373 6.61703 6.99813 6.50643 7.04889 6.38457C7.09966 6.26271 7.1258 6.13201 7.1258 5.99999C7.1258 5.86798 7.09966 5.73728 7.04889 5.61542C6.99813 5.49356 6.92373 5.38296 6.83 5.28999Z" fill="#FAFBFC"></path>
</svg>
</span>
</div>
</Link>
<Link v-else-if="props.block.showbutton && props.block.buttonaction === 'pagelink'" class="relative group inline-block mb-12 lg:mb-0 md:mr-10 lg:mr-18 py-4 px-6 text-white font-semibold bg-orange-900 rounded-full overflow-hidden" :href="route('page.view', props.block.buttonlink)">
<div class="absolute top-0 right-full w-full h-full bg-gray-900 transform group-hover:translate-x-full group-hover:scale-102 transition duration-500"></div>
<div class="relative flex items-center justify-center">
<span class="mr-4">{{ props.block.buttontext }}</span>
<span>
<svg width="8" height="12" viewbox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.83 5.28999L2.59 1.04999C2.49704 0.956266 2.38644 0.881872 2.26458 0.831103C2.14272 0.780334 2.01202 0.754196 1.88 0.754196C1.74799 0.754196 1.61729 0.780334 1.49543 0.831103C1.37357 0.881872 1.26297 0.956266 1.17 1.04999C0.983753 1.23736 0.879211 1.49081 0.879211 1.75499C0.879211 2.01918 0.983753 2.27263 1.17 2.45999L4.71 5.99999L1.17 9.53999C0.983753 9.72736 0.879211 9.98081 0.879211 10.245C0.879211 10.5092 0.983753 10.7626 1.17 10.95C1.26344 11.0427 1.37426 11.116 1.4961 11.1658C1.61794 11.2155 1.7484 11.2408 1.88 11.24C2.01161 11.2408 2.14207 11.2155 2.26391 11.1658C2.38575 11.116 2.49656 11.0427 2.59 10.95L6.83 6.70999C6.92373 6.61703 6.99813 6.50643 7.04889 6.38457C7.09966 6.26271 7.1258 6.13201 7.1258 5.99999C7.1258 5.86798 7.09966 5.73728 7.04889 5.61542C6.99813 5.49356 6.92373 5.38296 6.83 5.28999Z" fill="#FAFBFC"></path>
</svg>
</span>
</div>
</Link>
<div v-show="props.block.showlogo" class="flex items-center">
<ArcticLogo class="w-14 h-14" />
<div class="flex-col items-center justify-center text-center">
<p class="font-topsecret text-white text-lg ml-2">Østfold Milsim</p>
<p class="font-topsecret text-white text-lg ml-2">Airsoftklubb</p>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
@@ -0,0 +1,85 @@
<script setup>
import { defineProps } from 'vue';
import { usePage } from '@inertiajs/vue3';
const props = defineProps({
block: {
type: Object,
required: true,
},
});
const page = usePage();
const events = [
{
headline: 'Familiekveld hos ENHET',
text: 'Ta med familien for en hyggelig kveld med mat, drikke og underholdning.',
time: '15/04/2025, klokken 12:31'
},
{
headline: 'ENHETs Veldedighetsløp for Lokalsamfunnet',
text: 'Dette arrangementet kombinerer fysisk aktivitet med en god sak. ENHETs spillere og støttespillere samles for å løpe en forhåndsbestemt distanse.',
time: '22/04/2025, klokken 12:31'
},
{
headline: 'Bli Med på ENHETs Fotballklinikk for Barn',
text: 'En helg dedikert til å lære opp unge fotballspillere med trenere og spillere fra ENHET. Barn i alderen 6-14 år inviteres til å delta i treningsøkter hvor de kan forbedre sine fotballferdigheter, lære om teamarbeid og sportsånd, og ha det gøy',
time: '29/04/2025, klokken 12:31'
},
{
headline: 'ENHET Fanfest og Sesongkickoff',
text: 'En fest for å markere starten på den nye fotballsesongen, hvor fansen kan samles for å feire starten på en ny jakt på seier. Arrangementet kan inkludere live musikk, matboder fra lokale leverandører, autografsignering med spillere, og avduking av ENHET...',
time: '06/05/2025, klokken 12:31'
}
];
</script>
<template>
<section class="relative block w-full h-screen overflow-hidden">
<div class="absolute inset-0 bg-cover" :style="{ backgroundImage: `url('${block.image}')` }"></div>
<div class="absolute inset-0 bg-black opacity-80"></div>
<div class="absolute p-12 w-full overflow-auto" style="top: 0; bottom: 0;">
<div class="flex-row justify-center items-center">
<h1 v-if="block.showtitle" class="text-center text-white font-arctic text-2xl md:text-4xl">{{ block.title || 'Tittel' }}</h1>
<h2 v-if="block.showlabel" class="text-center text-amber-600 font-serif text-md md:text-xl">{{ block.label || 'Merkelapp' }}</h2>
<h3 v-if="block.showsecondarylabel" class="text-center text-gray-400 font-serif text-sm md:text-md">{{ block.secondarylabel || 'Sekundær merkelapp' }}</h3>
</div>
<div class="mx-auto max-w-[1280px]">
<div class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 mt-12">
<!-- Begin events loop -->
<div
v-for="(event, index) in events"
:key="index"
class="max-w-sm rounded-lg shadow-xl bg-gray-800 border-gray-700 relative"
>
<div class="relative">
<a href="#">
<img class="rounded-t-lg" src="/img/blocks/arrangement_placeholder.jpg" alt="Eksempelbilde" />
<!-- Badge positioned absolutely within the relative container -->
<div class="absolute top-0 right-0 bg-green-100 text-green-800 text-sm md:text-md font-medium me-2 px-2.5 py-0.5 rounded border-green-400 inline-flex items-center justify-center z-20" style="margin: 0.5rem;">
Ingen plassrestriksjoner
</div>
</a>
</div>
<div class="p-5">
<a href="#">
<h5 class="mb-2 text-2xl font-bold tracking-tight text-white">{{ event.headline }}</h5>
</a>
<p class="mb-2 text-sm text-green-400">Oppstart: {{ event.time }}</p>
<p class="mb-3 font-normal text-gray-400">{{ event.text }}</p>
<a href="#" class="inline-flex items-center px-3 py-2 text-sm font-medium text-center text-white rounded-lg focus:ring-4 focus:outline-none bg-green-600 hover:bg-green-700 focus:ring-green-800">
Les mer
<svg class="rtl:rotate-180 w-3.5 h-3.5 ms-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 10">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 5h12m0 0L9 1m4 4L9 9"/>
</svg>
</a>
</div>
</div>
<!-- End events loop -->
</div>
</div>
</div>
</section>
</template>
@@ -0,0 +1,182 @@
<script setup>
import treeview from "vue3-treeview";
import "vue3-treeview/dist/style.css";
import { toRef, watch, reactive, watchEffect, onMounted } from 'vue';
import { initFlowbite } from 'flowbite';
onMounted(() => {
initFlowbite();
});
const props = defineProps({
block: {
type: Object,
required: true,
},
images: {
type: Array
}
});
const emit = defineEmits(['update:block']);
const local = toRef(props, 'block');
watch(local, (val) => emit('update:block', val), { deep: true });
const treeData = reactive({
nodes: {},
config: {
checkboxes: false,
dragAndDrop: false,
editable: false,
roots: []
}
});
function buildTreeNodes(images) {
const nodes = {};
const roots = [];
images.forEach(item => {
const id = `id${item.id}`
nodes[id] = {
nodeId: item.id,
text: item.name,
fs_type: item.fs_type,
fs_meta: item.fs_meta,
children: images.filter(child => child.parent_id === item.id).map(child => `id${child.id}`),
};
if (item.parent_id === null) roots.push(id);
})
return { nodes, roots };
}
const onNodeClick = (node) => {
if (node.fs_type === 'file') {
local.value.image = extractMetaValue(node, 'symbolic_name');
}
};
watchEffect(() => {
const { nodes, roots } = buildTreeNodes(props.images);
treeData.nodes = nodes;
treeData.config.roots = roots;
});
function extractMetaValue(node, key) {
let meta = JSON.parse(node.fs_meta);
return meta[key];
};
</script>
<template>
<div class="space-y-4">
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Anker navn (DOM ID)</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.anchorName"
/>
</div>
<div class="mb-2">
<div class="flex">
<div class="flex items-center h-5">
<input
id="title-checkbox"
aria-describedby="title-checkbox-text"
type="checkbox"
v-model="local.showtitle"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
>
</div>
<div class="ms-2 text-sm">
<label for="title-checkbox" class="font-medium text-gray-900 dark:text-gray-300">Vis tittel</label>
<p id="title-checkbox-text" class="text-xs font-normal text-gray-500 dark:text-gray-300">Skru tittelen på komponenten av, eller på</p>
</div>
</div>
</div>
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tittel tekst</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.title"
:disabled="!local.showtitle"
/>
</div>
<div class="mb-2">
<div class="flex">
<div class="flex items-center h-5">
<input
id="label-checkbox"
aria-describedby="label-checkbox-text"
type="checkbox"
v-model="local.showlabel"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
>
</div>
<div class="ms-2 text-sm">
<label for="label-checkbox" class="font-medium text-gray-900 dark:text-gray-300">Vis merkelapp</label>
<p id="label-checkbox-text" class="text-xs font-normal text-gray-500 dark:text-gray-300">Skru merkelappen øverst på komponenten av, eller på</p>
</div>
</div>
</div>
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Merkelapp tekst</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.label"
:disabled="!local.showlabel"
/>
</div>
<div class="mb-2">
<div class="flex">
<div class="flex items-center h-5">
<input
id="label-checkbox"
aria-describedby="label-checkbox-text"
type="checkbox"
v-model="local.showsecondarylabel"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
>
</div>
<div class="ms-2 text-sm">
<label for="label-checkbox" class="font-medium text-gray-900 dark:text-gray-300">Vis sekundær merkelapp</label>
<p id="label-checkbox-text" class="text-xs font-normal text-gray-500 dark:text-gray-300">Skru den sekundære merkelappen øverst på komponenten av, eller på (denne merkelappen vises bare for ikke innloggede brukere)</p>
</div>
</div>
</div>
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Sekundær Merkelapp tekst</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.secondarylabel"
:disabled="!local.showsecondarylabel"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Velg bakgrunnsbilde</label>
<treeview
v-if="images && images.length"
@nodeFocus="onNodeClick"
:nodes="treeData.nodes"
:config="treeData.config"
class="mt-4"
/>
</div>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,193 @@
<script setup>
import { defineProps } from 'vue';
import { usePage } from '@inertiajs/vue3';
import { marked } from 'marked';
const page = usePage();
const props = defineProps({
block: {
type: Object,
required: true,
},
});
</script>
<template>
<section class="relative block w-full h-screen overflow-hidden">
<div class="absolute inset-0 bg-cover" :style="{ backgroundImage: `url('${block.image}')` }"></div>
<div class="absolute inset-0 bg-black opacity-80"></div>
<div class="absolute p-12 w-full overflow-auto" style="top: 0; bottom: 0;">
<div class="flex-row justify-center items-center">
<h1 class="text-center text-white font-arctic text-2xl md:text-4xl">{{ block.title || 'Medlemskap' }}</h1>
</div>
<div class="mx-auto max-w-[1280px]">
<p v-if="block.showprices" class="mt-2 text-white">
Prisen for medlemskap er
{{ page.props.pageSettings['membership_fee'] }},- kr per år for voksne eller {{ page.props.pageSettings['membership_child_fee'] }}
for barn/ungdom under {{ page.props.pageSettings['membership_child_age_limit'] }} år. Du må være
{{ page.props.pageSettings['membership_age_limit'] }} år eller eldre for å bli medlem hos oss. Medlemskapet
varer i {{ page.props.pageSettings['membership_time_interval_months'] }} måneder
<span v-if="page.props.pageSettings['membership_annual']"> eller ut kalenderåret </span>
før det må fornyes. Betalinger kan gjøres på nettsiden via vipps <span v-if="page.props.pageSettings['module_stripe_payment']">eller bankkort</span> ved å besøke
<Link :href="route('dashboard')" class="text-amber-400 hover:text-amber-300 underline hover:no-underline">
din medlemsside
</Link>
.
</p>
<p v-if="$page.props.auth.user && block.showprices" class="mt-2 text-white">
<span class="font-semibold">Merk:</span> Priser og betingelser kan variere. Besøk medlemssiden for å se
dine spesifike betingelser.
</p>
<div class="mt-2 text-white memberShipBlock" v-html="marked(block.text)">
</div>
</div>
</div>
</section>
</template>
<style>
.memberShipBlock p {
margin-bottom: 12px;
}
.memberShipBlock p:last-child {
margin-bottom: 0;
}
.memberShipBlock a {
color: #f59e0b;
text-decoration: underline;
}
.memberShipBlock a:hover {
color: #d97706;
text-decoration: none;
}
.memberShipBlock a:active {
color: #d97706;
text-decoration: none;
}
.memberShipBlock a:focus {
color: #d97706;
text-decoration: none;
}
.memberShipBlock a:visited {
color: #fcd34d;
text-decoration: underline;
}
.memberShipBlock ul {
color: #9ca3af;
margin-left: 20px;
margin-top: 6px;
margin-bottom: 6px;
list-style-type: circle;
}
.memberShipBlock li p {
color: #9ca3af;
font-size: 1rem;
}
.memberShipBlock ul li {
margin-bottom: 6px;
}
.memberShipBlock ul li:last-child {
margin-bottom: 0;
}
.memberShipBlock ol {
color: #9ca3af;
margin-left: 20px;
margin-top: 6px;
margin-bottom: 6px;
list-style-type: decimal;
}
.memberShipBlock ol li {
margin-bottom: 6px;
}
.memberShipBlock ol li:last-child {
margin-bottom: 0;
}
.memberShipBlock hr {
border-top-color: #f59e0b;
margin-top: 12px;
margin-bottom: 12px;
}
.memberShipBlock h1 {
@apply font-arctic text-4xl;
}
.memberShipBlock h2 {
color: #f59e0b;
font-family: 'Arctic';
margin-top: 6px;
margin-bottom: 6px;
font-size: x-large;
}
.memberShipBlock h3 {
color: #fff;
margin-top: 6px;
margin-bottom: 6px;
font-size: large;
font-weight: bold;
}
.memberShipBlock h4 {
color: #f59e0b;
margin-top: 6px;
margin-bottom: 6px;
font-size: medium;
font-weight: bold;
}
.memberShipBlock h5 {
color: #f59e0b;
margin-top: 6px;
margin-bottom: 6px;
font-size: small;
font-weight: bold;
}
.memberShipBlock h6 {
color: #f59e0b;
margin-top: 6px;
margin-bottom: 6px;
font-size: x-small;
font-weight: bold;
}
.memberShipBlock blockquote {
color: #9ca3af;
margin-top: 6px;
margin-bottom: 6px;
margin-left: 20px;
padding-left: 10px;
border-left: 3px solid #ccc;
font-style: italic;
}
.memberShipBlock code {
background-color: #1f2937;
color: #ef4444;
border-radius: 0.3em;
padding: 8px 10px 12px;
white-space: nowrap;
margin-top: 12px;
margin-bottom: 12px;
}
</style>
@@ -0,0 +1,128 @@
<script setup>
import treeview from "vue3-treeview";
import "vue3-treeview/dist/style.css";
import { toRef, watch, reactive, watchEffect, onMounted } from 'vue';
import { initFlowbite } from 'flowbite';
onMounted(() => {
initFlowbite();
});
const props = defineProps({
block: { type: Object, required: true },
images: { type: Array }
});
const emit = defineEmits(['update:block']);
const local = toRef(props, 'block');
watch(local, (val) => emit('update:block', val), { deep: true });
const treeData = reactive({
nodes: {},
config: {
checkboxes: false,
dragAndDrop: false,
editable: false,
roots: []
}
});
function buildTreeNodes(images) {
const nodes = {};
const roots = [];
images.forEach(item => {
const id = `id${item.id}`
nodes[id] = {
nodeId: item.id,
text: item.name,
fs_type: item.fs_type,
fs_meta: item.fs_meta,
children: images.filter(child => child.parent_id === item.id).map(child => `id${child.id}`),
};
if (item.parent_id === null) roots.push(id);
})
return { nodes, roots };
}
const onNodeClick = (node) => {
if (node.fs_type === 'file') {
local.value.image = extractMetaValue(node, 'symbolic_name');
}
};
watchEffect(() => {
const { nodes, roots } = buildTreeNodes(props.images);
treeData.nodes = nodes;
treeData.config.roots = roots;
});
function extractMetaValue(node, key) {
let meta = JSON.parse(node.fs_meta);
return meta[key];
};
</script>
<template>
<div class="space-y-4">
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Anker navn (DOM ID)</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.anchorName"
/>
</div>
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tittel</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.title"
/>
</div>
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tekst</label>
<textarea
rows="3"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.text"
></textarea>
<span class="text-xs">(Dette feltet er markdown kompatibelt)</span>
</div>
<div class="mb-2">
<div class="flex">
<div class="flex items-center h-5">
<input
id="prices-checkbox"
aria-describedby="prices-checkbox-text"
type="checkbox"
v-model="local.showprices"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
>
</div>
<div class="ms-2 text-sm">
<label for="prices-checkbox" class="font-medium text-gray-900 dark:text-gray-300">Vis prissammendrag</label>
<p id="prices-checkbox-text" class="text-xs font-normal text-gray-500 dark:text-gray-300">Skru av eller på visningen av prissammendraget nederst i blokken</p>
</div>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Velg bakgrunnsbilde</label>
<treeview
v-if="images && images.length"
@nodeFocus="onNodeClick"
:nodes="treeData.nodes"
:config="treeData.config"
class="mt-4"
/>
</div>
</div>
</template>
@@ -0,0 +1,237 @@
<script setup>
import { Link, usePage } from '@inertiajs/vue3';
import { onMounted, defineProps, computed } from 'vue';
import { initFlowbite } from 'flowbite';
import { marked } from 'marked';
const { block, blockData, frontpage } = defineProps({
block: {
type: Object,
required: true,
},
blockData: {
type: Object,
default: () => ({})
},
frontpage: {
type: Boolean
}
});
onMounted(() => {
initFlowbite();
});
const page = usePage();
const userRoles = computed(() => page.props.userRoles);
/**
* Check if the user has the given role.
*
* @param string roleName
* @return boolean
*/
const hasRole = (roleName) => userRoles.value.includes(roleName);
/**
* Calculates the number of days until dd.mm.YYYY
*
* @param string date
* @return string
*/
const calculateDaysUntil = (dateStr) => {
const [day, month, year] = dateStr.split('.');
const targetDate = new Date(year, month - 1, day);
const today = new Date();
today.setHours(0, 0, 0, 0);
const differenceInMilliseconds = targetDate - today;
const differenceInDays = differenceInMilliseconds / (1000 * 60 * 60 * 24);
return Math.floor(differenceInDays);
};
</script>
<template>
<section :id="block.anchorName" class="relative block w-full h-screen overflow-hidden">
<div class="absolute inset-0 bg-cover" :style="{ backgroundImage: `url('${block.image}')` }"></div>
<div class="absolute inset-0 bg-black opacity-80"></div>
<div class="absolute p-12 w-full overflow-auto" style="top: 0; bottom: 0;">
<div class="flex-row justify-center items-center">
<h1 class="text-center text-white font-arctic text-2xl md:text-4xl">{{ block.title || 'Medlemskap' }}</h1>
</div>
<div class="mx-auto max-w-[1280px]">
<!-- <img src="/img/medlemmer.jpg" class="border-2 w-[200px] sm:w-[320px] md:w-[640px] lg:w-[800px] border-green-700 rounded-lg float-left mb-1 md:mb-4 mx-2 md:mx-6" alt="ØMS Medlemmer"> -->
<p v-if="block.showprices" class="mt-2 text-white">
Prisen for medlemskap er
{{ page.props.pageSettings['membership_fee'] }},- kr per år for voksne eller {{ page.props.pageSettings['membership_child_fee'] }}
for barn/ungdom under {{ page.props.pageSettings['membership_child_age_limit'] }} år. Du må være
{{ page.props.pageSettings['membership_age_limit'] }} år eller eldre for å bli medlem. hos oss. Medlemskapet
varer i {{ page.props.pageSettings['membership_time_interval_months'] }} måneder
<span v-if="page.props.pageSettings['membership_annual']"> eller ut kalenderåret </span>
før det må fornyes. Betalinger kan gjøres på nettsiden via vipps <span v-if="page.props.pageSettings['module_stripe_payment']">eller bankkort</span> ved å besøke
<Link :href="route('dashboard')" class="text-amber-400 hover:text-amber-300 underline hover:no-underline">
din medlemsside
</Link>
.
</p>
<p v-if="$page.props.auth.user && block.showprices" class="mt-2 text-white">
<span class="font-semibold">Merk:</span> Priser og betingelser kan variere. Besøk medlemssiden for å se
dine spesifike betingelser.
</p>
<div class="mt-2 text-white memberShipBlock" v-html="marked(block.text)">
</div>
</div>
</div>
</section>
</template>
<style>
.memberShipBlock p {
margin-bottom: 12px;
}
.memberShipBlock p:last-child {
margin-bottom: 0;
}
.memberShipBlock a {
color: #f59e0b;
text-decoration: underline;
}
.memberShipBlock a:hover {
color: #d97706;
text-decoration: none;
}
.memberShipBlock a:active {
color: #d97706;
text-decoration: none;
}
.memberShipBlock a:focus {
color: #d97706;
text-decoration: none;
}
.memberShipBlock a:visited {
color: #fcd34d;
text-decoration: underline;
}
.memberShipBlock ul {
color: #9ca3af;
margin-left: 20px;
margin-top: 6px;
margin-bottom: 6px;
list-style-type: circle;
}
.memberShipBlock li p {
color: #9ca3af;
font-size: 1rem;
}
.memberShipBlock ul li {
margin-bottom: 6px;
}
.memberShipBlock ul li:last-child {
margin-bottom: 0;
}
.memberShipBlock ol {
color: #9ca3af;
margin-left: 20px;
margin-top: 6px;
margin-bottom: 6px;
list-style-type: decimal;
}
.memberShipBlock ol li {
margin-bottom: 6px;
}
.memberShipBlock ol li:last-child {
margin-bottom: 0;
}
.memberShipBlock hr {
border-top-color: #f59e0b;
margin-top: 12px;
margin-bottom: 12px;
}
.memberShipBlock h1 {
@apply font-arctic;
@apply text-4xl;
}
.memberShipBlock h2 {
color: #f59e0b;
font-family: 'Arctic';
margin-top: 6px;
margin-bottom: 6px;
font-size: x-large;
}
.memberShipBlock h3 {
color: #fff;
margin-top: 6px;
margin-bottom: 6px;
font-size: large;
font-weight: bold;
}
.memberShipBlock h4 {
color: #f59e0b;
margin-top: 6px;
margin-bottom: 6px;
font-size: medium;
font-weight: bold;
}
.memberShipBlock h5 {
color: #f59e0b;
margin-top: 6px;
margin-bottom: 6px;
font-size: small;
font-weight: bold;
}
.memberShipBlock h6 {
color: #f59e0b;
margin-top: 6px;
margin-bottom: 6px;
font-size: x-small;
font-weight: bold;
}
.memberShipBlock blockquote {
color: #9ca3af;
margin-top: 6px;
margin-bottom: 6px;
margin-left: 20px;
padding-left: 10px;
border-left: 3px solid #ccc;
font-style: italic;
}
.memberShipBlock code {
background-color: #1f2937;
color: #ef4444;
border-radius: 0.3em;
padding: 8px 10px 12px;
white-space: nowrap;
margin-top: 12px;
margin-bottom: 12px;
}
</style>
@@ -0,0 +1,36 @@
<script setup>
import { onMounted, ref, toRef, watch } from 'vue';
import { initFlowbite } from 'flowbite';
onMounted(() => {
initFlowbite();
});
const clubName = ref(null);
const props = defineProps({
block: { type: Object, required: true }
});
const emit = defineEmits(['update:block']);
const local = toRef(props, 'block');
watch(local, (val) => emit('update:block', val), { deep: true });
</script>
<template>
<header id="frontPageHeader" class="h-[400px] bg-heroBg bg-no-repeat bg-center bg-cover">
<div class="h-[400px] bg-heroSoldiers bg-no-repeat bg-bottom z-0">
<div class="h-[400px] bg-heroFgR bg-repeat-x bg-bottom z-10">
<div class="flex items-end justify-center content-end pt-12">
<Link id="logoHeader" :href="route('forsiden')" class="flex justify-center items-center p-2">
<img id="clubLogo" src="/img/oms-glow.png" class="w-14 md:w-20 lg:w-28 mr-2 md:mr-6 shadow-2xl" alt="">
<p class="text-white text-2xl md:text-4xl lg:text-6xl font-topsecret">
<span ref="clubName">{{ block.title || 'Østfold Milsim' }}</span>
</p>
</Link>
</div>
</div>
</div>
</header>
</template>
@@ -0,0 +1,49 @@
<script setup>
import treeview from "vue3-treeview";
import "vue3-treeview/dist/style.css";
import { toRef, watch, reactive, watchEffect } from 'vue'
const props = defineProps({
block: {
type: Object,
required: true,
}
})
const emit = defineEmits(['update:block']);
// Work directly with a reactive reference to the parent block
const local = toRef(props, 'block');
// Optionally: auto-emit updated block on change (if needed elsewhere)
watch(local, (val) => {
emit('update:block', val);
}, { deep: true });
</script>
<template>
<div class="space-y-4">
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Anker navn (DOM ID)</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.anchorName"
/>
</div>
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tittel</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.title"
@input="update"
/>
</div>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,78 @@
<script setup>
import { Link } from '@inertiajs/vue3';
import { onMounted, ref, defineProps } from 'vue';
import { gsap } from 'gsap';
import { initFlowbite } from 'flowbite';
const { block, blockData, frontpage } = defineProps({
block: {
type: Object,
required: true,
},
blockData: {
type: Object,
default: () => ({})
},
frontpage: {
type: Boolean
}
});
onMounted(() => {
initFlowbite();
animateLogo();
});
const clubName = ref(null);
/**
* Club logo animation in the header
*/
function animateLogo() {
const text = clubName.value.textContent.trim();
const chars = Array.from(text);
// Clear the text content and create a span for each character
clubName.value.textContent = '';
chars.forEach((char) => {
const span = document.createElement('span');
span.textContent = char;
clubName.value.appendChild(span);
});
const logoTimeline = gsap.timeline({ defaults: { ease: 'power1.out' } });
// Animate each letter
logoTimeline.from(clubName.value.children, {
duration: 0.5,
opacity: 0,
y: 20,
stagger: 0.05,
});
// Start fading in the logo before the text animation completes
logoTimeline.to("#clubLogo", {
duration: 1,
opacity: 1,
}, "-=0.5");
}
</script>
<template>
<header :id="block.anchorName" class="h-[400px] bg-heroBg bg-no-repeat bg-center bg-cover">
<div class="h-[400px] bg-heroSoldiers bg-no-repeat bg-bottom z-0">
<div class="h-[400px] bg-heroFgR bg-repeat-x bg-bottom z-10">
<div class="flex items-end justify-center content-end pt-12">
<Link id="logoHeader" :href="route('forsiden')" class="flex justify-center items-center p-2">
<img id="clubLogo" src="/img/oms-glow.png" class="w-14 md:w-20 lg:w-28 mr-2 md:mr-6 shadow-2xl" alt="">
<p class="text-white text-2xl md:text-4xl lg:text-6xl font-topsecret">
<span ref="clubName">{{ block.title || 'Østfold Milsim' }}</span>
</p>
</Link>
</div>
</div>
</div>
</header>
</template>
@@ -0,0 +1,77 @@
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
block: {
type: Object,
required: true,
},
});
const articles = [
{
headline: 'Taktikker som endret spillet vårt',
text: 'I denne grundige analysen utforsker vi de revolusjonerende taktikkene introdusert av treneren som fullstendig forvandlet vårt spill denne sesongen. Fra en defensiv holdning til en offensiv overmakt, dette er historien om hvordan vårt lag gikk fra under...',
time: '17/03/2025',
category: 'Spillstrategi'
},
{
headline: 'Støtte fra tribunen: Våre fantastiske fans',
text: 'Et hav av farger og sang fylte stadion, mens tilhengernes lidenskap og energi ga en ekstra "spiller" på banen. Vi dykker ned i hvordan supporterne våre har blitt en uunnværlig del av lagånden og ser på de mest minneverdige øyeblikkene fra tribunen denn...',
time: '17/03/2025',
category: 'Klubbliv'
},
{
headline: 'Hvor er de nå? Oppdateringer fra tidligere ENHET-spillere',
text: 'Livet etter å ha forlatt vårt kjære ENHET-lag kan ta mange vendinger. Vi tar et nostalgisk tilbakeblikk og oppdaterer deg på hvor tidligere stjernespillere har endt opp. Bli med på en reise gjennom karrierene deres etter ENHET, fra nye lag til personli...',
time: '17/03/2025',
category: 'Spillerfokus'
}
];
</script>
<template>
<section class="relative block w-full h-screen overflow-hidden">
<div class="absolute inset-0 bg-cover" :style="{ backgroundImage: `url('${block.image}')` }"></div>
<div class="absolute inset-0 bg-black opacity-80"></div>
<div class="absolute p-12 w-full overflow-auto" style="top: 0; bottom: 0;">
<div class="flex-row justify-center items-center">
<h1 v-if="block.showtitle" class="text-center text-white font-arctic text-2xl md:text-4xl">{{ block.title || 'Tittel/Overskrift' }}</h1>
<h2 v-if="block.showlabel" class="text-center text-amber-600 font-serif text-md md:text-xl">{{ block.label || 'Tittel Merkelapp' }}</h2>
</div>
<div class="mx-auto max-w-[1280px]">
<div class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 mt-12">
<!-- Begin news article loop -->
<div
v-for="(article, index) in articles"
:key="article.id"
class="news-article flex justify-normal items-start max-w-md"
>
<div class="flex-shrink-0">
<img class="h-48 w-32 object-cover border-2 border-amber-800 rounded-lg shadow-md" src="/img/blocks/nyheter_placeholder.jpg" alt="Eksempel bilde">
</div>
<div class="ml-4">
<h3 class="text-sm font-medium text-white font-arctic">
<Link href="#" class="hover:underline">
{{ article.headline }}
</Link>
</h3>
<span class="mt-1 text-xs text-gray-500">{{ article.time }}</span>
<Link href="#" class="ml-4 bg-amber-100 hover:bg-amber-200 text-amber-800 text-xs font-semibold me-2 px-2.5 py-0.5 rounded border border-amber-400 inline-flex items-center justify-center">
{{ article.category }}
</Link>
<p class="mt-1 text-sm text-gray-300">
{{ article.text }}
</p>
</div>
</div>
<!-- End news article loop -->
</div>
<div class="mt-12 flex justify-end items-end">
<a href="#" class="font-arctic text-amber-600 hover:text-amber-400 no-underline hover:underline">{{ block.buttontext || 'Vis mer' }}</a>
</div>
</div>
</div>
</section>
</template>
@@ -0,0 +1,164 @@
<script setup>
import treeview from "vue3-treeview";
import "vue3-treeview/dist/style.css";
import { toRef, watch, reactive, watchEffect, onMounted } from 'vue';
import { initFlowbite } from 'flowbite';
onMounted(() => {
initFlowbite();
});
const props = defineProps({
block: {
type: Object,
required: true,
},
images: {
type: Array
}
});
const emit = defineEmits(['update:block']);
const local = toRef(props, 'block');
watch(local, (val) => emit('update:block', val), { deep: true });
const treeData = reactive({
nodes: {},
config: {
checkboxes: false,
dragAndDrop: false,
editable: false,
roots: []
}
});
function buildTreeNodes(images) {
const nodes = {};
const roots = [];
images.forEach(item => {
const id = `id${item.id}`
nodes[id] = {
nodeId: item.id,
text: item.name,
fs_type: item.fs_type,
fs_meta: item.fs_meta,
children: images.filter(child => child.parent_id === item.id).map(child => `id${child.id}`),
};
if (item.parent_id === null) roots.push(id);
})
return { nodes, roots };
}
const onNodeClick = (node) => {
if (node.fs_type === 'file') {
local.value.image = extractMetaValue(node, 'symbolic_name');
}
};
watchEffect(() => {
const { nodes, roots } = buildTreeNodes(props.images);
treeData.nodes = nodes;
treeData.config.roots = roots;
});
function extractMetaValue(node, key) {
let meta = JSON.parse(node.fs_meta);
return meta[key];
};
</script>
<template>
<div class="space-y-4">
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Anker navn (DOM ID)</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.anchorName"
/>
</div>
<div class="mb-2">
<div class="flex">
<div class="flex items-center h-5">
<input
id="title-checkbox"
aria-describedby="title-checkbox-text"
type="checkbox"
v-model="local.showtitle"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
>
</div>
<div class="ms-2 text-sm">
<label for="title-checkbox" class="font-medium text-gray-900 dark:text-gray-300">Vis tittel</label>
<p id="title-checkbox-text" class="text-xs font-normal text-gray-500 dark:text-gray-300">Skru tittelen på komponenten av, eller på</p>
</div>
</div>
</div>
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tittel tekst</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.title"
:disabled="!local.showtitle"
/>
</div>
<div class="mb-2">
<div class="flex">
<div class="flex items-center h-5">
<input
id="label-checkbox"
aria-describedby="label-checkbox-text"
type="checkbox"
v-model="local.showlabel"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
>
</div>
<div class="ms-2 text-sm">
<label for="label-checkbox" class="font-medium text-gray-900 dark:text-gray-300">Vis merkelapp</label>
<p id="label-checkbox-text" class="text-xs font-normal text-gray-500 dark:text-gray-300">Skru merkelappen øverst på komponenten av, eller på</p>
</div>
</div>
</div>
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Merkelapp tekst</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.label"
:disabled="!local.showlabel"
/>
</div>
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Knapp/Lenke tekst</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.buttontext"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Velg bakgrunnsbilde</label>
<treeview
v-if="images && images.length"
@nodeFocus="onNodeClick"
:nodes="treeData.nodes"
:config="treeData.config"
class="mt-4"
/>
</div>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,115 @@
<script setup>
import { Link } from '@inertiajs/vue3';
import { onMounted, computed, defineProps } from 'vue';
import { initFlowbite } from 'flowbite';
const { block, blockData, frontpage } = defineProps({
block: {
type: Object,
required: true,
},
blockData: {
type: Object,
default: () => ({})
},
frontpage: {
type: Boolean
}
});
onMounted(() => {
initFlowbite();
});
/**
* Retrieve only 3 news articles
*/
const limitedArticles = computed(() => blockData.articles.data.slice(0, 3));
/** Get a node value from json */
const extractMetaValue = (node, key) => {
let meta = JSON.parse(node.fs_meta);
return meta[key];
};
/**
* formatDate
*
* Formats a date string to a Norwegian date format.
*
* @param {
* } dateString
*/
const formatDate = (dateString) => {
const date = new Date(dateString);
const day = ('0' + date.getDate()).slice(-2);
const month = ('0' + (date.getMonth() + 1)).slice(-2);
const year = date.getFullYear();
return `${day}/${month}/${year}`;
};
/**
* dateString
*
* Formats a date string to a Norwegian time format.
* @param {*} dateString
*/
const formatTime = (dateString) => {
const date = new Date(dateString);
return date.toLocaleTimeString('nb-NO', { hour: '2-digit', minute: '2-digit' });
};
</script>
<template>
<section :id="block.anchorName" class="relative block w-full h-screen overflow-hidden">
<div class="absolute inset-0 bg-cover" :style="{ backgroundImage: `url('${block.image}')` }"></div>
<div class="absolute inset-0 bg-black opacity-80"></div>
<div class="absolute p-12 w-full overflow-auto" style="top: 0; bottom: 0;">
<div class="flex-row justify-center items-center">
<h1 v-if="block.showtitle" class="text-center text-white font-arctic text-2xl md:text-4xl">{{ block.title || 'Tittel/Overskrift' }}</h1>
<h2 v-if="block.showlabel" class="text-center text-amber-600 font-serif text-md md:text-xl">{{ block.label || 'Tittel Merkelapp' }}</h2>
</div>
<div class="mx-auto max-w-[1280px]">
<div class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 mt-12">
<!-- Begin news article loop -->
<div
v-for="(article, index) in blockData.articles.data"
:key="article.id"
class="news-article flex justify-normal items-start max-w-md"
>
<div class="flex-shrink-0">
<img v-if="article.image_id" class="h-48 w-32 object-cover border-2 border-amber-800 rounded-lg shadow-md" :src="extractMetaValue(article.image, 'symbolic_name')" :alt="extractMetaValue(article.image, 'title')">
</div>
<div class="ml-4">
<h3 class="text-sm font-medium text-white font-arctic">
<Link :href="route('newsArticle', article.slug)" class="hover:underline">
{{ article.title }}
</Link>
</h3>
<span class="mt-1 text-xs text-gray-500">{{ formatDate(article.created_at) }}</span>
<Link v-if="article.news_category_id" :href="route('newsCategory', article.category.slug)" class="ml-4 bg-amber-100 hover:bg-amber-200 text-amber-800 text-xs font-semibold me-2 px-2.5 py-0.5 rounded border border-amber-400 inline-flex items-center justify-center">
{{ article.category.title }}
</Link>
<p class="mt-1 text-sm text-gray-300">
{{ article.excerpt }}
</p>
</div>
</div>
<!-- End news article loop -->
</div>
<div class="mt-12 flex justify-end items-end">
<Link :href="route('news')" class="font-arctic text-amber-600 hover:text-amber-400 no-underline hover:underline">{{ block.buttontext || 'Vis mer' }}</Link>
</div>
</div>
</div>
</section>
</template>
@@ -0,0 +1,71 @@
<script setup>
import { onMounted, toRef, watch } from 'vue';
import { initFlowbite } from 'flowbite';
onMounted(() => {
initFlowbite();
});
const props = defineProps({
block: { type: Object, required: true }
});
const emit = defineEmits(['update:block']);
const local = toRef(props, 'block');
watch(local, (val) => emit('update:block', val), { deep: true });
const sponsors = [
{ name: 'Greenleaf Organics A/S', type: 'Sponsor' },
{ name: 'Futuretech Robotics AS', type: 'Sponsor' },
{ name: 'EcoInnovate Solutions AS', type: 'Sponsor' },
{ name: 'Nordic Nest A/S', type: 'Sponsor' },
{ name: 'Bright Mind AS', type: 'Sponsor' },
{ name: 'Globetrotter Travel Co AS', type: 'Samarbeidspartner' },
{ name: 'Aurora Health & Wellness A/S', type: 'Samarbeidspartner' },
{ name: 'Artisinal Brews AS', type: 'Samarbeidspartner' }
];
</script>
<template>
<section class="relative block w-full h-screen overflow-hidden">
<div class="absolute inset-0 bg-cover" :style="{ backgroundImage: `url('${block.image}')` }"></div>
<div class="absolute inset-0 bg-black opacity-80"></div>
<div class="absolute p-12 w-full overflow-auto" style="top: 0; bottom: 0;">
<div class="flex-row justify-center items-center">
<h1 class="text-center text-white font-arctic text-2xl md:text-4xl">{{ block.title || 'Tittel' }}</h1>
</div>
<div class="mx-auto max-w-[1280px]">
<div class="col-6 d-flex flex-column sponsor-container">
<p v-if="block.showtext" class="text-gray-200 mt-6">
{{ block.text || 'Intro tekst' }}
</p>
<div class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 p-4 font-arctic text-xl md:text-2xl">
<!-- Sponsoroppføring -->
<template v-for="(sponsor, index) in sponsors" :key="index">
<a v-if="sponsor.type == 'Sponsor'" href="#" class="anchor sponsor-label text-gray-500 hover:text-gray-400 no-underline hover:underline">
<span>{{ sponsor.name }}</span>
</a>
</template>
</div>
<p v-if="block.showpartnertext" class="text-gray-200 mt-2">
{{ block.partnertext || 'Partner intro tekst' }}
</p>
<div class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 p-4 font-arctic text-xl md:text-2xl">
<!-- Partneroppføring -->
<template v-for="(partner, index) in sponsors" :key="index" href="#">
<a v-if="partner.type == 'Samarbeidspartner'" class="anchor sponsor-label text-gray-500 hover:text-gray-400 no-underline hover:underline">
<span>{{ partner.name }}</span>
</a>
</template>
</div>
</div>
</div>
</div>
</section>
</template>
@@ -0,0 +1,164 @@
<script setup>
import treeview from "vue3-treeview";
import "vue3-treeview/dist/style.css";
import { toRef, watch, reactive, watchEffect, onMounted } from 'vue';
import { initFlowbite } from 'flowbite';
onMounted(() => {
initFlowbite();
});
const props = defineProps({
block: {
type: Object,
required: true,
},
images: {
type: Array
}
});
const emit = defineEmits(['update:block']);
const local = toRef(props, 'block');
watch(local, (val) => emit('update:block', val), { deep: true });
const treeData = reactive({
nodes: {},
config: {
checkboxes: false,
dragAndDrop: false,
editable: false,
roots: []
}
});
function buildTreeNodes(images) {
const nodes = {};
const roots = [];
images.forEach(item => {
const id = `id${item.id}`
nodes[id] = {
nodeId: item.id,
text: item.name,
fs_type: item.fs_type,
fs_meta: item.fs_meta,
children: images.filter(child => child.parent_id === item.id).map(child => `id${child.id}`),
};
if (item.parent_id === null) roots.push(id);
})
return { nodes, roots };
}
const onNodeClick = (node) => {
if (node.fs_type === 'file') {
local.value.image = extractMetaValue(node, 'symbolic_name');
}
};
watchEffect(() => {
const { nodes, roots } = buildTreeNodes(props.images);
treeData.nodes = nodes;
treeData.config.roots = roots;
});
function extractMetaValue(node, key) {
let meta = JSON.parse(node.fs_meta);
return meta[key];
};
</script>
<template>
<div class="space-y-4">
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Anker navn (DOM ID)</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.anchorName"
/>
</div>
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tittel tekst</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.title"
/>
</div>
<div class="mb-2">
<div class="flex">
<div class="flex items-center h-5">
<input
id="title-checkbox"
aria-describedby="title-checkbox-text"
type="checkbox"
v-model="local.showtext"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
>
</div>
<div class="ms-2 text-sm">
<label for="title-checkbox" class="font-medium text-gray-900 dark:text-gray-300">Vis tekst</label>
<p id="title-checkbox-text" class="text-xs font-normal text-gray-500 dark:text-gray-300">Skru introteksten i komponenten av, eller på</p>
</div>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tekst</label>
<textarea
rows="3"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.text"
:disabled="!local.showtext"
></textarea>
</div>
<div class="mb-2">
<div class="flex">
<div class="flex items-center h-5">
<input
id="title-checkbox"
aria-describedby="title-checkbox-text"
type="checkbox"
v-model="local.showpartnertext"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
>
</div>
<div class="ms-2 text-sm">
<label for="title-checkbox" class="font-medium text-gray-900 dark:text-gray-300">Vis partner tekst</label>
<p id="title-checkbox-text" class="text-xs font-normal text-gray-500 dark:text-gray-300">Skru partnerteksten i komponenten av, eller på</p>
</div>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Partnertekst</label>
<textarea
rows="3"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.partnertext"
:disabled="!local.showpartnertext"
></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Velg bakgrunnsbilde</label>
<treeview
v-if="images && images.length"
@nodeFocus="onNodeClick"
:nodes="treeData.nodes"
:config="treeData.config"
class="mt-4"
/>
</div>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,62 @@
<script setup>
import { Link } from '@inertiajs/vue3';
import { onMounted, defineProps } from 'vue';
import { initFlowbite } from 'flowbite';
const { block, blockData, frontpage } = defineProps({
block: {
type: Object,
required: true,
},
blockData: {
type: Object,
default: () => ({})
},
frontpage: {
type: Boolean
}
});
onMounted(() => {
initFlowbite();
});
</script>
<template>
<section :id="block.anchorName" class="relative block w-full h-screen overflow-hidden">
<div class="absolute inset-0 bg-cover" :style="{ backgroundImage: `url('${block.image}')` }"></div>
<div class="absolute inset-0 bg-black opacity-80"></div>
<div class="absolute p-12 w-full overflow-auto" style="top: 0; bottom: 0;">
<div class="flex-row justify-center items-center">
<h1 class="text-center text-white font-arctic text-2xl md:text-4xl">{{ block.title || 'Tittel' }}</h1>
</div>
<div class="mx-auto max-w-[1280px]">
<div class="col-6 d-flex flex-column sponsor-container">
<p v-if="block.showtext" class="text-gray-200">
{{ block.text || 'Intro tekst' }}
</p>
<div class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 p-4 font-arctic text-xl md:text-2xl">
<!-- Sponsoroppføring -->
<Link v-for="(sponsor, index) in blockData.sponsors" :key="sponsor.id" :href="route('sponsor', sponsor.slug)" class="anchor sponsor-label text-gray-500 hover:text-gray-400 no-underline hover:underline">
{{ sponsor.name }}
</Link>
</div>
<p v-if="blockData.partners && blockData.partners.length && block.showpartnertext" class="text-gray-200 mt-2">
{{ block.partnertext || 'Partner intro tekst' }}
</p>
<div class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 p-4 font-arctic text-xl md:text-2xl">
<!-- Sponsoroppføring -->
<Link v-for="(partner, index) in blockData.partners" :key="partner.id" :href="route('sponsor', partner.slug)" class="anchor sponsor-label text-gray-500 hover:text-gray-400 no-underline hover:underline">
{{ partner.name }}
</Link>
</div>
</div>
</div>
</div>
</section>
</template>
@@ -0,0 +1,23 @@
<script setup>
import { onMounted } from 'vue';
import { initFlowbite } from 'flowbite';
import { usePage } from '@inertiajs/vue3';
import ArcticLogo from '@/Components/FrontPage/ArcticSoftwareFrontpageLogo.vue';
import FooterLinks from '@/Components/FrontPage/FooterSection.vue';
onMounted(() => {
initFlowbite();
});
const page = usePage();
</script>
<template>
<footer class="w-full bg-white border-t-2 border-green-700">
<FooterLinks :pageSettings="page.props.pageSettings"></FooterLinks>
<div class="p-4 flex items-center justify-center">
<ArcticLogo></ArcticLogo>
</div>
</footer>
</template>
@@ -0,0 +1,23 @@
<script setup>
import { onMounted } from 'vue';
import { initFlowbite } from 'flowbite';
import { usePage } from '@inertiajs/vue3';
import ArcticLogo from '@/Components/FrontPage/ArcticSoftwareFrontpageLogo.vue';
import FooterLinks from '@/Components/FrontPage/FooterSection.vue';
onMounted(() => {
initFlowbite();
});
const page = usePage();
</script>
<template>
<footer class="w-full bg-white border-t-2 border-green-700">
<FooterLinks :pageSettings="page.props.pageSettings"></FooterLinks>
<div class="p-4 flex items-center justify-center">
<ArcticLogo></ArcticLogo>
</div>
</footer>
</template>
@@ -0,0 +1,89 @@
<script setup>
import { onMounted, defineProps } from 'vue';
import { initFlowbite } from 'flowbite';
onMounted(() => {
initFlowbite();
});
const props = defineProps({
block: {
type: Object,
required: true,
},
});
const firstColumn = props.block.firstcolumn || [
{ text: 'Vårt samhold styrkes gjennom regelmessige arrangementer, med månedlige lokale turneringer og kvartalsvise vennskapsspill mot naboklubber.', style: 'heading' },
{ text: 'Hvert år arrangerer ENHET også en spennende utenlandstur hvor våre lag får utfordre internasjonale motstandere og oppleve nye kulturer. Bli med og opplev spenningen ved å representere ENHET både hjemme og på turer.', style: 'heading' },
{ text: 'Fotball er mer enn et spill; det er en verdensomspennende lidenskap som lærer oss verdien av lagarbeid og fair play. Som den mest populære sporten i verden, bringer fotball sammen mennesker fra alle livets hjørner, og bygger broer på tvers av kulturelle og språklige barrierer.', style: 'regular' },
{ text: 'Denne vakre sporten er en fest for fysisk dyktighet og strategisk tenkning, hvor spenningen på banen kun er matchet av fansens entusiasme. Fra spennende lokale kamper til den elektriske atmosfæren ved verdensmesterskapet, fotball tilbyr en unik kilde til glede og fellesskapsfølelse', style: 'regular' }
];
const secondColumn = props.block.secondcolumn || [
{ text: 'Bli med på våre ukentlige treninger, strategiseminarer og veldedighetsaktiviteter som styrker både kropp og fellesskap.', style: 'headingbold' },
{ text: 'Og trenger du utstyr har vi har et bredt spekter av fotballrelatert utstyr tilgjengelig for våre medlemmer, for å sikre at alle har det de trenger for å spille sitt beste.', style: 'regular' },
{ text: 'Hvordan begynne? Å starte med fotball har aldri vært enklere. Kom til en åpen trening eller kontakt oss for en prat om hvordan du kan bli en del av ENHET. Alt du trenger for å starte er et par fotballsko og et ønske om å lære. Vi tar oss av resten – fra drakter til treningsutstyr.', style: 'regular' }
];
</script>
<template>
<section class="mx-auto max-w-[1280px] bg-black">
<div class="grid gap-8 grid-cols-1 md:grid-cols-2 p-6">
<div class="max-w-2xl text-gray-200">
<template v-for="(paragraph, index) in firstColumn" :index="index">
<h1 v-if="paragraph.style === 'headinglarge'" class="text-gray-500 text-2xl md:text-4xl font-arctic">
{{ paragraph.text || '' }}
</h1>
<h2 v-if="paragraph.style === 'headingbold'" class="text-gray-500 text-xl md:text-2xl font-arctic mt-4">
{{ paragraph.text || '' }}
</h2>
<h3 v-else-if="paragraph.style === 'heading'" class="text-amber-600 text-xl md:text-2xl font-serif mt-1">
{{ paragraph.text || '' }}
</h3>
<p v-else-if="paragraph.style === 'regular'" class="mt-2">
{{ paragraph.text || '' }}
</p>
<iframe
v-else-if="paragraph.style === 'video'"
class="mt-6 w-72 h-72 md:w-96 md:h-96 lg:w-[560px] lg:h-[315px]"
:src="paragraph.text"
title="YouTube video spiller"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
>
</iframe>
</template>
</div>
<div class="max-w-2xl text-gray-200">
<template v-for="(paragraph, index) in secondColumn" :index="index">
<h1 v-if="paragraph.style === 'headinglarge'" class="text-gray-500 text-2xl md:text-4xl font-arctic">
{{ paragraph.text || '' }}
</h1>
<h2 v-if="paragraph.style === 'headingbold'" class="text-gray-500 text-xl md:text-2xl font-arctic mt-4">
{{ paragraph.text || '' }}
</h2>
<h3 v-else-if="paragraph.style === 'heading'" class="text-amber-600 text-xl md:text-2xl font-serif mt-1">
{{ paragraph.text || '' }}
</h3>
<p v-else-if="paragraph.style === 'regular'" class="mt-2">
{{ paragraph.text || '' }}
</p>
<iframe
v-else-if="paragraph.style === 'video'"
class="mt-6 w-72 h-72 md:w-96 md:h-96 lg:w-[560px] lg:h-[315px]"
:src="paragraph.text"
title="YouTube video spiller"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
>
</iframe>
</template>
</div>
</div>
</section>
</template>
@@ -0,0 +1,213 @@
<script setup>
import Icon from '@/Components/Icon.vue';
import { toRef, ref, onMounted } from 'vue';
import { initFlowbite } from 'flowbite';
onMounted(() => {
initFlowbite();
});
const props = defineProps({
block: {
type: Object,
required: true,
},
});
const emit = defineEmits(['update:block']);
const local = toRef(props, 'block');
// Drag state first column
const firstDraggedIndex = ref(null);
function handleDragStartPFirst(event, index) {
firstDraggedIndex.value = index;
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', index);
};
function handleDragOverPFirst(event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
};
function handleDropPFirst(event, index) {
event.preventDefault();
const fromIndex = firstDraggedIndex.value;
if (fromIndex === null || fromIndex === index) return;
const item = local.value.firstcolumn.splice(fromIndex, 1)[0];
local.value.firstcolumn.splice(index, 0, item);
firstDraggedIndex.value = null;
};
function addPFirst() {
local.value.firstcolumn.push({
text: '',
style: 'regular',
});
}
function removePFirst(index) {
local.value.firstcolumn.splice(index, 1);
};
// Drag state second column
const secondDraggedIndex = ref(null);
function handleDragStartPSecond(event, index) {
secondDraggedIndex.value = index;
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', index);
};
function handleDragOverPSecond(event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
};
function handleDropPSecond(event, index) {
event.preventDefault();
const fromIndex = secondDraggedIndex.value;
if (fromIndex === null || fromIndex === index) return;
const item = local.value.secondcolumn.splice(fromIndex, 1)[0];
local.value.secondcolumn.splice(index, 0, item);
secondDraggedIndex.value = null;
};
function addPSecond() {
local.value.secondcolumn.push({
text: '',
style: 'regular',
});
}
function removePSecond(index) {
local.value.secondcolumn.splice(index, 1);
}
</script>
<template>
<div class="space-y-4">
<div class="mb-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Anker navn (DOM ID)</label>
<input
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
v-model="local.anchorName"
/>
</div>
<div class="space-y-4">
<h3 class="font-bold text-sm text-gray-800 dark:text-white">
Venstre kolonne
</h3>
</div>
<div class="space-y-4">
<div
v-for="(paragraph, index) in local.firstcolumn"
:key="index"
class="flex items-start gap-2 p-4 rounded border dark:border-gray-600 bg-gray-50 dark:bg-gray-800"
draggable="true"
@dragstart="handleDragStartPFirst($event, index)"
@dragover="handleDragOverPFirst"
@drop="handleDropPFirst($event, index)"
>
<div class="cursor-move mt-1 text-gray-500 dark:text-gray-400">
<Icon name="move" class="w-4 h-4" />
</div>
<div class="flex-1 space-y-2">
<div class="flex justify-between items-center">
<span class="text-sm font-semibold text-gray-700 dark:text-gray-300">Paragraf {{ index + 1 }}</span>
<button @click="removePFirst(index)" class="text-red-500 hover:underline text-xs">Fjern</button>
</div>
<div class="mb-2">
<label :for="`style-l-${index}`" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Velg en tekststil</label>
<select
:id="`style-l-${index}`"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
v-model="paragraph.style"
>
<option value="headinglarge">Stor overskrift</option>
<option value="heading">Overskrift</option>
<option value="headingbold">Fet overskrift</option>
<option value="regular">Vanlig</option>
<option value="video">Video</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400">Tekst/Innhold</label>
<textarea rows="2" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white" v-model="paragraph.text"></textarea>
</div>
</div>
</div>
</div>
<button @click="addPFirst" class="mt-2 px-4 py-2 bg-blue-600 text-white text-sm rounded hover:bg-blue-700">
Legg til ny paragraf
</button>
<div class="space-y-4">
<h3 class="font-bold text-sm text-gray-800 dark:text-white">
Høyre kolonne
</h3>
</div>
<div class="space-y-4">
<div
v-for="(paragraph, index) in local.secondcolumn"
:key="index"
class="flex items-start gap-2 p-4 rounded border dark:border-gray-600 bg-gray-50 dark:bg-gray-800"
draggable="true"
@dragstart="handleDragStartPSecond($event, index)"
@dragover="handleDragOverPSecond"
@drop="handleDropPSecond($event, index)"
>
<div class="cursor-move mt-1 text-gray-500 dark:text-gray-400">
<Icon name="move" class="w-4 h-4" />
</div>
<div class="flex-1 space-y-2">
<div class="flex justify-between items-center">
<span class="text-sm font-semibold text-gray-700 dark:text-gray-300">Paragraf {{ index + 1 }}</span>
<button @click="removePSecond(index)" class="text-red-500 hover:underline text-xs">Fjern</button>
</div>
<div class="mb-2">
<label :for="`style-r-${index}`" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Velg en tekststil</label>
<select
:id="`style-r-${index}`"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
v-model="paragraph.style"
>
<option value="headinglarge">Stor overskrift</option>
<option value="heading">Overskrift</option>
<option value="headingbold">Fet overskrift</option>
<option value="regular">Vanlig</option>
<option value="video">Video</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400">Tekst/Innhold</label>
<textarea rows="2" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:text-white" v-model="paragraph.text"></textarea>
</div>
</div>
</div>
</div>
<button @click="addPSecond" class="mt-2 px-4 py-2 bg-blue-600 text-white text-sm rounded hover:bg-blue-700">
Legg til ny paragraf
</button>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,89 @@
<script setup>
import { onMounted, defineProps } from 'vue';
import { initFlowbite } from 'flowbite';
onMounted(() => {
initFlowbite();
});
const props = defineProps({
block: {
type: Object,
required: true,
},
});
const firstColumn = props.block.firstcolumn || [
{ text: 'Vårt samhold styrkes gjennom regelmessige arrangementer, med månedlige lokale turneringer og kvartalsvise vennskapsspill mot naboklubber.', style: 'heading' },
{ text: 'Hvert år arrangerer ENHET også en spennende utenlandstur hvor våre lag får utfordre internasjonale motstandere og oppleve nye kulturer. Bli med og opplev spenningen ved å representere ENHET både hjemme og på turer.', style: 'heading' },
{ text: 'Fotball er mer enn et spill; det er en verdensomspennende lidenskap som lærer oss verdien av lagarbeid og fair play. Som den mest populære sporten i verden, bringer fotball sammen mennesker fra alle livets hjørner, og bygger broer på tvers av kulturelle og språklige barrierer.', style: 'regular' },
{ text: 'Denne vakre sporten er en fest for fysisk dyktighet og strategisk tenkning, hvor spenningen på banen kun er matchet av fansens entusiasme. Fra spennende lokale kamper til den elektriske atmosfæren ved verdensmesterskapet, fotball tilbyr en unik kilde til glede og fellesskapsfølelse', style: 'regular' }
];
const secondColumn = props.block.secondcolumn || [
{ text: 'Bli med på våre ukentlige treninger, strategiseminarer og veldedighetsaktiviteter som styrker både kropp og fellesskap.', style: 'headingbold' },
{ text: 'Og trenger du utstyr har vi har et bredt spekter av fotballrelatert utstyr tilgjengelig for våre medlemmer, for å sikre at alle har det de trenger for å spille sitt beste.', style: 'regular' },
{ text: 'Hvordan begynne? Å starte med fotball har aldri vært enklere. Kom til en åpen trening eller kontakt oss for en prat om hvordan du kan bli en del av ENHET. Alt du trenger for å starte er et par fotballsko og et ønske om å lære. Vi tar oss av resten – fra drakter til treningsutstyr.', style: 'regular' }
];
</script>
<template>
<section :id="block.anchorName" class="mx-auto max-w-[1280px] bg-black">
<div class="grid gap-8 grid-cols-1 md:grid-cols-2 p-6">
<div class="max-w-2xl text-gray-200">
<template v-for="(paragraph, index) in firstColumn" :index="index">
<h1 v-if="paragraph.style === 'headinglarge'" class="text-gray-500 text-2xl md:text-4xl font-arctic">
{{ paragraph.text || '' }}
</h1>
<h2 v-if="paragraph.style === 'headingbold'" class="text-gray-500 text-xl md:text-2xl font-arctic mt-4">
{{ paragraph.text || '' }}
</h2>
<h3 v-else-if="paragraph.style === 'heading'" class="text-amber-600 text-xl md:text-2xl font-serif mt-1">
{{ paragraph.text || '' }}
</h3>
<p v-else-if="paragraph.style === 'regular'" class="mt-2">
{{ paragraph.text || '' }}
</p>
<iframe
v-else-if="paragraph.style === 'video'"
class="mt-6 w-72 h-72 md:w-96 md:h-96 lg:w-[560px] lg:h-[315px]"
:src="paragraph.text"
title="YouTube video spiller"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
>
</iframe>
</template>
</div>
<div class="max-w-2xl text-gray-200">
<template v-for="(paragraph, index) in secondColumn" :index="index">
<h1 v-if="paragraph.style === 'headinglarge'" class="text-gray-500 text-2xl md:text-4xl font-arctic">
{{ paragraph.text || '' }}
</h1>
<h2 v-if="paragraph.style === 'headingbold'" class="text-gray-500 text-xl md:text-2xl font-arctic mt-4">
{{ paragraph.text || '' }}
</h2>
<h3 v-else-if="paragraph.style === 'heading'" class="text-amber-600 text-xl md:text-2xl font-serif mt-1">
{{ paragraph.text || '' }}
</h3>
<p v-else-if="paragraph.style === 'regular'" class="mt-2">
{{ paragraph.text || '' }}
</p>
<iframe
v-else-if="paragraph.style === 'video'"
class="mt-6 w-72 h-72 md:w-96 md:h-96 lg:w-[560px] lg:h-[315px]"
:src="paragraph.text"
title="YouTube video spiller"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
>
</iframe>
</template>
</div>
</div>
</section>
</template>