0351d front end linting (#377)

* feat: disable custom script for  trial users

* after lint fix

* frontend linting

---------

Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
Favour Olayinka
2024-04-15 18:39:03 +01:00
committed by GitHub
parent 8d35fc8b1a
commit bcd45ce8a6
228 changed files with 17036 additions and 8744 deletions

View File

@@ -1,51 +1,66 @@
<template>
<div :class="classes">
<Icon v-if="beforeIcon" :name="beforeIcon" :class="iconClasses"/>
<slot></slot>
<Icon v-if="afterIcon" :name="afterIcon" :class="iconClasses"/>
<Icon
v-if="beforeIcon"
:name="beforeIcon"
:class="iconClasses"
/>
<slot />
<Icon
v-if="afterIcon"
:name="afterIcon"
:class="iconClasses"
/>
</div>
</template>
<script setup>
import { default as _has } from 'lodash/has'
import { default as _has } from "lodash/has"
const props = defineProps({
color: {
type: String,
default: 'green'
default: "green",
},
beforeIcon: {
type: String,
default: null
default: null,
},
afterIcon: {
type: String,
default: null
}
default: null,
},
})
const baseClasses = {
'green': ['bg-green-100', 'border', 'border-green-300', 'text-green-700'],
'red': ['bg-red-100', 'border', 'border-red-300', 'text-red-700'],
'gray': ['bg-gray-100', 'border', 'border-gray-300', 'text-gray-700'],
green: ["bg-green-100", "border", "border-green-300", "text-green-700"],
red: ["bg-red-100", "border", "border-red-300", "text-red-700"],
gray: ["bg-gray-100", "border", "border-gray-300", "text-gray-700"],
}
const iconBaseClasses = {
'green': ['text-green-500'],
'red': ['text-red-500'],
'gray': ['text-gray-500'],
green: ["text-green-500"],
red: ["text-red-500"],
gray: ["text-gray-500"],
}
const activeColor = computed(() => {
return _has(baseClasses, props.color) ? props.color : 'gray'
return _has(baseClasses, props.color) ? props.color : "gray"
})
const classes = computed(() => {
const classes = ['border', 'text-xs', 'px-2', 'inline-flex', 'items-center', 'rounded-full'].concat(baseClasses[activeColor.value])
return classes.join(' ')
const classes = [
"border",
"text-xs",
"px-2",
"inline-flex",
"items-center",
"rounded-full",
].concat(baseClasses[activeColor.value])
return classes.join(" ")
})
const iconClasses = computed(() => {
return iconBaseClasses[activeColor.value].concat(['w-2 h-2 mr-1']).join(' ')
return iconBaseClasses[activeColor.value].concat(["w-2 h-2 mr-1"]).join(" ")
})
</script>

View File

@@ -1,46 +1,77 @@
<template>
<section class="sticky flex items-center inset-x-0 top-0 z-20 py-3 bg-white border-b border-gray-200">
<section
class="sticky flex items-center inset-x-0 top-0 z-20 py-3 bg-white border-b border-gray-200"
>
<div class="hidden md:flex flex-grow">
<slot name="left" />
</div>
<div class="px-4 mx-auto sm:px-6 lg:px-8 max-w-7xl">
<div class="flex items-center justify-center space-x-4">
<div v-if="displayHome" class="flex items-center">
<NuxtLink class="text-gray-400 hover:text-gray-500" :to="{ name: (authenticated) ? 'home' : 'index' }">
<svg class="flex-shrink-0 w-5 h-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
d="M9.293 2.293a1 1 0 011.414 0l7 7A1 1 0 0117 11h-1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-3a1 1 0 00-1-1H9a1 1 0 00-1 1v3a1 1 0 01-1 1H5a1 1 0 01-1-1v-6H3a1 1 0 01-.707-1.707l7-7z"
clip-rule="evenodd"
<div
v-if="displayHome"
class="flex items-center"
>
<NuxtLink
class="text-gray-400 hover:text-gray-500"
:to="{ name: authenticated ? 'home' : 'index' }"
>
<svg
class="flex-shrink-0 w-5 h-5"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M9.293 2.293a1 1 0 011.414 0l7 7A1 1 0 0117 11h-1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-3a1 1 0 00-1-1H9a1 1 0 00-1 1v3a1 1 0 01-1 1H5a1 1 0 01-1-1v-6H3a1 1 0 01-.707-1.707l7-7z"
clip-rule="evenodd"
/>
</svg>
<span class="sr-only">Home</span>
</NuxtLink>
<svg class="flex-shrink-0 w-5 h-5 text-gray-400 ml-4" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true"
<svg
class="flex-shrink-0 w-5 h-5 text-gray-400 ml-4"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path fill-rule="evenodd"
d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
clip-rule="evenodd"
<path
fill-rule="evenodd"
d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
clip-rule="evenodd"
/>
</svg>
</div>
<div v-for="(item,index) in path" :key="index" class="flex items-center">
<NuxtLink v-if="item.route" class="text-sm font-semibold text-gray-500 hover:text-gray-700 truncate"
:to="item.route"
<div
v-for="(item, index) in path"
:key="index"
class="flex items-center"
>
<NuxtLink
v-if="item.route"
class="text-sm font-semibold text-gray-500 hover:text-gray-700 truncate"
:to="item.route"
>
{{ item.label }}
</NuxtLink>
<div v-else class="text-sm font-semibold sm:w-full w-36 text-blue-500 truncate">
<div
v-else
class="text-sm font-semibold sm:w-full w-36 text-blue-500 truncate"
>
{{ item.label }}
</div>
<div v-if="index!==path.length-1">
<svg class="flex-shrink-0 w-5 h-5 text-gray-400 ml-4" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true"
<div v-if="index !== path.length - 1">
<svg
class="flex-shrink-0 w-5 h-5 text-gray-400 ml-4"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path fill-rule="evenodd"
d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
clip-rule="evenodd"
<path
fill-rule="evenodd"
d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
clip-rule="evenodd"
/>
</svg>
</div>
@@ -54,36 +85,36 @@
</template>
<script>
import { computed } from 'vue'
import { useAuthStore } from '../../stores/auth';
import { computed } from "vue"
import { useAuthStore } from "../../stores/auth"
export default {
name: 'Breadcrumb',
name: "Breadcrumb",
props: {
/**
* route: Route object
* label: Label
*/
path: { type: Array }
path: { type: Array },
},
setup () {
setup() {
const authStore = useAuthStore()
return {
authenticated : computed(() => authStore.check)
authenticated: computed(() => authStore.check),
}
},
data () {
data() {
return {
displayHome: true
displayHome: true,
}
},
computed: {},
mounted () {},
mounted() {},
methods: {}
methods: {},
}
</script>

View File

@@ -1,8 +1,12 @@
<template>
<div class="flex flex-col w-full bg-white rounded-lg shadow"
:class="{'px-4 py-8 sm:px-6 md:px-8 lg:px-10':padding}"
<div
class="flex flex-col w-full bg-white rounded-lg shadow"
:class="{ 'px-4 py-8 sm:px-6 md:px-8 lg:px-10': padding }"
>
<div v-if="title" class="self-center mb-6 text-xl font-light text-gray-900 sm:text-3xl font-bold dark:text-white">
<div
v-if="title"
class="self-center mb-6 text-xl font-light text-gray-900 sm:text-3xl font-bold dark:text-white"
>
{{ title }}
</div>
<slot />
@@ -11,17 +15,17 @@
<script>
export default {
name: 'Card',
name: "Card",
props: {
padding: {
type: Boolean,
default: true
default: true,
},
title: {
type: String,
default: null
}
}
default: null,
},
},
}
</script>

View File

