From 522f8e8882ce314019895ab73b900df660f92dcf Mon Sep 17 00:00:00 2001 From: ptruessel Date: Thu, 24 Oct 2024 17:41:37 +0200 Subject: [PATCH 1/5] Bugfix HTML in modal --- www/backend/core/com/translate.cfc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/www/backend/core/com/translate.cfc b/www/backend/core/com/translate.cfc index 3b2cf1a2..4463483e 100644 --- a/www/backend/core/com/translate.cfc +++ b/www/backend/core/com/translate.cfc @@ -106,8 +106,7 @@ component displayname="translate" accessors="true" { writeOutput("SELECT #variables.thisField# as myField FROM #variables.thisTable# WHERE #variables.thisPrimKey# = #variables.thisID#"); } - local.replacedCode = replaceList(local.qContent.myField, "<,>", "<,>"); - local.replacedCode = replace(local.replacedCode, "#chr(13)#", "
", "all"); + local.replacedCode = replace(local.qContent.myField, "#chr(13)#", "
", "all"); writeOutput(local.replacedCode); return; From 955878b3c57db292a11fbf99d5989cf986c4fcd9 Mon Sep 17 00:00:00 2001 From: ptruessel Date: Fri, 25 Oct 2024 15:33:44 +0200 Subject: [PATCH 2/5] Setting up pixel counters --- www/backend/dist/js/backend.js | 106 +++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/www/backend/dist/js/backend.js b/www/backend/dist/js/backend.js index 0a2e5e1c..60a90e7f 100644 --- a/www/backend/dist/js/backend.js +++ b/www/backend/dist/js/backend.js @@ -227,6 +227,52 @@ function generateUUID() { } +// Constants for max pixels +const maxPixels = { + metatitle: 580, + metadescription: 1000 +}; + +/** + * Calculates the pixel width of the given text based on the specified font settings. + * @param {string} text - The text to measure. + * @param {string} fontSettings - The font settings to apply (e.g., '400 14px Arial'). + * @returns {number} - The width of the text in pixels. + */ +function getTextWidth(text, fontSettings) { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + context.font = fontSettings; + return Math.round(context.measureText(text).width); +} + +/** + * Updates the pixel count progress for a given input, progress bar, and max pixel threshold. + * @param {string} inputId - The ID of the input field. + * @param {string} progressId - The ID of the progress bar element. + * @param {string} progressBarId - The ID of the progress text element. + * @param {number} maxPixels - The maximum pixel width allowed. + * @param {string} fontSettings - The font settings for calculating pixel width. + */ +function updatePixelCount(inputId, progressId, progressBarId, maxPixels, fontSettings) { + const input = document.getElementById(inputId); + const progress = document.getElementById(progressId); + const progressbar = document.getElementById(progressBarId); + const text = input.value; + const pixels = getTextWidth(text, fontSettings); + const percentage = (pixels / maxPixels) * 100; + + progress.style.width = `${percentage}%`; + progressbar.textContent = `${pixels} px / ${maxPixels} px`; + + if (percentage <= 20) { + progress.style.backgroundColor = 'orange'; + } else if (percentage > 100) { + progress.style.backgroundColor = 'red'; + } else { + progress.style.backgroundColor = 'green'; + } +} @@ -660,6 +706,66 @@ $(document).ready(function() { }); + /** + * Initializes event bindings and DOM interactions for dynamic pixel counting. + * This section depends on the utility functions defined above (`updatePixelCount`, `getTextWidth`), + * which are set outside `$(document).ready()` as they do not require the DOM to be fully loaded. + * + * - Setting up pixel counters for inputs with IDs starting with 'input' + * - Adding event listeners for `input` and `shown.bs.modal` to update pixel counts and progress bars dynamically. + */ + $('[id^=input]').each(function () { + const id = $(this).attr('id').replace('input', ''); + const maxPixelValue = id.includes('Desc') ? maxPixels.metadescription : maxPixels.metatitle; + const fontSettings = id.includes('Desc') ? '400 14px Arial, sans-serif' : '400 20px Roboto, HelveticaNeue, Arial, sans-serif'; + + updatePixelCount(`input${id}`, `progress${id}`, `progressbar${id}`, maxPixelValue, fontSettings); + + $(this).on('input', function () { + updatePixelCount(`input${id}`, `progress${id}`, `progressbar${id}`, maxPixelValue, fontSettings); + }); + }); + + // Event listener for modals being shown + $(document).on('shown.bs.modal', '.modal', function (e) { + const targetId = $(e.relatedTarget).data('bs-target'); + + // Check if modal target involves metatitle or metadescription + if (targetId && (targetId.includes('frontend_metatitle') || targetId.includes('frontend_metadescription'))) { + const modal = $(e.target); + const textareas = modal.find('textarea'); + + textareas.each(function () { + const textarea = $(this); + const textareaId = textarea.attr('id') || `textarea${Date.now() + Math.random().toString(36).substr(2, 9)}`; + textarea.attr('id', textareaId); + const progressId = `progress${textareaId}`; + const progressbarId = `progressbar${textareaId}`; + const maxPixelValue = targetId.includes('metadescription') ? maxPixels.metadescription : maxPixels.metatitle; + const fontSettings = targetId.includes('metadescription') ? '400 14px Arial, sans-serif' : '400 20px Roboto, HelveticaNeue, Arial, sans-serif'; + + // Add progress bar if not already present + if (!document.getElementById(progressId)) { + textarea.after(` +
+
+
+
+
0 px / ${maxPixelValue} px
+
+ `); + } + + updatePixelCount(textareaId, progressId, progressbarId, maxPixelValue, fontSettings); + + textarea.on('input', function () { + updatePixelCount(textareaId, progressId, progressbarId, maxPixelValue, fontSettings); + }); + }); + } + }); + + }); From 3d49558ca2cf3a4bd31576c89c58d450ab6a37a4 Mon Sep 17 00:00:00 2001 From: ptruessel Date: Fri, 15 Nov 2024 11:14:51 +0100 Subject: [PATCH 3/5] Add custom merge rules --- .gitattributes | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..468ab74d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +/frontend/core/* merge=theirs +/frontend/default_theme/* merge=theirs +/frontend/index.cfm merge=ours +/frontend/custom_theme/* merge=ours +/backend/core/* merge=theirs \ No newline at end of file From 53d17f95d2c865515afd4221d705d511a76d86c3 Mon Sep 17 00:00:00 2001 From: ptruessel Date: Fri, 15 Nov 2024 13:02:23 +0100 Subject: [PATCH 4/5] Make frontend more dynamic --- config/db/core/V3__frontend-changes.sql | 11 + www/Application.cfc | 17 +- .../img/payments/card_post-finance-pay.svg | 13 + www/backend/core/com/global.cfc | 14 +- www/frontend/core/com/coreutility.cfc | 253 ++++++++++++++++++ www/frontend/core/handler/register.cfm | 4 +- www/frontend/css-include.cfm | 9 - .../default_theme/css/css-include.cfm | 10 + www/frontend/default_theme/css/styles.css | 18 ++ www/frontend/default_theme/images/favicon.png | Bin 0 -> 21351 bytes www/frontend/default_theme/js/js-include.cfm | 9 + .../js/scripts.js} | 138 ++++++---- .../default_theme/templates/footer.cfm | 4 + .../templates/forms}/login.cfm | 40 +-- .../default_theme/templates/forms/mfa.cfm | 43 +++ .../templates/forms/password_1.cfm | 34 +++ .../templates/forms/password_2.cfm | 35 +++ .../templates/forms/register_1.cfm | 65 +++++ .../templates/forms/register_2.cfm | 35 +++ .../templates/forms/register_3.cfm | 69 +++++ .../default_theme/templates/header.cfm | 20 ++ www/frontend/default_theme/templates/home.cfm | 11 + .../default_theme/templates/login.cfm | 21 ++ www/frontend/default_theme/templates/mfa.cfm | 8 + .../templates/modals/privacy.cfm | 17 ++ .../default_theme/templates/password.cfm | 13 + .../default_theme/templates/plans.cfm | 27 ++ .../default_theme/templates/plans/box.cfm | 81 ++++++ .../templates/plans/plan_boxes.cfm | 14 + .../templates/plans/plan_features.cfm | 102 +++++++ .../default_theme/templates/plans/toggle.cfm | 21 ++ .../default_theme/templates/register.cfm | 39 +++ www/frontend/dist/css/frontend.css | 191 ------------- www/frontend/index.cfm | 36 +-- www/frontend/js-include.cfm | 7 - www/frontend/mfa.cfm | 55 ---- www/frontend/password.cfm | 64 ----- www/frontend/plan_boxes.cfm | 198 -------------- www/frontend/plan_features.cfm | 45 ---- www/frontend/plans.cfm | 64 ----- www/frontend/register.cfm | 188 ------------- 41 files changed, 1101 insertions(+), 942 deletions(-) create mode 100644 config/db/core/V3__frontend-changes.sql create mode 100644 www/assets/img/payments/card_post-finance-pay.svg create mode 100644 www/frontend/core/com/coreutility.cfc delete mode 100644 www/frontend/css-include.cfm create mode 100644 www/frontend/default_theme/css/css-include.cfm create mode 100644 www/frontend/default_theme/css/styles.css create mode 100644 www/frontend/default_theme/images/favicon.png create mode 100644 www/frontend/default_theme/js/js-include.cfm rename www/frontend/{dist/js/frontend.js => default_theme/js/scripts.js} (61%) create mode 100644 www/frontend/default_theme/templates/footer.cfm rename www/frontend/{ => default_theme/templates/forms}/login.cfm (53%) create mode 100644 www/frontend/default_theme/templates/forms/mfa.cfm create mode 100644 www/frontend/default_theme/templates/forms/password_1.cfm create mode 100644 www/frontend/default_theme/templates/forms/password_2.cfm create mode 100644 www/frontend/default_theme/templates/forms/register_1.cfm create mode 100644 www/frontend/default_theme/templates/forms/register_2.cfm create mode 100644 www/frontend/default_theme/templates/forms/register_3.cfm create mode 100644 www/frontend/default_theme/templates/header.cfm create mode 100644 www/frontend/default_theme/templates/home.cfm create mode 100644 www/frontend/default_theme/templates/login.cfm create mode 100644 www/frontend/default_theme/templates/mfa.cfm create mode 100644 www/frontend/default_theme/templates/modals/privacy.cfm create mode 100644 www/frontend/default_theme/templates/password.cfm create mode 100644 www/frontend/default_theme/templates/plans.cfm create mode 100644 www/frontend/default_theme/templates/plans/box.cfm create mode 100644 www/frontend/default_theme/templates/plans/plan_boxes.cfm create mode 100644 www/frontend/default_theme/templates/plans/plan_features.cfm create mode 100644 www/frontend/default_theme/templates/plans/toggle.cfm create mode 100644 www/frontend/default_theme/templates/register.cfm delete mode 100644 www/frontend/dist/css/frontend.css delete mode 100644 www/frontend/js-include.cfm delete mode 100644 www/frontend/mfa.cfm delete mode 100644 www/frontend/password.cfm delete mode 100644 www/frontend/plan_boxes.cfm delete mode 100644 www/frontend/plan_features.cfm delete mode 100644 www/frontend/plans.cfm delete mode 100644 www/frontend/register.cfm diff --git a/config/db/core/V3__frontend-changes.sql b/config/db/core/V3__frontend-changes.sql new file mode 100644 index 00000000..ffcddf34 --- /dev/null +++ b/config/db/core/V3__frontend-changes.sql @@ -0,0 +1,11 @@ +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +UPDATE frontend_mappings SET strPath = 'templates/login.cfm' WHERE strMapping = 'login'; +UPDATE frontend_mappings SET strPath = 'templates/register.cfm' WHERE strMapping = 'register'; +UPDATE frontend_mappings SET strPath = 'templates/password.cfm' WHERE strMapping = 'password'; +UPDATE frontend_mappings SET strPath = 'templates/plans.cfm' WHERE strMapping = 'plans'; +UPDATE frontend_mappings SET strPath = 'templates/mfa.cfm' WHERE strMapping = 'mfa'; + + +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/www/Application.cfc b/www/Application.cfc index 8e5250da..8053e8b6 100644 --- a/www/Application.cfc +++ b/www/Application.cfc @@ -36,6 +36,7 @@ component displayname="Application" output="false" extends="backend.myapp.ownApp application.errorMail = variables.errorEmail; application.mainURL = variables.mainURL; application.environment = variables.environment; + application.activeTheme = variables.activeTheme; // Payrexx initialising local.payrexxStruct = structNew(); @@ -55,6 +56,7 @@ component displayname="Application" output="false" extends="backend.myapp.ownApp application.objNotifications = new backend.core.com.notifications(); application.objSysadmin = new backend.core.com.sysadmin(); application.objMeta = new backend.core.com.meta(); + application.objCoreUtil = new frontend.core.com.coreutility(); // Save all choosable languages into a list local.qLanguages = queryExecute( @@ -244,12 +246,23 @@ component displayname="Application" output="false" extends="backend.myapp.ownApp // Protect the 'backend' folder excluding the pdf print page if (listFirst(thiscontent.thisPath, "/") eq "backend" and !structKeyExists(session, "user_id")) { + if (structKeyExists(url, "pdf")) { + location url="#application.mainURL#/backend/core/views/invoices/print.cfm?pdf=#url.pdf#" addtoken="false"; + } else { - getAlert('alertSessionExpired', 'warning'); - location url="#application.mainURL#/login" addtoken="false"; + + // Redirect to the register form with plan redirect + if (structKeyExists(url, "plan")) { + location url="#application.mainURL#/register?redirect=book?plan=#url.plan#" addtoken="false"; + } else { + getAlert('alertSessionExpired', 'warning'); + location url="#application.mainURL#/login" addtoken="false"; + } + } + } diff --git a/www/assets/img/payments/card_post-finance-pay.svg b/www/assets/img/payments/card_post-finance-pay.svg new file mode 100644 index 00000000..aad4bfcf --- /dev/null +++ b/www/assets/img/payments/card_post-finance-pay.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/backend/core/com/global.cfc b/www/backend/core/com/global.cfc index 2f0aa77d..18ff8e05 100644 --- a/www/backend/core/com/global.cfc +++ b/www/backend/core/com/global.cfc @@ -30,15 +30,15 @@ component displayname="globalFunctions" output="false" { strMapping: {type: "nvarchar", value: local.sefString} }, sql = " - SELECT strPath, blnOnlyAdmin, blnOnlySuperAdmin, blnOnlySysAdmin + SELECT strPath, blnOnlyAdmin, blnOnlySuperAdmin, blnOnlySysAdmin, 0 as itsFrontend FROM system_mappings WHERE strMapping = :strMapping UNION - SELECT strPath, blnOnlyAdmin, blnOnlySuperAdmin, blnOnlySysAdmin + SELECT strPath, blnOnlyAdmin, blnOnlySuperAdmin, blnOnlySysAdmin, 0 as itsFrontend FROM custom_mappings WHERE strMapping = :strMapping UNION - SELECT strPath, 0, 0, 0 + SELECT strPath, 0, 0, 0, 1 as itsFrontend FROM frontend_mappings WHERE strMapping = :strMapping UNION @@ -47,7 +47,7 @@ component displayname="globalFunctions" output="false" { SELECT strPath FROM frontend_mappings WHERE intFrontendMappingsID = frontend_mappings_trans.intFrontendMappingsID - ) as strPath, 0, 0, 0 + ) as strPath, 0, 0, 0, 1 as itsFrontend FROM frontend_mappings_trans WHERE strMapping = :strMapping LIMIT 1 @@ -55,7 +55,11 @@ component displayname="globalFunctions" output="false" { ) if (local.qCheckSEF.recordCount) { - local.returnStruct['thisPath'] = local.qCheckSEF.strPath; + if (local.qCheckSEF.itsFrontend) { + local.returnStruct['thisPath'] = "frontend/" & application.activeTheme & "/" & local.qCheckSEF.strPath; + } else { + local.returnStruct['thisPath'] = local.qCheckSEF.strPath; + } local.returnStruct['onlyAdmin'] = trueFalseFormat(local.qCheckSEF.blnOnlyAdmin); local.returnStruct['onlySuperAdmin'] = trueFalseFormat(local.qCheckSEF.blnOnlySuperAdmin); local.returnStruct['onlySysAdmin'] = trueFalseFormat(local.qCheckSEF.blnOnlySysAdmin); diff --git a/www/frontend/core/com/coreutility.cfc b/www/frontend/core/com/coreutility.cfc new file mode 100644 index 00000000..43549ae3 --- /dev/null +++ b/www/frontend/core/com/coreutility.cfc @@ -0,0 +1,253 @@ +component displayname="coreutility" output="false" { + + /** + * Prepares session data for the registration form and initializes default values if needed. + */ + public struct function getRegisterSessionData() { + + sessionData = structNew(); + + // Check if the user is logged in and set step defaults + sessionData.userLoggedIn = structKeyExists(session, "user_id") and session.user_id gt 0; + sessionData.step = session.step ?: 1; + sessionData.first_name = session.first_name ?: ""; + sessionData.name = session.name ?: ""; + sessionData.company = session.company ?: ""; + sessionData.email = session.email ?: ""; + + // Redirect logged-in users without a defined step to the dashboard + if (sessionData.userLoggedIn and !structKeyExists(sessionData, "step")) { + location url="#application.mainURL#/dashboard" addtoken="false"; + } + + return sessionData; + } + + + /** + * Retrieves additional registration data (countries and time zones) for step 3. + */ + public struct function getAdditionalRegisterData() { + + local.additionalData = structNew(); + + // Fetch data only if step 3 is active + if (sessionData.step eq 3) { + local.additionalData.qCountries = application.objGlobal.getCountry(language=session.lng); + local.additionalData.timeZones = new backend.core.com.time().getTimezones(); + } + + return local.additionalData; + } + + + /** + * Returns the reCAPTCHA script if the Site Key is set. + */ + public string function getRecaptchaScript(reCAPTCHA_site_key) { + if (len(trim(arguments.reCAPTCHA_site_key))) { + return '' & + ''; + } + return ""; + } + + + /** + * Handles logout action and returns an alert message if user is logging out + */ + public void function handleLogout() { + if (structKeyExists(url, "logout")) { + session.alert = application.objGlobal.getAlert('alertLoggedOut', 'info'); + } + return; + } + + /** + * Checks if a user is logged in and redirects them to the dashboard if so. + */ + public void function redirectIfLoggedIn() { + if (structKeyExists(session, "user_id") and session.user_id gt 0) { + structDelete(session, "alert"); + location url="#application.mainURL#/dashboard" addtoken="false"; + } + } + + /** + * Returns sysadmin data for branding purposes + */ + public struct function getSysadminData() { + return new backend.core.com.sysadmin().getSysAdminData(); + } + + /** + * Handles UUID and MFA checks, returning an alert message if applicable + */ + public struct function handleUUIDandMFA() { + + local.result = { uuid: "", alertMessage: "" }; + + // Check if the URL contains a UUID + if (structKeyExists(url, 'uuid')) { + local.result.uuid = url.uuid; + + // Check if the MFA check count is 3 or more, and set an alert if so + if (structKeyExists(session, "mfaCheckCount") and session.mfaCheckCount gte 3) { + local.result.alertMessage = application.objGlobal.getAlert(application.objLanguage.getTrans('txtThreeTimeTry'), 'warning'); + } + } + + return local.result; + } + + /** + * Returns an array with plan details + */ + public array function retrievePlans() { + + // Initialize variables + local.fixedGroupID = (structKeyExists(url, "g") and isNumeric(url.g) and url.g gt 0) ? url.g : 0; + local.ipAddress = structKeyExists(session, "usersIP") ? session.usersIP : variables.usersIP; + local.customerID = (structKeyExists(session, "customer_id") and isNumeric(session.customer_id) and session.customer_id gt 0) ? session.customer_id : 0; + local.objPlans = new backend.core.com.plans(language=session.lng); + + // Retrieve the groupID + local.groupID = local.objPlans.prepareForGroupID(local.customerID, local.ipAddress, local.fixedGroupID).groupID; + + // Get the plans array using the groupID + local.arrayPlans = local.objPlans.getPlans(local.groupID); + + // Append dynamic values with elements for monthly/yearly toggle functionality + if (arrayLen(local.arrayPlans)) { + + for (var i = 1; i <= arrayLen(local.arrayPlans); i++) { + + // Create the dynamic price HTML string with monthly and yearly spans + local.dynPrice = + '' & local.arrayPlans[i].priceMonthly & '' & + ''; + local.arrayPlans[i]['dynPrice'] = local.dynPrice; + + // Create the dynamic VAT HTML string with monthly and yearly spans + local.dynVAT = + '' & local.arrayPlans[i].vat_text_monthly & '' & + ''; + local.arrayPlans[i]['dynVAT'] = local.dynVAT; + + // Create the dynamic cycling text A + local.dynBillingCycleTextA = + '' & lCase(application.objLanguage.getTrans('txtMonthly')) & '' & + ''; + local.arrayPlans[i]['dynBillingCycleTextA'] = local.dynBillingCycleTextA; + + // Create the dynamic cycling text B + local.dynBillingCycleTextB = + '' & lCase(application.objLanguage.getTrans('TitMonth')) & '' & + ''; + local.arrayPlans[i]['dynBillingCycleTextB'] = local.dynBillingCycleTextB; + + + // Create the dynamic booking link and button text + local.dynBookingLinkM; + local.dynBookingLinkY; + local.dynBookingButtonText = application.objLanguage.getTrans('btnActivate'); + + // If there is a user session + if (structKeyExists(session, "customer_id") and session.customer_id gt 0) { + + // Do not create a booking link for sysadmins + if (session.sysAdmin) { + + local.dynBookingLink; + local.dynBookingButtonText = "{SYSADMIN}"; + + } else { + + // Only super admins can book plans + if (session.superAdmin) { + + // If there is already a plan, send the user to the plan edit page + if (structKeyExists(session.currentPlan, "planID") and session.currentPlan.planID gt 0) { + + local.dynBookingLinkM = application.mainURL & "/account-settings/plans"; + local.dynBookingLinkY = local.dynBookingLinkM; + + // There is no booked plan yet + } else { + + // If its a free plan + if (local.arrayPlans[i].itsFree eq 1) { + + local.dynBookingLinkM = local.arrayPlans[i].bookingLinkO; + local.dynBookingLinkY = local.dynBookingLinkM; + + // Its a paid plan + } else { + + // Check if the user has already added a payment method or the plan has any test days + local.getWebhook = new backend.core.com.payrexx().getWebhook(session.customer_id, 'authorized'); + if (local.getWebhook.recordCount or local.arrayPlans[i].testDays gt 0) { + + local.dynBookingLinkM = local.arrayPlans[i].bookingLinkM; + local.dynBookingLinkY = local.arrayPlans[i].bookingLinkY; + + // Send the user to the payment page in order to add a payment method + } else { + + local.dynBookingLinkM = application.mainURL & "/account-settings/payment"; + local.dynBookingLinkY = local.dynBookingLinkM; + + } + + } + + } + + + // If its not a super admin, send the user to the dashboard + } else { + + local.dynBookingLinkM = application.mainURL & "/dashboard"; + local.dynBookingLinkY = local.dynBookingLinkM; + + } + + } + + + // There is no user session, so send to the registration form + } else { + + // Build the booking link for free plans + if (local.arrayPlans[i].itsFree eq 1) { + + local.dynBookingLinkM = urlEncodedFormat(replace(replace(local.arrayPlans[i].bookingLinkO, application.mainURL, "", "one"), "/", "", "one")); + local.dynBookingLinkY = dynBookingLinkM; + + // Build the booking link for paid plans + } else { + + local.dynBookingLinkM = urlEncodedFormat(replace(replace(local.arrayPlans[i].bookingLinkM, application.mainURL, "", "one"), "/", "", "one")); + local.dynBookingLinkY = urlEncodedFormat(replace(replace(local.arrayPlans[i].bookingLinkY, application.mainURL, "", "one"), "/", "", "one")); + + } + + if (len(trim(local.arrayPlans[i].buttonName))) { + local.dynBookingButtonText = local.arrayPlans[i].buttonName; + } + + } + + local.arrayPlans[i]['dynBookingLinkM'] = local.dynBookingLinkM; + local.arrayPlans[i]['dynBookingLinkY'] = local.dynBookingLinkY; + local.arrayPlans[i]['dynBookingButtonText'] = local.dynBookingButtonText; + + } + } + + return local.arrayPlans; + + } + + +} \ No newline at end of file diff --git a/www/frontend/core/handler/register.cfm b/www/frontend/core/handler/register.cfm index 431b3b76..fb70a1b8 100644 --- a/www/frontend/core/handler/register.cfm +++ b/www/frontend/core/handler/register.cfm @@ -768,7 +768,9 @@ if (structKeyExists(url, 'resend')) { // Logout and delete all sessions if (structKeyExists(url, "logout")) { - logWrite("user", "info", "User has logged out [CustomerID: #session.customer_id#, UserID: #session.user_id#]"); + if (structKeyExists(session, "customer_id") and structKeyExists(session, "user_id")) { + logWrite("user", "info", "User has logged out [CustomerID: #session.customer_id#, UserID: #session.user_id#]"); + } structClear(SESSION); onSessionStart(); diff --git a/www/frontend/css-include.cfm b/www/frontend/css-include.cfm deleted file mode 100644 index af8bdd2f..00000000 --- a/www/frontend/css-include.cfm +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/www/frontend/default_theme/css/css-include.cfm b/www/frontend/default_theme/css/css-include.cfm new file mode 100644 index 00000000..c45a9fec --- /dev/null +++ b/www/frontend/default_theme/css/css-include.cfm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/www/frontend/default_theme/css/styles.css b/www/frontend/default_theme/css/styles.css new file mode 100644 index 00000000..9cf0d887 --- /dev/null +++ b/www/frontend/default_theme/css/styles.css @@ -0,0 +1,18 @@ +.planDescription ul { + list-style: none; + padding: 0; + margin: 0; +} + +.planDescription ul li { + position: relative; + padding-left: 25px; +} + +.planDescription ul li::before { + content: "\2713"; + font-family: "Bootstrap Icons"; + position: absolute; + left: 0; + color: green; +} diff --git a/www/frontend/default_theme/images/favicon.png b/www/frontend/default_theme/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..7c6950eaa4ceb81a43365280b5b5be3aaaa70b45 GIT binary patch literal 21351 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 + + + + + + + \ No newline at end of file diff --git a/www/frontend/dist/js/frontend.js b/www/frontend/default_theme/js/scripts.js similarity index 61% rename from www/frontend/dist/js/frontend.js rename to www/frontend/default_theme/js/scripts.js index a3a95430..1651f641 100644 --- a/www/frontend/dist/js/frontend.js +++ b/www/frontend/default_theme/js/scripts.js @@ -5,23 +5,33 @@ * This function listens to changes in the "monthly" and "yearly" radio buttons and updates * both the displayed pricing boxes and the focus (visual highlight) of the active label. */ - document.addEventListener('DOMContentLoaded', function() { + // Radio buttons const monthlyRadio = document.getElementById('monthly'); const yearlyRadio = document.getElementById('yearly'); - // Labels - const monthlyLabel = document.querySelector('label.monthly'); - const yearlyLabel = document.querySelector('label.yearly'); + // Check if the elements are present on the page + if (!monthlyRadio || !yearlyRadio) { + return; // Exit if either element is missing + } + + // Labels for styling + const monthlyLabel = document.querySelector('label[for="monthly"]'); + const yearlyLabel = document.querySelector('label[for="yearly"]'); // Price box elements const monthlyBoxes = document.querySelectorAll('.price_box.monthly'); const yearlyBoxes = document.querySelectorAll('.price_box.yearly'); - // Function to toggle visibility and button focus + // Select all booking buttons + const bookingButtons = document.querySelectorAll('.bookingButton'); + + // Function to toggle visibility and button focus, and update each booking button's link function togglePriceBoxes() { + if (monthlyRadio.checked) { + // Show monthly boxes, hide yearly boxes monthlyBoxes.forEach(box => box.style.display = 'block'); yearlyBoxes.forEach(box => box.style.display = 'none'); @@ -29,7 +39,15 @@ document.addEventListener('DOMContentLoaded', function() { // Add active class to monthly, remove from yearly monthlyLabel.classList.add('active'); yearlyLabel.classList.remove('active'); + + // Update href for all booking buttons to the monthly link + bookingButtons.forEach(button => { + const monthlyLink = button.getAttribute('data-monthly'); + button.setAttribute('href', monthlyLink); + }); + } else if (yearlyRadio.checked) { + // Show yearly boxes, hide monthly boxes monthlyBoxes.forEach(box => box.style.display = 'none'); yearlyBoxes.forEach(box => box.style.display = 'block'); @@ -37,7 +55,15 @@ document.addEventListener('DOMContentLoaded', function() { // Add active class to yearly, remove from monthly yearlyLabel.classList.add('active'); monthlyLabel.classList.remove('active'); + + // Update href for all booking buttons to the yearly link + bookingButtons.forEach(button => { + const yearlyLink = button.getAttribute('data-yearly'); + button.setAttribute('href', yearlyLink); + }); + } + } // Add event listeners to both radio buttons @@ -46,89 +72,81 @@ document.addEventListener('DOMContentLoaded', function() { // Initial toggle based on the default selection togglePriceBoxes(); -}); - +}); /** - * This script handles the 6-digit Multi-Factor Authentication (MFA) code input process. - * It ensures that the user can only enter numeric values, automatically moves the focus - * to the next field upon entry, supports pasting the full code, and submits the form when - * all fields are filled. + * This script manages the 6-digit MFA code input, handling numeric validation, + * focus shifting, and form submission upon completion. */ - document.addEventListener('DOMContentLoaded', function () { - const inputs = document.querySelectorAll('#mfa_form input[type="number"]'); const form = document.getElementById('mfa_form'); + // Check if the form exists on the page + if (!form) { + return; // Exit if the form is not present + } + + const inputs = form.querySelectorAll('.code-input'); + + // Set focus on the first input with the class `.code-input` + const firstInput = document.querySelector('.code-input'); + if (firstInput) { + firstInput.focus(); + } + /** - * Prevents non-digit characters from being entered in input fields. + * Restricts input to numeric characters only. * * @param {Event} event - The keypress event triggered when the user types a character. */ function onlyDigits(event) { - const charCode = event.which ? event.which : event.keyCode; + const charCode = event.which || event.keyCode; if (charCode < 48 || charCode > 57) { event.preventDefault(); } } /** - * Checks if all input fields are filled with exactly one digit each. - * If all fields are filled, the form is submitted automatically. + * Checks if all input fields are filled and submits the form if they are. */ function checkAndSubmit() { - let code = ''; - inputs.forEach(input => { - code += input.value; - }); + const code = Array.from(inputs).map(input => input.value).join(''); if (code.length === 6) { form.submit(); } } /** - * Adds event listeners to each input field for handling input events, digit validation, - * focus shift, and paste functionality. + * Event handling for each input field: numeric-only restriction, focus shifts, + * and backspace handling. */ inputs.forEach((input, index) => { - /** - * Handles input events on each field. - * Moves focus to the next field after a digit is entered, and checks for paste cases. - * - * @param {Event} event - The input event triggered when the user types in the input. - */ + input.addEventListener('keypress', onlyDigits); + input.addEventListener('input', function (event) { const value = event.target.value; - if (value.length === 1) { - if (index < inputs.length - 1) { - inputs[index + 1].focus(); // Move to the next field - } + + if (value.length === 1 && index < inputs.length - 1) { + inputs[index + 1].focus(); // Move focus to the next field } else if (value.length > 1) { - // If multiple characters are pasted + // Handle paste or multiple characters const values = value.split(''); - for (let i = 0; i < values.length && index + i < inputs.length; i++) { - inputs[index + i].value = values[i]; - if (index + i < inputs.length - 1) { - inputs[index + i + 1].focus(); + values.forEach((val, i) => { + if (index + i < inputs.length) { + inputs[index + i].value = val; } + }); + if (index + values.length < inputs.length) { + inputs[index + values.length].focus(); } } - checkAndSubmit(); // Check if all fields are filled - }); - /** - * Ensures that only numeric characters are allowed in the input fields. - */ - input.addEventListener('keypress', onlyDigits); + checkAndSubmit(); // Submit if all fields are filled + }); - /** - * Handles backspace behavior. Moves focus to the previous field if backspace is pressed and the field is empty. - * - * @param {Event} event - The keydown event triggered when the user presses a key. - */ input.addEventListener('keydown', function (event) { if (event.key === 'Backspace' && input.value === '' && index > 0) { inputs[index - 1].focus(); @@ -137,25 +155,27 @@ document.addEventListener('DOMContentLoaded', function () { }); /** - * Adds support for pasting a 6-digit code directly into the first input field. - * The pasted code is split into the individual fields and the form is checked for completion. - * - * @param {Event} event - The paste event triggered when the user pastes content. + * Handles paste of a full 6-digit code into the first field. */ inputs[0].addEventListener('paste', function (event) { const paste = event.clipboardData.getData('text'); if (/^\d{6}$/.test(paste)) { - const values = paste.split(''); - inputs.forEach((input, i) => { - input.value = values[i]; + paste.split('').forEach((char, i) => { + if (i < inputs.length) { + inputs[i].value = char; + } }); - checkAndSubmit(); // Check if all fields are filled + checkAndSubmit(); } - event.preventDefault(); // Prevent the default paste behavior + event.preventDefault(); // Prevent default paste action }); + + + }); + /** * Prevents the user from navigating back using the browser's back button * after logging out. This script runs only when `isLogout` is true. @@ -197,4 +217,4 @@ document.addEventListener('DOMContentLoaded', function () { }; }; } -})(window); \ No newline at end of file +})(window); diff --git a/www/frontend/default_theme/templates/footer.cfm b/www/frontend/default_theme/templates/footer.cfm new file mode 100644 index 00000000..523e980f --- /dev/null +++ b/www/frontend/default_theme/templates/footer.cfm @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/www/frontend/login.cfm b/www/frontend/default_theme/templates/forms/login.cfm similarity index 53% rename from www/frontend/login.cfm rename to www/frontend/default_theme/templates/forms/login.cfm index 50cd3a7f..f99ba54d 100644 --- a/www/frontend/login.cfm +++ b/www/frontend/default_theme/templates/forms/login.cfm @@ -1,35 +1,16 @@ - - - if (structKeyExists(url, "logout")) { - getAlert('alertLoggedOut', 'info'); - } else if (structKeyExists(session, "user_id") and session.user_id gt 0) { - structDelete(session, "alert"); - location url="#application.mainURL#/dashboard" addtoken="false"; - } - param name="session.email" default=""; - - getSysadminData = new backend.core.com.sysadmin().getSysAdminData(); - - +
-
- -
+
-
- - Logo - - Logo - +
+ #getTrans('formSignIn')#
-
+
-

#getTrans('formSignIn')#

#session.alert# @@ -37,7 +18,7 @@ autofocus>
-
+
autofocus> @@ -53,15 +34,10 @@
#getTrans('formForgotPassword')# #getTrans('formReset')#
-
-
- - +
- \ No newline at end of file + \ No newline at end of file diff --git a/www/frontend/default_theme/templates/forms/mfa.cfm b/www/frontend/default_theme/templates/forms/mfa.cfm new file mode 100644 index 00000000..142096c3 --- /dev/null +++ b/www/frontend/default_theme/templates/forms/mfa.cfm @@ -0,0 +1,43 @@ + + +
+ +
+ +
+ #getTrans('titMfa')# +
+ +
+ +
+ + #session.alert# + + +
+
+

#getTrans('txtmfaLead')#

+
+ + + + + + +
+
+
+
+
+
+ + + +
+ +
+ +
\ No newline at end of file diff --git a/www/frontend/default_theme/templates/forms/password_1.cfm b/www/frontend/default_theme/templates/forms/password_1.cfm new file mode 100644 index 00000000..1aa20935 --- /dev/null +++ b/www/frontend/default_theme/templates/forms/password_1.cfm @@ -0,0 +1,34 @@ + + +
+ +
+ +
+ #getTrans('titResetPassword')# +
+ +
+ +
+ + #session.alert# + +
+ + +
+ +
+ #getTrans('formAlreadyHaveAccount')# #getTrans('formSignIn')# +
+
+
+ +
+ +
+ +
\ No newline at end of file diff --git a/www/frontend/default_theme/templates/forms/password_2.cfm b/www/frontend/default_theme/templates/forms/password_2.cfm new file mode 100644 index 00000000..e5f58125 --- /dev/null +++ b/www/frontend/default_theme/templates/forms/password_2.cfm @@ -0,0 +1,35 @@ + + +
+ +
+ +
+ #getTrans('titChoosePassword')# +
+ +
+ +
+ + #session.alert# + +
+ + +
+
+ + +
+ +
+
+ +
+ +
+ +
\ No newline at end of file diff --git a/www/frontend/default_theme/templates/forms/register_1.cfm b/www/frontend/default_theme/templates/forms/register_1.cfm new file mode 100644 index 00000000..0e009575 --- /dev/null +++ b/www/frontend/default_theme/templates/forms/register_1.cfm @@ -0,0 +1,65 @@ + + +
+ +
+ +
+ #getTrans('formSignUp')# +
+ +
+ +
+ + #session.alert# + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ +
+ #getTrans('formAlreadyHaveAccount')# #getTrans('formSignIn')# +
+
+
+ +
+ +
+ +
\ No newline at end of file diff --git a/www/frontend/default_theme/templates/forms/register_2.cfm b/www/frontend/default_theme/templates/forms/register_2.cfm new file mode 100644 index 00000000..4c630492 --- /dev/null +++ b/www/frontend/default_theme/templates/forms/register_2.cfm @@ -0,0 +1,35 @@ + + +
+ +
+ +
+ #getTrans('titChoosePassword')# +
+ +
+ +
+ + #session.alert# + +
+ + +
+
+ + +
+ +
+
+ +
+ +
+ +
\ No newline at end of file diff --git a/www/frontend/default_theme/templates/forms/register_3.cfm b/www/frontend/default_theme/templates/forms/register_3.cfm new file mode 100644 index 00000000..ad2a31dc --- /dev/null +++ b/www/frontend/default_theme/templates/forms/register_3.cfm @@ -0,0 +1,69 @@ + + +
+ +
+ +
+ #getTrans('txtUpdateInformation')# +
+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ +
+
+
+ +
+ +
+ +
\ No newline at end of file diff --git a/www/frontend/default_theme/templates/header.cfm b/www/frontend/default_theme/templates/header.cfm new file mode 100644 index 00000000..5e3ca37a --- /dev/null +++ b/www/frontend/default_theme/templates/header.cfm @@ -0,0 +1,20 @@ + + + + + + + #getMeta(cgi.path_info, session.lng).metaTtile# + + #getMeta(cgi.path_info, session.lng).metaHTML# + + + + + + + + +
+ Saaster +
\ No newline at end of file diff --git a/www/frontend/default_theme/templates/home.cfm b/www/frontend/default_theme/templates/home.cfm new file mode 100644 index 00000000..1fad82ea --- /dev/null +++ b/www/frontend/default_theme/templates/home.cfm @@ -0,0 +1,11 @@ +
+

With Saaster, you make it faster!

+
+

Saaster is more than a SaaS framework — it's your all-in-one platform for building, managing, and scaling applications with ease. With powerful tools, seamless integrations, and a focus on speed, Saaster lets you turn ideas into products faster than ever.

+ +
+
\ No newline at end of file diff --git a/www/frontend/default_theme/templates/login.cfm b/www/frontend/default_theme/templates/login.cfm new file mode 100644 index 00000000..3c8be5cf --- /dev/null +++ b/www/frontend/default_theme/templates/login.cfm @@ -0,0 +1,21 @@ + + + +// Handle logout logic and display alert if applicable +application.objCoreUtil.handleLogout(); + +// Redirect user if already logged in +application.objCoreUtil.redirectIfLoggedIn(); + +// Set default session email if not set +param name="session.email" default=""; + + + + + + + + \ No newline at end of file diff --git a/www/frontend/default_theme/templates/mfa.cfm b/www/frontend/default_theme/templates/mfa.cfm new file mode 100644 index 00000000..ad8f0049 --- /dev/null +++ b/www/frontend/default_theme/templates/mfa.cfm @@ -0,0 +1,8 @@ + + + // Call the utility function to handle UUID and MFA checks + application.objCoreUtil.handleUUIDandMFA(); + + + + \ No newline at end of file diff --git a/www/frontend/default_theme/templates/modals/privacy.cfm b/www/frontend/default_theme/templates/modals/privacy.cfm new file mode 100644 index 00000000..8c57fd19 --- /dev/null +++ b/www/frontend/default_theme/templates/modals/privacy.cfm @@ -0,0 +1,17 @@ + + + \ No newline at end of file diff --git a/www/frontend/default_theme/templates/password.cfm b/www/frontend/default_theme/templates/password.cfm new file mode 100644 index 00000000..a73f5397 --- /dev/null +++ b/www/frontend/default_theme/templates/password.cfm @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/www/frontend/default_theme/templates/plans.cfm b/www/frontend/default_theme/templates/plans.cfm new file mode 100644 index 00000000..1f6770ea --- /dev/null +++ b/www/frontend/default_theme/templates/plans.cfm @@ -0,0 +1,27 @@ + + + // Call the utility function to retrieve plans + planData = application.objCoreUtil.retrievePlans(); + + hasPlans = false; + + if (structKeyExists(planData[1], "planGroupID") and planData[1].planGroupID gt 0) { + + hasPlans = true; + + // Call the core function to retrieve features + objPlans = new backend.core.com.plans(language=session.lng); + planFeatures = objPlans.getPlanFeatures(); + + } + + + + + + + + + + + \ No newline at end of file diff --git a/www/frontend/default_theme/templates/plans/box.cfm b/www/frontend/default_theme/templates/plans/box.cfm new file mode 100644 index 00000000..d3f20d3f --- /dev/null +++ b/www/frontend/default_theme/templates/plans/box.cfm @@ -0,0 +1,81 @@ + + + + + + + + + + + +
+ +
+ +
+

#i.planName#

+
+ +
+ +
+ + + + +
#getTrans('txtOnRequest')#
+ + + + + + + + +
#getTrans('txtFree')#
+ + + + +
+ #i.currencySign# #i.dynPrice# #i.dynBillingCycleTextA# +
+ + +
+ #i.dynVAT# +
+ +
+ +
+ + +
+ #i.shortDescription# +
+ +
+ + +
+ #i.description# +
+ + + + #i.dynBookingButtonText# + + +
+ +
+ +
+
+ + diff --git a/www/frontend/default_theme/templates/plans/plan_boxes.cfm b/www/frontend/default_theme/templates/plans/plan_boxes.cfm new file mode 100644 index 00000000..0b44679b --- /dev/null +++ b/www/frontend/default_theme/templates/plans/plan_boxes.cfm @@ -0,0 +1,14 @@ + +
+
+
+ + + + + + +
+
+
+
\ No newline at end of file diff --git a/www/frontend/default_theme/templates/plans/plan_features.cfm b/www/frontend/default_theme/templates/plans/plan_features.cfm new file mode 100644 index 00000000..48b55169 --- /dev/null +++ b/www/frontend/default_theme/templates/plans/plan_features.cfm @@ -0,0 +1,102 @@ + + +
+ +

Compare plans

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Feature#p.planName#
+ #f.name# +
+ #f.name#
+ #f.description# +
+ + + + + + + + #replace(featureContent.value, chr(13), "
")# + + + + + + + + +
+ +
+ +
+ +
+ +
+ diff --git a/www/frontend/default_theme/templates/plans/toggle.cfm b/www/frontend/default_theme/templates/plans/toggle.cfm new file mode 100644 index 00000000..242cfb2b --- /dev/null +++ b/www/frontend/default_theme/templates/plans/toggle.cfm @@ -0,0 +1,21 @@ + + +
+ +
+
+ + + + + + + + + +
+
+ +
+ +
\ No newline at end of file diff --git a/www/frontend/default_theme/templates/register.cfm b/www/frontend/default_theme/templates/register.cfm new file mode 100644 index 00000000..ead2ff37 --- /dev/null +++ b/www/frontend/default_theme/templates/register.cfm @@ -0,0 +1,39 @@ + + +// Retrieve prepared session data for registration +sessionData = application.objCoreUtil.getRegisterSessionData(); + +// Retrieve additional data (countries, time zones) if step 3 is active +additionalData = application.objCoreUtil.getAdditionalRegisterData(); + +// Load the reCAPTCHA script if the Site Key is set +recaptchaScript = application.objCoreUtil.getRecaptchaScript(variables.reCAPTCHA_site_key); + + + + + + +#recaptchaScript# + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/frontend/dist/css/frontend.css b/www/frontend/dist/css/frontend.css deleted file mode 100644 index e1079a6c..00000000 --- a/www/frontend/dist/css/frontend.css +++ /dev/null @@ -1,191 +0,0 @@ -.currency { - font-size: 60%; -} - -.plan-cards>ul { - list-style-type: none; - padding: 0; -} - -.plan-cards>ul li::before { - font-family: "FontAwesome"; - content: "\f00c"; - padding: 0 10px 0 0; - color: green; - font-size: large; -} - -.plan_price { - font-size: 270%; - font-weight: 600; -} - -table.planFeatures { - width: 100%; -} - -table.planFeatures>th, -td { - padding: 15px; -} - -th.planName { - font-size: 20px; -} - -td.feat { - text-align: left; -} - -td.category { - font-size: 18px; - font-weight: 600; -} - -td.feature { - font-size: 14px; -} - -.content-td-head { - border: 1px solid #cccccc; - border-left: none; - padding: 25px 15px; -} - -.content-td-body { - border: 1px solid #cccccc; - border-right: none; -} - -.planName, -.categoryName { - border-bottom: 1px #000000 solid; -} - -.categoryName { - padding-top: 50px; -} - -.allPlans { - font-size: 0px -} - -.categorie1 .allPlans { - font-size: 0.875rem; -} - -.feat { - width: 30%; -} - - -.has-toggle-input label, -.has-toggle-input.radio label { - display: block; - float: left; - padding: 5px 10px; - margin: auto auto auto -1px; - background: #f4f4f4; - color: #444; - /* border: 1px #d0d0d0 solid; */ - line-height: normal; - overflow: hidden; - cursor: pointer; -} - -.has-toggle-input label.active { - background: #1d60b0; - border-color: #1d60b0; - color: white; - font-weight: bolder; -} - -.has-toggle-input label:first-of-type { - margin-left: auto; -} - -.toggleradio { - width: 15%; - margin: 0 auto; -} - - -.otc { - position: relative; - width: 100%; - margin: 0 auto; -} - -.otc fieldset { - border: 0; - padding: 0; - margin: 0; -} - -.otc fieldset div { - display: flex; - align-items: center; -} - -.otc legend { - margin: 0 auto 1em; - color: #5555FF; -} -.otc input:active, -.otc input:focus{ - border-color: #a4cdfd; - outline: none; -} -.otc input::selection{ - background-color: transparent; - color: #206bc4; - border-color: #a4cdfd; -} - -.otc input[type="number"] { - width: 12%; - line-height: 1; - margin: .1em; - padding: 8px 0 4px; - font-size: 2.65em; - text-align: center; - appearance: textfield; - -webkit-appearance: textfield; - border: 2px solid #a4cdfd; - color: #206bc4; - border-radius: 4px; -} - -input::-webkit-outer-spin-button, -input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} - -/* 2 group of 3 items */ -.otc input[type="number"]:nth-child(n+4) { - order: 2; -} -.otc div.mb-3::before { - content: ''; - height: 2px; - width: 24px; - margin: 0 .25em; - order: 1; - background: #a4cdfd; -} - -/* From: https://gist.github.com/ffoodd/000b59f431e3e64e4ce1a24d5bb36034 */ -.otc label { - border: 0 !important; - clip: rect(1px, 1px, 1px, 1px) !important; - -webkit-clip-path: inset(50%) !important; - clip-path: inset(50%) !important; - height: 1px !important; - margin: -1px !important; - overflow: hidden !important; - padding: 0 !important; - position: absolute !important; - width: 1px !important; - white-space: nowrap !important; -} diff --git a/www/frontend/index.cfm b/www/frontend/index.cfm index 7da8da5a..a97c586e 100644 --- a/www/frontend/index.cfm +++ b/www/frontend/index.cfm @@ -1,31 +1,23 @@ - - - - - - #getMeta(cgi.path_info, session.lng).metaTtile# - - #getMeta(cgi.path_info, session.lng).metaHTML# - - - -
+ + - + + + + + + -

SAASTER

-

Plans

-

Login page

-

Registration page

+
-
- + + - - +
- \ No newline at end of file + + \ No newline at end of file diff --git a/www/frontend/js-include.cfm b/www/frontend/js-include.cfm deleted file mode 100644 index 59a6f61c..00000000 --- a/www/frontend/js-include.cfm +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/www/frontend/mfa.cfm b/www/frontend/mfa.cfm deleted file mode 100644 index bd72e865..00000000 --- a/www/frontend/mfa.cfm +++ /dev/null @@ -1,55 +0,0 @@ - - - getSysadminData = application.objSysadmin.getSysAdminData(); - if(structKeyExists(url, 'uuid')){ - uuid = url.uuid; - if (structKeyExists(session, "mfaCheckCount") and session.mfaCheckCount gte 3) { - getAlert(getTrans('txtThreeTimeTry'), 'warning'); - } - }; - - - - -
- -
-
- - Logo - - Logo - -
- -
- -
- -

#getTrans('titMfa')#

- - #session.alert# - - -

#getTrans('txtmfaLead')#

-
- - - - - - - -
-
-
-
- - - -
-
- -
diff --git a/www/frontend/password.cfm b/www/frontend/password.cfm deleted file mode 100644 index a46889f9..00000000 --- a/www/frontend/password.cfm +++ /dev/null @@ -1,64 +0,0 @@ - - getSysadminData = application.objSysadmin.getSysAdminData(); - - - -
- -
- -
- - Logo - - Logo - -
- - -
- -
-
#getTrans('titResetPassword')#
- - #session.alert# - -
- - -
- -
- #getTrans('formAlreadyHaveAccount')# #getTrans('formSignIn')# -
-
-
- -
- -
-
#getTrans('titChoosePassword')#
- - #session.alert# - -
- - -
-
- - -
- -
-
-
-
-
-
- - \ No newline at end of file diff --git a/www/frontend/plan_boxes.cfm b/www/frontend/plan_boxes.cfm deleted file mode 100644 index 2a2fe939..00000000 --- a/www/frontend/plan_boxes.cfm +++ /dev/null @@ -1,198 +0,0 @@ - - - - if (structKeyExists(session, "customer_id") and session.customer_id gt 0 and session.superAdmin) { - getWebhook = new backend.core.com.payrexx().getWebhook(session.customer_id, 'authorized'); - } - - - - - -
-
#getTrans('titPayment')#
-
-
- - -
-
-
-
- - -
-
- -
- -
-
-
- - -
#i.planName#
- - -
-
- - #getTrans('txtOnRequest')# - - - #getTrans('txtFree')# - - #i.currencySign# #lsCurrencyFormat(i.priceMonthly, "none")# - - -
- -
- - #getTrans('txtMonthlyPayment')# - -
-
- - - - - -
- #replace(i.shortDescription, chr(13), "
")# -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- #i.description# -
- -
- -
- - - - -
-

#i.vat_text_monthly#

-
- - - - -
- -
- -
-
-
-
\ No newline at end of file diff --git a/www/frontend/plan_features.cfm b/www/frontend/plan_features.cfm deleted file mode 100644 index 60b1adce..00000000 --- a/www/frontend/plan_features.cfm +++ /dev/null @@ -1,45 +0,0 @@ - - - planFeatures = objPlans.getPlanFeatures(session.lng); - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- #f.name# - #p.planName#
#f.name# - - #replace(featureContent.value, chr(13), "
")# - - -
-
-
-
\ No newline at end of file diff --git a/www/frontend/plans.cfm b/www/frontend/plans.cfm deleted file mode 100644 index 30b0d53c..00000000 --- a/www/frontend/plans.cfm +++ /dev/null @@ -1,64 +0,0 @@ - - - // Is there coming a redirect from the PSP? - if (structKeyExists(url, "psp_response")) { - if (url.psp_response eq "failed") { - getAlert('alertErrorOccured', 'warning'); - } - } - - objPlans = new backend.core.com.plans(); - - // Check if the groupID is coming via URL - local.groupID = 0; - if (structKeyExists(url, "g") and isNumeric(url.g) and url.g gt 0) { - local.groupID = url.g; - } - - if (structKeyExists(session, "customer_id")) { - groupStruct = objPlans.prepareForGroupID(customerID=session.customer_id, groupID=local.groupID); - } else { - groupStruct = objPlans.prepareForGroupID(ipAddress=session.usersIP, groupID=local.groupID); - } - - hasPlans = true; - - if (groupStruct.groupID gt 0) { - - planArray = objPlans.init(language=session.lng, currencyID=groupStruct.defaultCurrencyID).getPlans(groupStruct.groupID); - - if (!arrayLen(planArray)) { - hasPlans = false; - } - - } else { - - hasPlans = false; - - } - - if (!hasPlans) { - getAlert('Sorry, no plans were found!', 'warning'); - } - - - - - -
-
- - #session.alert# - - -
- -
-
- -
-
-
-
-
- \ No newline at end of file diff --git a/www/frontend/register.cfm b/www/frontend/register.cfm deleted file mode 100644 index 60bd9567..00000000 --- a/www/frontend/register.cfm +++ /dev/null @@ -1,188 +0,0 @@ - - - if (structKeyExists(session, "user_id") and session.user_id gt 0) { - if (!structKeyExists(session, "step")) { - location url="#application.mainURL#/dashboard" addtoken="false"; - } - } - - param name="session.step" default="1"; - param name="session.first_name" default=""; - param name="session.name" default=""; - param name="session.company" default=""; - param name="session.email" default=""; - getSysadminData = application.objSysadmin.getSysAdminData(); - - if (session.step eq 3) { - qCountries = application.objGlobal.getCountry(language=session.lng); - timeZones = new backend.core.com.time().getTimezones(); - } - - - - - - - - -
-
-
- - Logo - - Logo - -
- -
- -
-

#getTrans('formSignUp')#

- - #session.alert# - -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- -
-
-
- -
- #getTrans('formAlreadyHaveAccount')# #getTrans('formSignIn')# -
-
-
- - -
- -
-

#getTrans('titChoosePassword')#

- - #session.alert# - -
- - -
-
- - -
- -
-
- -
- -
-

#getTrans('txtUpdateInformation')#

-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- -
- - -
- -
- - -
-
-
- -
-
-
-
-
-
- -
- \ No newline at end of file From f007e1e1608bab80cda652aafc577149e0ae8b22 Mon Sep 17 00:00:00 2001 From: ptruessel Date: Fri, 15 Nov 2024 13:08:51 +0100 Subject: [PATCH 5/5] Remove dump --- www/frontend/default_theme/templates/plans/plan_features.cfm | 5 ----- 1 file changed, 5 deletions(-) diff --git a/www/frontend/default_theme/templates/plans/plan_features.cfm b/www/frontend/default_theme/templates/plans/plan_features.cfm index 48b55169..8c80bd37 100644 --- a/www/frontend/default_theme/templates/plans/plan_features.cfm +++ b/www/frontend/default_theme/templates/plans/plan_features.cfm @@ -57,13 +57,8 @@ - - - - -