Skip to content

Commit

Permalink
Merge pull request #4343 from FlowFuse/4317-contact-us-team-tier
Browse files Browse the repository at this point in the history
Add option to require contact via HS form for create/upgrade of team type
  • Loading branch information
joepavitt authored Aug 8, 2024
2 parents 068fc44 + 868ecb3 commit 9787935
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 32 deletions.
41 changes: 40 additions & 1 deletion frontend/src/api/billing.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,49 @@ const setTrialExpiry = async (teamId, trialEndsAt) => {
})
}

const sendTeamTypeContact = async (user, teamType, pageName) => {
const hsPortalId = teamType.properties?.billing?.contactHSPortalId
const hsFormId = teamType.properties?.billing?.contactHSFormId
if (hsPortalId && hsFormId) {
return new Promise((resolve, reject) => {
const url = `https://api.hsforms.com/submissions/v3/integration/submit/${hsPortalId}/${hsFormId}`
const xhr = new XMLHttpRequest()
xhr.open('POST', url)
xhr.setRequestHeader('Content-Type', 'application/json')
const body = JSON.stringify({
submittedAt: '' + Date.now(),
fields: [{
objectTypeId: '0-1',
name: 'email',
value: user.email
}],
context: {
pageUri: window.location.href,
pageName
}
})
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
switch (xhr.status) {
case 200:
resolve()
break
default:
reject(xhr.responseText)
break
}
}
}
xhr.send(body)
})
}
}

export default {
toCustomerPortal,
getSubscriptionInfo,
createSubscription,
setupManualBilling,
setTrialExpiry
setTrialExpiry,
sendTeamTypeContact
}
12 changes: 12 additions & 0 deletions frontend/src/pages/admin/TeamTypes/dialogs/TeamTypeEditDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@
</FormRow>
<template v-if="billingEnabled">
<FormHeading>Billing</FormHeading>
<div class="space-y-2">
<FormRow v-model="input.properties.billing.requireContact" type="checkbox" class="mb-4">Require contact to upgrade</FormRow>
<div v-if="input.properties.billing.requireContact" class="grid gap-2 grid-cols-2 pl-4">
<FormRow v-model="input.properties.billing.contactHSPortalId" :type="editDisabled?'uneditable':''">HubSpot Portal Id</FormRow>
<FormRow v-model="input.properties.billing.contactHSFormId" :type="editDisabled?'uneditable':''">HubSpot Form Id</FormRow>
</div>
</div>

<div class="grid gap-2 grid-cols-3">
<FormRow v-model="input.properties.billing.productId" :type="editDisabled?'uneditable':''">Product Id</FormRow>
<FormRow v-model="input.properties.billing.priceId" :type="editDisabled?'uneditable':''">Price Id</FormRow>
Expand Down Expand Up @@ -331,6 +339,10 @@ export default {
} else {
opts.properties.trial = { active: false }
}
if (!opts.properties.billing.requireContact) {
delete opts.properties.billing.contactHSPortalId
delete opts.properties.billing.contactHSFormId
}
}
formatNumber(opts.properties.features, 'fileStorageLimit')
formatNumber(opts.properties.features, 'contextLimit')
Expand Down
40 changes: 31 additions & 9 deletions frontend/src/pages/team/changeType.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,25 @@
<div>
<template v-if="billingEnabled">
<div class="mb-8 text-sm text-gray-500 space-y-2">
<template v-if="trialMode && !trialHasEnded">
<p>Setting up billing will bring your free trial to an end</p>
</template>
<p v-if="isContactRequired">To learn more about our {{ input.teamType?.name }} plan, click below to contact our sales team.</p>
<p v-if="trialMode && !trialHasEnded">Setting up billing will bring your free trial to an end</p>
<p v-if="isTypeChange">Your billing subscription will be updated to reflect the new costs</p>
</div>
</template>
<div class="flex gap-x-4">
<ff-button v-if="isTypeChange" :disabled="!formValid" data-action="change-team-type" @click="updateTeam()">
Change team type
</ff-button>
<ff-button v-else :disabled="!formValid" data-action="setup-team-billing" @click="setupBilling()">
Setup Payment Details
</ff-button>
<template v-if="!isContactRequired">
<ff-button v-if="isTypeChange" :disabled="!formValid" data-action="change-team-type" @click="updateTeam()">
Change team type
</ff-button>
<ff-button v-else :disabled="!formValid" data-action="setup-team-billing" @click="setupBilling()">
Setup Payment Details
</ff-button>
</template>
<template v-else>
<ff-button :disabled="!formValid" data-action="contact-sales" @click="sendContact()">
Contact Sales
</ff-button>
</template>
<ff-button kind="secondary" data-action="cancel-change-team-type" @click="$router.back()">
Cancel
</ff-button>
Expand Down Expand Up @@ -129,6 +135,11 @@ export default {
},
isUnmanaged () {
return this.team.billing?.unmanaged
},
isContactRequired () {
return this.billingEnabled &&
!this.user.admin &&
this.input.teamType && this.input.teamType.properties?.billing?.requireContact
}
},
watch: {
Expand Down Expand Up @@ -175,6 +186,17 @@ export default {
this.loading = true
const response = await billingApi.createSubscription(this.team.id, this.input.teamTypeId)
window.open(response.billingURL, '_self')
},
sendContact: async function () {
if (this.input.teamType) {
billingApi.sendTeamTypeContact(this.user, this.input.teamType, 'Team: ' + this.team.name).then(() => {
this.$router.push({ name: 'Team', params: { team_slug: this.team.slug } })
Alerts.emit('Thanks for getting in touch. We will contact you soon regarding your request.', 'info', 15000)
}).catch(err => {
Alerts.emit('Something went wrong with the request. Please try again or contact support for help.', 'info', 15000)
console.error('Failed to submit hubspot form: ', err)
})
}
}
}
}
Expand Down
72 changes: 50 additions & 22 deletions frontend/src/pages/team/create.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<div v-else class="m-auto">
<form class="space-y-6">
<FormHeading>Create a new team</FormHeading>
<div class="mb-8 text-sm text-gray-500">Teams are how you organize who collaborates on your projects.</div>
<div class="mb-8 text-sm text-gray-500">Teams are how you organize who collaborates on your applications.</div>
<!-- TeamType Type -->
<div class="grid">
<ff-tile-selection v-model="input.teamTypeId">
Expand All @@ -27,33 +27,43 @@
/>
</ff-tile-selection>
</div>
<FormRow id="team" v-model="input.name" :error="errors.name">
Team Name
<template #description>
eg. 'Development'
</template>
</FormRow>
<template v-if="!isContactRequired">
<FormRow id="team" v-model="input.name" :error="errors.name">
Team Name
<template #description>
eg. 'Development'
</template>
</FormRow>

