Trial->Paid conversion tracking with GTM
* Added trial to paid conversion tracking with GTM * Removed WHMCS roundtrip from Showbuilder * Moved all Analytics code into common/GoogleAnalytics.php * Added a new Thank You page after plan changes to capture conversions * Added a ConversionTracking plugin to facilitate that * Also backported some minor staticBaseDir compatibility changes * Fixed a logic error in creating the baseDir
This commit is contained in:
parent
7b9efb988f
commit
3d03f837d2
11 changed files with 222 additions and 102 deletions
|
@ -24,12 +24,14 @@ require_once "FileIO.php";
|
||||||
require_once "OsPath.php";
|
require_once "OsPath.php";
|
||||||
require_once "Database.php";
|
require_once "Database.php";
|
||||||
require_once "ProvisioningHelper.php";
|
require_once "ProvisioningHelper.php";
|
||||||
|
require_once "GoogleAnalytics.php";
|
||||||
require_once "Timezone.php";
|
require_once "Timezone.php";
|
||||||
require_once "Auth.php";
|
require_once "Auth.php";
|
||||||
require_once __DIR__.'/forms/helpers/ValidationTypes.php';
|
require_once __DIR__.'/forms/helpers/ValidationTypes.php';
|
||||||
require_once __DIR__.'/forms/helpers/CustomDecorators.php';
|
require_once __DIR__.'/forms/helpers/CustomDecorators.php';
|
||||||
require_once __DIR__.'/controllers/plugins/RabbitMqPlugin.php';
|
require_once __DIR__.'/controllers/plugins/RabbitMqPlugin.php';
|
||||||
require_once __DIR__.'/controllers/plugins/Maintenance.php';
|
require_once __DIR__.'/controllers/plugins/Maintenance.php';
|
||||||
|
require_once __DIR__.'/controllers/plugins/ConversionTracking.php';
|
||||||
require_once __DIR__.'/modules/rest/controllers/ShowImageController.php';
|
require_once __DIR__.'/modules/rest/controllers/ShowImageController.php';
|
||||||
require_once __DIR__.'/modules/rest/controllers/MediaController.php';
|
require_once __DIR__.'/modules/rest/controllers/MediaController.php';
|
||||||
|
|
||||||
|
@ -52,6 +54,7 @@ Application_Model_Auth::pinSessionToClient(Zend_Auth::getInstance());
|
||||||
|
|
||||||
$front = Zend_Controller_Front::getInstance();
|
$front = Zend_Controller_Front::getInstance();
|
||||||
$front->registerPlugin(new RabbitMqPlugin());
|
$front->registerPlugin(new RabbitMqPlugin());
|
||||||
|
$front->registerPlugin(new Zend_Controller_Plugin_ConversionTracking());
|
||||||
$front->throwExceptions(false);
|
$front->throwExceptions(false);
|
||||||
|
|
||||||
//localization configuration
|
//localization configuration
|
||||||
|
|
90
airtime_mvc/application/common/GoogleAnalytics.php
Normal file
90
airtime_mvc/application/common/GoogleAnalytics.php
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Application_Common_GoogleAnalytics
|
||||||
|
{
|
||||||
|
|
||||||
|
/** Returns a string containing the JavaScript code to pass some billing account info
|
||||||
|
* into Google Tag Manager / Google Analytics, so we can track things like the plan type.
|
||||||
|
*/
|
||||||
|
public static function generateGoogleTagManagerDataLayerJavaScript()
|
||||||
|
{
|
||||||
|
$code = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$clientId = Application_Model_Preference::GetClientId();
|
||||||
|
|
||||||
|
$plan = Application_Model_Preference::GetPlanLevel();
|
||||||
|
$isTrial = ($plan == "trial");
|
||||||
|
|
||||||
|
//Figure out how long the customer has been around using a mega hack.
|
||||||
|
//(I'm avoiding another round trip to WHMCS for now...)
|
||||||
|
//We calculate it based on the trial end date...
|
||||||
|
$trialEndDateStr = Application_Model_Preference::GetTrialEndingDate();
|
||||||
|
if ($trialEndDateStr == '') {
|
||||||
|
$accountDuration = 0;
|
||||||
|
} else {
|
||||||
|
$today = new DateTime();
|
||||||
|
$trialEndDate = new DateTime($trialEndDateStr);
|
||||||
|
$trialDuration = new DateInterval("P30D"); //30 day trial duration
|
||||||
|
$accountCreationDate = $trialEndDate->sub($trialDuration);
|
||||||
|
$interval = $today->diff($accountCreationDate);
|
||||||
|
$accountDuration = $interval->days;
|
||||||
|
}
|
||||||
|
|
||||||
|
$code = "$( document ).ready(function() {
|
||||||
|
dataLayer.push({
|
||||||
|
'UserID': '" . $clientId . "',
|
||||||
|
'Customer': 'Customer',
|
||||||
|
'PlanType': '" . $plan . "',
|
||||||
|
'Trial': '" . $isTrial . "',
|
||||||
|
'AccountDuration': '" . strval($accountDuration) . "'
|
||||||
|
});
|
||||||
|
});";
|
||||||
|
//No longer sending these variables because we used to make a query to WHMCS
|
||||||
|
//to fetch them, which was slow.
|
||||||
|
// 'ZipCode': '" . $postcode . "',
|
||||||
|
// 'Country': '" . $country . "',
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Logging::error($e);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generate the JavaScript snippet that logs a trial to paid conversion */
|
||||||
|
public static function generateConversionTrackingJavaScript()
|
||||||
|
{
|
||||||
|
$newPlan = Application_Model_Preference::GetPlanLevel();
|
||||||
|
$oldPlan = Application_Model_Preference::GetOldPlanLevel();
|
||||||
|
|
||||||
|
$code = "dataLayer.push({'event': 'Conversion',
|
||||||
|
'Conversion': 'Trial to Paid',
|
||||||
|
'Old Plan' : '$oldPlan',
|
||||||
|
'New Plan' : '$newPlan'});";
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return true if the user used to be on a trial plan and was just converted to a paid plan. */
|
||||||
|
public static function didPaidConversionOccur($request)
|
||||||
|
{
|
||||||
|
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
|
||||||
|
if ($userInfo) {
|
||||||
|
$user = new Application_Model_User($userInfo->id);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$oldPlan = Application_Model_Preference::GetOldPlanLevel();
|
||||||
|
|
||||||
|
if ($user->isSuperAdmin() && $request->getControllerKey() !== "thank-you")
|
||||||
|
{
|
||||||
|
//Only tracking trial->paid conversions for now.
|
||||||
|
if ($oldPlan == "trial")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ class Application_Common_HTTPHelper
|
||||||
if (empty($baseDir)) {
|
if (empty($baseDir)) {
|
||||||
$baseDir = "/";
|
$baseDir = "/";
|
||||||
}
|
}
|
||||||
if ($baseDir[0] != "") {
|
if ($baseDir[0] != "/") {
|
||||||
$baseDir = "/" . $baseDir;
|
$baseDir = "/" . $baseDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ $ccAcl->add(new Zend_Acl_Resource('library'))
|
||||||
->add(new Zend_Acl_Resource('rest:media'))
|
->add(new Zend_Acl_Resource('rest:media'))
|
||||||
->add(new Zend_Acl_Resource('rest:show-image'))
|
->add(new Zend_Acl_Resource('rest:show-image'))
|
||||||
->add(new Zend_Acl_Resource('billing'))
|
->add(new Zend_Acl_Resource('billing'))
|
||||||
|
->add(new Zend_Acl_Resource('thank-you'))
|
||||||
->add(new Zend_Acl_Resource('provisioning'));
|
->add(new Zend_Acl_Resource('provisioning'));
|
||||||
|
|
||||||
/** Creating permissions */
|
/** Creating permissions */
|
||||||
|
@ -69,6 +70,7 @@ $ccAcl->allow('G', 'index')
|
||||||
->allow('A', 'user')
|
->allow('A', 'user')
|
||||||
->allow('A', 'systemstatus')
|
->allow('A', 'systemstatus')
|
||||||
->allow('A', 'preference')
|
->allow('A', 'preference')
|
||||||
|
->allow('S', 'thank-you')
|
||||||
->allow('S', 'billing');
|
->allow('S', 'billing');
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,13 @@ class Config {
|
||||||
$CC_CONFIG['dev_env'] = 'production';
|
$CC_CONFIG['dev_env'] = 'production';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Backported static_base_dir default value into saas for now.
|
||||||
|
if (array_key_exists('static_base_dir', $values['general'])) {
|
||||||
|
$CC_CONFIG['staticBaseDir'] = $values['general']['static_base_dir'];
|
||||||
|
} else {
|
||||||
|
$CC_CONFIG['staticBaseDir'] = '/';
|
||||||
|
}
|
||||||
|
|
||||||
// Parse separate conf file for cloud storage values
|
// Parse separate conf file for cloud storage values
|
||||||
$cloudStorageConfig = "/etc/airtime-saas/".$CC_CONFIG['dev_env']."/cloud_storage.conf";
|
$cloudStorageConfig = "/etc/airtime-saas/".$CC_CONFIG['dev_env']."/cloud_storage.conf";
|
||||||
if (!file_exists($cloudStorageConfig)) {
|
if (!file_exists($cloudStorageConfig)) {
|
||||||
|
|
|
@ -35,7 +35,7 @@ class ShowbuilderController extends Zend_Controller_Action
|
||||||
$user = Application_Model_User::GetCurrentUser();
|
$user = Application_Model_User::GetCurrentUser();
|
||||||
$userType = $user->getType();
|
$userType = $user->getType();
|
||||||
$this->view->headScript()->appendScript("localStorage.setItem( 'user-type', '$userType' );");
|
$this->view->headScript()->appendScript("localStorage.setItem( 'user-type', '$userType' );");
|
||||||
$this->view->headScript()->appendScript($this->generateGoogleTagManagerDataLayerJavaScript());
|
$this->view->headScript()->appendScript(Application_Common_GoogleAnalytics::generateGoogleTagManagerDataLayerJavaScript());
|
||||||
|
|
||||||
$this->view->headScript()->appendFile($baseUrl.'js/contextmenu/jquery.contextMenu.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
|
$this->view->headScript()->appendFile($baseUrl.'js/contextmenu/jquery.contextMenu.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
|
||||||
$this->view->headScript()->appendFile($baseUrl.'js/datatables/js/jquery.dataTables.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
|
$this->view->headScript()->appendFile($baseUrl.'js/datatables/js/jquery.dataTables.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
|
||||||
|
@ -368,103 +368,4 @@ class ShowbuilderController extends Zend_Controller_Action
|
||||||
code");
|
code");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a string containing the JavaScript code to pass some billing account info
|
|
||||||
* into Google Tag Manager / Google Analytics, so we can track things like the plan type.
|
|
||||||
*/
|
|
||||||
private static function generateGoogleTagManagerDataLayerJavaScript()
|
|
||||||
{
|
|
||||||
$code = "";
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
$accessKey = $_SERVER["WHMCS_ACCESS_KEY"];
|
|
||||||
$username = $_SERVER["WHMCS_USERNAME"];
|
|
||||||
$password = $_SERVER["WHMCS_PASSWORD"];
|
|
||||||
$url = "https://account.sourcefabric.com/includes/api.php?accesskey=" . $accessKey; # URL to WHMCS API file goes here
|
|
||||||
|
|
||||||
$postfields = array();
|
|
||||||
$postfields["username"] = $username;
|
|
||||||
$postfields["password"] = md5($password);
|
|
||||||
$postfields["action"] = "getclientsdetails";
|
|
||||||
$postfields["stats"] = true;
|
|
||||||
$postfields["clientid"] = Application_Model_Preference::GetClientId();
|
|
||||||
$postfields["responsetype"] = "json";
|
|
||||||
|
|
||||||
$query_string = "";
|
|
||||||
foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&";
|
|
||||||
|
|
||||||
$ch = curl_init();
|
|
||||||
curl_setopt($ch, CURLOPT_URL, $url);
|
|
||||||
curl_setopt($ch, CURLOPT_POST, 1);
|
|
||||||
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); // WHMCS IP whitelist doesn't support IPv6
|
|
||||||
curl_setopt($ch, CURLOPT_TIMEOUT, 5); //Aggressive 5 second timeout
|
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $query_string);
|
|
||||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
|
||||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
|
||||||
$jsondata = curl_exec($ch);
|
|
||||||
if (curl_error($ch)) {
|
|
||||||
//die("Connection Error: ".curl_errno($ch).' - '.curl_error($ch));
|
|
||||||
throw new Exception("WHMCS server down or invalid request.");
|
|
||||||
}
|
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
$arr = json_decode($jsondata); # Decode JSON String
|
|
||||||
|
|
||||||
if ($arr->result !== "success") {
|
|
||||||
Logging::warn("WHMCS API call failed in " . __FUNCTION__);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$client = $arr->client;
|
|
||||||
$stats = $arr->stats;
|
|
||||||
$currencyCode = $client->currency_code;
|
|
||||||
//$incomeCents = NumberFormatter::parseCurrency($stats->income, $currencyCode);
|
|
||||||
|
|
||||||
$isTrial = true;
|
|
||||||
if (strpos($stats->income, "0.00") === FALSE) {
|
|
||||||
$isTrial = false;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
if ($incomeCents > 0) {
|
|
||||||
$isTrial = false;
|
|
||||||
}*/
|
|
||||||
$plan = Application_Model_Preference::GetPlanLevel();
|
|
||||||
$country = $client->country;
|
|
||||||
$postcode = $client->postcode;
|
|
||||||
|
|
||||||
//Figure out how long the customer has been around using a mega hack.
|
|
||||||
//(I'm avoiding another round trip to WHMCS for now...)
|
|
||||||
//We calculate it based on the trial end date...
|
|
||||||
$trialEndDateStr = Application_Model_Preference::GetTrialEndingDate();
|
|
||||||
if ($trialEndDateStr == '') {
|
|
||||||
$accountDuration = 0;
|
|
||||||
} else {
|
|
||||||
$today = new DateTime();
|
|
||||||
$trialEndDate = new DateTime($trialEndDateStr);
|
|
||||||
$trialDuration = new DateInterval("P30D"); //30 day trial duration
|
|
||||||
$accountCreationDate = $trialEndDate->sub($trialDuration);
|
|
||||||
$interval = $today->diff($accountCreationDate);
|
|
||||||
$accountDuration = $interval->days;
|
|
||||||
}
|
|
||||||
|
|
||||||
$code = "$( document ).ready(function() {
|
|
||||||
dataLayer.push({
|
|
||||||
'ZipCode': '" . $postcode . "',
|
|
||||||
'UserID': '" . $client->id . "',
|
|
||||||
'Customer': 'Customer',
|
|
||||||
'PlanType': '" . $plan . "',
|
|
||||||
'Trial': '" . $isTrial . "',
|
|
||||||
'Country': '" . $country . "',
|
|
||||||
'AccountDuration': '" . strval($accountDuration) . "'
|
|
||||||
});
|
|
||||||
});";
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (Exception $e)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return $code;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
48
airtime_mvc/application/controllers/ThankYouController.php
Normal file
48
airtime_mvc/application/controllers/ThankYouController.php
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class ThankYouController extends Zend_Controller_Action
|
||||||
|
{
|
||||||
|
public function indexAction()
|
||||||
|
{
|
||||||
|
//Variable for the template (thank-you/index.phtml)
|
||||||
|
$this->view->stationUrl = Application_Common_HTTPHelper::getStationUrl();
|
||||||
|
$this->view->conversionUrl = Application_Common_HTTPHelper::getStationUrl() . 'thank-you/confirm-conversion';
|
||||||
|
$this->view->gaEventTrackingJsCode = ""; //Google Analytics event tracking code that logs an event.
|
||||||
|
|
||||||
|
// Embed the Google Analytics conversion tracking code if the
|
||||||
|
// user is a super admin and old plan level is set to trial.
|
||||||
|
if (Application_Common_GoogleAnalytics::didPaidConversionOccur($this->getRequest())) {
|
||||||
|
$this->view->gaEventTrackingJsCode = Application_Common_GoogleAnalytics::generateConversionTrackingJavaScript();
|
||||||
|
}
|
||||||
|
|
||||||
|
$csrf_namespace = new Zend_Session_Namespace('csrf_namespace');
|
||||||
|
$csrf_element = new Zend_Form_Element_Hidden('csrf');
|
||||||
|
$csrf_element->setValue($csrf_namespace->authtoken)->setRequired('true')->removeDecorator('HtmlTag')->removeDecorator('Label');
|
||||||
|
$csrf_form = new Zend_Form();
|
||||||
|
$csrf_form->addElement($csrf_element);
|
||||||
|
$this->view->form = $csrf_form;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Confirm that a conversion was tracked. */
|
||||||
|
public function confirmConversionAction()
|
||||||
|
{
|
||||||
|
$this->view->layout()->disableLayout();
|
||||||
|
$this->_helper->viewRenderer->setNoRender(true);
|
||||||
|
|
||||||
|
$current_namespace = new Zend_Session_Namespace('csrf_namespace');
|
||||||
|
$observed_csrf_token = $this->_getParam('csrf_token');
|
||||||
|
$expected_csrf_token = $current_namespace->authtoken;
|
||||||
|
|
||||||
|
if($observed_csrf_token != $expected_csrf_token) {
|
||||||
|
Logging::info("Invalid CSRF token");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->getRequest()->isPost()) {
|
||||||
|
Logging::info("Goal conversion from trial to paid.");
|
||||||
|
// Clear old plan level so we prevent duplicate events.
|
||||||
|
// This should only be called from AJAX. See thank-you/index.phtml
|
||||||
|
Application_Model_Preference::ClearOldPlanLevel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Zend_Controller_Plugin_ConversionTracking extends Zend_Controller_Plugin_Abstract
|
||||||
|
{
|
||||||
|
public function preDispatch(Zend_Controller_Request_Abstract $request)
|
||||||
|
{
|
||||||
|
//If user is a super admin and old plan level is set to trial....
|
||||||
|
if (Application_Common_GoogleAnalytics::didPaidConversionOccur($request))
|
||||||
|
{
|
||||||
|
//Redirect to Thank you page, unless the request was already going there...
|
||||||
|
if ($request->getControllerName() != 'thank-you')
|
||||||
|
{
|
||||||
|
$request->setModuleName('default')
|
||||||
|
->setControllerName('thank-you')
|
||||||
|
->setActionName('index')
|
||||||
|
->setDispatched(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -824,7 +824,10 @@ class Application_Model_Preference
|
||||||
|
|
||||||
public static function SetPlanLevel($plan)
|
public static function SetPlanLevel($plan)
|
||||||
{
|
{
|
||||||
|
$oldPlanLevel = GetPlanLevel();
|
||||||
self::setValue("plan_level", $plan);
|
self::setValue("plan_level", $plan);
|
||||||
|
//We save the old plan level temporarily to facilitate conversion tracking
|
||||||
|
self::setValue("old_plan_level", $oldPlanLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function GetPlanLevel()
|
public static function GetPlanLevel()
|
||||||
|
@ -837,6 +840,19 @@ class Application_Model_Preference
|
||||||
return $plan;
|
return $plan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function GetOldPlanLevel()
|
||||||
|
{
|
||||||
|
$oldPlan = self::getValue("old_plan_level");
|
||||||
|
return $oldPlan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clearing the old plan level indicates a change in your plan has been tracked (Google Analytics) */
|
||||||
|
public static function ClearOldPlanLevel()
|
||||||
|
{
|
||||||
|
self::setValue("old_plan_level", '');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static function SetTrialEndingDate($date)
|
public static function SetTrialEndingDate($date)
|
||||||
{
|
{
|
||||||
self::setValue("trial_end_date", $date);
|
self::setValue("trial_end_date", $date);
|
||||||
|
|
20
airtime_mvc/application/views/scripts/thank-you/index.phtml
Normal file
20
airtime_mvc/application/views/scripts/thank-you/index.phtml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
<?php if ($this->gaEventTrackingJsCode != "") {
|
||||||
|
echo($this->gaEventTrackingJsCode);
|
||||||
|
?>
|
||||||
|
jQuery.post("<?=$this->conversionUrl?>", { "csrf_token" : $("#csrf").attr('value')},
|
||||||
|
function( data ) {});
|
||||||
|
<?php }; //endif ?>
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<?php echo $this->form->getElement('csrf') ?>
|
||||||
|
|
||||||
|
<div class="ui-widget ui-widget-content block-shadow clearfix padded-strong thankyou-panel">
|
||||||
|
<center>
|
||||||
|
<div class="logobox" style="margin-left: 32px;"></div>
|
||||||
|
<h2><?php echo _pro("Thank you!")?></h2>
|
||||||
|
<h3><?php echo _pro("Your station has been upgraded successfully.")?></h3>
|
||||||
|
<p><a href="<?=$this->stationUrl?>"><?php echo _pro("Return to Airtime")?></a></p>
|
||||||
|
</center>
|
||||||
|
</div>
|
|
@ -3141,3 +3141,15 @@ dd .stream-status {
|
||||||
.quota-reached {
|
.quota-reached {
|
||||||
font-size: 14px !important;
|
font-size: 14px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.thankyou-panel
|
||||||
|
{
|
||||||
|
width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thankyou-panel h3
|
||||||
|
{
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue