Referencesidebar-dropdown-icon
Configurationsidebar-dropdown-icon
Basic Featuressidebar-dropdown-icon
Advanced Featuressidebar-dropdown-icon
Serverlesssidebar-dropdown-icon
Testingsidebar-dropdown-icon
Errors and Validationssidebar-dropdown-icon
Guidelinessidebar-dropdown-icon
Marketplace Listingsidebar-dropdown-icon
Migration Guidesidebar-dropdown-icon
Other Resourcessidebar-dropdown-icon
Quick Start

You can build a Freshteam app by using the following steps.

  • Install NVM
  • Install Node
  • Install the CLI
  • Create an App
  • Test the App
  • Validate and Pack

Apps built on the Freshworks platform are compatible with the latest and immediately preceding version of the following browsers.

  • Google Chrome
  • Firefox
  • Edge
  • Safari

Install NVM

Node Version Manager (NVM) enables you to install and work with multiple versions of Node.js. You can use NVM to install Node.

  1. To install NVM:
    1. On Mac or Linux, follow the installation and upgrade steps.
    2. On Windows, navigate to the release channel > Assets, download the nvm-setup.zip file, extract the contents of the file, and use the installer in the extracted files.
  2. To verify the NVM installation, run the following command.
    nvm --version

Install Node

  1. To install Node using NVM, run the following command.

    nvm install 14

    Note

    With the latest FDK version, support for building apps on earlier versions of Node.js is unavailable.

  2. Run the following command to verify the Node installation.

    node --version

    Note

    On Windows, if the Node version is not displayed, run the nvm on command to enable NVM.

  3. Run the following command to set the default Node version.

    nvm alias default 14
  4. On Windows, to install the required tools and configurations to start using Node, use the following command.

    npm install --global --production windows-build-tools

    For more information, see the write-up on node-gyp.

Install the Freshworks CLI

Note

  • Ensure to use npm for CLI installation. Also, ensure to use the npm version that is shipped with Node. For information on supported Node versions, see FDK and compatible Node.js versions. Use of any other npm version or use of alternative package managers such as YARN can affect the CLI installation and dependencies management.
  • Uninstall the previous CLI version by using the npm uninstall fdk -g command.
  • The Developer portal and SDK Terms of Use apply to the use of the CLI.
  1. Run the following command to install the latest CLI version.
    npm install https://dl.freshdev.io/cli/fdk.tgz -g
  2. Run the following command to verify the CLI installation.
    fdk version

Create an App

To create an app on the Job Details page:

  1. From the command line, navigate to the empty directory in which you want to create an app.
  2. Run the following command.
    fdk create
    A prompt to choose a product is displayed.
  3. Select freshteam and press Enter. A new app is created based on the your_first_app template.

fdk create provides an interactive prompt to generate a new app

The following directories and files are created as a result of the fdk create command.

Directory/FileDescription
app/Contains all the files required for the front-end component of an app. The JS file follows the ES5 standard.
app/icon.svgContains the product icon. If you intend to extend the app, you can replace the icon.svg file. The icon file should be of SVG type with a resolution of 64 x 64 pixels.
app/template.htmlContains the HTML code required for the app's UI, which is rendered in an IFrame.
config/Contains the installation parameters and OAuth configuration files.
config/iparams.jsonSpecifies all the installation parameters whose values are set when the app is installed. For more information, see Installation Parameters.
manifest.jsonContains details such as the platform version the app uses, product to which the app belongs, event listeners for serverless apps, SMI functions that can be invoked from the app's front end component, and npm packages (dependencies) that the app uses.
README.mdContains additional instructions, information, and specifications.

Note

  1. The iparam_test_data.json file has been deprecated. Before testing the app, navigate to http://localhost:10001/custom_configs and enter appropriate values for the configured installation parameters.
  2. If you use React, Ember, Vue, or any other front-end framework, include the source files of your app in the src directory to ensure quick app reviews.

Test the App

