diff --git a/website/api/controllers/entrance/signup.js b/website/api/controllers/entrance/signup.js index 652b20e802..5c970562e6 100644 --- a/website/api/controllers/entrance/signup.js +++ b/website/api/controllers/entrance/signup.js @@ -63,18 +63,6 @@ the account verification message.)`, defaultsTo: 'Buy a license', }, - primaryBuyingSituation: { - type: 'string', - description: 'What the user will be using Fleet for.', - required: true, - isIn: [ - 'eo-security', - 'eo-it', - 'mdm', - 'vm' - ], - } - }, @@ -104,7 +92,7 @@ the account verification message.)`, }, - fn: async function ({emailAddress, password, firstName, lastName, organization, signupReason, primaryBuyingSituation}) { + fn: async function ({emailAddress, password, firstName, lastName, organization, signupReason}) { // Note: in Oct. 2023, the Fleet Sandbox related code was removed from this action. For more details, see https://github.com/fleetdm/fleet/pull/14638/files var newEmailAddress = emailAddress.toLowerCase(); @@ -153,7 +141,6 @@ the account verification message.)`, signupReason, password: await sails.helpers.passwords.hashPassword(password), stripeCustomerId, - primaryBuyingSituation, tosAcceptedByIp: this.req.ip }, sails.config.custom.verifyEmailAddresses? { emailProofToken: await sails.helpers.strings.random('url-friendly'), @@ -173,7 +160,6 @@ the account verification message.)`, lastName, organization, signupReason, - primaryBuyingSituation, webhookSecret: sails.config.custom.zapierSandboxWebhookSecret, } }) diff --git a/website/api/controllers/entrance/view-login.js b/website/api/controllers/entrance/view-login.js index 0bdc59dfac..cba099dbca 100644 --- a/website/api/controllers/entrance/view-login.js +++ b/website/api/controllers/entrance/view-login.js @@ -26,10 +26,8 @@ module.exports = { if (this.req.me) { if(this.req.me.isSuperAdmin){ throw {redirect: '/admin/generate-license'}; - } else if(this.req.me.hasBillingCard) { - throw {redirect: '/customers/new-license'}; } else { - throw {redirect: '/try-fleet/sandbox'}; + throw {redirect: '/start'}; } } diff --git a/website/api/controllers/save-questionnaire-progress.js b/website/api/controllers/save-questionnaire-progress.js new file mode 100644 index 0000000000..b2559c4ee3 --- /dev/null +++ b/website/api/controllers/save-questionnaire-progress.js @@ -0,0 +1,71 @@ +module.exports = { + + + friendlyName: 'Save questionnaire progress and continue', + + + description: 'Saves the user\'s current progress in the get started questionnaire', + + + inputs: { + currentStep: { + type: 'string', + description: 'The step of the get started questionnaire that is being saved.', + isIn: [ + 'start', + 'what-are-you-using-fleet-for', + 'have-you-ever-used-fleet', + 'how-many-hosts', + 'will-you-be-self-hosting', + 'what-are-you-working-on-eo-security', + 'is-it-any-good', + 'what-did-you-think', + 'deploy-fleet-in-your-environment', + 'managed-cloud-for-growing-deployments', + 'self-hosted-deploy', + ] + }, + formData: { + type: {}, + description: 'The formdata that will be saved for this step of the get started questionnaire' + } + }, + + + exits: { + + }, + + + fn: async function ({currentStep, formData}) { + // find this user's DB record. + let userRecord = await User.findOne({id: this.req.me.id}); + if(!userRecord){ + throw new Error(`Consistency violation: when trying to save a user's progress in the get started questionnaire, a User record with the ID ${this.req.me.id} could not be found.`); + } + let questionnaireProgress; + // If this user doesn't have a lastSubmittedGetStartedQuestionnaireStep or getStartedQuestionnaireAnswers, create an empty dictionary to store their answers. + if(!userRecord.lastSubmittedGetStartedQuestionnaireStep || _.isEmpty(userRecord.getStartedQuestionnaireAnswers)) { + questionnaireProgress = {}; + } else {// other wise clone it from the user record. + questionnaireProgress = _.clone(userRecord.getStartedQuestionnaireAnswers); + } + // When the 'what-are-you-using-fleet-for' is completed, update this user's DB record and session to include their answer. + if(currentStep === 'what-are-you-using-fleet-for') { + let primaryBuyingSituation = formData.primaryBuyingSituation; + await User.updateOne({id: this.req.me.id}).set({primaryBuyingSituation}); + // Set the primary buying situation in the user's session. + this.req.session.primaryBuyingSituation = primaryBuyingSituation; + } + // Set the user's answer to the current step. + questionnaireProgress[currentStep] = formData; + // Clone the questionnaireProgress to prevent any mutations from sending it through the updateOne Waterline method. + let getStartedProgress = _.clone(questionnaireProgress); + // Update the user's database model. + await User.updateOne({id: userRecord.id}).set({getStartedQuestionnaireAnswers: questionnaireProgress, lastSubmittedGetStartedQuestionnaireStep: currentStep}); + // Return the JSON dictionary of form data submitted by this user. + return getStartedProgress; + } + + +}; diff --git a/website/api/controllers/view-contact.js b/website/api/controllers/view-contact.js index 627c688a8a..8281db2fb2 100644 --- a/website/api/controllers/view-contact.js +++ b/website/api/controllers/view-contact.js @@ -12,6 +12,11 @@ module.exports = { description: 'A boolean that determines whether or not to display the talk to us form when the contact page loads.', defaultsTo: false, }, + + prefillFormDataFromUserRecord: { + type: 'boolean', + description: 'If true, the contact form will be prefilled in with information from this user\'s account.', + }, }, exits: { @@ -23,15 +28,19 @@ module.exports = { }, - fn: async function ({sendMessage}) { + fn: async function ({sendMessage, prefillFormDataFromUserRecord}) { let formToShow = 'talk-to-us'; if(sendMessage) { formToShow = 'contact'; } + // If the prefillFormDataFromUserRecord flag was set to true, but this user is not logged in, set it to false. + if(prefillFormDataFromUserRecord && !this.req.me){ + prefillFormDataFromUserRecord = false; + } // Respond with view. - return {formToShow}; + return {formToShow, prefillFormDataFromUserRecord}; } diff --git a/website/api/controllers/view-start.js b/website/api/controllers/view-start.js index 080e234a09..a18fdd8ed1 100644 --- a/website/api/controllers/view-start.js +++ b/website/api/controllers/view-start.js @@ -17,9 +17,15 @@ module.exports = { fn: async function () { - - // Respond with view. - return {}; + if(this.req.me.lastSubmittedGetStartedQuestionnaireStep && !_.isEmpty(this.req.me.getStartedQuestionnaireAnswers)){ + let currentStep = this.req.me.lastSubmittedGetStartedQuestionnaireStep; + let previouslyAnsweredQuestions = this.req.me.getStartedQuestionnaireAnswers; + // Respond with view. + return {currentStep, previouslyAnsweredQuestions}; + } else { + // Respond with view. + return; + } } diff --git a/website/api/models/User.js b/website/api/models/User.js index 513c4d083e..f664e0420d 100644 --- a/website/api/models/User.js +++ b/website/api/models/User.js @@ -212,6 +212,17 @@ without necessarily having a billing card.` 'mdm', 'vm', ] + }, + + lastSubmittedGetStartedQuestionnaireStep: { + type: 'string', + description: 'The last step the user reached in the get started form.' + }, + + getStartedQuestionnaireAnswers: { + type: 'json', + description: 'This answers the user provided when they filled out the get started form.', + defaultsTo: {}, } // ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗ diff --git a/website/assets/images/icon-deploy-fleet-64x64@2x.png b/website/assets/images/icon-deploy-fleet-64x64@2x.png new file mode 100644 index 0000000000..f70c0ac9fd Binary files /dev/null and b/website/assets/images/icon-deploy-fleet-64x64@2x.png differ diff --git a/website/assets/images/icon-form-success-12x12@2x.png b/website/assets/images/icon-form-success-12x12@2x.png new file mode 100644 index 0000000000..d09d7c9214 Binary files /dev/null and b/website/assets/images/icon-form-success-12x12@2x.png differ diff --git a/website/assets/js/cloud.setup.js b/website/assets/js/cloud.setup.js index 95044bcbb0..cf4a555bd5 100644 --- a/website/assets/js/cloud.setup.js +++ b/website/assets/js/cloud.setup.js @@ -13,7 +13,7 @@ Cloud.setup({ /* eslint-disable */ - methods: {"downloadSitemap":{"verb":"GET","url":"/sitemap.xml","args":[]},"downloadRssFeed":{"verb":"GET","url":"/rss/:categoryName","args":["categoryName"]},"receiveUsageAnalytics":{"verb":"POST","url":"/api/v1/webhooks/receive-usage-analytics","args":["anonymousIdentifier","fleetVersion","licenseTier","numHostsEnrolled","numUsers","numTeams","numPolicies","numLabels","softwareInventoryEnabled","vulnDetectionEnabled","systemUsersEnabled","hostsStatusWebHookEnabled","numWeeklyActiveUsers","numWeeklyPolicyViolationDaysActual","numWeeklyPolicyViolationDaysPossible","hostsEnrolledByOperatingSystem","hostsEnrolledByOrbitVersion","hostsEnrolledByOsqueryVersion","storedErrors","numHostsNotResponding","organization","mdmMacOsEnabled","mdmWindowsEnabled","liveQueryDisabled","hostExpiryEnabled"]},"receiveFromGithub":{"verb":"GET","url":"/api/v1/webhooks/github","args":["botSignature","action","sender","repository","changes","issue","comment","pull_request","label","release"]},"receiveFromStripe":{"verb":"POST","url":"/api/v1/webhooks/receive-from-stripe","args":["id","type","data","webhookSecret"]},"deliverContactFormMessage":{"verb":"POST","url":"/api/v1/deliver-contact-form-message","args":["emailAddress","firstName","lastName","message"]},"sendPasswordRecoveryEmail":{"verb":"POST","url":"/api/v1/entrance/send-password-recovery-email","args":["emailAddress"]},"signup":{"verb":"POST","url":"/api/v1/customers/signup","args":["emailAddress","password","organization","firstName","lastName","signupReason"]},"updateProfile":{"verb":"POST","url":"/api/v1/account/update-profile","args":["firstName","lastName","organization","emailAddress"]},"updatePassword":{"verb":"POST","url":"/api/v1/account/update-password","args":["oldPassword","newPassword"]},"updateBillingCard":{"verb":"POST","url":"/api/v1/account/update-billing-card","args":["stripeToken","billingCardLast4","billingCardBrand","billingCardExpMonth","billingCardExpYear"]},"login":{"verb":"POST","url":"/api/v1/customers/login","args":["emailAddress","password","rememberMe"]},"logout":{"verb":"GET","url":"/api/v1/account/logout","args":[]},"createQuote":{"verb":"POST","url":"/api/v1/customers/create-quote","args":["numberOfHosts"]},"saveBillingInfoAndSubscribe":{"verb":"POST","url":"/api/v1/customers/save-billing-info-and-subscribe","args":["quoteId","organization","firstName","lastName","paymentSource"]},"updatePasswordAndLogin":{"verb":"POST","url":"/api/v1/entrance/update-password-and-login","args":["password","token"]},"deliverDemoSignup":{"verb":"POST","url":"/api/v1/deliver-demo-signup","args":["emailAddress"]},"createOrUpdateOneNewsletterSubscription":{"verb":"POST","url":"/api/v1/create-or-update-one-newsletter-subscription","args":["emailAddress","subscribeTo"]},"unsubscribeFromAllNewsletters":{"verb":"GET","url":"/api/v1/unsubscribe-from-all-newsletters","args":["emailAddress"]},"buildLicenseKey":{"verb":"POST","url":"/api/v1/admin/build-license-key","args":["numberOfHosts","organization","expiresAt","partnerName"]},"createVantaAuthorizationRequest":{"verb":"POST","url":"/api/v1/create-vanta-authorization-request","args":["emailAddress","fleetInstanceUrl","fleetApiKey"]},"deliverMdmBetaSignup":{"verb":"POST","url":"/api/v1/deliver-mdm-beta-signup","args":["emailAddress","fullName","jobTitle","numberOfHosts"]},"deliverAppleCsr":{"verb":"POST","url":"/api/v1/deliver-apple-csr","args":["unsignedCsrData"]},"deliverPremiumUpgradeForm":{"verb":"POST","url":"/api/v1/deliver-premium-upgrade-form","args":["organization","monthsUsingFleetFree","emailAddress","numberOfHosts"]},"deliverLaunchPartySignup":{"verb":"POST","url":"/api/v1/deliver-launch-party-signup","args":["emailAddress","firstName","lastName","jobTitle","phoneNumber"]},"deliverMdmDemoEmail":{"verb":"POST","url":"/api/v1/deliver-mdm-demo-email","args":["emailAddress"]},"provisionSandboxInstanceAndDeliverEmail":{"verb":"POST","url":"/api/v1/admin/provision-sandbox-instance-and-deliver-email","args":["userId"]},"deliverTalkToUsFormSubmission":{"verb":"POST","url":"/api/v1/deliver-talk-to-us-form-submission","args":["emailAddress","firstName","lastName","organization","numberOfHosts","primaryBuyingSituation"]}} + methods: {"downloadSitemap":{"verb":"GET","url":"/sitemap.xml","args":[]},"downloadRssFeed":{"verb":"GET","url":"/rss/:categoryName","args":["categoryName"]},"receiveUsageAnalytics":{"verb":"POST","url":"/api/v1/webhooks/receive-usage-analytics","args":["anonymousIdentifier","fleetVersion","licenseTier","numHostsEnrolled","numUsers","numTeams","numPolicies","numLabels","softwareInventoryEnabled","vulnDetectionEnabled","systemUsersEnabled","hostsStatusWebHookEnabled","numWeeklyActiveUsers","numWeeklyPolicyViolationDaysActual","numWeeklyPolicyViolationDaysPossible","hostsEnrolledByOperatingSystem","hostsEnrolledByOrbitVersion","hostsEnrolledByOsqueryVersion","storedErrors","numHostsNotResponding","organization","mdmMacOsEnabled","mdmWindowsEnabled","liveQueryDisabled","hostExpiryEnabled"]},"receiveFromGithub":{"verb":"GET","url":"/api/v1/webhooks/github","args":["botSignature","action","sender","repository","changes","issue","comment","pull_request","label","release"]},"receiveFromStripe":{"verb":"POST","url":"/api/v1/webhooks/receive-from-stripe","args":["id","type","data","webhookSecret"]},"deliverContactFormMessage":{"verb":"POST","url":"/api/v1/deliver-contact-form-message","args":["emailAddress","firstName","lastName","message"]},"sendPasswordRecoveryEmail":{"verb":"POST","url":"/api/v1/entrance/send-password-recovery-email","args":["emailAddress"]},"signup":{"verb":"POST","url":"/api/v1/customers/signup","args":["emailAddress","password","organization","firstName","lastName","signupReason","primaryBuyingSituation"]},"updateProfile":{"verb":"POST","url":"/api/v1/account/update-profile","args":["firstName","lastName","organization","emailAddress"]},"updatePassword":{"verb":"POST","url":"/api/v1/account/update-password","args":["oldPassword","newPassword"]},"updateBillingCard":{"verb":"POST","url":"/api/v1/account/update-billing-card","args":["stripeToken","billingCardLast4","billingCardBrand","billingCardExpMonth","billingCardExpYear"]},"login":{"verb":"POST","url":"/api/v1/customers/login","args":["emailAddress","password","rememberMe"]},"logout":{"verb":"GET","url":"/api/v1/account/logout","args":[]},"createQuote":{"verb":"POST","url":"/api/v1/customers/create-quote","args":["numberOfHosts"]},"saveBillingInfoAndSubscribe":{"verb":"POST","url":"/api/v1/customers/save-billing-info-and-subscribe","args":["quoteId","organization","firstName","lastName","paymentSource"]},"updatePasswordAndLogin":{"verb":"POST","url":"/api/v1/entrance/update-password-and-login","args":["password","token"]},"deliverDemoSignup":{"verb":"POST","url":"/api/v1/deliver-demo-signup","args":["emailAddress"]},"createOrUpdateOneNewsletterSubscription":{"verb":"POST","url":"/api/v1/create-or-update-one-newsletter-subscription","args":["emailAddress","subscribeTo"]},"unsubscribeFromAllNewsletters":{"verb":"GET","url":"/api/v1/unsubscribe-from-all-newsletters","args":["emailAddress"]},"buildLicenseKey":{"verb":"POST","url":"/api/v1/admin/build-license-key","args":["numberOfHosts","organization","expiresAt","partnerName"]},"createVantaAuthorizationRequest":{"verb":"POST","url":"/api/v1/create-vanta-authorization-request","args":["emailAddress","fleetInstanceUrl","fleetApiKey"]},"deliverMdmBetaSignup":{"verb":"POST","url":"/api/v1/deliver-mdm-beta-signup","args":["emailAddress","fullName","jobTitle","numberOfHosts"]},"deliverAppleCsr":{"verb":"POST","url":"/api/v1/deliver-apple-csr","args":["unsignedCsrData"]},"deliverLaunchPartySignup":{"verb":"POST","url":"/api/v1/deliver-launch-party-signup","args":["emailAddress","firstName","lastName","jobTitle","phoneNumber"]},"deliverMdmDemoEmail":{"verb":"POST","url":"/api/v1/deliver-mdm-demo-email","args":["emailAddress"]},"provisionSandboxInstanceAndDeliverEmail":{"verb":"POST","url":"/api/v1/admin/provision-sandbox-instance-and-deliver-email","args":["userId"]},"deliverTalkToUsFormSubmission":{"verb":"POST","url":"/api/v1/deliver-talk-to-us-form-submission","args":["emailAddress","firstName","lastName","organization","numberOfHosts","primaryBuyingSituation"]},"saveQuestionnaireProgress":{"verb":"POST","url":"/api/v1/save-questionnaire-progress","args":["currentStep","formData"]}} /* eslint-enable */ }); diff --git a/website/assets/js/pages/contact.page.js b/website/assets/js/pages/contact.page.js index e5132f4fb8..2b629aa352 100644 --- a/website/assets/js/pages/contact.page.js +++ b/website/assets/js/pages/contact.page.js @@ -32,6 +32,8 @@ parasails.registerPage('contact', { message: {required: false}, }, + formDataToPrefillForLoggedInUsers: {}, + // Server error state for the form cloudError: '', @@ -46,6 +48,17 @@ parasails.registerPage('contact', { if(this.formToShow === 'contact'){ this.formToDisplay = this.formToShow; } + if(this.prefillFormDataFromUserRecord){ + this.formDataToPrefillForLoggedInUsers.emailAddress = this.me.emailAddress; + this.formDataToPrefillForLoggedInUsers.firstName = this.me.firstName; + this.formDataToPrefillForLoggedInUsers.lastName = this.me.lastName; + this.formDataToPrefillForLoggedInUsers.organization = this.me.organization; + // Only prefil this information if the user has this value set. + if(this.me.primaryBuyingSituation) { + this.formDataToPrefillForLoggedInUsers.primaryBuyingSituation = this.me.primaryBuyingSituation; + } + this.formData = _.clone(this.formDataToPrefillForLoggedInUsers); + } if(window.location.search){ window.history.replaceState({}, document.title, '/contact' ); } @@ -78,7 +91,11 @@ parasails.registerPage('contact', { }, clickSwitchForms: function(form) { - this.formData = {}; + if(this.prefillFormDataFromUserRecord){ + this.formData = _.clone(this.formDataToPrefillForLoggedInUsers); + } else { + this.formData = {}; + } this.formErrors = {}; this.cloudError = ''; this.formToDisplay = form; diff --git a/website/assets/js/pages/entrance/signup.page.js b/website/assets/js/pages/entrance/signup.page.js index 1528c7d780..dc9a3a7781 100644 --- a/website/assets/js/pages/entrance/signup.page.js +++ b/website/assets/js/pages/entrance/signup.page.js @@ -17,7 +17,6 @@ parasails.registerPage('signup', { organization: {required: true}, emailAddress: {required: true, isEmail: true}, password: {required: true, minLength: 8}, - primaryBuyingSituation: {required: true}, }, // Syncing / loading state syncing: false, diff --git a/website/assets/js/pages/start.page.js b/website/assets/js/pages/start.page.js index d1db7b5511..ab27d73540 100644 --- a/website/assets/js/pages/start.page.js +++ b/website/assets/js/pages/start.page.js @@ -3,7 +3,54 @@ parasails.registerPage('start', { // ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣ // ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝ data: { - //… + currentStep: 'start', + + syncing: false, + + // Form data + formData: { + 'start': {stepCompleted: true}, + 'what-are-you-using-fleet-for': {}, + 'have-you-ever-used-fleet': {}, + 'how-many-hosts': {}, + 'will-you-be-self-hosting': {}, + 'what-are-you-working-on-eo-security': {}, + 'is-it-any-good': {stepCompleted: true}, + 'what-did-you-think': {}, + }, + // For tracking client-side validation errors in our form. + // > Has property set to `true` for each invalid property in `formData`. + formErrors: { /* … */ }, + + formRules: {}, + primaryBuyingSituationFormRules: { + primaryBuyingSituation: {required: true} + }, + isUsingFleetFormRules: { + fleetUseStatus: {required: true} + }, + numberOfHostsFormRules: { + numberOfHosts: {required: true} + }, + hostingFleetFormRules: { + willSelfHost: {required: true} + }, + endpointOpsSecurityWorkingOnFormRules: { + endpointOpsSecurityUseCase: {required: true} + }, + endpointOpsSecurityIsItAnyGoodFormRules: { + isItAnyGood: {required: true} + }, + endpointOpsSecurityWhatDidYouThinkFormRules: { + whatDidYouThink: {required: true} + }, + previouslyAnsweredQuestions: {}, + + // Server error state for the forms + cloudError: '', + + // Success state when form has been submitted + cloudSuccess: false, }, // ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗ @@ -11,6 +58,9 @@ parasails.registerPage('start', { // ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝ beforeMount: function() { //… + if(this.currentStep !== 'start'){ + this.prefillPreviousAnswers(); + } }, mounted: async function() { //… @@ -20,6 +70,125 @@ parasails.registerPage('start', { // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗ // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝ methods: { - //… + handleSubmittingForm: async function(argins) { + let formDataForThisStep = _.clone(argins); + let nextStep = this.getNextStep(); + let getStartedProgress = await Cloud.saveQuestionnaireProgress.with({ + currentStep: this.currentStep, + formData: formDataForThisStep, + }); + this.previouslyAnsweredQuestions[this.currentStep] = getStartedProgress[this.currentStep]; + this.syncing = false; + this.currentStep = nextStep; + }, + clickGoToPreviousStep: async function() { + switch(this.currentStep) { + case 'have-you-ever-used-fleet': + this.currentStep = 'what-are-you-using-fleet-for'; + break; + case 'how-many-hosts': + this.currentStep = 'have-you-ever-used-fleet'; + break; + case 'will-you-be-self-hosting': + this.currentStep = 'how-many-hosts'; + break; + case 'self-hosted-deploy': + this.currentStep = 'will-you-be-self-hosting'; + break; + case 'managed-cloud-for-growing-deployments': + this.currentStep = 'will-you-be-self-hosting'; + break; + case 'what-are-you-working-on-eo-security': + this.currentStep = 'have-you-ever-used-fleet'; + break; + case 'is-it-any-good': + this.currentStep = 'what-are-you-working-on-eo-security'; + break; + case 'lets-talk-to-your-team': + this.currentStep = 'how-many-hosts'; + break; + case 'welcome-to-fleet': + this.currentStep = 'have-you-ever-used-fleet'; + break; + case 'deploy-fleet-in-your-environment': + this.currentStep = 'what-did-you-think'; + break; + case 'what-did-you-think': + this.currentStep = 'is-it-any-good'; + break; + } + }, + getNextStep: function() { + let nextStepInForm; + switch(this.currentStep) { + case 'start': + nextStepInForm = 'what-are-you-using-fleet-for'; + break; + case 'what-are-you-using-fleet-for': + nextStepInForm = 'have-you-ever-used-fleet'; + break; + case 'have-you-ever-used-fleet': + let fleetUseStatus = this.formData['have-you-ever-used-fleet'].fleetUseStatus; + let primaryBuyingSituation = this.formData['what-are-you-using-fleet-for'].primaryBuyingSituation; + if(fleetUseStatus === 'yes-recently-deployed' || fleetUseStatus === 'yes-deployed') { + nextStepInForm = 'how-many-hosts'; + } else { + if(primaryBuyingSituation === 'eo-security'){ + nextStepInForm = 'what-are-you-working-on-eo-security'; + } else { + nextStepInForm = 'welcome-to-fleet'; + } + } + break; + case 'how-many-hosts': + if(this.formData['how-many-hosts'].numberOfHosts === '1-100' || + this.formData['how-many-hosts'].numberOfHosts === '100-700') { + nextStepInForm = 'will-you-be-self-hosting'; + } else { + nextStepInForm = 'lets-talk-to-your-team'; + } + break; + case 'will-you-be-self-hosting': + if(this.formData['will-you-be-self-hosting'].willSelfHost === 'true'){ + nextStepInForm = 'self-hosted-deploy'; + } else { + nextStepInForm = 'managed-cloud-for-growing-deployments'; + } + break; + case 'what-are-you-working-on-eo-security': + nextStepInForm = 'is-it-any-good'; + break; + case 'is-it-any-good': + nextStepInForm = 'what-did-you-think'; + break; + case 'what-did-you-think': + if(this.formData['what-did-you-think'].whatDidYouThink === 'let-me-think-about-it'){ + nextStepInForm = 'is-it-any-good'; + } else { + nextStepInForm = 'deploy-fleet-in-your-environment'; + } + break; + } + return nextStepInForm; + }, + clickGoToCalendly: function() { + window.location = `https://calendly.com/fleetdm/talk-to-us?email=${encodeURIComponent(this.me.emailAddress)}&name=${encodeURIComponent(this.me.firstName+' '+this.me.lastName)}`; + }, + clickGoToContactPage: function() { + window.location = `/contact?prefillFormDataFromUserRecord`; + }, + clickClearOneFormError: function(field) { + if(this.formErrors[field]){ + this.formErrors = _.omit(this.formErrors, field); + } + }, + prefillPreviousAnswers: function() { + if(!_.isEmpty(this.previouslyAnsweredQuestions)){ + for(let step in this.previouslyAnsweredQuestions){ + this.formData[step] = this.previouslyAnsweredQuestions[step]; + } + this.currentStep = this.getNextStep(); + } + }, } }); diff --git a/website/assets/styles/pages/start.less b/website/assets/styles/pages/start.less index 2ab70735ef..aa299005f0 100644 --- a/website/assets/styles/pages/start.less +++ b/website/assets/styles/pages/start.less @@ -8,18 +8,139 @@ font-weight: 800; line-height: 150%; } + h2 { + margin-bottom: 32px; + font-size: 24px; + font-weight: 800; + line-height: 120%; + } [purpose='logo-container'] { max-width: 524px; margin-left: auto; margin-right: auto; } + [purpose='form-container'] { + width: 528px; + margin-left: auto; + margin-right: auto; + } [purpose='page-container'] { - padding-top: 80px; + padding-top: 64px; padding-left: 64px; padding-right: 64px; + padding-bottom: 64px; max-width: unset; display: flex; flex-direction: column; + justify-content: center; + } + [purpose='progress-bar-container'] { + display: flex; + flex-direction: row; + align-items: center; + margin-bottom: 32px; + img { + height: 18px; + display: inline; + margin-left: 10px; + } + } + [purpose='form-progress-bar'] { + height: 6px; + width: 200px; + background: #E2E4EA; + border-radius: 3px; + [purpose='current-progress'] { + background-color: #3DB67B; + height: 6px; + border-radius: 3px; + } + } + .form-group.is-invalid { + color: @core-vibrant-red; + .form-control { + color: @core-vibrant-red; + } + .invalid-feedback { + display: block; + } + } + [purpose='form-option'] { + user-select: none; + cursor: pointer; + width: fit-content; + padding: 8px 12px 8px 8px; + margin-bottom: 16px; + display: flex; + flex-direction: row; + align-items: center; + border-radius: 7px; + border: 1px solid #E2E4EA; + font-size: 16px; + line-height: 24px; + color: #515774; + white-space: nowrap; + height: fit-content; + input { + cursor: pointer; + margin-right: 8px; + display: none; + } + [purpose='custom-radio'] { + margin-right: 8px; + display: flex; + min-width: 18px; + min-height: 18px; + border-radius: 50%; + border: 1px solid #E2E4EA; + justify-content: center; + align-items: center; + [purpose='custom-radio-selected'] { + min-width: 10px; + min-height: 10px; + border-radius: 50%; + background-color: @core-vibrant-blue; + transform: scale(0); + transition: 180ms transform ease-in-out; + } + } + input[type='radio']:checked + [purpose='custom-radio'] { + [purpose='custom-radio-selected'] { + transform: scale(1); + } + } + .form-control { + height: 40px; + } + &:hover { + border: 1px solid @core-vibrant-blue; + } + &.selected { + border: 1px solid @core-vibrant-blue; + } + } + + [purpose='form-buttons'] { + margin-top: 32px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: start; + user-select: none; + } + [purpose='submit-button'] { + padding: 12px; + font-size: 14px; + font-weight: 700; + line-height: 150%; + margin-right: 24px; + } + [purpose='back-button'] { + cursor: pointer; + font-size: 14px; + font-weight: 700; + line-height: 150%; + margin-right: 24px; } [purpose='start-cards'] { display: flex; @@ -31,13 +152,14 @@ } [purpose='card'] { width: 252px; + height: 200px; display: flex; flex-direction: column; justify-content: center; align-items: center; text-decoration: none; - - padding: 43px 52px; + cursor: pointer; + padding: 24px; background: #FFF; color: @core-fleet-black-75; border-radius: 12px; @@ -57,7 +179,7 @@ font-size: 12px; font-weight: 400; line-height: 150%; - white-space: nowrap; + // white-space: nowrap; margin-bottom: 0px; } &:first-of-type { @@ -68,6 +190,51 @@ } } + [purpose='quote'] { + display: flex; + min-height: 300px; + padding: 32px; + flex-direction: column; + justify-content: center; + align-items: flex-start; + align-self: stretch; + border-radius: 16px; + background: #FFF; + box-shadow: none; + background-color: #FFF; + height: fit-content; + max-width: 100%; + text-align: left; + border: 1px solid @core-vibrant-blue-15; + [purpose='logo'] { + img { + max-height: 32px; + } + min-height: 0; + margin-bottom: 24px; + } + [purpose='quote-text'] { + margin-bottom: 24px; + font-size: 14px; + line-height: 150%; + } + [purpose='quote-author-info'] { + [purpose='job-title'] { + font-size: 12px; + line-height: 18px; + } + [purpose='name'] { + color: @core-fleet-black; + } + [purpose='profile-picture'] { + margin-right: 16px; + img { + height: 48px; + width: 48px; + } + } + } + } @media (max-width: 991px) { [purpose='page-container'] { padding-top: 60px; @@ -91,11 +258,23 @@ } [purpose='start-cards'] { flex-direction: column; + [purpose='card'] { + width: 100%; + } } [purpose='card']:first-of-type { margin-right: unset; margin-bottom: 20px; } + [purpose='form-container'] { + width: unset; + margin-left: 0; + margin-right: 0; + } + [purpose='form-option'] { + white-space: unset; + width: unset; + } } @media (max-width: 375px) { h1 { diff --git a/website/config/routes.js b/website/config/routes.js index 6a486f1db6..be70757038 100644 --- a/website/config/routes.js +++ b/website/config/routes.js @@ -280,8 +280,8 @@ module.exports.routes = { 'GET /start': { action: 'view-start', locals: { - hideHeaderLinks: true, hideFooterLinks: true, + hideGetStartedButton: true, pageTitleForMeta: 'Start | Fleet', pageDescriptionForMeta: 'Get Started with Fleet. Spin up a local demo or get your premium license key.', } @@ -577,4 +577,5 @@ module.exports.routes = { 'POST /api/v1/deliver-mdm-demo-email': { action: 'deliver-mdm-demo-email' }, 'POST /api/v1/admin/provision-sandbox-instance-and-deliver-email': { action: 'admin/provision-sandbox-instance-and-deliver-email' }, 'POST /api/v1/deliver-talk-to-us-form-submission': { action: 'deliver-talk-to-us-form-submission' }, + 'POST /api/v1/save-questionnaire-progress': { action: 'save-questionnaire-progress' }, }; diff --git a/website/generators/landing-page/index.js b/website/generators/landing-page/index.js index 5199522bb4..2ed7891caa 100644 --- a/website/generators/landing-page/index.js +++ b/website/generators/landing-page/index.js @@ -219,8 +219,8 @@ module.exports = {
Vitae architecto reiciendis in temporibus consequatur doloremque reprehenderit perferendis? Eaque quod voluptates earum corporis, quo labore reprehenderit libero sint.
@@ -274,8 +274,8 @@ module.exports = { @@ -296,8 +296,8 @@ module.exports = {You can use Fleet’s API to customize every aspect of conditional access – even the stuff your CISO hasn’t thought of yet.
@@ -206,8 +206,8 @@You don’t need to be an osquery expert to get the answers you need from your devices, Fleet does some of that for you.
@@ -194,8 +194,8 @@This email is already linked to a Fleet account.
Please sign in with your email and password.
Replace the sprawl with open-source code that works the way you want.
@@ -277,8 +277,8 @@Automate security workflows in a single application by creating or installing policies to identify which devices comply with your security guidelines.
@@ -85,8 +85,8 @@Bringh-your-own MDM. Enjoy enterprise-ready open-source MDM and leverage the best of DevOps and GitOps inside a full-featured Macbook MDM.
@@ -59,8 +59,8 @@Fleet, an open-source security platform, empowers you to take control of vulnerability management for your organization. Designed with modern DevOps practices in mind, Fleet offers superior visibility, rapid response capabilities, and seamless integration with your existing workflows.
@@ -100,8 +100,8 @@Fleet, the leading open-source, flexible device management solution, offers unprecedented visibility into your IT infrastructure, making it the ideal tool to discover and manage unused software licenses. This capability is essential to unlocking more IT budget, enhancing security, and ultimately improving the employee experience.
@@ -64,8 +64,8 @@ diff --git a/website/views/pages/pricing.ejs b/website/views/pages/pricing.ejs index 40dc82125c..e69f64189a 100644 --- a/website/views/pages/pricing.ejs +++ b/website/views/pages/pricing.ejs @@ -43,7 +43,7 @@ diff --git a/website/views/pages/start.ejs b/website/views/pages/start.ejs index e48d94933c..ea6dad2f8d 100644 --- a/website/views/pages/start.ejs +++ b/website/views/pages/start.ejs @@ -1,23 +1,519 @@Spin up a local demo or get your Fleet Premium license key.
+To see whether Fleet’s right for your team, let's have a look at your hosts and what you're trying to do.
+You can come back at any time and pick up where you left off.
+
- Run a local demo of Fleet
- - -
- Purchase a Fleet Premium license
- + <%// ┬ ┬┬ ┬┌─┐┌┬┐ ┌─┐┬─┐┌─┐ ┬ ┬┌─┐┬ ┬ ┬ ┬┌─┐┬┌┐┌┌─┐ ┌─┐┬ ┌─┐┌─┐┌┬┐ ┌─┐┌─┐┬─┐ + // │││├─┤├─┤ │ ├─┤├┬┘├┤ └┬┘│ ││ │ │ │└─┐│││││ ┬ ├┤ │ ├┤ ├┤ │ ├┤ │ │├┬┘ + // └┴┘┴ ┴┴ ┴ ┴ ┴ ┴┴└─└─┘ ┴ └─┘└─┘ └─┘└─┘┴┘└┘└─┘ └ ┴─┘└─┘└─┘ ┴ └ └─┘┴└─%> +We don’t have a public sample environment, but you can try Fleet out locally on your computer.
+
+ Try Fleet locally on your device
+ + +See what Fleet can do
+ +You can come back here any time to continue with your deployment.
+Now that you’ve seen what Fleet can do, what do you want to do next?
++ Something I really appreciate about working with you guys is that it doesn't feel like I'm talking to a vendor. It actually feels like I'm talking to my team, and I really appreciate it. +
+
+ Chandra Majumdar
+Partner - Cyber and Strategic Risk
++ Exciting. This is a team that listens to feedback. +
+
+ Erik Gomez
+Staff Client Platform Engineer
++ The visibility down into the assets covered by the agent is phenomenal. Fleet has become the central source for a lot of things. +
+
+ Andre Shields
+Staff Cybersecurity Engineer, Vulnerability Management
++ When we look at vendors, we look for ones that are very receptive to feedback, where you’re just part of the family, I guess. Fleet’s really good at that. +
+
+ Harrison Ravazzolo
+Lead platform and identity engineer
++ I love the steady and consistent delivery of features that help teams work how they want to work, not how your product dictates they work. +
+
+ Dan Grzelak
+Security Chief of Staff
+Great, let’s jump on a call to talk more about your fleet.
+Get support and training or schedule a personalized demo for your team.
+ + +Grab some time and ask us anything.
+ +Learn how to deploy and roll out Fleet in your environment.
+Learn how to deploy Fleet
+ + +Ask Fleet’s community of helpful, knowledgeable people.
+ +Learn how to deploy and rollout Fleet in your environment, get a Fleet Premium license, or both. You can come back in any time to upgrade.
+Learn how to deploy Fleet
+ + + +
+ Purchase a Fleet Premium license
+ +Unfortunately, managed cloud hosting is not yet available for growing deployments of less than 700 hosts.
+Spin up a local demo or get your Fleet Premium license key.
+
+ Run a local demo of Fleet
+ + +
+ Purchase a Fleet Premium license
+ +Use open data and APIs to connect your vulnerability solution with osquery, the agent you might already have deployed.