openInstallAppPage(app.name)"
+ @click.capture="() => openInstallAppPage(app)"
>
-import { reactive } from 'vue';
-import { createResource } from 'frappe-ui';
-import StarRatingInput from '@/components/StarRatingInput.vue';
-import { getDocResource } from '../../utils/resource';
-import { DashboardError } from '../../utils/error';
-
-const props = defineProps({
- marketplaceApp: String
-});
-
-const app = getDocResource({
- doctype: 'Marketplace App',
- name: props.marketplaceApp
-});
-
-const review = reactive({
- app: props.marketplaceApp,
- title: '',
- rating: 5,
- review: ''
-});
-
-const submitReview = createResource({
- url: 'press.api.marketplace.submit_user_review',
- validate() {
- if (!review.title) {
- throw new DashboardError('Please add a title to your review');
- }
-
- if (!review.review) {
- throw new DashboardError('Review cannot be empty');
- }
- },
- onSuccess() {
- window.location.href = `/marketplace/apps/${props.marketplaceApp}`;
- }
-});
-
-
-
-
-
-
-
{{ app?.doc.title }}
-
-
-
-
- Rating
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/dashboard/src2/objects/common/patches.ts b/dashboard/src2/objects/common/patches.ts
index 72d30f9b72..7727b5fcec 100644
--- a/dashboard/src2/objects/common/patches.ts
+++ b/dashboard/src2/objects/common/patches.ts
@@ -47,16 +47,16 @@ export function getPatchesTab(forBench: boolean) {
}
] satisfies FilterField[],
columns: getPatchesTabColumns(forBench),
- primaryAction({ listResource: apps, documentResource: releaseGroup }) {
+ primaryAction({ listResource: apps, documentResource: doc }) {
return {
label: 'Apply Patch',
slots: {
prefix: icon('plus')
},
onClick() {
- renderDialog(
- h(PatchAppDialog, { group: releaseGroup.name, app: '' })
- );
+ const group = doc.doctype === 'Bench' ? doc.doc.group : doc.name;
+
+ renderDialog(h(PatchAppDialog, { group: group, app: '' }));
}
};
},
diff --git a/dashboard/src2/pages/InstallApp.vue b/dashboard/src2/pages/InstallApp.vue
index c8f2dc496d..f00594f726 100644
--- a/dashboard/src2/pages/InstallApp.vue
+++ b/dashboard/src2/pages/InstallApp.vue
@@ -348,6 +348,14 @@ export default {
params: { app: this.app },
query: { siteGroupDeployName: doc.name }
});
+ } else if (doc.doctype === 'Product Trial Request') {
+ this.$router.push({
+ name: 'SaaSSignupSetup',
+ params: { productId: doc.product_trial },
+ query: {
+ account_request: doc.account_request
+ }
+ });
}
}
};
diff --git a/dashboard/src2/pages/LoginSignup.vue b/dashboard/src2/pages/LoginSignup.vue
index 2d0529c01d..79c8d7ee65 100644
--- a/dashboard/src2/pages/LoginSignup.vue
+++ b/dashboard/src2/pages/LoginSignup.vue
@@ -480,7 +480,7 @@ export default {
onSuccess: res => {
let loginRoute = `/dashboard${res.dashboard_route || '/'}`;
if (this.$route.query.product) {
- loginRoute = `/dashboard/app-trial/setup/${this.$route.query.product}`;
+ loginRoute = `/dashboard/start-center?product=${this.$route.query.product}`;
}
localStorage.setItem('login_email', this.email);
window.location.href = loginRoute;
@@ -582,7 +582,7 @@ export default {
return 'Sign in to your account';
} else {
if (this.saasProduct) {
- return `Sign up to create ${this.saasProduct.title} site`;
+ return `Sign up to create a ${this.saasProduct.title} site`;
}
return 'Create a new account';
}
diff --git a/dashboard/src2/pages/SetupAccount.vue b/dashboard/src2/pages/SetupAccount.vue
index af517878ec..9b487a90c8 100644
--- a/dashboard/src2/pages/SetupAccount.vue
+++ b/dashboard/src2/pages/SetupAccount.vue
@@ -175,12 +175,21 @@ export default {
oauth_signup: this.oauthSignup,
oauth_domain: this.oauthDomain
},
- onSuccess() {
+ onSuccess(account_request) {
let path = '/dashboard';
if (this.saasProduct) {
- path = `/dashboard/app-trial/setup/${this.saasProduct.name}`;
+ path = `/dashboard/create-site/${this.saasProduct}/setup?account_request=${account_request}`;
}
window.location.href = path;
+ // if (this.saasProduct) {
+ // this.$router.push({
+ // name: 'SaaSSignupSetup',
+ // params: { productId: this.saasProduct },
+ // query: {
+ // account_request: account_request
+ // }
+ // });
+ // } else this.$router.push({ name: 'Home' });
}
};
}
diff --git a/dashboard/src2/pages/StartCenter.vue b/dashboard/src2/pages/StartCenter.vue
new file mode 100644
index 0000000000..9fe923d2f0
--- /dev/null
+++ b/dashboard/src2/pages/StartCenter.vue
@@ -0,0 +1,127 @@
+
+
+
+
+
diff --git a/dashboard/src2/pages/onboarding/SiteCreation.vue b/dashboard/src2/pages/onboarding/SiteCreation.vue
new file mode 100644
index 0000000000..6cb1d4d6de
--- /dev/null
+++ b/dashboard/src2/pages/onboarding/SiteCreation.vue
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+ Loading...
+
+
+
+
+
+
{{ appDoc.title }}
+
{{ appDoc.description }}
+
+
+
+
+
+
+
+
Failed to create the site .
+
+
+
or,
+
+ Contact at
+ support@frappe.io
+ to resolve the issue
+
+
+
+
+
+
+
+
+
+
diff --git a/dashboard/src2/pages/saas/SetupSite.vue b/dashboard/src2/pages/saas/SetupSite.vue
index caaefd9034..d03b4ad315 100644
--- a/dashboard/src2/pages/saas/SetupSite.vue
+++ b/dashboard/src2/pages/saas/SetupSite.vue
@@ -124,7 +124,7 @@ export default {
dn: this.$resources.siteRequest.data.name,
method: 'create_site',
args: {
- cluster: this.closestCluster,
+ cluster: this.closestCluster ?? 'Default',
signup_values: this.signupValues
}
};
diff --git a/dashboard/src2/router.js b/dashboard/src2/router.js
index 3113d083bd..50e9c9863f 100644
--- a/dashboard/src2/router.js
+++ b/dashboard/src2/router.js
@@ -21,7 +21,8 @@ let router = createRouter({
{
path: '/welcome',
name: 'Welcome',
- component: () => import('./pages/Welcome.vue')
+ component: () => import('./pages/Welcome.vue'),
+ meta: { hideSidebar: true }
},
{
path: '/login',
@@ -35,6 +36,12 @@ let router = createRouter({
component: () => import('./pages/LoginSignup.vue'),
meta: { isLoginPage: true }
},
+ {
+ path: '/start-center',
+ name: 'Start Center',
+ component: () => import('./pages/StartCenter.vue'),
+ meta: { hideSidebar: true }
+ },
{
path: '/setup-account/:requestKey/:joinRequest?',
name: 'Setup Account',
@@ -206,7 +213,7 @@ let router = createRouter({
},
{
name: 'SaaS',
- path: '/saas',
+ path: '/create-site',
redirect: { name: 'Home' },
children: [
{
@@ -272,10 +279,9 @@ let router = createRouter({
props: true
},
{
- path: '/user-review/:marketplaceApp',
- name: 'ReviewMarketplaceApp',
- component: () =>
- import('./components/marketplace/ReviewMarketplaceApp.vue'),
+ name: 'CreateSiteForMarketplaceAppPublic',
+ path: '/site-creation/:app',
+ component: () => import('./pages/onboarding/SiteCreation.vue'),
props: true
},
{
@@ -329,7 +335,7 @@ router.beforeEach(async (to, from, next) => {
await waitUntilTeamLoaded();
let $team = getTeam();
let onboardingComplete = $team.doc.onboarding.complete;
- let defaultRoute = 'Site List';
+ let defaultRoute = 'Start Center';
let onboardingRoute = 'Welcome';
// identify user in posthog
diff --git a/press/api/account.py b/press/api/account.py
index 00930992b9..4fea44c4dc 100644
--- a/press/api/account.py
+++ b/press/api/account.py
@@ -36,7 +36,7 @@
@frappe.whitelist(allow_guest=True)
-def signup(email, referrer=None):
+def signup(email, product=None, referrer=None):
frappe.utils.validate_email_address(email, True)
current_user = frappe.session.user
@@ -58,6 +58,7 @@ def signup(email, referrer=None):
"role": "Press Admin",
"referrer_id": referrer,
"send_email": True,
+ "product_trial": product,
}
).insert()
@@ -160,6 +161,8 @@ def setup_account( # noqa: C901
capture("completed_signup", "fc_signup", account_request.email)
frappe.local.login_manager.login_as(email)
+ return account_request.name
+
@frappe.whitelist(allow_guest=True)
@rate_limit(limit=5, seconds=60 * 60)
@@ -321,6 +324,7 @@ def validate_request_key(key, timezone=None):
"oauth_domain": frappe.db.exists(
"OAuth Domain Mapping", {"email_domain": account_request.email.split("@")[1]}
),
+ "product_trial": account_request.product_trial,
}
return None
@@ -1084,7 +1088,7 @@ def get_user_ssh_keys():
@frappe.whitelist(allow_guest=True)
-@rate_limit(limit=5, seconds=60 * 60)
+# @rate_limit(limit=5, seconds=60 * 60)
def is_2fa_enabled(user):
return frappe.db.get_value("User 2FA", user, "enabled")
diff --git a/press/api/developer/saas.py b/press/api/developer/saas.py
index 60d73d0edd..57f1dec728 100644
--- a/press/api/developer/saas.py
+++ b/press/api/developer/saas.py
@@ -114,8 +114,8 @@ def get_trial_expiry(secret_key):
"""
NOTE: These mentioned apis are used for all type of saas sites to allow login to frappe cloud
-- request_login_to_fc
-- validate_login_to_fc
+- send_verification_code
+- verify_verification_code
- login_to_fc
Don't change the file name or the method names
@@ -125,7 +125,7 @@ def get_trial_expiry(secret_key):
@frappe.whitelist(allow_guest=True, methods=["POST"])
@rate_limit(limit=5, seconds=60)
-def request_login_to_fc(domain: str):
+def send_verification_code(domain: str):
domain_info = frappe.get_value("Site Domain", domain, ["site", "status"], as_dict=True)
if not domain_info or domain_info.get("status") != "Active":
frappe.throw("The domain is not active currently. Please try again.")
@@ -184,9 +184,9 @@ def request_login_to_fc(domain: str):
@frappe.whitelist(allow_guest=True, methods=["POST"])
-def validate_login_to_fc(domain: str, otp: str):
- otp_hash = frappe.cache().get_value(f"otp_hash_for_fc_login_via_saas_flow:{domain}", expires=True)
- if not otp_hash or otp_hash != frappe.utils.sha256_hash(str(otp)):
+def verify_verification_code(domain: str, verification_code: str, target: str = "start-center"):
+ otp_hash = frappe.cache.get_value(f"otp_hash_for_fc_login_via_saas_flow:{domain}", expires=True)
+ if not otp_hash or otp_hash != frappe.utils.sha256_hash(str(verification_code)):
frappe.throw("Invalid Code. Please try again.")
site = frappe.get_value("Site Domain", domain, "site")
@@ -194,21 +194,30 @@ def validate_login_to_fc(domain: str, otp: str):
user = frappe.get_value("Team", team, "user")
# as otp is valid, delete the otp from redis
- frappe.cache().delete_value(f"otp_hash_for_fc_login_via_saas_flow:{domain}")
+ frappe.cache.delete_value(f"otp_hash_for_fc_login_via_saas_flow:{domain}")
# login and generate a login_token to store sid
login_token = frappe.generate_hash(length=64)
frappe.cache.set_value(f"saas_fc_login_token:{login_token}", user, expires_in_sec=60)
+ if target == "site-dashboard":
+ frappe.cache.set_value(f"saas_fc_login_site:{login_token}", domain, expires_in_sec=60)
frappe.response["login_token"] = login_token
@frappe.whitelist(allow_guest=True)
def login_to_fc(token: str):
- cache_key = f"saas_fc_login_token:{token}"
- email = frappe.cache().get_value(cache_key, expires=True)
+ email_cache_key = f"saas_fc_login_token:{token}"
+ domain_cache_key = f"saas_fc_login_site:{token}"
+ email = frappe.cache.get_value(email_cache_key, expires=True)
+ domain = frappe.cache.get_value(domain_cache_key, expires=True)
+
if email:
- frappe.cache().delete_value(cache_key)
+ frappe.cache.delete_value(email_cache_key)
frappe.local.login_manager.login_as(email)
frappe.response.type = "redirect"
- frappe.response.location = "/dashboard"
+ if domain:
+ frappe.cache.delete_value(domain_cache_key)
+ frappe.response.location = f"/dashboard/sites/{domain}"
+ else:
+ frappe.response.location = "/dashboard/start-center"
diff --git a/press/api/marketplace.py b/press/api/marketplace.py
index 0468895b9a..9ff3e0b14b 100644
--- a/press/api/marketplace.py
+++ b/press/api/marketplace.py
@@ -159,6 +159,73 @@ def get_install_app_options(marketplace_app: str) -> dict:
}
+def site_should_be_created_on_saas_bench(apps: list[dict]) -> bool:
+ """Check if site should be created on SaaS bench"""
+
+ ProductTrial = frappe.qb.DocType("Product Trial")
+ ProductTrialApp = frappe.qb.DocType("Product Trial App")
+ saas_apps = (
+ frappe.qb.from_(ProductTrialApp)
+ .join(ProductTrial)
+ .on(ProductTrialApp.parent == ProductTrial.name)
+ .select(ProductTrialApp.app)
+ .where(ProductTrial.published == 1)
+ .run(as_dict=True, pluck="app")
+ )
+ return all(app["app"] in saas_apps for app in apps)
+
+
+def create_site_on_saas_bench(
+ subdomain: str,
+ apps: list[dict],
+ cluster: str,
+ site_plan: str,
+ latest_stable_version: str,
+ group: str | None = None,
+ trial: bool = False,
+) -> dict:
+ """Create site on SaaS bench"""
+
+ from press.api.product_trial import _get_active_site as get_active_site_of_product_trial
+
+ ProductTrial = frappe.qb.DocType("Product Trial")
+ ProductTrialApp = frappe.qb.DocType("Product Trial App")
+ product = (
+ frappe.qb.from_(ProductTrial)
+ .join(ProductTrialApp)
+ .on(ProductTrialApp.parent == ProductTrial.name)
+ .select(ProductTrial.name)
+ .where(ProductTrial.published == 1)
+ .distinct()
+ .run(as_dict=True, pluck="app")
+ )
+ team = frappe.local.team()
+ site = get_active_site_of_product_trial(product[0], team.name)
+
+ account_request = frappe.new_doc(
+ "Account Request",
+ email=team.user,
+ team=team.name,
+ ).insert(ignore_permissions=True)
+
+ if site:
+ site_request = frappe.get_doc(
+ "Product Trial Request", {"product_trial": product, "team": team, "site": site}
+ )
+ else:
+ # check if account request is valid
+ # is_valid_account_request = frappe.get_value("Account Request", account_request, "email") == team.user
+ # create a new one
+ site_request = frappe.new_doc(
+ "Product Trial Request",
+ product_trial=product[0],
+ team=team.name,
+ account_request=account_request.name,
+ ).insert(ignore_permissions=True)
+
+ return site_request
+
+
def site_should_be_created_on_public_bench(apps: list[dict]) -> bool:
"""Check if site should be created on public bench"""
@@ -317,7 +384,7 @@ def create_site_for_app(
subdomain: str,
apps: list[dict],
cluster: str,
- site_plan: str,
+ site_plan: str = "$10",
group: str | None = None,
trial: bool = False,
):
@@ -327,6 +394,11 @@ def create_site_for_app(
"Frappe Version", {"status": "Stable"}, "name", order_by="number desc"
)
+ if site_should_be_created_on_saas_bench(apps):
+ return create_site_on_saas_bench(
+ subdomain, apps, cluster, site_plan, latest_stable_version, group, trial
+ )
+
if site_should_be_created_on_public_bench(apps):
return create_site_on_public_bench(
subdomain, apps, cluster, site_plan, latest_stable_version, group, trial
@@ -749,8 +821,28 @@ def get_marketplace_apps_for_onboarding() -> list[dict]:
filters={"show_for_site_creation": True, "status": "Published"},
)
total_installs_by_app = get_total_installs_by_app()
+
+ ProductTrial = frappe.qb.DocType("Product Trial")
+ ProductTrialApp = frappe.qb.DocType("Product Trial App")
+
+ product_trials = (
+ frappe.qb.from_(ProductTrial)
+ .join(ProductTrialApp)
+ .on(ProductTrial.name == ProductTrialApp.parent)
+ .select(
+ ProductTrial.name,
+ ProductTrialApp.app,
+ )
+ .where(ProductTrial.published == 1)
+ .distinct()
+ .run(as_dict=True)
+ )
+
for app in apps:
app["total_installs"] = total_installs_by_app.get(app["name"], 0)
+ app_with_product_id = find(product_trials, lambda x: x.app == app["name"])
+ app["product_id"] = app_with_product_id.name if app_with_product_id else None
+
# sort by total installs
apps = sorted(apps, key=lambda x: x["total_installs"], reverse=True)
return apps # noqa: RET504
diff --git a/press/press/doctype/site/site.json b/press/press/doctype/site/site.json
index 4026cf299c..d16a88cda0 100644
--- a/press/press/doctype/site/site.json
+++ b/press/press/doctype/site/site.json
@@ -80,6 +80,7 @@
"column_break_63",
"hybrid_saas_pool",
"saas_communication_secret",
+ "site_label",
"backups_section",
"backup_time",
"column_break_zgig",
@@ -600,6 +601,12 @@
"fieldname": "database_access_connection_limit",
"fieldtype": "Int",
"label": "Database Access Connection Limit"
+ },
+ {
+ "description": "Set for sites created through SaaS flow",
+ "fieldname": "site_label",
+ "fieldtype": "Data",
+ "label": "Site Label"
}
],
"links": [
@@ -674,7 +681,7 @@
"link_fieldname": "site"
}
],
- "modified": "2024-11-28 15:40:02.431631",
+ "modified": "2024-12-18 14:20:04.696835",
"modified_by": "Administrator",
"module": "Press",
"name": "Site",
diff --git a/press/press/doctype/site/site.py b/press/press/doctype/site/site.py
index 39fd53254d..b5d4b753f4 100644
--- a/press/press/doctype/site/site.py
+++ b/press/press/doctype/site/site.py
@@ -151,6 +151,7 @@ class Site(Document, TagHelpers):
setup_wizard_complete: DF.Check
setup_wizard_status_check_next_retry_on: DF.Datetime | None
setup_wizard_status_check_retries: DF.Int
+ site_label: DF.Data | None
skip_auto_updates: DF.Check
skip_failing_patches: DF.Check
skip_scheduled_backups: DF.Check
@@ -196,6 +197,7 @@ class Site(Document, TagHelpers):
"host_name",
"skip_auto_updates",
"additional_system_user_created",
+ "site_label",
)
@staticmethod
diff --git a/press/press/doctype/team/team.py b/press/press/doctype/team/team.py
index 546d9cb06d..13617f7779 100644
--- a/press/press/doctype/team/team.py
+++ b/press/press/doctype/team/team.py
@@ -1046,13 +1046,14 @@ def get_route_on_login(self):
return "/sites"
if self.is_saas_user:
- pending_site_request = self.get_pending_saas_site_request()
- if pending_site_request:
- product_trial = pending_site_request.product_trial
- else:
- product_trial = frappe.db.get_value("Account Request", self.account_request, "product_trial")
- if product_trial:
- return f"/app-trial/setup/{product_trial}"
+ return "/start-center"
+ # pending_site_request = self.get_pending_saas_site_request()
+ # if pending_site_request:
+ # product_trial = pending_site_request.product_trial
+ # else:
+ # product_trial = frappe.db.get_value("Account Request", self.account_request, "product_trial")
+ # if product_trial:
+ # return f"/app-trial/setup/{product_trial}"
return "/welcome"
@@ -1094,7 +1095,9 @@ def suspend_sites(self, reason=None):
def get_sites_to_suspend(self):
plan = frappe.qb.DocType("Site Plan")
query = (
- frappe.qb.from_(plan).select(plan.name).where((plan.enabled == 1) & ((plan.is_frappe_plan == 1) | (plan.is_trial_plan == 1)))
+ frappe.qb.from_(plan)
+ .select(plan.name)
+ .where((plan.enabled == 1) & ((plan.is_frappe_plan == 1) | (plan.is_trial_plan == 1)))
).run(as_dict=True)
frappe_plans = [d.name for d in query]
diff --git a/press/saas/api/site.py b/press/saas/api/site.py
index 99ee337cfb..12abe37c83 100644
--- a/press/saas/api/site.py
+++ b/press/saas/api/site.py
@@ -12,8 +12,8 @@ def info():
site = frappe.get_value("Site", frappe.local.site_name, ["plan", "trial_end_date"], as_dict=True)
return {
"name": frappe.local.site_name,
- "trial_end_date": frappe.get_value("Site", frappe.local.site_name, "trial_end_date"),
- "plan": frappe.get_doc("Site Plan", site.plan),
+ "trial_end_date": site.trial_end_date,
+ "plan": frappe.get_doc("Site Plan", site.plan) if site.plan else None,
}
diff --git a/press/saas/doctype/product_trial/product_trial.py b/press/saas/doctype/product_trial/product_trial.py
index 2bb72292e8..02231c2204 100644
--- a/press/saas/doctype/product_trial/product_trial.py
+++ b/press/saas/doctype/product_trial/product_trial.py
@@ -109,7 +109,6 @@ def setup_trial_site(self, team, plan, cluster=None, account_request=None):
from press.press.doctype.site.site import get_plan_config
standby_site = self.get_standby_site(cluster)
- team_record = frappe.get_doc("Team", team)
trial_end_date = frappe.utils.add_days(None, self.trial_days or 14)
site = None
agent_job_name = None
@@ -123,7 +122,7 @@ def setup_trial_site(self, team, plan, cluster=None, account_request=None):
if standby_site:
site = frappe.get_doc("Site", standby_site)
site.is_standby = False
- site.team = team_record.name
+ site.team = team
site.trial_end_date = trial_end_date
site.account_request = account_request
site._update_configuration(apps_site_config, save=False)
@@ -139,6 +138,7 @@ def setup_trial_site(self, team, plan, cluster=None, account_request=None):
is_frappe_app_present = any(d["app"] == "frappe" for d in apps)
if not is_frappe_app_present:
apps.insert(0, {"app": "frappe"})
+ user = frappe.get_doc("User", current_user)
site = frappe.get_doc(
doctype="Site",
subdomain=self.get_unique_site_name(),
@@ -152,6 +152,7 @@ def setup_trial_site(self, team, plan, cluster=None, account_request=None):
team=team,
apps=apps,
trial_end_date=trial_end_date,
+ site_label=f"{user.first_name or user.full_name}'s {self.title} site",
)
site._update_configuration(apps_site_config, save=False)
site._update_configuration(get_plan_config(plan), save=False)
diff --git a/press/saas/doctype/product_trial_request/product_trial_request.py b/press/saas/doctype/product_trial_request/product_trial_request.py
index 0e9e633332..190316ec01 100644
--- a/press/saas/doctype/product_trial_request/product_trial_request.py
+++ b/press/saas/doctype/product_trial_request/product_trial_request.py
@@ -200,6 +200,9 @@ def validate_signup_fields(self):
def create_site(self, cluster: str | None = None, signup_values: dict | None = None):
if not signup_values:
signup_values = {}
+ if not cluster:
+ cluster = get_default_cluster()
+
product = frappe.get_doc("Product Trial", self.product_trial)
for field in product.signup_fields:
if field.fieldtype == "Password" and field.fieldname in signup_values:
diff --git a/press/www/saas/billing.js b/press/www/saas/billing.js
index 11b3e82401..58d63f5ef5 100644
--- a/press/www/saas/billing.js
+++ b/press/www/saas/billing.js
@@ -1,8 +1,8 @@
-const frappe_cloud_base_endpoint = 'https://frappecloud.com';
+const frappe_cloud_base_endpoint = 'https://suhaildev.tanmoysrt.xyz';
function calculate_trial_end_days() {
// try to check for trial_end_date in frappe.boot.subscription_conf
- if (frappe.boot.subscription_conf.trial_end_date) {
+ if (frappe.boot?.subscription_conf?.trial_end_date) {
const trial_end_date = new Date(
frappe.boot.subscription_conf.trial_end_date,
);
@@ -83,7 +83,7 @@ $(document).ready(function () {
if (frappe.boot.setup_complete === 1) {
if (
!frappe.is_mobile() &&
- frappe.boot.subscription_conf.status !== 'Subscribed' &&
+ frappe.boot?.subscription_conf?.status !== 'Subscribed' &&
trial_end_days > 0
) {
$('.layout-main-section').before($floatingBar);
@@ -115,7 +115,7 @@ function showBanner() {
$(d.body).html(`