Feature flags (#543)

* Re-organize a bit controllers

* Added the featureflagcontroller

* Implement feature flags in the front-end

* Clean env files

* Clean console.log messages

* Fix feature flag test
This commit is contained in:
Julien Nahum
2024-08-27 16:49:43 +02:00
committed by GitHub
parent 1dffd27390
commit 79d3dd7888
40 changed files with 304 additions and 147 deletions

View File

@@ -71,7 +71,7 @@
:max-date="maxDate"
:is-dark="props.isDark"
color="form-color"
@update:modelValue="updateModelValue"
@update:model-value="updateModelValue"
/>
<DatePicker
v-else
@@ -84,7 +84,7 @@
:max-date="maxDate"
:is-dark="props.isDark"
color="form-color"
@update:modelValue="updateModelValue"
@update:model-value="updateModelValue"
/>
</template>
</UPopover>
@@ -201,7 +201,7 @@ const formattedDate = (value) => {
try {
return format(new Date(value), props.dateFormat + (props.timeFormat == 12 ? ' p':' HH:mm'))
} catch (e) {
console.log(e)
console.log('Error formatting date', e)
return ''
}
}

View File

@@ -54,7 +54,7 @@
</a>
</template>
<NuxtLink
v-if="($route.name !== 'ai-form-builder' && user === null) && (!appStore.selfHosted || appStore.aiFeaturesEnabled)"
v-if="($route.name !== 'ai-form-builder' && user === null) && (!useFeatureFlag('self_hosted') || useFeatureFlag('ai_features'))"
:to="{ name: 'ai-form-builder' }"
:class="navLinkClasses"
class="hidden lg:inline"
@@ -63,9 +63,9 @@
</NuxtLink>
<NuxtLink
v-if="
(appStore.paidPlansEnabled &&
(useFeatureFlag('billing.enabled') &&
(user === null || (user && workspace && !workspace.is_pro)) &&
$route.name !== 'pricing') && !appStore.selfHosted
$route.name !== 'pricing') && !isSelfHosted
"
:to="{ name: 'pricing' }"
:class="navLinkClasses"
@@ -248,7 +248,7 @@
</NuxtLink>
<v-button
v-if="!appStore.selfHosted"
v-if="!isSelfHosted"
v-track.nav_create_form_click
size="small"
class="shrink-0"
@@ -274,6 +274,7 @@ import Dropdown from "~/components/global/Dropdown.vue"
import WorkspaceDropdown from "./WorkspaceDropdown.vue"
import opnformConfig from "~/opnform.config.js"
import { useRuntimeConfig } from "#app"
import { useFeatureFlag } from "~/composables/useFeatureFlag"
export default {
components: {
@@ -294,6 +295,7 @@ export default {
config: useRuntimeConfig(),
user: computed(() => authStore.user),
isIframe: useIsIframe(),
isSelfHosted: computed(() => useFeatureFlag('self_hosted')),
}
},

View File

@@ -33,7 +33,7 @@ const user = computed(() => authStore.user)
const workspace = computed(() => workspacesStore.getCurrent)
const shouldDisplayProTag = computed(() => {
if (!useRuntimeConfig().public.paidPlansEnabled) return false
if (!useFeatureFlag('billing.enabled')) return false
if (!user.value || !workspace.value) return true
return !workspace.value.is_pro
})

View File

@@ -1,27 +1,37 @@
<template>
<ErrorBoundary @on-error="onFormEditorError">
<template #error="{ error, clearError }">
<div class="flex-grow w-full flex items-center justify-center flex-col gap-4">
<h1 class="text-blue-800 text-2xl font-medium">Oops! Something went wrong.</h1>
<p class="text-gray-500 max-w-lg text-center">It looks like your last action caused an issue on our side. We
apologize for
the
inconvenience.</p>
<div class="flex gap-2 mt-4">
<UButton icon="i-material-symbols-undo" @click="clearEditorError(error, clearError)">Go back one step
</UButton>
<UButton variant="outline" icon="i-heroicons-chat-bubble-left-right-16-solid"
@click="onErrorContact(error)">
Report this error
</UButton>
</div>
<ErrorBoundary @on-error="onFormEditorError">
<template #error="{ error, clearError }">
<div class="flex-grow w-full flex items-center justify-center flex-col gap-4">
<h1 class="text-blue-800 text-2xl font-medium">
Oops! Something went wrong.
</h1>
<p class="text-gray-500 max-w-lg text-center">
It looks like your last action caused an issue on our side. We
apologize for
the
inconvenience.
</p>
<div class="flex gap-2 mt-4">
<UButton
icon="i-material-symbols-undo"
@click="clearEditorError(error, clearError)"
>
Go back one step
</UButton>
<UButton
variant="outline"
icon="i-heroicons-chat-bubble-left-right-16-solid"
@click="onErrorContact(error)"
>
Report this error
</UButton>
</div>
</template>
</div>
</template>
<slot />
</ErrorBoundary>
</template>
<slot />
</ErrorBoundary>
</template>
<script setup>
import { computed } from 'vue'
@@ -51,7 +61,6 @@
}
}
const onErrorContact = (error) => {
console.log('Contacting via crisp for an error', error)
crisp.pauseChatBot()
let errorReport = 'Hi there, I have a technical issue with the form editor.'
if (form.value.slug) {

View File

@@ -25,7 +25,7 @@
on other sites (Open Graph).
</p>
<select-input
v-if="customDomainAllowed"
v-if="useFeatureFlag('custom_domains')"
v-model="form.custom_domain"
:clearable="true"
:disabled="customDomainOptions.length <= 0"
@@ -98,9 +98,6 @@ export default {
})
: []
},
customDomainAllowed() {
return useRuntimeConfig().public.customDomainsEnabled
},
},
watch: {},
mounted() {

View File

@@ -53,23 +53,25 @@
label="Form Theme"
/>
<label class="text-gray-700 font-medium text-sm">Font Style</label>
<v-button
color="white"
class="w-full mb-4"
size="small"
@click="showGoogleFontPicker = true"
>
<span :style="{ 'font-family': (form.font_family?form.font_family+' !important':null) }">
{{ form.font_family || 'Default' }}
</span>
</v-button>
<GoogleFontPicker
:show="showGoogleFontPicker"
:font="form.font_family || null"
@close="showGoogleFontPicker=false"
@apply="onApplyFont"
/>
<template v-if="useFeatureFlag('services.google.fonts')">
<label class="text-gray-700 font-medium text-sm">Font Style</label>
<v-button
color="white"
class="w-full mb-4"
size="small"
@click="showGoogleFontPicker = true"
>
<span :style="{ 'font-family': (form.font_family?form.font_family+' !important':null) }">
{{ form.font_family || 'Default' }}
</span>
</v-button>
<GoogleFontPicker
:show="showGoogleFontPicker"
:font="form.font_family || null"
@close="showGoogleFontPicker=false"
@apply="onApplyFont"
/>
</template>
<div class="flex space-x-4 justify-stretch">
<select-input
@@ -216,7 +218,7 @@ const user = computed(() => authStore.user)
const workspace = computed(() => workspacesStore.getCurrent)
const isPro = computed(() => {
if (!useRuntimeConfig().public.paidPlansEnabled) return true
if (!useFeatureFlag('billing.enabled')) return true
if (!user.value || !workspace.value) return false
return workspace.value.is_pro
})
@@ -237,7 +239,6 @@ const onChangeNoBranding = (val) => {
subscriptionModalStore.openModal()
setTimeout(() => {
form.value.no_branding = false
console.log("form.value.no_branding", form.value.no_branding)
}, 300)
}
}