@@ -1,22 +1,36 @@
<template>
<div>
<div class="w-full relative">
<div class="cursor-pointer" @click="trigger">
<div
class="cursor-pointer"
@click="trigger"
>
<slot name="title" />
</div>
<div class="text-gray-400 hover:text-gray-600 absolute -right-2 -top-1 cursor-pointer p-2" @click="trigger">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 transition transform duration-500"
:class="{'rotate-180':showContent}" viewBox="0 0 20 20" fill="currentColor"
<div
class="text-gray-400 hover:text-gray-600 absolute -right-2 -top-1 cursor-pointer p-2"
@click="trigger"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 transition transform duration-500"
:class="{ 'rotate-180': showContent }"
viewBox="0 0 20 20"
fill="currentColor"
>
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v3.586L7.707 9.293a1 1 0 00-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L11 10.586V7z"
clip-rule="evenodd"
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v3.586L7.707 9.293a1 1 0 00-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L11 10.586V7z"
clip-rule="evenodd"
/>
</svg>
</div>
</div>
<VTransition>
<div v-if="showContent" class="w-full">
<div
v-if="showContent"
class="w-full"
>
<slot />
</div>
</VTransition>
@@ -24,18 +38,18 @@
</template>
<script setup>
import VTransition from './transitions/VTransition.vue'
import { ref, defineProps, defineEmits } from 'vue'
import VTransition from "./transitions/VTransition.vue"
import { ref, defineProps, defineEmits } from "vue"
const props = defineProps({
modelValue: { type: Boolean, default: null }
modelValue: { type: Boolean, default: null },
})
const showContent = ref(props.modelValue)
const emit = defineEmits()
const emit = defineEmits(['update:modelValue'])
const trigger = () => {
showContent.value = !showContent.value
emit('update:modelValue', showContent.value)
emit("update:modelValue", showContent.value)
}
</script>

View File

@@ -1,42 +1,56 @@
<template>
<div class="relative" ref="dropdown">
<slot name="trigger"
:toggle="toggle"
:open="open"
:close="close"
<div
ref="dropdown"
class="relative"
>
<slot
name="trigger"
:toggle="toggle"
:open="open"
:close="close"
/>
<collapsible v-model="isOpen" :class="dropdownClass" @click-away="onClickAway">
<div class="py-1 " role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
<slot/>
<collapsible
v-model="isOpen"
:class="dropdownClass"
@click-away="onClickAway"
>
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu"
>
<slot />
</div>
</collapsible>
</div>
</template>
<script setup>
import {ref} from 'vue'
import Collapsible from './transitions/Collapsible.vue'
import { ref } from "vue"
import Collapsible from "./transitions/Collapsible.vue"
const props = defineProps({
defineProps({
dropdownClass: {
type: String,
default: 'origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 z-20'
}
default:
"origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 z-20",
},
})
const isOpen = ref(false)
const dropdown = ref(null)
const open = (event) => {
const open = () => {
isOpen.value = true
}
const close = (event) => {
const close = () => {
isOpen.value = false
}
const toggle = (event) => {
const toggle = () => {
isOpen.value = !isOpen.value
}
@@ -50,6 +64,6 @@ const onClickAway = (event) => {
defineExpose({
open,
close,
toggle
toggle,
})
</script>

View File

@@ -1,40 +1,51 @@
<template>
<div ref="parentRef"
tabindex="0"
:class="{
'hover:bg-gray-100 dark:hover:bg-gray-800 rounded px-2 cursor-pointer': !editing
}"
class="relative"
:style="{ height: editing ? divHeight + 'px' : 'auto' }"
@focus="startEditing"
<div
ref="parentRef"
tabindex="0"
:class="{
'hover:bg-gray-100 dark:hover:bg-gray-800 rounded px-2 cursor-pointer':
!editing,
}"
class="relative"
:style="{ height: editing ? divHeight + 'px' : 'auto' }"
@focus="startEditing"
>
<slot v-if="!editing" :content="content">
<slot
v-if="!editing"
:content="content"
>
<label class="cursor-pointer truncate w-full">
{{ content }}
</label>
</slot>
<div v-if="editing" class="absolute inset-0 border-2 transition-colors"
:class="{ 'border-transparent': !editing, 'border-blue-500': editing }"
<div
v-if="editing"
class="absolute inset-0 border-2 transition-colors"
:class="{ 'border-transparent': !editing, 'border-blue-500': editing }"
>
<input ref="editInputRef" v-model="content"
class="absolute inset-0 focus:outline-none bg-white transition-colors"
:class="[{'bg-blue-50': editing}, contentClass]" @blur="editing = false" @keyup.enter="editing = false"
@input="handleInput"
<input
ref="editInputRef"
v-model="content"
class="absolute inset-0 focus:outline-none bg-white transition-colors"
:class="[{ 'bg-blue-50': editing }, contentClass]"
@blur="editing = false"
@keyup.enter="editing = false"
@input="handleInput"
>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, watch, nextTick, defineProps, defineEmits } from 'vue'
import { ref, onMounted, watch, nextTick, defineProps, defineEmits } from "vue"
const props = defineProps({
modelValue: { type: String, required: true },
textAlign: { type: String, default: 'left' },
contentClass: { type: String, default: '' }
textAlign: { type: String, default: "left" },
contentClass: { type: String, default: "" },
})
const emit = defineEmits()
const emit = defineEmits(['update:modelValue'])
const content = ref(props.modelValue)
const editing = ref(false)
const divHeight = ref(0)
@@ -55,13 +66,16 @@ const startEditing = () => {
}
const handleInput = () => {
emit('update:modelValue', content.value)
emit("update:modelValue", content.value)
}
// Watch for changes in props.modelValue and update the local content
watch(() => props.modelValue, (newValue) => {
content.value = newValue
})
watch(
() => props.modelValue,
(newValue) => {
content.value = newValue
},
)
// Wait until the component is mounted to set the initial divHeight
onMounted(() => {

View File

@@ -1,13 +1,29 @@
<template>
<svg class="animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
<svg
class="animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
</template>
<script>
export default {
name: 'Loader',
props: {}
name: "Loader",
props: {},
}
</script>

View File

@@ -1,49 +1,90 @@
<template>
<Teleport to="body">
<transition @leave="onLeave">
<div v-if="show" ref="backdrop"
class="fixed z-30 top-0 inset-0 px-4 sm:px-0 flex items-top justify-center bg-gray-700/75 w-full h-screen overflow-y-scroll"
:class="{'backdrop-blur-sm':backdropBlur}"
@click.self="close"
<div
v-if="show"
ref="backdrop"
class="fixed z-30 top-0 inset-0 px-4 sm:px-0 flex items-top justify-center bg-gray-700/75 w-full h-screen overflow-y-scroll"
:class="{ 'backdrop-blur-sm': backdropBlur }"
@click.self="close"
>
<div ref="content"
class="self-start bg-white dark:bg-notion-dark w-full relative my-6 rounded-xl shadow-xl"
:class="maxWidthClass"
<div
ref="content"
class="self-start bg-white dark:bg-notion-dark w-full relative my-6 rounded-xl shadow-xl"
:class="maxWidthClass"
>
<div v-if="closeable" class="absolute top-4 right-4">
<button class="text-gray-500 hover:text-gray-900 cursor-pointer" @click="close()">
<svg class="h-6 w-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"
<div
v-if="closeable"
class="absolute top-4 right-4"
>
<button
class="text-gray-500 hover:text-gray-900 cursor-pointer"
@click="close()"
>
<svg
class="h-6 w-6"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18 6L6 18M6 6L18 18"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</button>
</div>
<div class="flex border-b pb-4"
v-if="_has($slots,'icon') || _has($slots,'title')"
:class="[{'flex-col sm:items-start':!compactHeader, 'items-center justify-center py-6 gap-x-4':compactHeader},headerInnerPadding]">
<div v-if="_has($slots,'icon')" :class="{'w-full mb-4 flex justify-center':!compactHeader}">
<div class="w-14 h-14 rounded-full flex justify-center items-center"
:class="'bg-'+iconColor+'-100 text-'+iconColor+'-600'"
<div
v-if="_has($slots, 'icon') || _has($slots, 'title')"
class="flex border-b pb-4"
:class="[
{
'flex-col sm:items-start': !compactHeader,
'items-center justify-center py-6 gap-x-4': compactHeader,
},
headerInnerPadding,
]"
>
<div
v-if="_has($slots, 'icon')"
:class="{ 'w-full mb-4 flex justify-center': !compactHeader }"
>
<div
class="w-14 h-14 rounded-full flex justify-center items-center"
:class="'bg-' + iconColor + '-100 text-' + iconColor + '-600'"
>
<slot name="icon"/>
<slot name="icon" />
</div>
</div>
<div class="mt-3 text-center sm:mt-0" :class="{'w-full':!compactHeader}">
<h2 v-if="_has($slots,'title')"
class="text-2xl font-semibold text-center text-gray-900"
<div
class="mt-3 text-center sm:mt-0"
:class="{ 'w-full': !compactHeader }"
>
<h2
v-if="_has($slots, 'title')"
class="text-2xl font-semibold text-center text-gray-900"
>
<slot name="title"/>
<slot name="title" />
</h2>
</div>
</div>
<div class="w-full" :class="innerPadding">
<slot/>
<div
class="w-full"
:class="innerPadding"
>
<slot />
</div>
<div v-if="_has($slots,'footer')" class="bg-gray-50 border-t rounded-b-xl text-right" :class="footerInnerPadding">
<slot name="footer"/>
<div
v-if="_has($slots, 'footer')"
class="bg-gray-50 border-t rounded-b-xl text-right"
:class="footerInnerPadding"
>
<slot name="footer" />
</div>
</div>
</div>
@@ -52,77 +93,84 @@
</template>
<script setup>
import {watch} from "vue";
import { default as _has } from 'lodash/has'
import { watch } from "vue"
import { default as _has } from "lodash/has"
const props = defineProps({
show: {
default: false
type: Boolean,
default: false,
},
backdropBlur: {
type: Boolean,
default: false
default: false,
},
iconColor: {
default: 'blue'
type: String,
default: "blue",
},
maxWidth: {
default: '2xl'
type: String,
default: "2xl",
},
innerPadding: {
default: 'p-6'
type: String,
default: "p-6",
},
headerInnerPadding: {
default: 'p-6'
type: String,
default: "p-6",
},
footerInnerPadding: {
default: 'p-6'
type: String,
default: "p-6",
},
closeable: {
default: true
type: Boolean,
default: true,
},
compactHeader: {
default: false,
type: Boolean
type: Boolean,
},
})
const emit = defineEmits(['close'])
const emit = defineEmits(["close"])
useHead({
bodyAttrs: computed(() => {
return {
class: {
'overflow-hidden': props.show
}
"overflow-hidden": props.show,
},
}
})
}),
})
const closeOnEscape = (e) => {
if (e.key === 'Escape' && props.show) {
if (e.key === "Escape" && props.show) {
close()
}
}
onMounted(() => {
if (import.meta.server) return
document.addEventListener('keydown', closeOnEscape)
document.addEventListener("keydown", closeOnEscape)
initMotions()
})
onBeforeUnmount(() => {
if (import.meta.server) return
document.removeEventListener('keydown', closeOnEscape)
document.removeEventListener("keydown", closeOnEscape)
})
const maxWidthClass = computed(() => {
return {
sm: 'sm:max-w-sm',
md: 'sm:max-w-md',
lg: 'sm:max-w-lg',
xl: 'sm:max-w-xl',
'2xl': 'sm:max-w-2xl'
sm: "sm:max-w-sm",
md: "sm:max-w-md",
lg: "sm:max-w-lg",
xl: "sm:max-w-xl",
"2xl": "sm:max-w-2xl",
}[props.maxWidth]
})
@@ -132,15 +180,15 @@ const motionFadeIn = {
transition: {
delay: 100,
duration: 200,
ease: 'easeIn'
}
ease: "easeIn",
},
},
enter: {
opacity: 1,
transition: {
duration: 200
}
}
duration: 200,
},
},
}
const motionSlideBottom = {
@@ -148,30 +196,29 @@ const motionSlideBottom = {
y: 150,
opacity: 0,
transition: {
ease: 'easeIn',
duration: 200
}
ease: "easeIn",
duration: 200,
},
},
enter: {
y: 0,
opacity: 1,
transition: {
duration: 250,
ease: 'easeOut',
delay: 100
}
}
ease: "easeOut",
delay: 100,
},
},
}
const onLeave = (el, done) => {
contentMotion.value.leave(() => {
})
contentMotion.value.leave(() => {})
backdropMotion.value.leave(done)
}
const close = () => {
if (props.closeable) {
emit('close')
emit("close")
}
}
@@ -190,5 +237,4 @@ const initMotions = () => {
}
watch(() => props.show, initMotions)
</script>

