Integration pages from Notion (#753)

* Integration pages from Notion

* fix integration page

* Refactor environment variables and update routing in integration pages

- Removed unused environment variables related to MUX from `.env.docker` and `.env.example` to streamline configuration.
- Updated routing links in `OpenFormFooter.vue` to correct navigation paths for "Integrations", "Report Abuse", and "Privacy Policy", enhancing user experience.
- Added middleware to `definePageMeta` in `index.vue` and `[slug].vue` for integration pages to enforce self-hosted logic.

These changes aim to improve code clarity and ensure proper routing functionality across the application.

---------

Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
Chirag Chhatrala
2025-05-19 18:38:15 +05:30
committed by GitHub
parent f9c734c826
commit c17f4776bc
12 changed files with 728 additions and 88 deletions

View File

@@ -1,7 +1,18 @@
<template>
<notion-renderer
<NotionRenderer
v-if="!loading"
:block-map="blockMap"
:block-map="blocks"
:block-overrides="blockOverrides"
:content-id="contentId"
:full-page="fullPage"
:hide-list="hideList"
:level="level"
:map-image-url="mapImageUrl"
:map-page-url="mapPageUrl"
:page-link-options="pageLinkOptions"
:image-options="imageOptions"
:prism="prism"
:todo="todo"
/>
<div
v-else
@@ -12,27 +23,69 @@
</template>
<script>
import { NotionRenderer } from "vue-notion"
import { NotionRenderer, defaultMapPageUrl, defaultMapImageUrl } from 'vue-notion'
export default {
name: "NotionPage",
name: 'NotionPage',
components: { NotionRenderer },
props: {
blockMap: {
type: Object
},
blockOverrides: {
type: Object,
default: () => ({})
},
loading: {
type: Boolean,
required: true,
type: Boolean
},
contentId: String,
contentIndex: { type: Number, default: 0 },
fullPage: { type: Boolean, default: false },
hideList: { type: Array, default: () => [] },
level: { type: Number, default: 0 },
mapImageUrl: { type: Function, default: defaultMapImageUrl },
mapPageUrl: { type: Function, default: defaultMapPageUrl },
pageLinkOptions: {
type: Object, default: () => {
const NuxtLink = resolveComponent('NuxtLink')
return { component: NuxtLink, href: 'to' }
}
},
imageOptions: Object,
prism: { type: Boolean, default: false },
todo: { type: Boolean, default: false }
},
computed: {
blocks () {
if (this.blockMap && this.blockMap.data) {
return this.blockMap.data
}
return this.blockMap
}
}
}
</script>
<style lang="scss">
<style lang='scss'>
@import "vue-notion/src/styles.css";
.notion-blue {
@apply text-nt-blue;
}
.notion-spacer {
width: 24px !important;
}
.notion-link {
text-decoration: none;
}
.notion {
img, iframe {
@apply rounded-md;
}
}
</style>

View File

@@ -50,6 +50,18 @@
Technical Docs
</a>
<template v-if="!useFeatureFlag('self_hosted')">
<router-link
:to="{ name: 'integrations' }"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
>
Integrations
</router-link>
<router-link
:to="{ name: 'report-abuse' }"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
>
Report Abuse
</router-link>
<router-link
:to="{ name: 'privacy-policy' }"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
@@ -63,13 +75,6 @@
>
Terms & Conditions
</router-link>
<router-link
:to="{ name: 'report-abuse' }"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
>
Report Abuse
</router-link>
</template>
</div>
</div>

View File

@@ -0,0 +1,68 @@
<template>
<div
v-if="innerJson"
id="custom-block"
>
<div
v-if="innerJson.type=='faq'"
class="rounded-lg bg-white z-10 pt-10"
>
<h2 class="font-medium">
Frequently Asked Questions
</h2>
<dl class="pt-4 space-y-6">
<div
v-for="question in innerJson.content"
:key="question.label"
class="space-y-2"
>
<dt class="font-semibold text-gray-900 dark:text-gray-100">
{{ question.label }}
</dt>
<dd
class="leading-6 text-gray-600 dark:text-gray-400"
v-html="question.content"
/>
</div>
</dl>
</div>
<div
v-else-if="innerJson.type=='cta'"
class="rounded-lg relative bg-gradient-to-r from-blue-400 to-blue-600 shadow-ld p-8 z-10"
>
<div class="absolute inset-px rounded-[calc(var(--radius)-1px)]">
<div class="flex justify-center w-full h-full">
<SpotlightCard
class="w-full p-2 rounded-[--radius] [--radius:theme(borderRadius.lg)] opacity-70"
from="#60a5fa"
:size="200"
/>
</div>
</div>
<div class="relative z-20 flex flex-col items-center gap-4 pb-1">
<h2 class="text-xl md:text-2xl text-center font-medium text-white">
{{ innerJson.title ? innerJson.title : 'Ready to upgrade your OpnForm forms?' }}
</h2>
<UButton
to="/register"
color="white"
class="hover:no-underline"
icon="i-heroicons-arrow-right"
trailing
>
Try OpnForm for free
</UButton>
</div>
</div>
</div>
</template>
<script setup>
import { blockProps } from 'vue-notion'
import useNotionBlock from '~/components/pages/notion/useNotionBlock.js'
const props = defineProps(blockProps)
const block = useNotionBlock(props)
const innerJson = computed(() => block.innerJson.value)
</script>

View File

@@ -0,0 +1,107 @@
import { computed } from 'vue'
export default function useNotionBlock (props) {
const block = computed(() => {
const id = props.contentId || Object.keys(props.blockMap)[0]
return props.blockMap[id]
})
const value = computed(() => {
return block.value?.value
})
const format = computed(() => {
return value.value?.format
})
const icon = computed(() => {
return format.value?.page_icon || ''
})
const width = computed(() => {
return format.value?.block_width
})
const properties = computed(() => {
return value.value?.properties
})
const caption = computed(() => {
return properties.value?.caption
})
const description = computed(() => {
return properties.value?.description
})
const src = computed(() => {
return mapImageUrl(properties.value?.source[0][0], block.value)
})
const title = computed(() => {
return properties.value?.title
})
const alt = computed(() => {
return caption.value?.[0][0]
})
const type = computed(() => {
return value.value?.type
})
const visible = computed(() => {
return !props.hideList.includes(type.value)
})
const hasPageLinkOptions = computed(() => {
return props.pageLinkOptions?.component && props.pageLinkOptions?.href
})
const parent = computed(() => {
return props.blockMap[value.value?.parent_id]
})
const innerJson = computed(() => {
if (type.value !== 'code') return
if (properties.value.language.flat('Infinity').join('') !== 'JSON') {
return
}
try {
return JSON.parse(
title.value.flat(Infinity).join('').replace(/\n/g, '').replace(/\t/g, '').trim()
)
} catch (error) {
console.error('Failed to parse JSON',
error,
title.value.flat(Infinity).join('').replace(/\n/g, '').replace(/\t/g, '').trim()
)
return
}
})
function mapImageUrl (source) {
// Implement your mapImageUrl logic here
return source
}
return {
icon,
width,
properties,
caption,
description,
src,
title,
alt,
block,
value,
format,
type,
visible,
hasPageLinkOptions,
parent,
innerJson
}
}