View File

@@ -21,21 +21,21 @@
</div>
<div class="flex justify-center mt-5 md:mt-0">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-x-4 gap-y-2">
<router-link
v-if="!appStore.selfHosted"
:to="{ name: 'privacy-policy' }"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
>
Privacy Policy
</router-link>
<template v-if="!useFeatureFlag('self_hosted')">
<router-link
:to="{ name: 'privacy-policy' }"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
>
Privacy Policy
</router-link>
<router-link
v-if="!appStore.selfHosted"
:to="{ name: 'terms-conditions' }"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
>
Terms & Conditions
</router-link>
<router-link
:to="{ name: 'terms-conditions' }"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
>
Terms & Conditions
</router-link>
</template>
<a
:href="opnformConfig.links.feature_requests"
target="_blank"

View File

@@ -59,6 +59,7 @@
</v-button>
<v-button
v-if="useFeatureFlag('services.google.auth')"
native-type="button"
color="white"
class="space-x-4 mt-4 flex items-center w-full"
@@ -72,7 +73,7 @@
<span class="mx-2">Sign in with Google</span>
</v-button>
<p
v-if="!appStore.selfHosted"
v-if="!useFeatureFlag('self_hosted')"
class="text-gray-500 text-sm text-center mt-4"
>
Don't have an account?

View File

@@ -87,19 +87,21 @@
Create account
</v-button>
<p class="text-gray-600/50 text-sm text-center my-4">
Or
</p>
<v-button
native-type="buttom"
color="white"
class="space-x-4 flex items-center w-full"
:loading="false"
@click.prevent="signInwithGoogle"
>
<Icon name="devicon:google" />
<span class="mx-2">Sign in with Google</span>
</v-button>
<template v-if="useFeatureFlag('services.google.auth')">
<p class="text-gray-600/50 text-sm text-center my-4">
Or
</p>
<v-button
native-type="buttom"
color="white"
class="space-x-4 flex items-center w-full"
:loading="false"
@click.prevent="signInwithGoogle"
>
<Icon name="devicon:google" />
<span class="mx-2">Sign in with Google</span>
</v-button>
</template>
<p class="text-gray-500 mt-4 text-sm text-center">
Already have an account?

View File

@@ -72,7 +72,7 @@
</p>
</div>
<div
v-if="aiFeaturesEnabled"
v-if="useFeatureFlag('ai_features')"
v-track.select_form_base="{ base: 'ai' }"
class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50"
role="button"
@@ -185,12 +185,6 @@ export default {
loading: false,
}),
computed: {
aiFeaturesEnabled() {
return this.runtimeConfig.public.aiFeaturesEnabled
},
},
methods: {
generateForm() {
if (this.loading) return

View File

@@ -1,6 +1,6 @@
<template>
<div
v-if="customDomainsEnabled"
v-if="useFeatureFlag('custom_domains')"
id="custom-domains"
>
<UButton
@@ -82,10 +82,6 @@ const customDomainsForm = useForm({
const customDomainsLoading = ref(false)
const showCustomDomainModal = ref(false)
const customDomainsEnabled = computed(
() => useRuntimeConfig().public.customDomainsEnabled,
)
onMounted(() => {
initCustomDomains()
})

View File

@@ -202,7 +202,7 @@ const showEditUserModal = ref(false)
const selectedUser = ref(null)
const userNewRole = ref("")
const paidPlansEnabled = computed(() => useRuntimeConfig().public.paidPlansEnabled)
const paidPlansEnabled = ref(useFeatureFlag('billing.enabled'))
const canInviteUser = computed(() => {
return paidPlansEnabled.value ? workspace.value.is_pro : true
})