Note

  1. Use the latest version of Chrome browser.
  2. Ensure to sign up for a Freshteam account.
  1. From the command line, navigate to the directory that contains the app related files and run the following command.
    fdk run
  2. Log in to your Freshteam account.
  3. To the Freshteam account URL, append ?dev=true. Example URL: https://domain.freshteam.com/dashboard/hire/jobs/0000000001/info?dev=true.
  4. To allow the Chrome browser to connect to the test server that runs on HTTP,
    1. On Chrome 79 and higher versions:
      1. Navigate to Settings > Advanced > Privacy and security > Site settings > Insecure content.
      2. In the Allow section, click Add and enter the Freshteam account URL. Example URL: https://domain.freshteam.com
    2. On Chrome 78 and lower versions:
      1. In the address bar, if a shield icon is displayed, click the icon. A warning message is displayed as the Support portal runs on HTTPS and the test server runs on HTTP.
      2. Click Load Unsafe Scripts to continue testing.
  5. Navigate to the Recruit > Jobs > Details page. If the app is created successfully, the app is displayed in the Job Boards > Other Job Boards section, as shown in the image.

Screenshot showing local simulation app in Freshteam

Note

  1. Each component in the coverage summary should be at least 80% for apps to be submitted in Freshworks Marketplace. See Code coverage for more information.
  2. If you have any issues when testing your app, attach detailed logs of the output in your support ticket for quick resolution from the support team.

Code

