Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pricing Upgrade Flow changes #514

Merged
merged 6 commits into from
Aug 23, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/app.vue
Original file line number Diff line number Diff line change
@@ -49,6 +49,7 @@

<NotificationsWrapper />
<feature-base />
<SubscriptionModal />
</div>
</template>

81 changes: 41 additions & 40 deletions client/components/global/Modal.vue
Original file line number Diff line number Diff line change
@@ -4,18 +4,18 @@
<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="fixed z-30 top-0 inset-0 px-2 sm:px-4 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="self-start bg-white dark:bg-notion-dark w-full relative my-2 sm:my-6 rounded-xl shadow-xl"
:class="maxWidthClass"
>
<div
v-if="closeable"
class="absolute top-4 right-4 z-10"
class="absolute top-4 right-4"
>
<button
class="text-gray-500 hover:text-gray-900 cursor-pointer"
@@ -93,84 +93,85 @@
</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: {
type: Boolean,
default: false,
default: false
},
backdropBlur: {
type: Boolean,
default: false,
default: false
},
iconColor: {
type: String,
default: "blue",
default: 'blue'
},
maxWidth: {
type: String,
default: "2xl",
default: '2xl'
},
innerPadding: {
type: String,
default: "p-6",
default: 'p-6'
},
headerInnerPadding: {
type: String,
default: "p-6",
default: 'p-6'
},
footerInnerPadding: {
type: String,
default: "p-6",
default: 'p-6'
},
closeable: {
type: Boolean,
default: true,
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': 'max-w-2xl',
'screen-lg': 'max-w-screen-lg'
}[props.maxWidth]
})

@@ -180,35 +181,35 @@ const motionFadeIn = {
transition: {
delay: 100,
duration: 200,
ease: "easeIn",
},
ease: 'easeIn'
}
},
enter: {
opacity: 1,
transition: {
duration: 200,
},
},
duration: 200
}
}
}

const motionSlideBottom = {
initial: {
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) => {
@@ -218,7 +219,7 @@ const onLeave = (el, done) => {

const close = () => {
if (props.closeable) {
emit("close")
emit('close')
}
}

80 changes: 26 additions & 54 deletions client/components/global/ProTag.vue
Original file line number Diff line number Diff line change
@@ -1,73 +1,45 @@
<template>
<div
<UTooltip
v-if="shouldDisplayProTag"
class="inline"
:text="upgradeModalTitle??'You need a Pro plan to use this feature'"
class="inline normal-case"
>
<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"
>
PRO
</div>
<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>
<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!
</p>
</div>

<div class="my-4 text-center">
<v-button
color="white"
@click="showPremiumModal = false"
>
Close
</v-button>
</div>
</modal>
</UTooltip>
</div>
<div
v-track.pro_tag_click="{title:upgradeModalTitle}"
class="bg-nt-blue text-white px-2 text-xs uppercase inline rounded-full font-semibold cursor-pointer"
@click.stop="onClick"
>
PRO
</div>
</UTooltip>
</template>

<script setup>
import { computed } from "vue"

const props = defineProps({
upgradeModalTitle: {
type: String
},
upgradeModalDescription: {
type: String
}
})

const subscriptionModalStore = useSubscriptionModalStore()
const authStore = useAuthStore()
const workspacesStore = useWorkspacesStore()
const user = computed(() => authStore.user)
const workspace = computed(() => workspacesStore.getCurrent)
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
})

function onClick () {
subscriptionModalStore.setModalContent(props.upgradeModalTitle, props.upgradeModalDescription)
subscriptionModalStore.openModal()
}
</script>
5 changes: 4 additions & 1 deletion client/components/open/forms/components/FormStats.vue
Original file line number Diff line number Diff line change
@@ -9,7 +9,10 @@
<div class="absolute inset-0 z-10">
<div class="p-5 max-w-md mx-auto mt-5">
<p class="text-center">
You need a <pro-tag class="mx-1" /> subscription to access your form
You need a <pro-tag
upgrade-modal-title="Upgrade today to access form analytics"
class="mx-1"
/> subscription to access your form
analytics.
</p>
<p class="mt-5 text-center">
Original file line number Diff line number Diff line change
@@ -149,12 +149,16 @@
<toggle-switch-input
name="no_branding"
:form="form"
@update:model-value="onChangeNoBranding"
>
<template #label>
<span class="text-sm">
Remove OpnForm Branding
</span>
<pro-tag class="-mt-1" />
<pro-tag
upgrade-modal-title="Upgrade today to remove OpnForm branding"
class="-mt-1"
/>
</template>
</toggle-switch-input>
<toggle-switch-input
@@ -200,22 +204,44 @@ import GoogleFontPicker from "../../../editors/GoogleFontPicker.vue"
import ProTag from "~/components/global/ProTag.vue"

const workingFormStore = useWorkingFormStore()
const subscriptionModalStore = useSubscriptionModalStore()
const authStore = useAuthStore()
const workspacesStore = useWorkspacesStore()
const form = storeToRefs(workingFormStore).content
const isMounted = ref(false)
const confetti = useConfetti()
const showGoogleFontPicker = ref(false)

const user = computed(() => authStore.user)
const workspace = computed(() => workspacesStore.getCurrent)

const isPro = computed(() => {
if (!useRuntimeConfig().public.paidPlansEnabled) return true
if (!user.value || !workspace.value) return false
return workspace.value.is_pro
})

onMounted(() => {
isMounted.value = true
})

const onChangeConfettiOnSubmission = (val) => {
form.confetti_on_submission = val
if (isMounted.value && val) {
confetti.play()
}
}

const onChangeNoBranding = (val) => {
if (!isPro.value && val) {
subscriptionModalStore.setModalContent("Upgrade today to remove OpnForm branding")
subscriptionModalStore.openModal()
setTimeout(() => {
form.value.no_branding = false
console.log("form.value.no_branding", form.value.no_branding)
}, 300)
}
Comment on lines +234 to +242
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider removing the setTimeout for immediate feedback.

The use of setTimeout to reset the no_branding state might cause a delay that could confuse users. Consider resetting the state immediately to provide instant feedback.

    subscriptionModalStore.openModal()
-   setTimeout(() => {
      form.value.no_branding = false
      console.log("form.value.no_branding", form.value.no_branding)
-   }, 300)
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const onChangeNoBranding = (val) => {
if (!isPro.value && val) {
subscriptionModalStore.setModalContent("Upgrade today to remove OpnForm branding")
subscriptionModalStore.openModal()
setTimeout(() => {
form.value.no_branding = false
console.log("form.value.no_branding", form.value.no_branding)
}, 300)
}
const onChangeNoBranding = (val) => {
if (!isPro.value && val) {
subscriptionModalStore.setModalContent("Upgrade today to remove OpnForm branding")
subscriptionModalStore.openModal()
form.value.no_branding = false
console.log("form.value.no_branding", form.value.no_branding)
}

}

const onApplyFont = (val) => {
form.value.font_family = val
showGoogleFontPicker.value = false
Loading