View File

@@ -1,152 +1,258 @@
<template>
<nav v-if="hasNavbar" class="bg-white dark:bg-notion-dark border-b">
<nav
v-if="hasNavbar"
class="bg-white dark:bg-notion-dark border-b"
>
<div class="max-w-7xl mx-auto px-8">
<div class="flex items-center justify-between h-16">
<div class="flex items-center">
<NuxtLink :to="{ name: user ? 'home' : 'index' }"
class="flex-shrink-0 font-semibold hover:no-underline flex items-center">
<img src="/img/logo.svg" alt="notion tools logo" class="w-8 h-8"/>
<span class="ml-2 text-md hidden sm:inline text-black dark:text-white">OpnForm</span>
<NuxtLink
:to="{ name: user ? 'home' : 'index' }"
class="flex-shrink-0 font-semibold hover:no-underline flex items-center"
>
<img
src="/img/logo.svg"
alt="notion tools logo"
class="w-8 h-8"
>
<span
class="ml-2 text-md hidden sm:inline text-black dark:text-white"
>OpnForm</span>
</NuxtLink>
<workspace-dropdown class="ml-6"/>
<workspace-dropdown class="ml-6" />
</div>
<div v-if="showAuth" class="hidden md:block ml-auto relative">
<NuxtLink v-if="$route.name !== 'templates'" :to="{name:'templates'}"
class="text-sm text-gray-600 dark:text-white hover:text-gray-800 cursor-pointer mt-1 mr-8"
<div
v-if="showAuth"
class="hidden md:block ml-auto relative"
>
<NuxtLink
v-if="$route.name !== 'templates'"
:to="{ name: 'templates' }"
class="text-sm text-gray-600 dark:text-white hover:text-gray-800 cursor-pointer mt-1 mr-8"
>
Templates
</NuxtLink>
<template v-if="featureBaseEnabled">
<button v-if="user" @click.prevent="openChangelog"
class="text-sm text-gray-600 dark:text-white hidden sm:inline hover:text-gray-800 cursor-pointer mt-1 mr-8"
<button
v-if="user"
class="text-sm text-gray-600 dark:text-white hidden sm:inline hover:text-gray-800 cursor-pointer mt-1 mr-8"
@click.prevent="openChangelog"
>
What's new? <span id="fb-update-badge"></span>
What's new? <span id="fb-update-badge" />
</button>
<a :href="opnformConfig.links.changelog_url" target="_blank" v-else
class="text-sm text-gray-600 dark:text-white hidden lg:inline hover:text-gray-800 cursor-pointer mt-1 mr-8"
<a
v-else
:href="opnformConfig.links.changelog_url"
target="_blank"
class="text-sm text-gray-600 dark:text-white hidden lg:inline hover:text-gray-800 cursor-pointer mt-1 mr-8"
>
What's new?
</a>
</template>
<NuxtLink v-if="$route.name !== 'ai-form-builder' && user === null" :to="{name:'ai-form-builder'}"
class="text-sm text-gray-600 dark:text-white hidden lg:inline hover:text-gray-800 cursor-pointer mt-1 mr-8"
<NuxtLink
v-if="$route.name !== 'ai-form-builder' && user === null"
:to="{ name: 'ai-form-builder' }"
class="text-sm text-gray-600 dark:text-white hidden lg:inline hover:text-gray-800 cursor-pointer mt-1 mr-8"
>
AI Form Builder
</NuxtLink>
<NuxtLink
v-if="paidPlansEnabled && (user===null || (user && workspace && !workspace.is_pro)) && $route.name !== 'pricing'"
:to="{name:'pricing'}"
v-if="
paidPlansEnabled &&
(user === null || (user && workspace && !workspace.is_pro)) &&
$route.name !== 'pricing'
"
:to="{ name: 'pricing' }"
class="text-sm text-gray-600 dark:text-white hover:text-gray-800 cursor-pointer mt-1 mr-8"
>
<span v-if="user">Upgrade</span>
<span v-else>Pricing</span>
</NuxtLink>
<a v-if="hasCrisp" href="#"
class="text-sm text-gray-600 dark:text-white hover:text-gray-800 cursor-pointer mt-1"
@click.prevent="openHelpdesk"
<a
v-if="hasCrisp"
href="#"
class="text-sm text-gray-600 dark:text-white hover:text-gray-800 cursor-pointer mt-1"
@click.prevent="openHelpdesk"
>
Help
</a>
<NuxtLink v-else :href="helpUrl"
class="text-sm text-gray-600 dark:text-white hover:text-gray-800 cursor-pointer mt-1"
target="_blank"
<NuxtLink
v-else
:href="helpUrl"
class="text-sm text-gray-600 dark:text-white hover:text-gray-800 cursor-pointer mt-1"
target="_blank"
>
Help
</NuxtLink>
</div>
<div v-if="showAuth" class="hidden md:block pl-5 border-gray-300 border-r h-5"/>
<div v-if="showAuth" class="block">
<div
v-if="showAuth"
class="hidden md:block pl-5 border-gray-300 border-r h-5"
/>
<div
v-if="showAuth"
class="block"
>
<div class="flex items-center">
<div class="ml-3 mr-4 relative">
<div class="relative inline-block text-left">
<dropdown v-if="user" dusk="nav-dropdown">
<template #trigger="{toggle}">
<button id="dropdown-menu-button" type="button"
class="flex items-center justify-center w-full rounded-md px-4 py-2 text-sm text-gray-700 dark:text-gray-50 hover:bg-gray-50 dark:hover:bg-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-gray-500"
dusk="nav-dropdown-button" @click.stop="toggle()"
<dropdown
v-if="user"
dusk="nav-dropdown"
>
<template #trigger="{ toggle }">
<button
id="dropdown-menu-button"
type="button"
class="flex items-center justify-center w-full rounded-md px-4 py-2 text-sm text-gray-700 dark:text-gray-50 hover:bg-gray-50 dark:hover:bg-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-gray-500"
dusk="nav-dropdown-button"
@click.stop="toggle()"
>
<img :src="user.photo_url" class="rounded-full w-6 h-6"/>
<img
:src="user.photo_url"
class="rounded-full w-6 h-6"
>
<p class="ml-2 hidden sm:inline">
{{ user.name }}
</p>
</button>
</template>
<NuxtLink v-if="userOnboarded" :to="{ name: 'home' }"
class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
<NuxtLink
v-if="userOnboarded"
:to="{ name: 'home' }"
class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
>
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 mr-2" fill="none" viewBox="0 0 24 24"
stroke="currentColor"
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-4 h-4 mr-2"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
/>
</svg>
My Forms
</NuxtLink>
<NuxtLink v-if="userOnboarded" :to="{ name: 'templates-my-templates' }"
class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
<NuxtLink
v-if="userOnboarded"
:to="{ name: 'templates-my-templates' }"
class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
>
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 mr-2" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"/>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-4 h-4 mr-2"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
/>
</svg>
My Templates
</NuxtLink>
<NuxtLink :to="{ name: 'settings-profile' }"
class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
<NuxtLink
:to="{ name: 'settings-profile' }"
class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
>
<svg class="w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke="currentColor"
<svg
class="w-4 h-4 mr-2"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
Settings
</NuxtLink>
<NuxtLink :to="{ name: 'settings-admin' }" v-if="user.moderator"
class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
<NuxtLink
v-if="user.moderator"
:to="{ name: 'settings-admin' }"
class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4 mr-2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z"/>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4 mr-2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z"
/>
</svg>
Admin
</NuxtLink>
<a href="#"
class="block block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
@click.prevent="logout"
<a
href="#"
class="block block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
@click.prevent="logout"
>
<svg class="w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke="currentColor"
<svg
class="w-4 h-4 mr-2"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
/>
</svg>
Logout
</a>
</dropdown>
<div v-else class="flex gap-2">
<NuxtLink v-if="$route.name !== 'login'" :to="{ name: 'login' }"
class="text-gray-600 dark:text-white hover:text-gray-800 dark:hover:text-white px-0 sm:px-3 py-2 rounded-md text-sm"
active-class="text-gray-800 dark:text-white"
<div
v-else
class="flex gap-2"
>
<NuxtLink
v-if="$route.name !== 'login'"
:to="{ name: 'login' }"
class="text-gray-600 dark:text-white hover:text-gray-800 dark:hover:text-white px-0 sm:px-3 py-2 rounded-md text-sm"
active-class="text-gray-800 dark:text-white"
>
Login
</NuxtLink>
<v-button v-track.nav_create_form_click size="small" :to="{ name: 'forms-create-guest' }"
color="outline-blue" :arrow="true">
<v-button
v-track.nav_create_form_click
size="small"
:to="{ name: 'forms-create-guest' }"
color="outline-blue"
:arrow="true"
>
Create a form
</v-button>
</div>
@@ -160,20 +266,20 @@
</template>
<script>
import {computed} from 'vue'
import Dropdown from '~/components/global/Dropdown.vue'
import WorkspaceDropdown from './WorkspaceDropdown.vue'
import opnformConfig from "~/opnform.config.js";
import {useRuntimeConfig} from "#app";
import { computed } from "vue"
import Dropdown from "~/components/global/Dropdown.vue"
import WorkspaceDropdown from "./WorkspaceDropdown.vue"
import opnformConfig from "~/opnform.config.js"
import { useRuntimeConfig } from "#app"
export default {
components: {
WorkspaceDropdown,
Dropdown
Dropdown,
},
async setup() {
const {openHelpdesk} = useCrisp()
const { openHelpdesk } = useCrisp()
const authStore = useAuthStore()
return {
authStore,
@@ -193,7 +299,7 @@ export default {
return this.opnformConfig.links.help_url
},
form() {
if (this.$route.name && this.$route.name.startsWith('forms-slug')) {
if (this.$route.name && this.$route.name.startsWith("forms-slug")) {
return this.formsStore.getByKey(this.$route.params.slug)
}
return null
@@ -208,12 +314,12 @@ export default {
return this.config.public.featureBaseOrganization !== null
},
showAuth() {
return this.$route.name && this.$route.name !== 'forms-slug'
return this.$route.name && this.$route.name !== "forms-slug"
},
hasNavbar() {
if (this.isIframe) return false
if (this.$route.name && this.$route.name === 'forms-slug') {
if (this.$route.name && this.$route.name === "forms-slug") {
if (this.form) {
// If there is a cover, or if branding is hidden remove nav
if (this.form.cover_picture || this.form.no_branding) {
@@ -229,14 +335,17 @@ export default {
return this.user && this.user.has_forms === true
},
hasCrisp() {
return this.config.public.crispWebsiteId && this.config.public.crispWebsiteId !== ''
}
return (
this.config.public.crispWebsiteId &&
this.config.public.crispWebsiteId !== ""
)
},
},
methods: {
openChangelog() {
if (import.meta.server) return
window.Featurebase('manually_open_changelog_popup')
window.Featurebase("manually_open_changelog_popup")
},
async logout() {
// Log out the user.
@@ -248,8 +357,8 @@ export default {
// Redirect to login.
const router = useRouter()
router.push({name: 'login'})
router.push({ name: "login" })
},
}
},
}
</script>

View File

@@ -1,29 +1,60 @@
<template>
<div class="fixed top-0 bottom-24 right-0 flex gap-y-4 items-start justify-end z-50 pointer-events-auto">
<div
class="fixed top-0 bottom-24 right-0 flex gap-y-4 items-start justify-end z-50 pointer-events-auto"
>
<NuxtNotifications>
<template #body="props">
<div class="p-2">
<div
class="flex max-w-sm w-full mx-auto bg-white shadow-md rounded-lg overflow-hidden relative"
>
<div class="flex justify-center items-center w-12" :class="notifTypes[props.item.type].background"
v-html="notifTypes[props.item.type].svg"/>
<div
class="flex justify-center items-center w-12"
:class="notifTypes[props.item.type].background"
v-html="notifTypes[props.item.type].svg"
/>
<div class="-mx-3 py-2 px-4">
<div class="mx-3">
<span :class="notifTypes[props.item.type].text" class="font-semibold pr-6">{{ props.item.title }}</span>
<p class="text-gray-600 text-sm">{{ props.item.text }}</p>
<div class="w-full flex gap-2 mt-1" v-if="props.item.type == 'confirm'">
<v-button color="blue" size="small" @click.prevent="props.item.data.success();props.close()">Yes
<span
:class="notifTypes[props.item.type].text"
class="font-semibold pr-6"
>{{ props.item.title }}</span>
<p class="text-gray-600 text-sm">
{{ props.item.text }}
</p>
<div
v-if="props.item.type == 'confirm'"
class="w-full flex gap-2 mt-1"
>
<v-button
color="blue"
size="small"
@click.prevent="
props.item.data.success();
props.close();
"
>
Yes
</v-button>
<v-button color="white" size="small"
@click.prevent="props.item.data.failure();props.close()">No
<v-button
color="white"
size="small"
@click.prevent="
props.item.data.failure();
props.close();
"
>
No
</v-button>
</div>
</div>
</div>
<button @click="props.close()" class="absolute top-0 right-0 px-2 py-2 cursor-pointer">
<button
class="absolute top-0 right-0 px-2 py-2 cursor-pointer"
@click="props.close()"
>
<svg
class="fill-current h-6 w-6 text-gray-300 hover:text-gray-500"
role="button"
@@ -45,60 +76,64 @@
<script>
export default {
name: 'Notifications',
name: "Notifications",
data() {
return {
notifTypes: {
success: {
background: 'bg-green-500',
text: 'text-green-500',
svg: '<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 fill-current text-white" viewBox="0 0 20 20" fill="currentColor">' +
background: "bg-green-500",
text: "text-green-500",
svg:
'<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 fill-current text-white" viewBox="0 0 20 20" fill="currentColor">' +
' <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />' +
' </svg>'
" </svg>",
},
warning: {
background: 'bg-yellow-500',
text: 'text-yellow-500',
svg: '<svg' +
background: "bg-yellow-500",
text: "text-yellow-500",
svg:
"<svg" +
' class="h-6 w-6 fill-current text-white"' +
' viewBox="0 0 40 40"' +
' xmlns="http://www.w3.org/2000/svg"' +
' >' +
' <path' +
" >" +
" <path" +
' d="M20 3.33331C10.8 3.33331 3.33337 10.8 3.33337 20C3.33337 29.2 10.8 36.6666 20 36.6666C29.2 36.6666 36.6667 29.2 36.6667 20C36.6667 10.8 29.2 3.33331 20 3.33331ZM21.6667 28.3333H18.3334V25H21.6667V28.3333ZM21.6667 21.6666H18.3334V11.6666H21.6667V21.6666Z"' +
' />' +
' </svg>',
" />" +
" </svg>",
},
error: {
background: 'bg-red-500',
text: 'text-red-500',
svg: '<svg' +
background: "bg-red-500",
text: "text-red-500",
svg:
"<svg" +
' class="h-6 w-6 fill-current text-white"' +
' viewBox="0 0 40 40"' +
' xmlns="http://www.w3.org/2000/svg"' +
' >' +
' <path' +
" >" +
" <path" +
' d="M20 3.33331C10.8 3.33331 3.33337 10.8 3.33337 20C3.33337 29.2 10.8 36.6666 20 36.6666C29.2 36.6666 36.6667 29.2 36.6667 20C36.6667 10.8 29.2 3.33331 20 3.33331ZM21.6667 28.3333H18.3334V25H21.6667V28.3333ZM21.6667 21.6666H18.3334V11.6666H21.6667V21.6666Z"' +
' />' +
' </svg>'
" />" +
" </svg>",
},
confirm: {
background: 'bg-blue-500',
text: 'text-blue-500',
svg: '<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 fill-current text-white" viewBox="0 0 20 20" fill="currentColor">' +
background: "bg-blue-500",
text: "text-blue-500",
svg:
'<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 fill-current text-white" viewBox="0 0 20 20" fill="currentColor">' +
' <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />' +
' </svg>'
" </svg>",
},
info: {
background: 'bg-blue-500',
text: 'text-blue-500',
svg: '<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 fill-current text-white" viewBox="0 0 20 20" fill="currentColor">' +
background: "bg-blue-500",
text: "text-blue-500",
svg:
'<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 fill-current text-white" viewBox="0 0 20 20" fill="currentColor">' +
' <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />' +
' </svg>'
}
}
" </svg>",
},
},
}
},
}

View File

@@ -1,24 +1,30 @@
<template>
<notion-renderer v-if="!loading" :block-map="blockMap"/>
<div class="p-6 flex items-center justify-center" v-else>
<loader class="w-6 h-6"/>
<notion-renderer
v-if="!loading"
:block-map="blockMap"
/>
<div
v-else
class="p-6 flex items-center justify-center"
>
<loader class="w-6 h-6" />
</div>
</template>
<script>
import {NotionRenderer} from 'vue-notion'
import { NotionRenderer } from "vue-notion"
export default {
name: 'NotionPage',
components: {NotionRenderer},
name: "NotionPage",
components: { NotionRenderer },
props: {
blockMap: {
type: Object
type: Object,
},
loading: {
type: Boolean,
required: true
}
required: true,
},
},
}
</script>

View File

@@ -1,35 +1,53 @@
<template>
<div class="inline" v-if="shouldDisplayProTag">
<div
v-if="shouldDisplayProTag"
class="inline"
>
<UTooltip text="Upgrade to use this feature">
<div role="button" class="bg-nt-blue text-white px-2 text-xs uppercase inline rounded-full font-semibold cursor-pointer"
@click="showPremiumModal=true">
<div
role="button"
class="bg-nt-blue text-white px-2 text-xs uppercase inline rounded-full font-semibold cursor-pointer"
@click="showPremiumModal = true"
>
PRO
</div>
<modal :show="showPremiumModal" @close="showPremiumModal=false">
<modal
:show="showPremiumModal"
@close="showPremiumModal = false"
>
<h2 class="text-nt-blue">
OpnForm PRO
</h2>
<h4 v-if="user && user.is_subscribed" class="text-center mt-5">
We're happy to have you as a Pro customer. If you're having any issue with OpnForm, or if you have a
feature request, please <a href="mailto:contact@opnform.com">contact us</a>.
<h4
v-if="user && user.is_subscribed"
class="text-center mt-5"
>
We're happy to have you as a Pro customer. If you're having any issue
with OpnForm, or if you have a feature request, please
<a href="mailto:contact@opnform.com">contact us</a>.
</h4>
<div v-if="!user || !user.is_subscribed" class="mt-4">
<div
v-if="!user || !user.is_subscribed"
class="mt-4"
>
<p>
All the features with a<span
class="bg-nt-blue text-white px-2 text-xs uppercase inline rounded-full font-semibold mx-1"
>
PRO
</span> tag are available in the Pro plan of OpnForm. <b>You can play around and try all Pro features
within
the form editor, but you can't use them in your real forms</b>. You can subscribe now to gain unlimited
access
to
all our pro features!
class="bg-nt-blue text-white px-2 text-xs uppercase inline rounded-full font-semibold mx-1"
>
PRO
</span>
tag are available in the Pro plan of OpnForm.
<b>You can play around and try all Pro features within the form
editor, but you can't use them in your real forms</b>. You can subscribe now to gain unlimited access to all our pro
features!
</p>
</div>
<div class="my-4 text-center">
<v-button color="white" @click="showPremiumModal=false">
<v-button
color="white"
@click="showPremiumModal = false"
>
Close
</v-button>
</div>
@@ -39,7 +57,7 @@
</template>
<script setup>
import {computed} from 'vue'
import { computed } from "vue"
const authStore = useAuthStore()
const workspacesStore = useWorkspacesStore()
@@ -50,6 +68,6 @@ const showPremiumModal = ref(false)
const shouldDisplayProTag = computed(() => {
if (!useRuntimeConfig().public.paidPlansEnabled) return false
if (!user.value || !workspace.value) return true
return !(workspace.value.is_pro)
return !workspace.value.is_pro
})
</script>

View File

@@ -1,47 +1,58 @@
<template>
<div class="scroll-shadow max-w-full" :class="[$style.wrap,{'w-max':!shadow.left && !shadow.right}]">
<div
class="scroll-shadow max-w-full"
:class="[$style.wrap, { 'w-max': !shadow.left && !shadow.right }]"
>
<div
ref="scrollContainer"
:class="[$style['scroll-container'],{'no-scrollbar':hideScrollbar}]"
:style="{ width: width?width:'auto', height }"
:class="[$style['scroll-container'], { 'no-scrollbar': hideScrollbar }]"
:style="{ width: width ? width : 'auto', height }"
@scroll.passive="throttled.toggleShadow"
>
<slot />
<span :class="[$style['shadow-top'], shadow.top && $style['is-active']]" :style="{
top: shadowTopOffset+'px',
}"
<span
:class="[$style['shadow-top'], shadow.top && $style['is-active']]"
:style="{
top: shadowTopOffset + 'px',
}"
/>
<span
:class="[$style['shadow-right'], shadow.right && $style['is-active']]"
/>
<span
:class="[$style['shadow-bottom'], shadow.bottom && $style['is-active']]"
/>
<span
:class="[$style['shadow-left'], shadow.left && $style['is-active']]"
/>
<span :class="[$style['shadow-right'], shadow.right && $style['is-active']]" />
<span :class="[$style['shadow-bottom'], shadow.bottom && $style['is-active']]" />
<span :class="[$style['shadow-left'], shadow.left && $style['is-active']]" />
</div>
</div>
</template>
<script>
import throttle from 'lodash/throttle'
function newResizeObserver (callback) {
import throttle from "lodash/throttle"
function newResizeObserver(callback) {
// Skip this feature for browsers which
// do not support ResizeObserver.
// https://caniuse.com/#search=resizeobserver
if (typeof ResizeObserver === 'undefined') return
if (typeof ResizeObserver === "undefined") return
return new ResizeObserver(e => e.map(callback))
return new ResizeObserver((e) => e.map(callback))
}
export default {
name: 'ScrollShadow',
name: "ScrollShadow",
props: {
hideScrollbar: {
type: Boolean,
default: false
default: false,
},
shadowTopOffset: {
type: Number,
default: 0
}
default: 0,
},
},
data () {
data() {
return {
width: undefined,
height: undefined,
@@ -49,21 +60,23 @@ export default {
top: false,
right: false,
bottom: false,
left: false
left: false,
},
scrollContainerObserver: null,
wrapObserver: null,
throttled: {}
throttled: {},
}
},
mounted () {
this.throttled.toggleShadow = throttle(this.toggleShadow, 100);
this.throttled.calcDimensions = throttle(this.calcDimensions, 100);
mounted() {
this.throttled.toggleShadow = throttle(this.toggleShadow, 100)
this.throttled.calcDimensions = throttle(this.calcDimensions, 100)
window.addEventListener('resize', this.throttled.calcDimensions)
window.addEventListener("resize", this.throttled.calcDimensions)
// Check if shadows are necessary after the element is resized.
const scrollContainerObserver = newResizeObserver(this.throttled.toggleShadow)
const scrollContainerObserver = newResizeObserver(
this.throttled.toggleShadow,
)
if (scrollContainerObserver) {
scrollContainerObserver.observe(this.$refs.scrollContainer)
}
@@ -74,8 +87,8 @@ export default {
this.wrapObserver.observe(this.$el)
}
},
unmounted () {
window.removeEventListener('resize', this.throttled.calcDimensions)
unmounted() {
window.removeEventListener("resize", this.throttled.calcDimensions)
// Cleanup when the component is unmounted.
this.wrapObserver.disconnect()
if (this.scrollContainerObserver) {
@@ -83,7 +96,7 @@ export default {
}
},
methods: {
async calcDimensions () {
async calcDimensions() {
// Reset dimensions for correctly recalculating parent dimensions.
this.width = undefined
this.height = undefined
@@ -93,7 +106,7 @@ export default {
this.height = `${this.$el.clientHeight}px`
},
// Check if shadows are needed.
toggleShadow () {
toggleShadow() {
if (!this.$refs.scrollContainer) return
const hasHorizontalScrollbar =
this.$refs.scrollContainer.clientWidth <
@@ -122,8 +135,8 @@ export default {
this.shadow.bottom = hasVerticalScrollbar && !scrolledToBottom
this.shadow.left = hasHorizontalScrollbar && !scrolledToLeft
})
}
}
},
},
}
</script>
@@ -155,7 +168,7 @@ export default {
height: 1em;
border-top-right-radius: 0;
border-top-left-radius: 0;
background-image: linear-gradient(rgba(#555, 0.1) 0%, rgba(#FFF, 0) 100%);
background-image: linear-gradient(rgba(#555, 0.1) 0%, rgba(#fff, 0) 100%);
}
.shadow-top {
@@ -174,7 +187,11 @@ export default {
width: 1em;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
background-image: linear-gradient(90deg, rgba(#555, 0.1) 0%, rgba(#FFF, 0) 100%);
background-image: linear-gradient(
90deg,
rgba(#555, 0.1) 0%,
rgba(#fff, 0) 100%
);
}
.shadow-right {

View File

@@ -1,22 +1,36 @@
<template>
<div class="py-4" :class="{'border-b-2':borderBottom}">
<div class="uppercase tracking-wide text-xs font-bold dark:text-gray-400 text-gray-500 mb-1 leading-tight">
<div
class="py-4"
:class="{ 'border-b-2': borderBottom }"
>
<div
class="uppercase tracking-wide text-xs font-bold dark:text-gray-400 text-gray-500 mb-1 leading-tight"
>
Step: {{ Math.min(current + 1, steps.length) }} of {{ steps.length }}
</div>
<div class="flex flex-col md:flex-row md:items-center md:justify-between">
<div class="flex-1">
<div class="text-lg font-bold dark:text-gray-300 text-gray-700 leading-tight">
{{ steps[current] ? steps[current] : 'Complete!' }}
<div
class="text-lg font-bold dark:text-gray-300 text-gray-700 leading-tight"
>
{{ steps[current] ? steps[current] : "Complete!" }}
</div>
</div>
<div class="flex items-center md:w-64">
<div class="w-full bg-gray-100 dark:bg-gray-700 rounded-full mr-2">
<div class="rounded-full bg-nt-blue text-xs leading-none h-2 text-center text-white transition-all"
:style="{'width': parseInt(current / steps.length * 100) +'%', 'min-width': '8px'}"
<div
class="rounded-full bg-nt-blue text-xs leading-none h-2 text-center text-white transition-all"
:style="{
width: parseInt((current / steps.length) * 100) + '%',
'min-width': '8px',
}"
/>
</div>
<div class="text-xs w-10 text-gray-600 dark:text-gray-400" v-text="parseInt(current / steps.length * 100) +'%'" />
<div
class="text-xs w-10 text-gray-600 dark:text-gray-400"
v-text="parseInt((current / steps.length) * 100) + '%'"
/>
</div>
</div>
</div>
@@ -24,21 +38,21 @@
<script>
export default {
name: 'Steps',
name: "Steps",
props: {
steps: {
type: Array,
required: true
required: true,
},
borderBottom: {
type: Boolean,
default: true
default: true,
},
current: {
type: Number,
default: 0
}
}
default: 0,
},
},
}
</script>

View File

@@ -1,27 +1,66 @@
<template>
<NuxtLink v-if="href" :class="btnClasses" :href="href" :target="target">
<NuxtLink
v-if="href"
:class="btnClasses"
:href="href"
:target="target"
>
<slot />
</NuxtLink>
<button v-else-if="!to" :type="nativeType" :disabled="loading?true:null" :class="btnClasses">
<button
v-else-if="!to"
:type="nativeType"
:disabled="loading ? true : null"
:class="btnClasses"
>
<template v-if="!loading">
<span class="no-underline mx-auto">
<slot />
</span>
<svg v-if="arrow" class="ml-2 w-3 h-3 inline" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 11L11 1M11 1H1M11 1V11" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"
<svg
v-if="arrow"
class="ml-2 w-3 h-3 inline"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 11L11 1M11 1H1M11 1V11"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
<Loader v-else class="h-6 w-6 mx-auto" :class="`text-${colorShades['text']}`" />
<Loader
v-else
class="h-6 w-6 mx-auto"
:class="`text-${colorShades['text']}`"
/>
</button>
<NuxtLink v-else :class="btnClasses" :to="to" :target="target">
<NuxtLink
v-else
:class="btnClasses"
:to="to"
:target="target"
>
<span class="no-underline mx-auto">
<slot />
</span>
<svg v-if="arrow" class="ml-2 w-3 h-3 inline" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 11L11 1M11 1H1M11 1V11" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"
<svg
v-if="arrow"
class="ml-2 w-3 h-3 inline"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 11L11 1M11 1H1M11 1V11"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</NuxtLink>
@@ -29,149 +68,152 @@
<script>
export default {
name: 'VButton',
name: "VButton",
props: {
color: {
type: String,
default: 'blue'
default: "blue",
},
size: {
type: String,
default: 'medium'
default: "medium",
},
nativeType: {
type: String,
default: null
default: null,
},
loading: {
type: Boolean,
default: false
default: false,
},
arrow: {
type: Boolean,
default: false
default: false,
},
to: {
type: Object,
default: null
default: null,
},
href: {
type: String,
default: null
default: null,
},
target: {
type: String,
default: '_self'
}
default: "_self",
},
},
computed: {
btnClasses () {
btnClasses() {
const sizes = this.sizes
const colorShades = this.colorShades
return `v-btn ${sizes['p-y']} ${sizes['p-x']}
${colorShades?.main} ${colorShades?.hover} ${colorShades?.ring} ${colorShades['ring-offset']}
return `v-btn ${sizes["p-y"]} ${sizes["p-x"]}
${colorShades?.main} ${colorShades?.hover} ${colorShades?.ring} ${colorShades["ring-offset"]}
${colorShades?.text} transition ease-in duration-200 text-center text-${sizes?.font} font-medium focus:outline-none focus:ring-2
focus:ring-offset-2 rounded-lg flex items-center hover:no-underline`
},
colorShades () {
if (this.color === 'blue') {
colorShades() {
if (this.color === "blue") {
return {
main: 'bg-blue-600',
hover: 'hover:bg-blue-700',
ring: 'focus:ring-blue-500',
'ring-offset': 'focus:ring-offset-blue-200',
text: 'text-white'
main: "bg-blue-600",
hover: "hover:bg-blue-700",
ring: "focus:ring-blue-500",
"ring-offset": "focus:ring-offset-blue-200",
text: "text-white",
}
} else if (this.color === 'outline-blue') {
} else if (this.color === "outline-blue") {
return {
main: 'bg-transparent border border-blue-600',
hover: 'hover:bg-blue-600',
ring: 'focus:ring-blue-500',
'ring-offset': 'focus:ring-offset-blue-200',
text: 'text-blue-600 hover:text-white'
main: "bg-transparent border border-blue-600",
hover: "hover:bg-blue-600",
ring: "focus:ring-blue-500",
"ring-offset": "focus:ring-offset-blue-200",
text: "text-blue-600 hover:text-white",
}
} else if (this.color === 'outline-gray') {
} else if (this.color === "outline-gray") {
return {
main: 'bg-transparent border border-gray-300',
hover: 'hover:bg-gray-500',
ring: 'focus:ring-gray-500',
'ring-offset': 'focus:ring-offset-gray-200',
text: 'text-gray-500 hover:text-white'
main: "bg-transparent border border-gray-300",
hover: "hover:bg-gray-500",
ring: "focus:ring-gray-500",
"ring-offset": "focus:ring-offset-gray-200",
text: "text-gray-500 hover:text-white",
}
} else if (this.color === 'red') {
} else if (this.color === "red") {
return {
main: 'bg-red-600',
hover: 'hover:bg-red-700',
ring: 'focus:ring-red-500',
'ring-offset': 'focus:ring-offset-red-200',
text: 'text-white'
main: "bg-red-600",
hover: "hover:bg-red-700",
ring: "focus:ring-red-500",
"ring-offset": "focus:ring-offset-red-200",
text: "text-white",
}
} else if (this.color === 'gray') {
} else if (this.color === "gray") {
return {
main: 'bg-gray-600',
hover: 'hover:bg-gray-700',
ring: 'focus:ring-gray-500',
'ring-offset': 'focus:ring-offset-gray-200',
text: 'text-white'
main: "bg-gray-600",
hover: "hover:bg-gray-700",
ring: "focus:ring-gray-500",
"ring-offset": "focus:ring-offset-gray-200",
text: "text-white",
}
} else if (this.color === 'light-gray') {
} else if (this.color === "light-gray") {
return {
main: 'bg-gray-50 border border-gray-300',
hover: 'hover:bg-gray-100',
ring: 'focus:ring-gray-500',
'ring-offset': 'focus:ring-offset-gray-300',
text: 'text-gray-700'
main: "bg-gray-50 border border-gray-300",
hover: "hover:bg-gray-100",
ring: "focus:ring-gray-500",
"ring-offset": "focus:ring-offset-gray-300",
text: "text-gray-700",
}
} else if (this.color === 'green') {
} else if (this.color === "green") {
return {
main: 'bg-green-600',
hover: 'hover:bg-green-700',
ring: 'focus:ring-green-500',
'ring-offset': 'focus:ring-offset-green-200',
text: 'text-white'
main: "bg-green-600",
hover: "hover:bg-green-700",
ring: "focus:ring-green-500",
"ring-offset": "focus:ring-offset-green-200",
text: "text-white",
}
} else if (this.color === 'yellow') {
} else if (this.color === "yellow") {
return {
main: 'bg-yellow-600',
hover: 'hover:bg-yellow-700',
ring: 'focus:ring-yellow-500',
'ring-offset': 'focus:ring-offset-yellow-200',
text: 'text-white'
main: "bg-yellow-600",
hover: "hover:bg-yellow-700",
ring: "focus:ring-yellow-500",
"ring-offset": "focus:ring-offset-yellow-200",
text: "text-white",
}
} else if (this.color === 'white') {
} else if (this.color === "white") {
return {
main: 'bg-transparent border border-gray-300',
hover: 'hover:bg-gray-200',
ring: 'focus:ring-white-500',
'ring-offset': 'focus:ring-offset-white-200',
text: 'text-gray-700'
main: "bg-transparent border border-gray-300",
hover: "hover:bg-gray-200",
ring: "focus:ring-white-500",
"ring-offset": "focus:ring-offset-white-200",
text: "text-gray-700",
}
}else{
console.error("Unknown color")
return null
}
console.error('Unknown color')
},
sizes () {
if (this.size === 'small') {
sizes() {
if (this.size === "small") {
return {
font: 'sm',
'p-y': 'py-1',
'p-x': 'px-2'
font: "sm",
"p-y": "py-1",
"p-x": "px-2",
}
}
return {
font: 'base',
'p-y': 'py-2',
'p-x': 'px-4'
font: "base",
"p-y": "py-2",
"p-x": "px-4",
}
}
},
},
}
</script>

View File

@@ -1,37 +1,66 @@
<template>
<dropdown v-if="user && workspaces && workspaces.length > 1" ref="dropdown"
dropdown-class="origin-top-left absolute left-0 mt-2 w-56 rounded-md shadow-lg bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 z-50"
dusk="workspace-dropdown"
<dropdown
v-if="user && workspaces && workspaces.length > 1"
ref="dropdown"
dropdown-class="origin-top-left absolute left-0 mt-2 w-56 rounded-md shadow-lg bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 z-50"
dusk="workspace-dropdown"
>
<template v-if="workspace" #trigger="{toggle}">
<div class="flex items-center cursor group" role="button" @click.stop="toggle()">
<template
v-if="workspace"
#trigger="{ toggle }"
>
<div
class="flex items-center cursor group"
role="button"
@click.stop="toggle()"
>
<div class="rounded-full h-8 8">
<img v-if="isUrl(workspace.icon)"
:src="workspace.icon"
:alt="workspace.name + ' icon'" class="flex-shrink-0 h-8 w-8 rounded-full shadow"
/>
<div v-else class="rounded-full pt-2 text-xs truncate bg-nt-blue-lighter h-8 w-8 text-center shadow"
v-text="workspace.icon"
<img
v-if="isUrl(workspace.icon)"
:src="workspace.icon"
:alt="workspace.name + ' icon'"
class="flex-shrink-0 h-8 w-8 rounded-full shadow"
>
<div
v-else
class="rounded-full pt-2 text-xs truncate bg-nt-blue-lighter h-8 w-8 text-center shadow"
v-text="workspace.icon"
/>
</div>
<p class="hidden group-hover:underline lg:block max-w-10 truncate ml-2 text-gray-800 dark:text-gray-200">
<p
class="hidden group-hover:underline lg:block max-w-10 truncate ml-2 text-gray-800 dark:text-gray-200"
>
{{ workspace.name }}
</p>
</div>
</template>
<template v-for="worksp in workspaces" :key="worksp.id">
<a href="#"
class="px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
:class="{'bg-blue-100 dark:bg-blue-900':workspace?.id === worksp?.id}" @click.prevent="switchWorkspace(worksp)"
<template
v-for="worksp in workspaces"
:key="worksp.id"
>
<a
href="#"
class="px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
:class="{
'bg-blue-100 dark:bg-blue-900': workspace?.id === worksp?.id,
}"
@click.prevent="switchWorkspace(worksp)"
>
<div class="rounded-full h-8 w-8 flex-shrink-0" role="button">
<img v-if="isUrl(worksp.icon)"
:src="worksp.icon"
:alt="worksp.name + ' icon'" class="flex-shrink-0 h-8 w-8 rounded-full shadow"
/>
<div v-else class="rounded-full flex-shrink-0 pt-1 text-xs truncate bg-nt-blue-lighter h-8 w-8 text-center shadow"
v-text="worksp.icon"
<div
class="rounded-full h-8 w-8 flex-shrink-0"
role="button"
>
<img
v-if="isUrl(worksp.icon)"
:src="worksp.icon"
:alt="worksp.name + ' icon'"
class="flex-shrink-0 h-8 w-8 rounded-full shadow"
>
<div
v-else
class="rounded-full flex-shrink-0 pt-1 text-xs truncate bg-nt-blue-lighter h-8 w-8 text-center shadow"
v-text="worksp.icon"
/>
</div>
<p class="ml-4 truncate">{{ worksp.name }}</p>
@@ -41,17 +70,16 @@
</template>
<script>
import { computed } from 'vue'
import Dropdown from '~/components/global/Dropdown.vue'
import { computed } from "vue"
import Dropdown from "~/components/global/Dropdown.vue"
export default {
name: 'WorkspaceDropdown',
name: "WorkspaceDropdown",
components: {
Dropdown
Dropdown,
},
setup () {
setup() {
const authStore = useAuthStore()
const formsStore = useFormsStore()
const workspacesStore = useWorkspacesStore()
@@ -60,45 +88,42 @@ export default {
workspacesStore,
user: computed(() => authStore.user),
workspaces: computed(() => workspacesStore.getAll),
loading: computed(() => workspacesStore.loading)
loading: computed(() => workspacesStore.loading),
}
},
computed: {
workspace () {
workspace() {
return this.workspacesStore.getCurrent
}
},
},
watch: {
},
watch: {},
mounted () {
},
mounted() {},
methods: {
switchWorkspace (workspace) {
switchWorkspace(workspace) {
this.workspacesStore.setCurrentId(workspace.id)
this.formsStore.resetState()
this.formsStore.loadAll(workspace.id)
const router = useRouter()
const route = useRoute()
if (route.name !== 'home') {
router.push({ name: 'home' })
if (route.name !== "home") {
router.push({ name: "home" })
}
this.formsStore.loadAll(workspace.id)
},
isUrl (str) {
isUrl(str) {
try {
new URL(str)
} catch (_) {
return false
}
return true
}
}
},
},
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,23 +1,23 @@
<template>
<transition @leave="onLeave">
<div
ref="collapsible"
v-if="modelValue"
ref="collapsible"
v-on-click-outside.bubble="onClickAway"
>
<slot/>
<slot />
</div>
</transition>
</template>
<script setup>
import {vOnClickOutside} from '@vueuse/components'
import { vOnClickOutside } from "@vueuse/components"
const props = defineProps({
modelValue: {type: Boolean},
maxHeight: {type: Number, default: 200},
modelValue: { type: Boolean },
maxHeight: { type: Number, default: 200 },
})
const emit = defineEmits(['click-away'])
const emit = defineEmits(["click-away"])
const motion = ref(null)
const collapsible = ref(null)
@@ -25,28 +25,31 @@ const variants = {
initial: {
opacity: 0,
y: -10,
transition: {duration: 75, ease: 'easeIn'}
transition: { duration: 75, ease: "easeIn" },
},
enter: {
opacity: 1,
y: 0,
transition: {duration: 150, ease: 'easeOut'}
}
transition: { duration: 150, ease: "easeOut" },
},
}
watch(() => props.modelValue, (newValue) => {
if (newValue) {
nextTick(() => {
motion.value = useMotion(collapsible.value, variants)
})
}
})
watch(
() => props.modelValue,
(newValue) => {
if (newValue) {
nextTick(() => {
motion.value = useMotion(collapsible.value, variants)
})
}
},
)
const onLeave = (el, done) => {
motion.value.leave(done)
}
const onClickAway = (event) => {
emit('click-away', event)
emit("click-away", event)
}
</script>

View File

@@ -1,11 +1,12 @@
<template>
<transition v-if="name=='slideInUp'"
enter-active-class="linear duration-300 overflow-hidden"
enter-from-class="max-h-0"
enter-to-class="max-h-screen"
leave-active-class="linear duration-300 overflow-hidden"
leave-from-class="max-h-screen"
leave-to-class="max-h-0"
<transition
v-if="name == 'slideInUp'"
enter-active-class="linear duration-300 overflow-hidden"
enter-from-class="max-h-0"
enter-to-class="max-h-screen"
leave-active-class="linear duration-300 overflow-hidden"
leave-from-class="max-h-screen"
leave-to-class="max-h-0"
>
<slot />
</transition>
@@ -13,7 +14,7 @@
<script>
export default {
name: 'VTransition',
props: { name: { default: 'slideInUp' } }
name: "VTransition",
props: { name: {type: String, default: "slideInUp" } },
}
</script>