<FormRow id="team" v-model="input.slug" :error="input.slugError" :placeholder="input.defaultSlug">
URL Slug
<template #description>
Use the default slug based on the team name or set your own.<br>
<pre>/team/&lt;slug&gt;</pre>
</template>
</FormRow>
<FormRow id="team" v-model="input.slug" :error="input.slugError" :placeholder="input.defaultSlug">
URL Slug
<template #description>
Use the default slug based on the team name or set your own.<br>
<pre>/team/&lt;slug&gt;</pre>
</template>
</FormRow>

<template v-if="billingEnabled">
<template v-if="billingEnabled">
<div class="mb-8 text-sm text-gray-500 space-y-2">
<p>To create the team we need to setup payment details via Stripe, our secure payment provider.</p>
</div>
<ff-button :disabled="!formValid" @click="createTeam()">
<template #icon-right><ExternalLinkIcon /></template>
Create team and setup payment details
</ff-button>
</template>
<ff-button v-else :disabled="!formValid" @click="createTeam()">
Create team
</ff-button>
</template>
<template v-else>
<div class="mb-8 text-sm text-gray-500 space-y-2">
<p>To create the team we need to setup payment details via Stripe, our secure payment provider.</p>
<p>To learn more about our {{ input.teamType?.name }} plan, click below to contact our sales team.</p>
</div>
<ff-button :disabled="!formValid" @click="createTeam()">
<template #icon-right><ExternalLinkIcon /></template>
Create team and setup payment details
<ff-button @click="sendContact()">
Contact Sales
</ff-button>
</template>
<ff-button v-else :disabled="!formValid" @click="createTeam()">
Create team
</ff-button>
</form>
</div>
</ff-page>
Expand All @@ -63,6 +73,7 @@
import { ChevronLeftIcon, ExternalLinkIcon } from '@heroicons/vue/solid'
import { mapState } from 'vuex'

import billingApi from '../../api/billing.js'
import teamApi from '../../api/team.js'
import teamTypesApi from '../../api/teamTypes.js'
import teamsApi from '../../api/teams.js'
Expand All @@ -71,6 +82,7 @@ import FormRow from '../../components/FormRow.vue'

import NavItem from '../../components/NavItem.vue'
import SideNavigation from '../../components/SideNavigation.vue'
import Alerts from '../../services/alerts.js'
import slugify from '../../utils/slugify.js'

export default {
Expand Down Expand Up @@ -135,6 +147,11 @@ export default {
},
slugValid () {
return /^[a-z0-9-_]+$/i.test(this.input.slug)
},
isContactRequired () {
return this.billingEnabled &&
!this.user.admin &&
this.input.teamType && this.input.teamType.properties?.billing?.requireContact
}
},
async created () {
Expand Down Expand Up @@ -194,6 +211,17 @@ export default {
})
}
}, 200)
},
sendContact: async function () {
if (this.input.teamType) {
billingApi.sendTeamTypeContact(this.user, this.input.teamType, 'Create Team').then(() => {
this.$router.go(-1)
Alerts.emit('Thanks for getting in touch. We will contact you soon regarding your request.', 'info', 15000)
}).catch(err => {
Alerts.emit('Something went wrong with the request. Please try again or contact support for help.', 'info', 15000)
console.error('Failed to submit hubspot form: ', err)
})
}
}
},
components: {
Expand Down

0 comments on commit 9787935

Please sign in to comment.