1import Cookie from 'js-cookie';
2import { SiteUtilities } from '../utils';
3import formSsoSignup from '../form-sso-signup';
4import fchatFormHandler from './fchat-form-handlers';
5// import fsalesFormHandler from './fsales-form-handlers';
6// import fmarketerFormHandler from './fmarketer-form-handlers';
7import fcallerFormHandler from './fcaller-form-handlers';
8import fdeskFormHandler from './fdesk-form-handlers';
9import fpingFormHandler from './fping-form-handlers';
10import fworksFormHandler from './fworks-form-handlers';
11import fstatusFormHandler from './fstatus-form-handlers';
12import fteamFormHandler from './fteam-form-handlers';
13import fwCrmFormHandler from './fwcrm-form-handlers';
14import GA4Utilities from '../utils/ga4-utilities';
15const { getFchatCurrencyUnit, fchatSignupFormSuccess } = fchatFormHandler;
16// const { fsalesSignupFormSuccess } = fsalesFormHandler;
17// const { fmarketerSignupFormSuccess } = fmarketerFormHandler;
18const { fcallerSignupFormSuccess } = fcallerFormHandler;
19const { fdeskSignupFormSuccess } = fdeskFormHandler;
20const { fpingSignupFormSuccess } = fpingFormHandler;
21const { fworksCrmSignupForm, fworksCrmSignupFormSuccess } = fworksFormHandler;
22const { fstatusSignupFormSuccess } = fstatusFormHandler;
23const { fteamSignupFormSuccess } = fteamFormHandler;
24const { fwCrmSignupForm, fwCrmSignupFormSuccess } = fwCrmFormHandler;
25const { handleGoogleSingupErrHandler } = formSsoSignup;
26
27const alohaForm = (classname, form) => {
28  const $form = $(form);
29  const product = $form.data('product');
30  SiteUtilities.resetSignupPerformanceLog();
31  localStorage.setItem('signupStartTime', new Date().toISOString());
32  // control form section in sso signup
33  const $formSection = $($form.parent('[data-form-section]'));
34  const $submitButton = $form.find('.button');
35  const $emailField = $form.find('input[name^="email"]');
36  const email = $emailField.val() || null;
37  const isEmailBusiness = email && !(email.includes('@gmail'));
38  let apiDomain = $form.data('signup-url');
39  const firstName = $form.find('.first-name-form').val() || null;
40  const lastName = $form.find('.last-name-form').val() || null;
41  const domain = $form.find('.domain-form').val() || null;
42  const phone = $form.find('.phone-form').val() || null;
43  const company = $form.find('.company-form').val() || null;
44  const plan = $form.find('input[name="plan-id"]').val() || null;
45  let remindEndPoint = $form.data('remind-path');
46  const $miscPayload = $form.find('.aloha-misc-payload');
47  let growSumo = false;
48  const isExternalIframeSignup = $form.parents('.external-iframe-signup').length;
49  const currentReferrer = SiteUtilities.getCurrentReferrer();
50  let currentLoc = SiteUtilities.getCurrentLocation();
51  const location = session.simpleGeoLocation;
52  const productDesign = $form.hasClass('product-signupform');
53  const planId = $form.data('plan_id') || null;
54  if (planId && !$form.find('.fm-asset-id').val() && !$form.data('fm_asset_id')) {
55    SiteUtilities.fwcrmAssetId($form, planId);
56  }
57  const fmAssetId = $form.find('.fm-asset-id').val() || $form.data('fm_asset_id') || 3649313;
58  const tacticId = SiteUtilities.getTacticID() || null;
59  const compositeKey = SiteUtilities.getCompositeKey(email, fmAssetId, tacticId);
60  const gclid = SiteUtilities.getGclId();
61  const latestPageVisits = localStorage.getItem('latest_page_visits') || null;
62
63  session.ga_client_id = SiteUtilities.getCombinedGaClientId(session, gclid, compositeKey);
64
65  let fsCookie;
66  try {
67    fsCookie = freshsales.anonymous_id || '';
68  } catch (exception) {
69    SiteUtilities.log(
70      'Freshsales session exception.',
71      'warning',
72      'submitHandlers.fsalesForm (fsCookie - Catch Exception)',
73      0,
74      exception
75    );
76  }
77
78  if (productDesign) {
79    $submitButton.addClass('button--product-disabled').attr('disabled', 'disabled');
80    if (product === 'fworks') {
81      fworksCrmSignupFormSuccess($form, true);
82    }
83    if (product === 'fwcrm' || product === 'fmarketer' || product === 'fsales') {
84      fwCrmSignupFormSuccess($form, true);
85    }
86  } else {
87    $submitButton.addClass('button--loading').attr('disabled', 'disabled');
88  }
89
90  if (currentReferrer && isExternalIframeSignup) {
91    currentLoc = currentReferrer;
92    if (session.current_session) {
93      session.current_session.url = `${SiteUtilities.getCurrentLocation()}?first_referrer=${currentReferrer}`;
94    }
95  }
96
97  let ipAddress;
98  const country = session.simpleGeoLocation.countryName;
99  let sessionObj = (typeof (session) === 'object') ? JSON.parse(JSON.stringify(session)) : null;
100  if (SiteUtilities.GDPRFunctionalCookieDisabled()) {
101    const { simpleGeoLocation: { countryCode, countryName, country: { iso_code: isoCode } = {} } = {} } = session || {};
102    if (sessionObj) {
103      sessionObj.location = {
104        country: {
105          iso_code: isoCode || ''
106        },
107        ipAddress: '',
108        countryCode: countryCode || '',
109        countryName: countryName || ''
110      };
111      sessionObj.simpleGeoLocation = sessionObj.location;
112      sessionObj.geoLocation = sessionObj.location;
113    }
114  } else {
115    // Binding maxmind_session
116    session.location = session.simpleGeoLocation;
117    ipAddress = (location && location.ipAddress) ? location.ipAddress : null;
118    sessionObj = session;
119  }
120
121  if (sessionObj) {
122    // attach Google click ID (gclid) in sessionObj
123    sessionObj.gclid = gclid;
124  }
125
126  const postData = {
127    user: {
128      first_name: firstName,
129      middle_name: null,
130      last_name: lastName,
131      email,
132      phone,
133      mobile: null,
134      job_title: null,
135      company_name: company
136    },
137    organisation: null,
138    session_json: sessionObj,
139    currency: null,
140    plan: plan,
141    first_referrer: (isExternalIframeSignup && currentReferrer) ? currentReferrer : (Cookie.get('fw_fr') || currentLoc),
142    first_landing_page: Cookie.get('fw_flu') || '',
143    misc: {
144      fs_cookie: fsCookie,
145      composite_stitching_key: compositeKey
146    }
147  };
148
149  $miscPayload.each((index, ele) => {
150    postData.misc[$(ele).data('key')] = $(ele).data('value');
151  });
152
153  const marketingCampaign = $form.find('[name="MarketingCampaign"],[name="Marketing Campaign"],[name="marketing_campaign"],[name="marketingcampaign"],[name="marketing campaign"]').data('custom-value');
154
155  if (marketingCampaign) {
156    postData.misc.marketing_campaign = marketingCampaign;
157  }
158
159  if (tacticId) {
160    postData.misc.allocadia_tactic_id = tacticId;
161  }
162
163  if (fmAssetId) {
164    postData.misc.allocadia_asset_id = fmAssetId;
165  }
166  const shopifyJWT = SiteUtilities.getParameterByName('shopify_jwt');
167  const JWTToken = $form.find('.google-jwt-token').val();
168  if (JWTToken && JWTToken.length) {
169    postData.user.oauth_details = {
170      oauth_provider: 'google',
171      token: JWTToken
172    };
173  }
174
175  switch (product) {
176    case 'fchat':
177      /**
178       * BUG:WEB-921
179       * Browser version is obtained from navigation.userAgent using `session.json`.
180       * when the browser version is, say, "13.0.4", parseFloat returns "13". But when it is "13.1.4",
181       * it will return "13.1". So it is because of parseFloat implementation, the payload is varying.
182       * Fchat expects this datatype to be "long". When "float" is sent, it fails.
183       */
184      if (postData.session_json.browser && postData.session_json.browser.version) {
185        postData.session_json.browser.version = parseInt(postData.session_json.browser.version);
186      }
187      const currencyUnit = getFchatCurrencyUnit(session.simpleGeoLocation.countryName, session.simpleGeoLocation.countryCode);
188      postData.misc.preferredDomain = company;
189      postData.currency = currencyUnit;
190      if (latestPageVisits) postData.misc.latest_page_visits = latestPageVisits;
191      break;
192
193    case 'fdesk':
194      const getSignupEndpointLang = (workflowLocale) => {
195        return {
196          de: 'de',
197          fr: 'fr',
198          es: 'es',
199          pt: 'pt-BR',
200          'latam-es': 'es-LA',
201          nl: 'nl',
202          se: 'sv-SE',
203          en: 'en',
204          ja: 'ja-JP',
205          ru: 'ru-RU',
206          ko: 'ko',
207          it: 'it',
208          cn: 'zh-CN',
209          th: 'th',
210          vi: 'vi',
211          pl: 'pl',
212          hk: 'zh-TW',
213          fi: 'fi',
214          tr: 'tr'
215        }[workflowLocale];
216      };
217      const currentLang = $('body').attr('data-lang');
218      postData.misc.account_name = company;
219      postData.misc.account_lang = getSignupEndpointLang(currentLang) || 'en';
220      growSumo = true;
221      /** ** Conditionally add mandatory params for aloha internal environment ** **/
222      if (window.location.href.includes('/signup-int')) {
223        postData.cloud_type = window.location.href.includes('omnichannel') ? 'omnichannel' : 'messaging';
224        postData.misc.account_domain = company;
225        postData.misc.preferredDomain = company;
226      }
227      if (latestPageVisits) postData.misc.latest_page_visits = latestPageVisits;
228      break;
229
230    case 'fcaller':
231      remindEndPoint = remindEndPoint || 'https://signup.freshcaller.com/find_domain?user_email=';
232      if (latestPageVisits) postData.misc.latest_page_visits = latestPageVisits;
233      break;
234
235    case 'fping':
236      postData.misc.application_url = $form.find('input[name^=url]').val() || null;
237      break;
238
239    case 'fworks':
240      postData.misc.account_domain = company ? company.replace(/[^a-zA-Z0-9-]/g, '') : null;
241      postData.misc.account_name = company;
242      growSumo = true;
243      break;
244
245    case 'fstatus':
246      postData.misc.website_url = $form.find('input[name^=url]').val() || null;
247      break;
248
249    case 'fteam':
250      postData.misc.source = SiteUtilities.getParameterByName('utm_source');
251      postData.misc.medium = SiteUtilities.getParameterByName('utm_medium');
252      postData.misc.campaign = SiteUtilities.getParameterByName('utm_campaign');
253      postData.misc.validate_duplicate = true;
254      postData.misc.time_zone = session.time.tz_offset;
255      postData.misc.gdpr_first_opt_in = ($form.find('input[name="send_promotions"]').is(':checked'));
256      postData.misc.country = country;
257      postData.misc.pre_visits = Cookie.get('fw_vi') || 0;
258      break;
259
260    case 'fsales':
261    case 'fmarketer':
262    case 'fwcrm':
263      const isShopifySignup = (shopifyJWT) || SiteUtilities.getParameterByName('att_referrer').includes('shopify');
264      let cloudType = $form.data('cloud_type') || null;
265      const currencies = {
266        US: 'USD',
267        EU: 'EUR',
268        GB: 'GBP',
269        AU: 'AUD',
270        IN: 'INR'
271      };
272      const selectedCurrency = SiteUtilities.selectCountry(currencies, location.countryCode, location.countryName);
273      const currency = currencies[selectedCurrency];
274      let planvalue = `sales360_sales_enterprise_${currency}_Yearly`;
275      const marketplaceProduct = $form.data('marketplace-product') || product;
276      if (planId) {
277        const planObj = {
278          mc: { name: `sales360_marketing_enterprise_${currency}_Yearly` },
279          c4l: { name: `sales360_clc_enterprise_${currency}_Yearly` },
280          suite: { name: `sales360_clc_enterprise_${currency}_Yearly` }
281        };
282        planvalue = (planObj[planId]) ? planObj[planId].name : planvalue;
283      }
284      if (isShopifySignup) {
285        const marketplacePlanValues = {
286          fmarketer: {
287            planId: `sales360_marketing_growth_${currency}_Yearly`,
288            cloudType: 'marketing_cloud'
289          },
290          fdmessaging: {
291            planId: `crm_messaging_growth_${currency}_Yearly`,
292            cloudType: 'freshdesk_messaging_cloud'
293          }
294        };
295        const planValues = marketplacePlanValues[marketplaceProduct];
296        planvalue = planValues.planId;
297        cloudType = planValues.cloudType;
298      }
299      if (cloudType) {
300        postData.cloud_type = cloudType;
301      }
302      const attReferrer = localStorage.getItem('att_referrer');
303      if (attReferrer) {
304        postData.misc.att_referrer = `${attReferrer}_${marketplaceProduct}`;
305      }
306      if (shopifyJWT) {
307        postData.user.oauth_details = {
308          oauth_provider: 'shopify',
309          token: shopifyJWT
310        };
311      }
312      postData.misc.plan_id = planvalue;
313      postData.misc.account_domain = company ? company.replace(/[^a-zA-Z0-9-]/g, '') : null;
314      postData.misc.account_name = company;
315      if (latestPageVisits) postData.misc.latest_page_visits = latestPageVisits;
316      growSumo = true;
317      break;
318  }
319
320  /** **ONLY NEEDED IN STAGING STARTS** **/
321  const currentLocation = window.location.href;
322  if (currentLocation.includes('/signup-int')) {
323    const apiURLFinal = new URL(apiDomain);
324    apiURLFinal.host = 'aloha-int.freshid.io';
325    apiDomain = apiURLFinal.href;
326  }
327  /** **ONLY NEEDED IN STAGING ENDS** **/
328
329  // Handling growsumo data
330  if (growSumo) {
331    const gsdata = {
332      first_name: firstName,
333      email_id: email,
334      domain_name: domain
335    };
336    localStorage.setItem('gsdata', JSON.stringify(gsdata));
337  }
338
339  const responseErrorHandler = (errorMsg, form) => {
340    if (errorMsg) {
341      errorMsg = typeof (errorMsg) === 'object' ? JSON.stringify(errorMsg) : errorMsg;
342      if (errorMsg.includes('only one signup is allowed per email id')) {
343        $('.login-trigger-button').trigger('click');
344
345        if (remindEndPoint) {
346          $submitButton.removeClass('button--loading').removeAttr('disabled');
347
348          const $signupLoginModal = $('.signup-login-modal');
349          $signupLoginModal.find('.email-reminder-button').on('click', function () {
350            $(this).addClass('button--loading').attr('disabled', 'disabled');
351            $.ajax({
352              url: remindEndPoint + encodeURIComponent(email),
353              type: 'GET',
354              success: (response) => {
355                $(this).removeClass('button--loading').removeAttr('disabled');
356                $signupLoginModal.find('.form-field-container,.thank-you-card').addClass('active');
357                $signupLoginModal.find('.thank-you-card').addClass('small-card');
358              },
359              error: () => {
360                $(this).removeClass('button--loading').removeAttr('disabled').text('Retry');
361              }
362            });
363          });
364        }
365
366        if (product === 'fteam') {
367          $('.new-account-button').on('click', function () {
368            postData.misc.validate_duplicate = false;
369            $(this).addClass('button--loading product-specific');
370            $.ajax({
371              url: apiDomain,
372              type: 'POST',
373              data: JSON.stringify(postData),
374              accept: 'application/json; charset=UTF-8',
375              contentType: 'application/json; charset=UTF-8',
376              dataType: 'json',
377              success: (response, textStatus, jqXHR) => {
378                const status = jqXHR.status;
379                if ((status >= 200 && status < 300) || status === 304) {
380                  fteamSignupFormSuccess($form, response, true);
381                }
382              },
383              error: (jqXHR) => {
384                $(this).removeClass('button--loading').removeAttr('disabled').text('Retry');
385                failureLogData['message'] = `Aloha v2 Signup API call Failed. StatusCode: ${jqXHR.status}, Error: ${jqXHR.responseText || 'jqXHR response text is undefined'}`;
386                SiteUtilities.signupErrorLog(failureLogData, form);
387              }
388            });
389          });
390        }
391      }
392      if (errorMsg.includes('domain_taken') || errorMsg.includes('The Full domain has already been taken')) {
393        $('.domain-taken-trigger-button').trigger('click');
394      }
395      if (errorMsg.includes('spam_email') || errorMsg.includes('invalid_email') || errorMsg.includes('blocked_domain')) {
396        $('.login-spam-trigger-button').trigger('click');
397      }
398      if (product === 'fping' && errorMsg.includes('User already Registered')) {
399        $('.email-form').parent('.form-field').addClass('error').find('.error-wrapper').html(`<em id="email-error" class="error">User already Registered</em>`);
400      }
401      if (product === 'fstatus' && errorMsg.includes('User already Registered')) {
402        form.find('.email-form').parent('.form-field').addClass('error').find('.error-wrapper').html(`<em id="email-error" class="error">Please try using a different email address</em>`);
403      }
404      if (errorMsg.includes('company_name parameter length should not be more than 128')) {
405        $('.company-form').parent('.form-field').addClass('error').find('.error-wrapper').html(`<em id="email-error" class="error">Company length should not be more than 128 characters</em>`);
406      }
407      if (errorMsg.includes('Register using your work email')) {
408        $('.email-form').parent('.form-field').addClass('error').find('.error-wrapper').html(`<em id="email-error" class="error">Register using your work email</em>`);
409      }
410      if (errorMsg.includes('Name cannot contain URLs or dots')) {
411        $('.first-name-form').parent('.form-field').addClass('error').find('.error-wrapper').html(`<em id="email-error" class="error">Name cannot contain URLs or dots</em>`);
412        $('.last-name-form').parent('.form-field').addClass('error');
413      }
414    }
415    $submitButton.removeClass('button--loading').val('Retry').removeAttr('disabled');
416  };
417
418  const checkErrorValidity = errorMsg => {
419    if (errorMsg) {
420      errorMsg = typeof (errorMsg) === 'object' ? JSON.stringify(errorMsg) : errorMsg;
421      if (
422        errorMsg.includes('only one signup is allowed per email id') ||
423        errorMsg.includes('domain_taken') ||
424        errorMsg.includes('The Full domain has already been taken') ||
425        errorMsg.includes('spam_email') ||
426        errorMsg.includes('invalid_email') ||
427        errorMsg.includes('blocked_domain') ||
428        errorMsg.includes('User already Registered')
429      ) {
430        return false;
431      }
432    }
433    return true;
434  };
435
436  const failureLogData = {
437    email: email,
438    source: 'FW.Submit.alohaForm',
439    product: product,
440    ip_address: ipAddress
441  };
442
443  const signupLogData = {
444    email: email,
445    source: 'FW.Submit.alohaForm',
446    ip_address: ipAddress,
447    type: 'signup',
448    signup_payload_payload: postData,
449    api_endpoint: apiDomain
450  };
451
452  localStorage.setItem('signupPerformanceLogData', JSON.stringify(signupLogData));
453
454  if ($formSection.length) $formSection.css('pointer-events', 'none');
455
456  const params = {
457    url: apiDomain,
458    type: 'POST',
459    data: JSON.stringify(postData),
460    dataType: 'json',
461    crossDomain: true,
462    timeout: 100000, // Increase timeout to 100 seconds
463    accept: 'application/json; charset=UTF-8',
464    contentType: 'application/json; charset=UTF-8',
465    success: (response, textStatus, jqXHR) => {
466      response.composite_stitching_key = compositeKey;
467      const status = jqXHR.status;
468      SiteUtilities.configureAssetId($form, product);
469      if ((status >= 200 && status < 300) || status === 304) {
470        localStorage.setItem('alohaEndTime', new Date().toISOString());
471        switch (product) {
472          case 'fchat':
473            fchatSignupFormSuccess($form, response, true);
474            break;
475
476          case 'fcaller':
477            fcallerSignupFormSuccess($form, response, true);
478            break;
479
480          case 'fdesk':
481            fdeskSignupFormSuccess($form, response, true);
482            break;
483
484          case 'fping':
485            fpingSignupFormSuccess($form, response, true);
486            break;
487
488          case 'fworks':
489            fworksCrmSignupForm($form, response, true);
490            break;
491
492          case 'fstatus':
493            fstatusSignupFormSuccess($form, response, true);
494            break;
495
496          case 'fteam':
497            fteamSignupFormSuccess($form, response, true);
498            break;
499
500          case 'fwcrm':
501          case 'fsales':
502          case 'fmarketer':
503            const redirectUrl = response['product_signup_response']['redirect_url'].replace(/\/$/, '');
504            const redirectUrlObj = new window.URL(redirectUrl);
505            const domainLink = redirectUrlObj.hostname;
506            if (growSumo) {
507              const gsdata = JSON.parse(localStorage.getItem('gsdata'));
508              gsdata.domain_link = domainLink;
509              localStorage.setItem('gsdata', JSON.stringify(gsdata));
510            }
511            fwCrmSignupForm($form, response, true);
512            break;
513        }
514        if (JWTToken && JWTToken.length) {
515          GA4Utilities.googleSSOEvent('fwFormSubmit', (isEmailBusiness ? 'Business' : 'Generic'), 'Form Submission', classname);
516        } else if ($('[data-idp="GOOGLE"]').length) {
517          GA4Utilities.googleSSOEvent('fwFormSubmit', 'Normal Signup', 'Normal Signup', classname);
518        } else if (shopifyJWT) {
519          GA4Utilities.postGAEvent('fwFormSubmit', 'Shopify Signup Page', 'Shopify Signup Page', classname, 'Shopify Signup Page');
520        } else {
521          GA4Utilities.fwFormSubmit(classname);
522        }
523        localStorage.setItem('signupStatusCode', status);
524        localStorage.setItem('signupStatusText', textStatus);
525        localStorage.setItem('signupStatusMsg', 'Aloha v2 Signup API call successful');
526        SiteUtilities.signupLog(signupLogData, form, status, { message: 'Aloha v2 Signup API call successful', response: `${jqXHR.responseText || 'jqXHR response text is undefined'}`, textStatus: `${textStatus}` });
527      } else {
528        localStorage.setItem('alohaEndTime', new Date().toISOString());
529        const responseObject = {
530          jqXHR,
531          textStatus,
532          response
533        };
534        $form.find('.button').removeClass('button--loading').attr('disabled', null);
535
536        // sso signup section
537        if ($formSection.length) {
538          $formSection.css('pointer-events', 'auto');
539          handleGoogleSingupErrHandler($formSection);
540        }
541
542        if (product === 'fworks') {
543          fworksCrmSignupFormSuccess($form, false);
544        }
545        if (product === 'fwcrm' || product === 'fmarketer' || product === 'fsales') {
546          fwCrmSignupFormSuccess($form, false);
547        }
548        SiteUtilities.classifyStatusCode(status, 'Aloha v2 Signup API call succeeded - Received Error Response', textStatus, responseObject);
549        responseErrorHandler(jqXHR.responseText, $form);
550        if (JWTToken && JWTToken.length) {
551          GA4Utilities.googleSSOEvent('retry', 'Google SSO', 'Unsuccessful form submission');
552        } else if ($('[data-idp="GOOGLE"]').length) {
553          GA4Utilities.googleSSOEvent('retry', 'Normal Signup', 'Unsuccessful form submission');
554        } else {
555          GA4Utilities.signupRetryEvent('retry', 'Form Unsuccessful Submission', 'Retry option shown');
556        }
557        // Log valid error scenarios in db(filter out spam errors from getting logged)
558        const errorValidity = checkErrorValidity(jqXHR.responseText);
559        if (errorValidity) {
560          failureLogData['message'] = `Aloha v2 Signup API call succeeded - Received Error Response. StatusCode: ${status}, Error: ${jqXHR.responseText || 'jqXHR response text is undefined'}`;
561          SiteUtilities.signupErrorLog(failureLogData, form);
562        }
563        SiteUtilities.signupLog(signupLogData, form, status, { message: 'Aloha v2 Signup API call succeeded - Received Error Response', error: `${jqXHR.responseText || 'jqXHR response text is undefined'}`, textStatus: `${textStatus}` });
564        SiteUtilities.signupPerformanceLog(signupLogData, product, textStatus, status, 'Aloha v2 Signup API call succeeded - Received Error Response');
565      }
566    },
567    error: (jqXHR, textStatus, err) => {
568      localStorage.setItem('alohaEndTime', new Date().toISOString());
569      const responseObject = {
570        jqXHR,
571        textStatus,
572        err
573      };
574      $form.find('.button').removeClass('button--loading').attr('disabled', null);
575
576      // sso signup section
577      if ($formSection.length) {
578        $formSection.css('pointer-events', 'auto');
579        handleGoogleSingupErrHandler($formSection);
580      }
581
582      if (product === 'fworks') {
583        fworksCrmSignupFormSuccess($form, false);
584      }
585      if (product === 'fwcrm' || product === 'fmarketer' || product === 'fsales') {
586        fwCrmSignupFormSuccess($form, false);
587      }
588      SiteUtilities.classifyStatusCode(jqXHR.status, 'Aloha v2 Signup API call Failed', textStatus, responseObject);
589      responseErrorHandler(jqXHR.responseText, $form);
590      if (JWTToken && JWTToken.length) {
591        GA4Utilities.googleSSOEvent('retry', 'Google SSO', 'Form Unsuccessful Submission');
592      } else if ($('[data-idp="GOOGLE"]').length) {
593        GA4Utilities.googleSSOEvent('retry', 'Normal Signup', 'Unsuccessful form submission');
594      } else {
595        GA4Utilities.signupRetryEvent('retry', 'Form Unsuccessful Submission', 'Retry option shown');
596      }
597      // Log valid error scenarios in db(filter out spam errors from getting logged)
598      const errorValidity = checkErrorValidity(jqXHR.responseText);
599      if (errorValidity) {
600        failureLogData['message'] = `Aloha v2 Signup API call Failed. StatusCode: ${jqXHR.status}, Error: ${jqXHR.responseText || 'jqXHR response text is undefined'}`;
601        SiteUtilities.signupErrorLog(failureLogData, form);
602      }
603      SiteUtilities.signupLog(signupLogData, form, jqXHR.status, { message: 'Aloha v2 Signup API call Failed', error: `${jqXHR.responseText || 'jqXHR response text is undefined'}`, textStatus: `${textStatus}` });
604      SiteUtilities.signupPerformanceLog(signupLogData, product, textStatus, jqXHR.status, 'Aloha v2 Signup API call Failed');
605    }
606  };
607
608  if (ipAddress) {
609    params['headers'] = {
610      'X-Forwarded-For': ipAddress
611    };
612  }
613
614  localStorage.setItem('alohaStartTime', new Date().toISOString());
615
616  // Add additional headers to signup int environment
617  if (product === 'fdesk' && window.location.href.includes('/signup-int')) {
618    const alohaBundle = 'freshdesk:omniint|freshchat:bots1|freshcaller:branch2';
619    if (params['headers']) {
620      params['headers']['x-fw-aloha-bundle'] = alohaBundle;
621    } else {
622      params['headers'] = { 'x-fw-aloha-bundle': alohaBundle };
623    }
624  }
625
626  $.ajax(params);
627};
628
629export default {
630  alohaForm
631};